作者 徐宝林

初始化项目0908

要显示太多修改。

为保证性能只显示 23 of 23+ 个文件。

@@ -23,12 +23,19 @@ @@ -23,12 +23,19 @@
23 <knife4j.version>4.4.0</knife4j.version> 23 <knife4j.version>4.4.0</knife4j.version>
24 <fastjson2.version>2.0.43</fastjson2.version> 24 <fastjson2.version>2.0.43</fastjson2.version>
25 <hutool.version>5.8.25</hutool.version> 25 <hutool.version>5.8.25</hutool.version>
26 - <jwt.version>4.4.0</jwt.version> 26 + <jwt.version>0.12.3</jwt.version>
27 <redisson.version>3.25.2</redisson.version> 27 <redisson.version>3.25.2</redisson.version>
28 - <jsqlparser.version>4.9</jsqlparser.version> 28 + <jsqlparser.version>4.7</jsqlparser.version>
  29 + <springdoc.version>2.6.0</springdoc.version>
29 </properties> 30 </properties>
30 31
31 <dependencies> 32 <dependencies>
  33 + <!-- Jackson JSR310依赖 -->
  34 + <dependency>
  35 + <groupId>com.fasterxml.jackson.datatype</groupId>
  36 + <artifactId>jackson-datatype-jsr310</artifactId>
  37 + </dependency>
  38 +
32 <!-- Spring Boot Starters --> 39 <!-- Spring Boot Starters -->
33 <dependency> 40 <dependency>
34 <groupId>org.springframework.boot</groupId> 41 <groupId>org.springframework.boot</groupId>
@@ -92,6 +99,13 @@ @@ -92,6 +99,13 @@
92 <version>${knife4j.version}</version> 99 <version>${knife4j.version}</version>
93 </dependency> 100 </dependency>
94 101
  102 + <!-- SpringDoc OpenAPI -->
  103 + <dependency>
  104 + <groupId>org.springdoc</groupId>
  105 + <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  106 + <version>${springdoc.version}</version>
  107 + </dependency>
  108 +
95 <!-- JSON Processing --> 109 <!-- JSON Processing -->
96 <dependency> 110 <dependency>
97 <groupId>com.alibaba.fastjson2</groupId> 111 <groupId>com.alibaba.fastjson2</groupId>
@@ -110,18 +124,18 @@ @@ -110,18 +124,18 @@
110 <dependency> 124 <dependency>
111 <groupId>io.jsonwebtoken</groupId> 125 <groupId>io.jsonwebtoken</groupId>
112 <artifactId>jjwt-api</artifactId> 126 <artifactId>jjwt-api</artifactId>
113 - <version>0.11.5</version> 127 + <version>${jwt.version}</version>
114 </dependency> 128 </dependency>
115 <dependency> 129 <dependency>
116 <groupId>io.jsonwebtoken</groupId> 130 <groupId>io.jsonwebtoken</groupId>
117 <artifactId>jjwt-impl</artifactId> 131 <artifactId>jjwt-impl</artifactId>
118 - <version>0.11.5</version> 132 + <version>${jwt.version}</version>
119 <scope>runtime</scope> 133 <scope>runtime</scope>
120 </dependency> 134 </dependency>
121 <dependency> 135 <dependency>
122 <groupId>io.jsonwebtoken</groupId> 136 <groupId>io.jsonwebtoken</groupId>
123 <artifactId>jjwt-jackson</artifactId> 137 <artifactId>jjwt-jackson</artifactId>
124 - <version>0.11.5</version> 138 + <version>${jwt.version}</version>
125 <scope>runtime</scope> 139 <scope>runtime</scope>
126 </dependency> 140 </dependency>
127 141
@@ -151,6 +165,28 @@ @@ -151,6 +165,28 @@
151 <artifactId>jakarta.annotation-api</artifactId> 165 <artifactId>jakarta.annotation-api</artifactId>
152 </dependency> 166 </dependency>
153 167
  168 + <!-- javax.annotation compatibility for older libraries -->
  169 + <dependency>
  170 + <groupId>javax.annotation</groupId>
  171 + <artifactId>javax.annotation-api</artifactId>
  172 + <version>1.3.2</version>
  173 + </dependency>
  174 +
  175 + <!-- JSR305 annotations for javax.annotation.meta.When -->
  176 + <dependency>
  177 + <groupId>com.google.code.findbugs</groupId>
  178 + <artifactId>jsr305</artifactId>
  179 + <version>3.0.2</version>
  180 + </dependency>
  181 +
  182 + <!-- SpotBugs annotations (替代JSR305) -->
  183 + <dependency>
  184 + <groupId>com.github.spotbugs</groupId>
  185 + <artifactId>spotbugs-annotations</artifactId>
  186 + <version>4.7.3</version>
  187 + <scope>provided</scope>
  188 + </dependency>
  189 +
154 <!-- Lombok --> 190 <!-- Lombok -->
155 <dependency> 191 <dependency>
156 <groupId>org.projectlombok</groupId> 192 <groupId>org.projectlombok</groupId>
@@ -173,46 +209,17 @@ @@ -173,46 +209,17 @@
173 <optional>true</optional> 209 <optional>true</optional>
174 </dependency> 210 </dependency>
175 211
176 - <!-- Test Dependencies --> 212 + <!-- Test -->
177 <dependency> 213 <dependency>
178 <groupId>org.springframework.boot</groupId> 214 <groupId>org.springframework.boot</groupId>
179 <artifactId>spring-boot-starter-test</artifactId> 215 <artifactId>spring-boot-starter-test</artifactId>
180 <scope>test</scope> 216 <scope>test</scope>
181 </dependency> 217 </dependency>
182 -  
183 - <dependency>  
184 - <groupId>org.springframework.security</groupId>  
185 - <artifactId>spring-security-test</artifactId>  
186 - <scope>test</scope>  
187 - </dependency>  
188 - <dependency>  
189 - <groupId>org.projectlombok</groupId>  
190 - <artifactId>lombok</artifactId>  
191 - <scope>provided</scope>  
192 - </dependency>  
193 </dependencies> 218 </dependencies>
194 - 219 +
195 <build> 220 <build>
196 <plugins> 221 <plugins>
197 <plugin> 222 <plugin>
198 - <groupId>org.apache.maven.plugins</groupId>  
199 - <artifactId>maven-compiler-plugin</artifactId>  
200 - <version>3.14.0</version>  
201 - <configuration>  
202 - <source>17</source>  
203 - <target>17</target>  
204 - <fork>true</fork>  
205 - <executable>F:/IntelliJ IDEA/jdk-17.0.12_windows-x64_bin/jdk-17.0.12/bin/javac.exe</executable>  
206 - <annotationProcessorPaths>  
207 - <path>  
208 - <groupId>org.projectlombok</groupId>  
209 - <artifactId>lombok</artifactId>  
210 - <version>1.18.34</version>  
211 - </path>  
212 - </annotationProcessorPaths>  
213 - </configuration>  
214 - </plugin>  
215 - <plugin>  
216 <groupId>org.springframework.boot</groupId> 223 <groupId>org.springframework.boot</groupId>
217 <artifactId>spring-boot-maven-plugin</artifactId> 224 <artifactId>spring-boot-maven-plugin</artifactId>
218 <configuration> 225 <configuration>
@@ -226,5 +233,4 @@ @@ -226,5 +233,4 @@
226 </plugin> 233 </plugin>
227 </plugins> 234 </plugins>
228 </build> 235 </build>
229 -  
230 </project> 236 </project>
@@ -2,44 +2,16 @@ package com.aigeo; @@ -2,44 +2,16 @@ package com.aigeo;
2 2
3 import org.springframework.boot.SpringApplication; 3 import org.springframework.boot.SpringApplication;
4 import org.springframework.boot.autoconfigure.SpringBootApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
5 -import org.springframework.boot.ApplicationRunner;  
6 -import org.springframework.context.annotation.Bean;  
7 -import org.springframework.scheduling.annotation.EnableAsync;  
8 -import org.springframework.scheduling.annotation.EnableScheduling;  
9 -import org.springframework.transaction.annotation.EnableTransactionManagement;  
10 -import org.slf4j.Logger;  
11 -import org.slf4j.LoggerFactory; 5 +import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
  6 +import org.springframework.context.annotation.ComponentScan;
12 7
13 -/**  
14 - * AIGEO AI内容生成平台主启动类  
15 - *  
16 - * @author AIGEO Team  
17 - * @since 1.0.0  
18 - */  
19 @SpringBootApplication 8 @SpringBootApplication
20 -@EnableScheduling  
21 -@EnableAsync  
22 -@EnableTransactionManagement 9 +@ComponentScan(basePackages = {"com.aigeo"})
  10 +@EnableJpaRepositories(basePackages = {"com.aigeo.company.repository", "com.aigeo.ai.repository", "com.aigeo.article.repository", "com.aigeo.landingpage.repository", "com.aigeo.website.repository", "com.aigeo.platform.repository", "com.aigeo.keyword.repository"})
23 public class AigeoApplication { 11 public class AigeoApplication {
24 12
25 - private static final Logger log = LoggerFactory.getLogger(AigeoApplication.class);  
26 -  
27 public static void main(String[] args) { 13 public static void main(String[] args) {
28 SpringApplication.run(AigeoApplication.class, args); 14 SpringApplication.run(AigeoApplication.class, args);
29 - log.info("\n========================================");  
30 - log.info(" AIGEO AI Content Platform Started!");  
31 - log.info(" API Documentation: /doc.html");  
32 - log.info("========================================\n");  
33 - }  
34 -  
35 - /**  
36 - * 提供一个空的ddlApplicationRunner bean以防止Spring Boot自动配置创建的bean类型不匹配  
37 - */  
38 - @Bean  
39 - public ApplicationRunner ddlApplicationRunner() {  
40 - return args -> {  
41 - System.out.println("DDL Application Runner executed");  
42 - };  
43 } 15 }
44 16
45 -} 17 +}
@@ -2,138 +2,116 @@ package com.aigeo.ai.controller; @@ -2,138 +2,116 @@ package com.aigeo.ai.controller;
2 2
3 import com.aigeo.ai.entity.DifyApiConfig; 3 import com.aigeo.ai.entity.DifyApiConfig;
4 import com.aigeo.ai.service.DifyApiConfigService; 4 import com.aigeo.ai.service.DifyApiConfigService;
5 -import com.aigeo.common.result.Result; 5 +import com.aigeo.common.Result;
6 import io.swagger.v3.oas.annotations.Operation; 6 import io.swagger.v3.oas.annotations.Operation;
7 -import io.swagger.v3.oas.annotations.Parameter;  
8 import io.swagger.v3.oas.annotations.tags.Tag; 7 import io.swagger.v3.oas.annotations.tags.Tag;
9 -import lombok.RequiredArgsConstructor;  
10 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
11 -import org.springframework.data.domain.Page;  
12 -import org.springframework.data.domain.PageRequest;  
13 -import org.springframework.data.domain.Pageable; 9 +import org.springframework.beans.factory.annotation.Autowired;
14 import org.springframework.web.bind.annotation.*; 10 import org.springframework.web.bind.annotation.*;
15 11
16 -import jakarta.validation.Valid;  
17 -import jakarta.validation.constraints.NotNull;  
18 import java.util.List; 12 import java.util.List;
19 -import java.util.Optional;  
20 13
21 /** 14 /**
22 - * AI配置管理控制器  
23 - *  
24 - * @author AIGEO Team  
25 - * @since 1.0.0 15 + * AI配置控制器
26 */ 16 */
27 -@Tag(name = "AI配置管理", description = "Dify API配置管理接口")  
28 @Slf4j 17 @Slf4j
29 @RestController 18 @RestController
30 @RequestMapping("/api/ai-configs") 19 @RequestMapping("/api/ai-configs")
31 -@RequiredArgsConstructor 20 +@Tag(name = "AI配置管理", description = "AI配置相关接口")
32 public class DifyApiConfigController { 21 public class DifyApiConfigController {
33 -  
34 - private final DifyApiConfigService difyApiConfigService;  
35 -  
36 - @Operation(summary = "获取所有配置", description = "获取所有AI配置列表")  
37 - @GetMapping  
38 - public Result<List<DifyApiConfig>> getAllConfigs() {  
39 - log.debug("获取所有AI配置");  
40 - List<DifyApiConfig> configs = difyApiConfigService.getAllConfigs();  
41 - return Result.success(configs);  
42 - }  
43 -  
44 - @Operation(summary = "分页查询配置", description = "分页查询AI配置列表")  
45 - @GetMapping("/list")  
46 - public Result<Page<DifyApiConfig>> listConfigs(  
47 - @Parameter(description = "页码") @RequestParam(defaultValue = "0") int page,  
48 - @Parameter(description = "页大小") @RequestParam(defaultValue = "10") int size) {  
49 - log.debug("分页查询AI配置,page: {}, size: {}", page, size);  
50 - Pageable pageable = PageRequest.of(page, size);  
51 - Page<DifyApiConfig> configs = difyApiConfigService.findAll(pageable);  
52 - return Result.success(configs); 22 +
  23 + @Autowired
  24 + private DifyApiConfigService difyApiConfigService;
  25 +
  26 + @PostMapping
  27 + @Operation(summary = "创建AI配置", description = "创建新的AI配置")
  28 + public Result<DifyApiConfig> createConfig(@RequestBody DifyApiConfig config) {
  29 + try {
  30 + DifyApiConfig savedConfig = difyApiConfigService.save(config);
  31 + return Result.success("配置创建成功", savedConfig);
  32 + } catch (Exception e) {
  33 + log.error("创建AI配置失败", e);
  34 + return Result.error("配置创建失败");
  35 + }
53 } 36 }
54 -  
55 - @Operation(summary = "根据ID获取配置", description = "根据配置ID获取单个AI配置") 37 +
56 @GetMapping("/{id}") 38 @GetMapping("/{id}")
57 - public Result<DifyApiConfig> getConfigById(  
58 - @Parameter(description = "配置ID") @PathVariable @NotNull Integer id) {  
59 - log.debug("获取AI配置,id: {}", id);  
60 - Optional<DifyApiConfig> config = difyApiConfigService.getConfigById(id);  
61 - if (config.isPresent()) {  
62 - return Result.success(config.get());  
63 - } else {  
64 - return Result.error(404, "配置不存在"); 39 + @Operation(summary = "获取AI配置详情", description = "根据ID获取AI配置详情")
  40 + public Result<DifyApiConfig> getConfigById(@PathVariable Integer id) {
  41 + try {
  42 + return difyApiConfigService.findById(id)
  43 + .map(config -> Result.success("查询成功", config))
  44 + .orElse(Result.error("配置不存在"));
  45 + } catch (Exception e) {
  46 + log.error("获取AI配置详情失败, id: {}", id, e);
  47 + return Result.error("查询失败");
65 } 48 }
66 } 49 }
67 -  
68 - @Operation(summary = "根据公司ID获取配置", description = "获取指定公司的AI配置列表")  
69 - @GetMapping("/company/{companyId}")  
70 - public Result<List<DifyApiConfig>> getConfigsByCompanyId(  
71 - @Parameter(description = "公司ID") @PathVariable @NotNull Integer companyId) {  
72 - log.debug("获取公司AI配置,companyId: {}", companyId);  
73 - List<DifyApiConfig> configs = difyApiConfigService.getConfigsByCompanyId(companyId);  
74 - return Result.success(configs);  
75 - }  
76 -  
77 - @Operation(summary = "获取共享配置", description = "获取所有共享的AI配置")  
78 - @GetMapping("/shared")  
79 - public Result<List<DifyApiConfig>> getSharedConfigs() {  
80 - log.debug("获取共享AI配置");  
81 - List<DifyApiConfig> configs = difyApiConfigService.getSharedConfigs();  
82 - return Result.success(configs); 50 +
  51 + @GetMapping
  52 + @Operation(summary = "获取AI配置列表", description = "获取所有AI配置列表")
  53 + public Result<List<DifyApiConfig>> getAllConfigs() {
  54 + try {
  55 + List<DifyApiConfig> configs = difyApiConfigService.findAll();
  56 + return Result.success("查询成功", configs);
  57 + } catch (Exception e) {
  58 + log.error("获取AI配置列表失败", e);
  59 + return Result.error("查询失败");
  60 + }
83 } 61 }
84 -  
85 - @Operation(summary = "获取启用的配置", description = "获取所有启用状态的AI配置")  
86 - @GetMapping("/active")  
87 - public Result<List<DifyApiConfig>> getActiveConfigs() {  
88 - log.debug("获取启用的AI配置");  
89 - List<DifyApiConfig> configs = difyApiConfigService.getActiveConfigs();  
90 - return Result.success(configs); 62 +
  63 + @GetMapping("/company/{companyId}")
  64 + @Operation(summary = "根据公司ID获取AI配置列表", description = "根据公司ID获取AI配置列表")
  65 + public Result<List<DifyApiConfig>> getConfigsByCompanyId(@PathVariable Integer companyId) {
  66 + try {
  67 + List<DifyApiConfig> configs = difyApiConfigService.findByCompanyId(companyId);
  68 + return Result.success("查询成功", configs);
  69 + } catch (Exception e) {
  70 + log.error("根据公司ID获取AI配置列表失败, companyId: {}", companyId, e);
  71 + return Result.error("查询失败");
  72 + }
91 } 73 }
92 -  
93 - @Operation(summary = "创建配置", description = "创建新的AI配置")  
94 - @PostMapping  
95 - public Result<DifyApiConfig> createConfig(  
96 - @Parameter(description = "配置信息") @Valid @RequestBody DifyApiConfig config) {  
97 - log.info("创建AI配置,name: {}", config.getName());  
98 - DifyApiConfig createdConfig = difyApiConfigService.saveConfig(config);  
99 - return Result.success(createdConfig); 74 +
  75 + @GetMapping("/company/{companyId}/active")
  76 + @Operation(summary = "根据公司ID获取启用的AI配置列表", description = "根据公司ID获取启用的AI配置列表")
  77 + public Result<List<DifyApiConfig>> getActiveConfigsByCompanyId(@PathVariable Integer companyId) {
  78 + try {
  79 + List<DifyApiConfig> configs = difyApiConfigService.findActiveByCompanyId(companyId);
  80 + return Result.success("查询成功", configs);
  81 + } catch (Exception e) {
  82 + log.error("根据公司ID获取启用的AI配置列表失败, companyId: {}", companyId, e);
  83 + return Result.error("查询失败");
  84 + }
100 } 85 }
101 -  
102 - @Operation(summary = "更新配置", description = "更新指定的AI配置") 86 +
103 @PutMapping("/{id}") 87 @PutMapping("/{id}")
104 - public Result<DifyApiConfig> updateConfig(  
105 - @Parameter(description = "配置ID") @PathVariable @NotNull Integer id,  
106 - @Parameter(description = "配置信息") @Valid @RequestBody DifyApiConfig configDetails) {  
107 - log.info("更新AI配置,id: {}, name: {}", id, configDetails.getName());  
108 - DifyApiConfig updatedConfig = difyApiConfigService.updateConfig(id, configDetails);  
109 - return Result.success(updatedConfig); 88 + @Operation(summary = "更新AI配置", description = "更新AI配置信息")
  89 + public Result<DifyApiConfig> updateConfig(@PathVariable Integer id, @RequestBody DifyApiConfig config) {
  90 + try {
  91 + if (!difyApiConfigService.findById(id).isPresent()) {
  92 + return Result.error("配置不存在");
  93 + }
  94 + config.setId(id);
  95 + DifyApiConfig updatedConfig = difyApiConfigService.save(config);
  96 + return Result.success("配置更新成功", updatedConfig);
  97 + } catch (Exception e) {
  98 + log.error("更新AI配置失败, id: {}", id, e);
  99 + return Result.error("配置更新失败");
  100 + }
110 } 101 }
111 -  
112 - @Operation(summary = "删除配置", description = "删除指定的AI配置") 102 +
113 @DeleteMapping("/{id}") 103 @DeleteMapping("/{id}")
114 - public Result<Void> deleteConfig(  
115 - @Parameter(description = "配置ID") @PathVariable @NotNull Integer id) {  
116 - log.info("删除AI配置,id: {}", id);  
117 - difyApiConfigService.deleteConfig(id);  
118 - return Result.success();  
119 - }  
120 -  
121 - @Operation(summary = "测试配置", description = "测试AI配置的连接性")  
122 - @PostMapping("/{id}/test")  
123 - public Result<Boolean> testConfig(  
124 - @Parameter(description = "配置ID") @PathVariable @NotNull Integer id) {  
125 - log.info("测试AI配置连接,id: {}", id);  
126 - boolean testResult = difyApiConfigService.testConnection(id);  
127 - return Result.success(testResult);  
128 - }  
129 -  
130 - @Operation(summary = "启用/禁用配置", description = "启用或禁用指定的AI配置")  
131 - @PutMapping("/{id}/toggle")  
132 - public Result<DifyApiConfig> toggleConfig(  
133 - @Parameter(description = "配置ID") @PathVariable @NotNull Integer id,  
134 - @Parameter(description = "是否启用") @RequestParam boolean active) {  
135 - log.info("切换AI配置状态,id: {}, active: {}", id, active);  
136 - DifyApiConfig config = difyApiConfigService.toggleActive(id, active);  
137 - return Result.success(config); 104 + @Operation(summary = "删除AI配置", description = "删除指定ID的AI配置")
  105 + public Result<String> deleteConfig(@PathVariable Integer id) {
  106 + try {
  107 + if (!difyApiConfigService.findById(id).isPresent()) {
  108 + return Result.error("配置不存在");
  109 + }
  110 + difyApiConfigService.deleteById(id);
  111 + return Result.success("配置删除成功");
  112 + } catch (Exception e) {
  113 + log.error("删除AI配置失败, id: {}", id, e);
  114 + return Result.error("配置删除失败");
  115 + }
138 } 116 }
139 -} 117 +}
1 -// ai/controller/PromptTemplateController.java  
2 -package com.aigeo.ai.controller;  
3 -  
4 -import com.aigeo.ai.entity.PromptTemplate;  
5 -import com.aigeo.ai.service.PromptTemplateService;  
6 -import org.springframework.beans.factory.annotation.Autowired;  
7 -import org.springframework.http.ResponseEntity;  
8 -import org.springframework.web.bind.annotation.*;  
9 -  
10 -import java.util.List;  
11 -import java.util.Optional;  
12 -  
13 -@RestController  
14 -@RequestMapping("/api/prompt-templates")  
15 -public class PromptTemplateController {  
16 -  
17 - @Autowired  
18 - private PromptTemplateService promptTemplateService;  
19 -  
20 - @GetMapping  
21 - public List<PromptTemplate> getAllTemplates() {  
22 - return promptTemplateService.getAllTemplates();  
23 - }  
24 -  
25 - @GetMapping("/{id}")  
26 - public ResponseEntity<PromptTemplate> getTemplateById(@PathVariable Integer id) {  
27 - Optional<PromptTemplate> template = promptTemplateService.getTemplateById(id);  
28 - return template.map(ResponseEntity::ok)  
29 - .orElse(ResponseEntity.notFound().build());  
30 - }  
31 -  
32 - @GetMapping("/company/{companyId}")  
33 - public List<PromptTemplate> getTemplatesByCompanyId(@PathVariable Integer companyId) {  
34 - return promptTemplateService.getTemplatesByCompanyId(companyId);  
35 - }  
36 -  
37 - @GetMapping("/system")  
38 - public List<PromptTemplate> getSystemTemplates() {  
39 - return promptTemplateService.getSystemTemplates();  
40 - }  
41 -  
42 - @GetMapping("/active")  
43 - public List<PromptTemplate> getActiveTemplates() {  
44 - return promptTemplateService.getActiveTemplates();  
45 - }  
46 -  
47 - @GetMapping("/company/{companyId}/active")  
48 - public List<PromptTemplate> getActiveTemplatesByCompanyId(@PathVariable Integer companyId) {  
49 - return promptTemplateService.getActiveTemplatesByCompanyId(companyId);  
50 - }  
51 -  
52 - @PostMapping  
53 - public PromptTemplate createTemplate(@RequestBody PromptTemplate template) {  
54 - return promptTemplateService.saveTemplate(template);  
55 - }  
56 -  
57 - @PutMapping("/{id}")  
58 - public ResponseEntity<PromptTemplate> updateTemplate(@PathVariable Integer id,  
59 - @RequestBody PromptTemplate templateDetails) {  
60 - Optional<PromptTemplate> template = promptTemplateService.getTemplateById(id);  
61 - if (template.isPresent()) {  
62 - PromptTemplate updatedTemplate = template.get();  
63 - updatedTemplate.setName(templateDetails.getName());  
64 - updatedTemplate.setDescription(templateDetails.getDescription());  
65 - updatedTemplate.setLanguage(templateDetails.getLanguage());  
66 - updatedTemplate.setContent(templateDetails.getContent());  
67 - updatedTemplate.setVariables(templateDetails.getVariables());  
68 - updatedTemplate.setIsActive(templateDetails.getIsActive());  
69 -  
70 - PromptTemplate savedTemplate = promptTemplateService.saveTemplate(updatedTemplate);  
71 - return ResponseEntity.ok(savedTemplate);  
72 - } else {  
73 - return ResponseEntity.notFound().build();  
74 - }  
75 - }  
76 -  
77 - @DeleteMapping("/{id}")  
78 - public ResponseEntity<Void> deleteTemplate(@PathVariable Integer id) {  
79 - promptTemplateService.deleteTemplate(id);  
80 - return ResponseEntity.noContent().build();  
81 - }  
82 -}  
1 -// ai/controller/UploadedFileController.java  
2 -package com.aigeo.ai.controller;  
3 -  
4 -import com.aigeo.ai.entity.UploadedFile;  
5 -import com.aigeo.common.enums.FileStatus;  
6 -import com.aigeo.common.enums.FileType;  
7 -import com.aigeo.ai.service.UploadedFileService;  
8 -import org.springframework.beans.factory.annotation.Autowired;  
9 -import org.springframework.http.ResponseEntity;  
10 -import org.springframework.web.bind.annotation.*;  
11 -  
12 -import java.util.List;  
13 -import java.util.Optional;  
14 -  
15 -@RestController  
16 -@RequestMapping("/api/uploaded-files")  
17 -public class UploadedFileController {  
18 -  
19 - @Autowired  
20 - private UploadedFileService uploadedFileService;  
21 -  
22 - @GetMapping  
23 - public List<UploadedFile> getAllFiles() {  
24 - return uploadedFileService.getAllFiles();  
25 - }  
26 -  
27 - @GetMapping("/{id}")  
28 - public ResponseEntity<UploadedFile> getFileById(@PathVariable Integer id) {  
29 - Optional<UploadedFile> file = uploadedFileService.getFileById(id);  
30 - return file.map(ResponseEntity::ok)  
31 - .orElse(ResponseEntity.notFound().build());  
32 - }  
33 -  
34 - @GetMapping("/company/{companyId}")  
35 - public List<UploadedFile> getFilesByCompanyId(@PathVariable Integer companyId) {  
36 - return uploadedFileService.getFilesByCompanyId(companyId);  
37 - }  
38 -  
39 - @GetMapping("/user/{userId}")  
40 - public List<UploadedFile> getFilesByUserId(@PathVariable Integer userId) {  
41 - return uploadedFileService.getFilesByUserId(userId);  
42 - }  
43 -  
44 - @GetMapping("/company/{companyId}/user/{userId}")  
45 - public List<UploadedFile> getFilesByCompanyIdAndUserId(@PathVariable Integer companyId,  
46 - @PathVariable Integer userId) {  
47 - return uploadedFileService.getFilesByCompanyIdAndUserId(companyId, userId);  
48 - }  
49 -  
50 - @GetMapping("/type/{fileType}")  
51 - public List<UploadedFile> getFilesByFileType(@PathVariable FileType fileType) {  
52 - return uploadedFileService.getFilesByFileType(fileType);  
53 - }  
54 -  
55 - @GetMapping("/status/{status}")  
56 - public List<UploadedFile> getFilesByStatus(@PathVariable FileStatus status) {  
57 - return uploadedFileService.getFilesByStatus(status);  
58 - }  
59 -  
60 - @GetMapping("/company/{companyId}/status/{status}")  
61 - public List<UploadedFile> getFilesByCompanyIdAndStatus(@PathVariable Integer companyId,  
62 - @PathVariable FileStatus status) {  
63 - return uploadedFileService.getFilesByCompanyIdAndStatus(companyId, status);  
64 - }  
65 -  
66 - @PostMapping  
67 - public UploadedFile createFile(@RequestBody UploadedFile file) {  
68 - return uploadedFileService.saveFile(file);  
69 - }  
70 -  
71 - @PutMapping("/{id}")  
72 - public ResponseEntity<UploadedFile> updateFile(@PathVariable Integer id,  
73 - @RequestBody UploadedFile fileDetails) {  
74 - Optional<UploadedFile> file = uploadedFileService.getFileById(id);  
75 - if (file.isPresent()) {  
76 - UploadedFile updatedFile = file.get();  
77 - updatedFile.setFileName(fileDetails.getFileName());  
78 - updatedFile.setFilePath(fileDetails.getFilePath());  
79 - updatedFile.setFileType(fileDetails.getFileType());  
80 - updatedFile.setFileSize(fileDetails.getFileSize());  
81 - updatedFile.setMimeType(fileDetails.getMimeType());  
82 - updatedFile.setStatus(fileDetails.getStatus());  
83 -  
84 - UploadedFile savedFile = uploadedFileService.saveFile(updatedFile);  
85 - return ResponseEntity.ok(savedFile);  
86 - } else {  
87 - return ResponseEntity.notFound().build();  
88 - }  
89 - }  
90 -  
91 - @DeleteMapping("/{id}")  
92 - public ResponseEntity<Void> deleteFile(@PathVariable Integer id) {  
93 - uploadedFileService.deleteFile(id);  
94 - return ResponseEntity.noContent().build();  
95 - }  
96 -}  
1 -// ai/entity/DifyApiConfig.java  
2 package com.aigeo.ai.entity; 1 package com.aigeo.ai.entity;
3 -import org.hibernate.annotations.JdbcTypeCode;  
4 -import java.sql.Types; 2 +
5 import jakarta.persistence.*; 3 import jakarta.persistence.*;
6 -import jakarta.validation.constraints.NotNull;  
7 -import jakarta.validation.constraints.NotBlank;  
8 import lombok.Data; 4 import lombok.Data;
9 -import lombok.Builder;  
10 -import lombok.NoArgsConstructor;  
11 -import lombok.AllArgsConstructor;  
12 -import com.aigeo.common.enums.AiProvider;  
13 -import java.time.LocalDateTime;  
14 -import java.util.Date;  
15 5
  6 +import java.time.LocalDateTime;
16 7
17 /** 8 /**
18 - * DifyApiConfig类是一个实体类,用于映射数据库中的ai_dify_api_configs表。  
19 - * 该类存储了Dify API的配置信息,包括提供商、API密钥、模型参数等。  
20 - *  
21 - * @author AIGEO Team  
22 - * @since 1.0.0 9 + * AI配置实体类
23 */ 10 */
24 @Data 11 @Data
25 -@Builder  
26 -@NoArgsConstructor  
27 -@AllArgsConstructor  
28 @Entity 12 @Entity
29 @Table(name = "ai_dify_api_configs") 13 @Table(name = "ai_dify_api_configs")
30 public class DifyApiConfig { 14 public class DifyApiConfig {
31 - /**  
32 - * 主键ID,采用自增策略  
33 - */ 15 +
34 @Id 16 @Id
35 @GeneratedValue(strategy = GenerationType.IDENTITY) 17 @GeneratedValue(strategy = GenerationType.IDENTITY)
36 private Integer id; 18 private Integer id;
37 19
38 - /**  
39 - * 公司ID,用于区分不同公司的配置  
40 - */  
41 @Column(name = "company_id") 20 @Column(name = "company_id")
42 private Integer companyId; 21 private Integer companyId;
43 22
44 - /**  
45 - * AI服务提供商,使用枚举类型存储  
46 - */  
47 @Enumerated(EnumType.STRING) 23 @Enumerated(EnumType.STRING)
48 - private AiProvider provider; 24 + @Column(name = "provider")
  25 + private Provider provider;
49 26
50 - /**  
51 - * 配置名称,不能为空  
52 - */  
53 - @NotBlank(message = "配置名称不能为空")  
54 - @Column(nullable = false) 27 + @Column(name = "name", nullable = false)
55 private String name; 28 private String name;
56 29
57 - /**  
58 - * API的基础URL  
59 - */  
60 @Column(name = "base_url") 30 @Column(name = "base_url")
61 private String baseUrl; 31 private String baseUrl;
62 32
63 - /**  
64 - * API密钥,用于身份验证  
65 - */  
66 @Column(name = "api_key") 33 @Column(name = "api_key")
67 private String apiKey; 34 private String apiKey;
68 35
69 - /**  
70 - * 模型名称,指定使用的具体模型  
71 - */  
72 @Column(name = "model_name") 36 @Column(name = "model_name")
73 private String modelName; 37 private String modelName;
74 38
75 - /**  
76 - * 温度参数,控制输出的随机性  
77 - */ 39 + @Column(name = "temperature")
78 private Double temperature; 40 private Double temperature;
79 41
80 - /**  
81 - * Top P参数,控制词汇采样的概率范围  
82 - */  
83 @Column(name = "top_p") 42 @Column(name = "top_p")
84 private Double topP; 43 private Double topP;
85 44
86 - /**  
87 - * 最大令牌数,限制生成内容的长度  
88 - */  
89 @Column(name = "max_tokens") 45 @Column(name = "max_tokens")
90 private Integer maxTokens; 46 private Integer maxTokens;
91 47
92 - /**  
93 - * 请求头信息,以JSON格式存储  
94 - */  
95 @Column(name = "request_headers") 48 @Column(name = "request_headers")
96 private String requestHeaders; 49 private String requestHeaders;
97 50
98 - /**  
99 - * 是否激活该配置  
100 - */  
101 @Column(name = "is_active") 51 @Column(name = "is_active")
102 private Boolean isActive; 52 private Boolean isActive;
103 53
104 - /**  
105 - * 创建时间,使用时间戳类型  
106 - */  
107 @Column(name = "created_at") 54 @Column(name = "created_at")
108 -  
109 private LocalDateTime createdAt; 55 private LocalDateTime createdAt;
110 56
111 - /**  
112 - * 更新时间,使用时间戳类型  
113 - */  
114 @Column(name = "updated_at") 57 @Column(name = "updated_at")
115 -  
116 private LocalDateTime updatedAt; 58 private LocalDateTime updatedAt;
117 59
118 - /**  
119 - * 创建时间设置,新建实体时自动设置  
120 - */  
121 @PrePersist 60 @PrePersist
122 protected void onCreate() { 61 protected void onCreate() {
123 createdAt = LocalDateTime.now(); 62 createdAt = LocalDateTime.now();
@@ -127,11 +66,39 @@ public class DifyApiConfig { @@ -127,11 +66,39 @@ public class DifyApiConfig {
127 } 66 }
128 } 67 }
129 68
130 - /**  
131 - * 更新时间设置,实体更新时自动设置  
132 - */  
133 @PreUpdate 69 @PreUpdate
134 protected void onUpdate() { 70 protected void onUpdate() {
135 updatedAt = LocalDateTime.now(); 71 updatedAt = LocalDateTime.now();
136 } 72 }
137 -} 73 +
  74 + /**
  75 + * AI提供方枚举
  76 + */
  77 + public enum Provider {
  78 + DIFY("dify"),
  79 + OPENAI("openai"),
  80 + ANTHROPIC("anthropic"),
  81 + GOOGLE("google"),
  82 + AZURE_OPENAI("azure_openai"),
  83 + OTHER("other");
  84 +
  85 + private final String code;
  86 +
  87 + Provider(String code) {
  88 + this.code = code;
  89 + }
  90 +
  91 + public String getCode() {
  92 + return code;
  93 + }
  94 +
  95 + public static Provider fromCode(String code) {
  96 + for (Provider provider : Provider.values()) {
  97 + if (provider.getCode().equals(code)) {
  98 + return provider;
  99 + }
  100 + }
  101 + throw new IllegalArgumentException("未知的AI提供方: " + code);
  102 + }
  103 + }
  104 +}
1 -// ai/entity/PromptTemplate.java  
2 -package com.aigeo.ai.entity;  
3 -import org.hibernate.annotations.JdbcTypeCode;  
4 -import java.sql.Types;  
5 -import jakarta.persistence.*;  
6 -import jakarta.validation.constraints.NotBlank;  
7 -import jakarta.validation.constraints.NotNull;  
8 -import lombok.AllArgsConstructor;  
9 -import lombok.Builder;  
10 -import lombok.Data;  
11 -import lombok.NoArgsConstructor;  
12 -import com.fasterxml.jackson.annotation.JsonFormat;  
13 -import org.hibernate.annotations.CreationTimestamp;  
14 -import org.hibernate.annotations.UpdateTimestamp;  
15 -import java.time.LocalDateTime;  
16 -  
17 -/**  
18 - * 提示模板实体类  
19 - * 对应数据库表:ai_prompt_templates  
20 - *  
21 - * 存储AI生成内容时使用的提示词模板,包括:  
22 - * - 模板内容和变量定义  
23 - * - 语言和适用场景配置  
24 - * - 使用统计和版本管理  
25 - *  
26 - * @author AIGEO Team  
27 - * @since 1.0.0  
28 - */  
29 -@Data  
30 -@Builder  
31 -@NoArgsConstructor  
32 -@AllArgsConstructor  
33 -@Entity  
34 -@Table(name = "ai_prompt_templates", indexes = {  
35 - @Index(name = "idx_prompt_templates_company", columnList = "company_id"),  
36 - @Index(name = "idx_prompt_templates_active", columnList = "is_active"),  
37 - @Index(name = "idx_prompt_templates_language", columnList = "language")  
38 -})  
39 -public class PromptTemplate {  
40 -  
41 - /**  
42 - * 主键ID  
43 - */  
44 - @Id  
45 - @GeneratedValue(strategy = GenerationType.IDENTITY)  
46 - @Column(name = "id", nullable = false, updatable = false)  
47 - private Integer id;  
48 -  
49 - /**  
50 - * 公司ID(多租户字段)  
51 - */  
52 - @NotNull(message = "公司ID不能为空")  
53 - @Column(name = "company_id", nullable = false)  
54 - private Integer companyId;  
55 -  
56 - /**  
57 - * 模板名称  
58 - */  
59 - @NotBlank(message = "模板名称不能为空")  
60 - @Column(name = "name", nullable = false, length = 200)  
61 - private String name;  
62 -  
63 - /**  
64 - * 模板描述  
65 - */  
66 - @Column(name = "description", length = 500)  
67 - private String description;  
68 -  
69 - /**  
70 - * 模板语言  
71 - */  
72 - @Column(name = "language", length = 10)  
73 - @Builder.Default  
74 - private String language = "zh-CN";  
75 -  
76 - /**  
77 - * 模板内容  
78 - */  
79 - @NotBlank(message = "模板内容不能为空")  
80 - @Column(name = "content", nullable = false, columnDefinition = "TEXT")  
81 - private String content;  
82 -  
83 - /**  
84 - * 模板变量(JSON格式存储变量定义)  
85 - */  
86 - @Column(name = "variables", columnDefinition = "JSON")  
87 - private String variables;  
88 -  
89 - /**  
90 - * 模板类型(如:article, product, seo等)  
91 - */  
92 - @Column(name = "template_type", length = 50)  
93 - @Builder.Default  
94 - private String templateType = "article";  
95 -  
96 - /**  
97 - * 使用次数统计  
98 - */  
99 - @Column(name = "usage_count")  
100 - @Builder.Default  
101 - private Integer usageCount = 0;  
102 -  
103 - /**  
104 - * 是否激活  
105 - */  
106 - @Column(name = "is_active")  
107 - @Builder.Default  
108 - private Boolean isActive = true;  
109 -  
110 - /**  
111 - * 创建时间  
112 - */  
113 - @CreationTimestamp  
114 - @Column(name = "created_at", updatable = false)  
115 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
116 - private LocalDateTime createdAt;  
117 -  
118 - /**  
119 - * 更新时间  
120 - */  
121 - @UpdateTimestamp  
122 - @Column(name = "updated_at")  
123 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
124 - private LocalDateTime updatedAt;  
125 -  
126 - /**  
127 - * 实体创建前的处理  
128 - */  
129 - @PrePersist  
130 - protected void onCreate() {  
131 - if (isActive == null) isActive = true;  
132 - if (language == null) language = "zh-CN";  
133 - if (templateType == null) templateType = "article";  
134 - if (usageCount == null) usageCount = 0;  
135 - }  
136 -  
137 - /**  
138 - * 检查是否为激活状态  
139 - */  
140 - public boolean isActive() {  
141 - return Boolean.TRUE.equals(isActive);  
142 - }  
143 -  
144 - /**  
145 - * 增加使用次数  
146 - */  
147 - public void incrementUsageCount() {  
148 - this.usageCount = (usageCount == null ? 0 : usageCount) + 1;  
149 - }  
150 -  
151 - /**  
152 - * 检查是否为热门模板(使用次数超过阈值)  
153 - */  
154 - public boolean isPopularTemplate() {  
155 - return usageCount != null && usageCount >= 50;  
156 - }  
157 -  
158 - /**  
159 - * 获取模板的复杂度评级(基于内容长度和变量数量)  
160 - */  
161 - public String getComplexityLevel() {  
162 - if (content == null) return "SIMPLE";  
163 -  
164 - int contentLength = content.length();  
165 - int variableCount = (variables != null && !variables.trim().isEmpty()) ?  
166 - variables.split(",").length : 0;  
167 -  
168 - if (contentLength > 2000 || variableCount > 10) {  
169 - return "COMPLEX";  
170 - } else if (contentLength > 500 || variableCount > 3) {  
171 - return "MEDIUM";  
172 - } else {  
173 - return "SIMPLE";  
174 - }  
175 - }  
176 -  
177 - /**  
178 - * 检查是否包含指定变量  
179 - */  
180 - public boolean hasVariable(String variableName) {  
181 - if (variables == null || variables.trim().isEmpty()) {  
182 - return false;  
183 - }  
184 - return variables.contains("\"" + variableName + "\"") ||  
185 - variables.contains(variableName);  
186 - }  
187 -}  
1 -// ai/entity/UploadedFile.java  
2 -package com.aigeo.ai.entity;  
3 -import org.hibernate.annotations.JdbcTypeCode;  
4 -import java.sql.Types;  
5 -import jakarta.persistence.*;  
6 -import jakarta.validation.constraints.NotBlank;  
7 -import jakarta.validation.constraints.NotNull;  
8 -import lombok.Data;  
9 -import lombok.Builder;  
10 -import lombok.NoArgsConstructor;  
11 -import lombok.AllArgsConstructor;  
12 -import com.aigeo.common.enums.FileType;  
13 -import com.aigeo.common.enums.FileStatus;  
14 -  
15 -import java.time.LocalDateTime;  
16 -  
17 -  
18 -/**  
19 - * 上传文件实体类,对应数据库表 ai_uploaded_files  
20 - *  
21 - * 管理系统中上传的文件信息,包括文件元数据、存储路径、  
22 - * 文件类型、大小、校验和等信息,支持多租户和版本控制  
23 - *  
24 - * @author AIGEO Team  
25 - * @since 1.0.0  
26 - */  
27 -@Data  
28 -@Builder  
29 -@NoArgsConstructor  
30 -@AllArgsConstructor  
31 -@Entity  
32 -@Table(name = "ai_uploaded_files", indexes = {  
33 - @Index(name = "idx_uploaded_files_company", columnList = "company_id"),  
34 - @Index(name = "idx_uploaded_files_user", columnList = "user_id"),  
35 - @Index(name = "idx_uploaded_files_type", columnList = "file_type"),  
36 - @Index(name = "idx_uploaded_files_status", columnList = "status"),  
37 - @Index(name = "idx_uploaded_files_created", columnList = "created_at")  
38 -})  
39 -public class UploadedFile {  
40 - @Id  
41 - @GeneratedValue(strategy = GenerationType.IDENTITY)  
42 - private Integer id;  
43 -  
44 - /**  
45 - * 公司ID,多租户标识  
46 - */  
47 - @NotNull(message = "公司ID不能为空")  
48 - @Column(name = "company_id", nullable = false)  
49 - private Integer companyId;  
50 -  
51 - /**  
52 - * 用户ID,文件上传者  
53 - */  
54 - @NotNull(message = "用户ID不能为空")  
55 - @Column(name = "user_id", nullable = false)  
56 - private Integer userId;  
57 -  
58 - /**  
59 - * 原始文件名  
60 - */  
61 - @NotBlank(message = "文件名不能为空")  
62 - @Column(name = "file_name", nullable = false, length = 255)  
63 - private String fileName;  
64 -  
65 - /**  
66 - * 文件存储路径  
67 - */  
68 - @NotBlank(message = "文件路径不能为空")  
69 - @Column(name = "file_path", nullable = false, length = 500)  
70 - private String filePath;  
71 -  
72 - /**  
73 - * 文件类型枚举  
74 - */  
75 - @NotNull(message = "文件类型不能为空")  
76 - @Enumerated(EnumType.STRING)  
77 - @Column(name = "file_type", nullable = false, length = 50)  
78 - private FileType fileType;  
79 -  
80 - /**  
81 - * 文件大小,单位字节  
82 - */  
83 - @Column(name = "file_size")  
84 - private Long fileSize;  
85 -  
86 - /**  
87 - * MIME类型  
88 - */  
89 - @Column(name = "mime_type", length = 100)  
90 - private String mimeType;  
91 -  
92 - /**  
93 - * 文件校验和,用于文件完整性验证  
94 - */  
95 - @Column(length = 128)  
96 - private String checksum;  
97 -  
98 - /**  
99 - * 文件版本号,支持文件版本管理  
100 - */  
101 - @Builder.Default  
102 - private Integer version = 1;  
103 -  
104 - /**  
105 - * 文件状态  
106 - */  
107 - @Enumerated(EnumType.STRING)  
108 - @Column(length = 20)  
109 - @Builder.Default  
110 - private FileStatus status = FileStatus.UPLOADED;  
111 -  
112 - /**  
113 - * 创建时间  
114 - */  
115 - @Column(name = "created_at", nullable = false, updatable = false)  
116 -  
117 - private LocalDateTime createdAt;  
118 -  
119 - /**  
120 - * 创建时间自动设置  
121 - */  
122 - @PrePersist  
123 - protected void onCreate() {  
124 - createdAt = LocalDateTime.now();  
125 - }  
126 -  
127 - /**  
128 - * 获取文件大小的可读格式  
129 - * @return 格式化的文件大小字符串  
130 - */  
131 - public String getFormattedFileSize() {  
132 - if (fileSize == null) return "未知";  
133 - if (fileSize < 1024) return fileSize + " B";  
134 - if (fileSize < 1024 * 1024) return String.format("%.1f KB", fileSize / 1024.0);  
135 - if (fileSize < 1024 * 1024 * 1024) return String.format("%.1f MB", fileSize / (1024.0 * 1024.0));  
136 - return String.format("%.1f GB", fileSize / (1024.0 * 1024.0 * 1024.0));  
137 - }  
138 -  
139 - /**  
140 - * 检查文件是否为图片类型  
141 - * @return 是否为图片  
142 - */  
143 - public boolean isImage() {  
144 - return fileType == FileType.IMAGE ||  
145 - (mimeType != null && mimeType.startsWith("image/"));  
146 - }  
147 -  
148 - /**  
149 - * 检查文件是否为文档类型  
150 - * @return 是否为文档  
151 - */  
152 - public boolean isDocument() {  
153 - return fileType == FileType.DOCUMENT ||  
154 - (mimeType != null && (mimeType.contains("pdf") ||  
155 - mimeType.contains("word") ||  
156 - mimeType.contains("text")));  
157 - }  
158 -}  
1 -// ai/repository/DifyApiConfigRepository.java  
2 package com.aigeo.ai.repository; 1 package com.aigeo.ai.repository;
3 2
4 import com.aigeo.ai.entity.DifyApiConfig; 3 import com.aigeo.ai.entity.DifyApiConfig;
@@ -7,9 +6,29 @@ import org.springframework.stereotype.Repository; @@ -7,9 +6,29 @@ import org.springframework.stereotype.Repository;
7 6
8 import java.util.List; 7 import java.util.List;
9 8
  9 +/**
  10 + * AI配置仓库接口
  11 + */
10 @Repository 12 @Repository
11 public interface DifyApiConfigRepository extends JpaRepository<DifyApiConfig, Integer> { 13 public interface DifyApiConfigRepository extends JpaRepository<DifyApiConfig, Integer> {
  14 +
  15 + /**
  16 + * 根据公司ID查找配置列表
  17 + */
12 List<DifyApiConfig> findByCompanyId(Integer companyId); 18 List<DifyApiConfig> findByCompanyId(Integer companyId);
13 - List<DifyApiConfig> findByCompanyIdIsNull(); // 共享配置  
14 - List<DifyApiConfig> findByIsActiveTrue();  
15 -} 19 +
  20 + /**
  21 + * 根据公司ID和启用状态查找配置列表
  22 + */
  23 + List<DifyApiConfig> findByCompanyIdAndIsActiveTrue(Integer companyId);
  24 +
  25 + /**
  26 + * 根据提供方查找配置列表
  27 + */
  28 + List<DifyApiConfig> findByProvider(DifyApiConfig.Provider provider);
  29 +
  30 + /**
  31 + * 根据提供方和启用状态查找配置列表
  32 + */
  33 + List<DifyApiConfig> findByProviderAndIsActiveTrue(DifyApiConfig.Provider provider);
  34 +}
1 -// ai/repository/PromptTemplateRepository.java  
2 -package com.aigeo.ai.repository;  
3 -  
4 -import com.aigeo.ai.entity.PromptTemplate;  
5 -import org.springframework.data.jpa.repository.JpaRepository;  
6 -import org.springframework.stereotype.Repository;  
7 -  
8 -import java.util.List;  
9 -  
10 -@Repository  
11 -public interface PromptTemplateRepository extends JpaRepository<PromptTemplate, Integer> {  
12 - List<PromptTemplate> findByCompanyId(Integer companyId);  
13 - List<PromptTemplate> findByCompanyIdIsNull(); // 系统模板  
14 - List<PromptTemplate> findByIsActiveTrue();  
15 - List<PromptTemplate> findByCompanyIdAndIsActiveTrue(Integer companyId);  
16 -}  
1 -// ai/repository/UploadedFileRepository.java  
2 -package com.aigeo.ai.repository;  
3 -  
4 -import com.aigeo.ai.entity.UploadedFile;  
5 -import com.aigeo.common.enums.FileStatus;  
6 -import com.aigeo.common.enums.FileType;  
7 -import org.springframework.data.jpa.repository.JpaRepository;  
8 -import org.springframework.stereotype.Repository;  
9 -  
10 -import java.util.List;  
11 -  
12 -@Repository  
13 -public interface UploadedFileRepository extends JpaRepository<UploadedFile, Integer> {  
14 - List<UploadedFile> findByCompanyId(Integer companyId);  
15 - List<UploadedFile> findByUserId(Integer userId);  
16 - List<UploadedFile> findByCompanyIdAndUserId(Integer companyId, Integer userId);  
17 - List<UploadedFile> findByFileType(FileType fileType);  
18 - List<UploadedFile> findByStatus(FileStatus status);  
19 - List<UploadedFile> findByCompanyIdAndStatus(Integer companyId, FileStatus status);  
20 -}  
1 -// ai/service/DifyApiConfigService.java  
2 package com.aigeo.ai.service; 1 package com.aigeo.ai.service;
3 2
4 import com.aigeo.ai.entity.DifyApiConfig; 3 import com.aigeo.ai.entity.DifyApiConfig;
5 -import com.aigeo.ai.repository.DifyApiConfigRepository;  
6 -import com.aigeo.common.exception.BusinessException;  
7 -import com.aigeo.common.result.ResultCode;  
8 -import jakarta.validation.Valid;  
9 -import jakarta.validation.constraints.NotNull;  
10 -import lombok.RequiredArgsConstructor;  
11 -import lombok.extern.slf4j.Slf4j;  
12 -import org.springframework.beans.factory.annotation.Autowired;  
13 -import org.springframework.data.domain.Page;  
14 -import org.springframework.data.domain.Pageable;  
15 -import org.springframework.stereotype.Service;  
16 -import org.springframework.transaction.annotation.Transactional;  
17 4
18 import java.util.List; 5 import java.util.List;
19 import java.util.Optional; 6 import java.util.Optional;
20 7
21 /** 8 /**
22 - * Dify API配置服务类  
23 - *  
24 - * 负责管理AI配置的业务逻辑,包括配置的CRUD操作、  
25 - * 连接测试、状态管理等功能,支持多租户和权限控制  
26 - *  
27 - * @author AIGEO Team  
28 - * @since 1.0.0 9 + * AI配置服务接口
29 */ 10 */
30 -@Slf4j  
31 -@Service  
32 -@RequiredArgsConstructor  
33 -@Transactional(readOnly = true)  
34 -public class DifyApiConfigService {  
35 -  
36 - private final DifyApiConfigRepository difyApiConfigRepository;  
37 - 11 +public interface DifyApiConfigService {
  12 +
38 /** 13 /**
39 - * 获取所有配置  
40 - * @return 所有配置列表 14 + * 保存配置
41 */ 15 */
42 - public List<DifyApiConfig> getAllConfigs() {  
43 - log.debug("获取所有AI配置");  
44 - return difyApiConfigRepository.findAll();  
45 - }  
46 - 16 + DifyApiConfig save(DifyApiConfig config);
  17 +
47 /** 18 /**
48 - * 根据公司ID获取配置列表  
49 - * @param companyId 公司ID  
50 - * @return 该公司的配置列表  
51 - * @throws BusinessException 当公司ID为空时抛出 19 + * 根据ID查找配置
52 */ 20 */
53 - public List<DifyApiConfig> getConfigsByCompanyId(Integer companyId) {  
54 - log.debug("根据公司ID获取AI配置,companyId: {}", companyId);  
55 - if (companyId == null) {  
56 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");  
57 - }  
58 - return difyApiConfigRepository.findByCompanyId(companyId);  
59 - }  
60 -  
61 - public List<DifyApiConfig> getSharedConfigs() {  
62 - return difyApiConfigRepository.findByCompanyIdIsNull();  
63 - }  
64 -  
65 - public List<DifyApiConfig> getActiveConfigs() {  
66 - return difyApiConfigRepository.findByIsActiveTrue();  
67 - }  
68 -  
69 - public Optional<DifyApiConfig> getConfigById(Integer id) {  
70 - return difyApiConfigRepository.findById(id);  
71 - }  
72 -  
73 - public DifyApiConfig saveConfig(DifyApiConfig config) {  
74 - return difyApiConfigRepository.save(config);  
75 - }  
76 -  
77 - public void deleteConfig(Integer id) {  
78 - difyApiConfigRepository.deleteById(id);  
79 - }  
80 -  
81 - public Page<DifyApiConfig> findAll(Pageable pageable) {  
82 - return null;  
83 - }  
84 -  
85 - public DifyApiConfig updateConfig(@NotNull Integer id, @Valid DifyApiConfig configDetails) {  
86 - return configDetails;  
87 - }  
88 - 21 + Optional<DifyApiConfig> findById(Integer id);
  22 +
89 /** 23 /**
90 - * 测试API配置的连接性  
91 - * @param id 配置ID  
92 - * @return 连接是否成功 24 + * 根据公司ID查找配置列表
93 */ 25 */
94 - public boolean testConnection(@NotNull Integer id) {  
95 - Optional<DifyApiConfig> configOpt = difyApiConfigRepository.findById(id);  
96 - if (!configOpt.isPresent()) {  
97 - return false;  
98 - }  
99 -  
100 - DifyApiConfig config = configOpt.get();  
101 - if (config.getApiKey() == null || config.getBaseUrl() == null) {  
102 - return false;  
103 - }  
104 -  
105 - // 这里应该实现具体的连接测试逻辑  
106 - // 比如发送HTTP请求到API端点进行验证  
107 - // 暂时返回true表示测试通过  
108 - return true;  
109 - }  
110 - 26 + List<DifyApiConfig> findByCompanyId(Integer companyId);
  27 +
  28 + /**
  29 + * 根据公司ID和启用状态查找配置列表
  30 + */
  31 + List<DifyApiConfig> findActiveByCompanyId(Integer companyId);
  32 +
  33 + /**
  34 + * 根据提供方查找配置列表
  35 + */
  36 + List<DifyApiConfig> findByProvider(DifyApiConfig.Provider provider);
  37 +
  38 + /**
  39 + * 根据提供方和启用状态查找配置列表
  40 + */
  41 + List<DifyApiConfig> findActiveByProvider(DifyApiConfig.Provider provider);
  42 +
  43 + /**
  44 + * 查找所有配置
  45 + */
  46 + List<DifyApiConfig> findAll();
  47 +
111 /** 48 /**
112 - * 切换配置的激活状态  
113 - * @param id 配置ID  
114 - * @param active 目标状态  
115 - * @return 更新后的配置对象  
116 - * @throws BusinessException 当配置不存在时抛出 49 + * 删除配置
117 */ 50 */
118 - @Transactional  
119 - public DifyApiConfig toggleActive(@NotNull Integer id, boolean active) {  
120 - Optional<DifyApiConfig> configOpt = difyApiConfigRepository.findById(id);  
121 - if (!configOpt.isPresent()) {  
122 - throw new BusinessException(ResultCode.DATA_NOT_FOUND, "配置不存在");  
123 - }  
124 -  
125 - DifyApiConfig config = configOpt.get();  
126 - config.setIsActive(active);  
127 - return difyApiConfigRepository.save(config);  
128 - }  
129 -} 51 + void deleteById(Integer id);
  52 +}
1 -// ai/service/PromptTemplateService.java  
2 -package com.aigeo.ai.service;  
3 -  
4 -import com.aigeo.ai.entity.PromptTemplate;  
5 -import com.aigeo.ai.repository.PromptTemplateRepository;  
6 -import org.springframework.beans.factory.annotation.Autowired;  
7 -import org.springframework.stereotype.Service;  
8 -  
9 -import java.util.List;  
10 -import java.util.Optional;  
11 -  
12 -@Service  
13 -public class PromptTemplateService {  
14 -  
15 - @Autowired  
16 - private PromptTemplateRepository promptTemplateRepository;  
17 -  
18 - public List<PromptTemplate> getAllTemplates() {  
19 - return promptTemplateRepository.findAll();  
20 - }  
21 -  
22 - public List<PromptTemplate> getTemplatesByCompanyId(Integer companyId) {  
23 - return promptTemplateRepository.findByCompanyId(companyId);  
24 - }  
25 -  
26 - public List<PromptTemplate> getSystemTemplates() {  
27 - return promptTemplateRepository.findByCompanyIdIsNull();  
28 - }  
29 -  
30 - public List<PromptTemplate> getActiveTemplates() {  
31 - return promptTemplateRepository.findByIsActiveTrue();  
32 - }  
33 -  
34 - public List<PromptTemplate> getActiveTemplatesByCompanyId(Integer companyId) {  
35 - return promptTemplateRepository.findByCompanyIdAndIsActiveTrue(companyId);  
36 - }  
37 -  
38 - public Optional<PromptTemplate> getTemplateById(Integer id) {  
39 - return promptTemplateRepository.findById(id);  
40 - }  
41 -  
42 - public PromptTemplate saveTemplate(PromptTemplate template) {  
43 - return promptTemplateRepository.save(template);  
44 - }  
45 -  
46 - public void deleteTemplate(Integer id) {  
47 - promptTemplateRepository.deleteById(id);  
48 - }  
49 -}  
1 -// ai/service/UploadedFileService.java  
2 -package com.aigeo.ai.service;  
3 -  
4 -import com.aigeo.ai.entity.UploadedFile;  
5 -import com.aigeo.common.enums.FileStatus;  
6 -import com.aigeo.common.enums.FileType;  
7 -import com.aigeo.ai.repository.UploadedFileRepository;  
8 -import org.springframework.beans.factory.annotation.Autowired;  
9 -import org.springframework.stereotype.Service;  
10 -  
11 -import java.util.List;  
12 -import java.util.Optional;  
13 -  
14 -@Service  
15 -public class UploadedFileService {  
16 -  
17 - @Autowired  
18 - private UploadedFileRepository uploadedFileRepository;  
19 -  
20 - public List<UploadedFile> getAllFiles() {  
21 - return uploadedFileRepository.findAll();  
22 - }  
23 -  
24 - public List<UploadedFile> getFilesByCompanyId(Integer companyId) {  
25 - return uploadedFileRepository.findByCompanyId(companyId);  
26 - }  
27 -  
28 - public List<UploadedFile> getFilesByUserId(Integer userId) {  
29 - return uploadedFileRepository.findByUserId(userId);  
30 - }  
31 -  
32 - public List<UploadedFile> getFilesByCompanyIdAndUserId(Integer companyId, Integer userId) {  
33 - return uploadedFileRepository.findByCompanyIdAndUserId(companyId, userId);  
34 - }  
35 -  
36 - public List<UploadedFile> getFilesByFileType(FileType fileType) {  
37 - return uploadedFileRepository.findByFileType(fileType);  
38 - }  
39 -  
40 - public List<UploadedFile> getFilesByStatus(FileStatus status) {  
41 - return uploadedFileRepository.findByStatus(status);  
42 - }  
43 -  
44 - public List<UploadedFile> getFilesByCompanyIdAndStatus(Integer companyId, FileStatus status) {  
45 - return uploadedFileRepository.findByCompanyIdAndStatus(companyId, status);  
46 - }  
47 -  
48 - public Optional<UploadedFile> getFileById(Integer id) {  
49 - return uploadedFileRepository.findById(id);  
50 - }  
51 -  
52 - public UploadedFile saveFile(UploadedFile file) {  
53 - return uploadedFileRepository.save(file);  
54 - }  
55 -  
56 - public void deleteFile(Integer id) {  
57 - uploadedFileRepository.deleteById(id);  
58 - }  
59 -}  
1 package com.aigeo.article.controller; 1 package com.aigeo.article.controller;
2 2
3 import com.aigeo.article.entity.ArticleGenerationTask; 3 import com.aigeo.article.entity.ArticleGenerationTask;
4 -import com.aigeo.article.service.ArticleGenerationService;  
5 -import com.aigeo.common.enums.TaskStatus;  
6 -import com.aigeo.common.result.Result;  
7 -import com.aigeo.common.result.ResultCode; 4 +import com.aigeo.article.service.ArticleGenerationTaskService;
  5 +import com.aigeo.common.Result;
8 import io.swagger.v3.oas.annotations.Operation; 6 import io.swagger.v3.oas.annotations.Operation;
9 -import io.swagger.v3.oas.annotations.Parameter;  
10 -import io.swagger.v3.oas.annotations.responses.ApiResponse;  
11 -import io.swagger.v3.oas.annotations.responses.ApiResponses;  
12 import io.swagger.v3.oas.annotations.tags.Tag; 7 import io.swagger.v3.oas.annotations.tags.Tag;
13 -import jakarta.validation.Valid;  
14 -import jakarta.validation.constraints.NotNull;  
15 -import lombok.RequiredArgsConstructor;  
16 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
17 -import org.springframework.data.domain.Page;  
18 -import org.springframework.data.domain.PageRequest;  
19 -import org.springframework.data.domain.Pageable;  
20 -import org.springframework.data.domain.Sort; 9 +import org.springframework.beans.factory.annotation.Autowired;
21 import org.springframework.web.bind.annotation.*; 10 import org.springframework.web.bind.annotation.*;
22 11
23 -import java.time.LocalDateTime;  
24 import java.util.List; 12 import java.util.List;
25 13
26 /** 14 /**
27 - * 文章生成任务管理控制器  
28 - *  
29 - * @author AIGEO Team  
30 - * @since 1.0.0 15 + * 文章生成任务控制器
31 */ 16 */
32 -@Tag(name = "文章生成任务管理", description = "AI文章生成任务管理接口,支持任务的创建、查询、更新和删除操作") 17 +@Slf4j
33 @RestController 18 @RestController
34 @RequestMapping("/api/article-tasks") 19 @RequestMapping("/api/article-tasks")
35 -@RequiredArgsConstructor  
36 -@Slf4j 20 +@Tag(name = "文章生成任务管理", description = "文章生成任务相关接口")
37 public class ArticleGenerationTaskController { 21 public class ArticleGenerationTaskController {
38 -  
39 - private final ArticleGenerationService articleGenerationService;  
40 -  
41 - @Operation(  
42 - summary = "分页查询文章生成任务列表",  
43 - description = "支持按任务状态、用户、公司等条件分页查询文章生成任务列表,默认按创建时间倒序排列"  
44 - )  
45 - @ApiResponses(value = {  
46 - @ApiResponse(responseCode = "200", description = "查询成功"),  
47 - @ApiResponse(responseCode = "400", description = "请求参数错误"),  
48 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
49 - })  
50 - @GetMapping("/list")  
51 - public Result<Page<ArticleGenerationTask>> listTasks(  
52 - @Parameter(description = "页码,从0开始", example = "0")  
53 - @RequestParam(defaultValue = "0") int page,  
54 -  
55 - @Parameter(description = "页大小,默认10条", example = "10")  
56 - @RequestParam(defaultValue = "10") int size,  
57 -  
58 - @Parameter(description = "公司ID(可选)")  
59 - @RequestParam(required = false) Integer companyId,  
60 -  
61 - @Parameter(description = "用户ID(可选)")  
62 - @RequestParam(required = false) Integer userId,  
63 -  
64 - @Parameter(description = "任务状态")  
65 - @RequestParam(required = false) TaskStatus status,  
66 -  
67 - @Parameter(description = "文章主题(模糊查询)")  
68 - @RequestParam(required = false) String articleTheme,  
69 -  
70 - @Parameter(description = "开始时间(格式:yyyy-MM-ddTHH:mm:ss)")  
71 - @RequestParam(required = false) String startTime,  
72 -  
73 - @Parameter(description = "结束时间(格式:yyyy-MM-ddTHH:mm:ss)")  
74 - @RequestParam(required = false) String endTime  
75 - ) { 22 +
  23 + @Autowired
  24 + private ArticleGenerationTaskService articleGenerationTaskService;
  25 +
  26 + @PostMapping
  27 + @Operation(summary = "创建文章生成任务", description = "创建新的文章生成任务")
  28 + public Result<ArticleGenerationTask> createTask(@RequestBody ArticleGenerationTask task) {
76 try { 29 try {
77 - Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt"));  
78 - LocalDateTime start = startTime != null ? LocalDateTime.parse(startTime) : null;  
79 - LocalDateTime end = endTime != null ? LocalDateTime.parse(endTime) : null;  
80 -  
81 - Page<ArticleGenerationTask> tasks = articleGenerationService.searchTasks(  
82 - companyId, userId, status, articleTheme, start, end, pageable  
83 - );  
84 - return Result.success("查询成功", tasks); 30 + ArticleGenerationTask savedTask = articleGenerationTaskService.save(task);
  31 + return Result.success("任务创建成功", savedTask);
85 } catch (Exception e) { 32 } catch (Exception e) {
86 - log.error("分页查询文章生成任务列表失败", e);  
87 - return Result.error("查询失败"); 33 + log.error("创建文章生成任务失败", e);
  34 + return Result.error("任务创建失败");
88 } 35 }
89 } 36 }
90 -  
91 - @Operation(  
92 - summary = "获取所有文章生成任务(简化版)",  
93 - description = "获取所有文章生成任务的基本信息,用于下拉选择等场景"  
94 - )  
95 - @ApiResponses(value = {  
96 - @ApiResponse(responseCode = "200", description = "查询成功"),  
97 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
98 - })  
99 - @GetMapping  
100 - public Result<List<ArticleGenerationTask>> getAllTasks() { 37 +
  38 + @GetMapping("/{id}")
  39 + @Operation(summary = "获取文章生成任务详情", description = "根据ID获取文章生成任务详情")
  40 + public Result<ArticleGenerationTask> getTaskById(@PathVariable Integer id) {
101 try { 41 try {
102 - List<ArticleGenerationTask> tasks = articleGenerationService.getAllTasks();  
103 - return Result.success("查询成功", tasks); 42 + return articleGenerationTaskService.findById(id)
  43 + .map(task -> Result.success("查询成功", task))
  44 + .orElse(Result.error("任务不存在"));
104 } catch (Exception e) { 45 } catch (Exception e) {
105 - log.error("获取所有文章生成任务失败", e); 46 + log.error("获取文章生成任务详情失败, id: {}", id, e);
106 return Result.error("查询失败"); 47 return Result.error("查询失败");
107 } 48 }
108 } 49 }
109 -  
110 - @Operation(  
111 - summary = "根据ID查询文章生成任务详情",  
112 - description = "通过任务ID获取文章生成任务的详细信息"  
113 - )  
114 - @ApiResponses(value = {  
115 - @ApiResponse(responseCode = "200", description = "查询成功"),  
116 - @ApiResponse(responseCode = "404", description = "任务不存在"),  
117 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
118 - })  
119 - @GetMapping("/{id}")  
120 - public Result<ArticleGenerationTask> getTaskById(  
121 - @Parameter(description = "任务ID", required = true, example = "1")  
122 - @PathVariable @NotNull Integer id  
123 - ) { 50 +
  51 + @GetMapping
  52 + @Operation(summary = "获取文章生成任务列表", description = "获取所有文章生成任务列表")
  53 + public Result<List<ArticleGenerationTask>> getAllTasks() {
124 try { 54 try {
125 - return articleGenerationService.getTaskById(id)  
126 - .map(task -> Result.success("查询成功", task))  
127 - .orElse(Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在")); 55 + List<ArticleGenerationTask> tasks = articleGenerationTaskService.findAll();
  56 + return Result.success("查询成功", tasks);
128 } catch (Exception e) { 57 } catch (Exception e) {
129 - log.error("根据ID查询文章生成任务详情失败, id: {}", id, e); 58 + log.error("获取文章生成任务列表失败", e);
130 return Result.error("查询失败"); 59 return Result.error("查询失败");
131 } 60 }
132 } 61 }
133 -  
134 - @Operation(  
135 - summary = "根据公司ID查询文章生成任务",  
136 - description = "获取指定公司下的所有文章生成任务"  
137 - )  
138 - @ApiResponses(value = {  
139 - @ApiResponse(responseCode = "200", description = "查询成功"),  
140 - @ApiResponse(responseCode = "404", description = "公司不存在"),  
141 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
142 - }) 62 +
143 @GetMapping("/company/{companyId}") 63 @GetMapping("/company/{companyId}")
144 - public Result<List<ArticleGenerationTask>> getTasksByCompanyId(  
145 - @Parameter(description = "公司ID", required = true, example = "1")  
146 - @PathVariable @NotNull Integer companyId  
147 - ) { 64 + @Operation(summary = "根据公司ID获取文章生成任务列表", description = "根据公司ID获取文章生成任务列表")
  65 + public Result<List<ArticleGenerationTask>> getTasksByCompanyId(@PathVariable Integer companyId) {
148 try { 66 try {
149 - List<ArticleGenerationTask> tasks = articleGenerationService.getTasksByCompanyId(companyId); 67 + List<ArticleGenerationTask> tasks = articleGenerationTaskService.findByCompanyId(companyId);
150 return Result.success("查询成功", tasks); 68 return Result.success("查询成功", tasks);
151 } catch (Exception e) { 69 } catch (Exception e) {
152 - log.error("根据公司ID查询文章生成任务失败, companyId: {}", companyId, e); 70 + log.error("根据公司ID获取文章生成任务列表失败, companyId: {}", companyId, e);
153 return Result.error("查询失败"); 71 return Result.error("查询失败");
154 } 72 }
155 } 73 }
156 -  
157 - @Operation(  
158 - summary = "根据用户ID查询文章生成任务",  
159 - description = "获取指定用户创建的所有文章生成任务"  
160 - )  
161 - @ApiResponses(value = {  
162 - @ApiResponse(responseCode = "200", description = "查询成功"),  
163 - @ApiResponse(responseCode = "404", description = "用户不存在"),  
164 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
165 - }) 74 +
166 @GetMapping("/user/{userId}") 75 @GetMapping("/user/{userId}")
167 - public Result<List<ArticleGenerationTask>> getTasksByUserId(  
168 - @Parameter(description = "用户ID", required = true, example = "1")  
169 - @PathVariable @NotNull Integer userId  
170 - ) { 76 + @Operation(summary = "根据用户ID获取文章生成任务列表", description = "根据用户ID获取文章生成任务列表")
  77 + public Result<List<ArticleGenerationTask>> getTasksByUserId(@PathVariable Integer userId) {
171 try { 78 try {
172 - List<ArticleGenerationTask> tasks = articleGenerationService.getTasksByUserId(userId); 79 + List<ArticleGenerationTask> tasks = articleGenerationTaskService.findByUserId(userId);
173 return Result.success("查询成功", tasks); 80 return Result.success("查询成功", tasks);
174 } catch (Exception e) { 81 } catch (Exception e) {
175 - log.error("根据用户ID查询文章生成任务失败, userId: {}", userId, e); 82 + log.error("根据用户ID获取文章生成任务列表失败, userId: {}", userId, e);
176 return Result.error("查询失败"); 83 return Result.error("查询失败");
177 } 84 }
178 } 85 }
179 -  
180 - @Operation(  
181 - summary = "根据任务状态查询文章生成任务",  
182 - description = "获取指定状态的所有文章生成任务"  
183 - )  
184 - @ApiResponses(value = {  
185 - @ApiResponse(responseCode = "200", description = "查询成功"),  
186 - @ApiResponse(responseCode = "400", description = "请求参数错误"),  
187 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
188 - }) 86 +
189 @GetMapping("/status/{status}") 87 @GetMapping("/status/{status}")
190 - public Result<List<ArticleGenerationTask>> getTasksByStatus(  
191 - @Parameter(description = "任务状态", required = true)  
192 - @PathVariable @NotNull TaskStatus status  
193 - ) { 88 + @Operation(summary = "根据状态获取文章生成任务列表", description = "根据状态获取文章生成任务列表")
  89 + public Result<List<ArticleGenerationTask>> getTasksByStatus(@PathVariable String status) {
194 try { 90 try {
195 - List<ArticleGenerationTask> tasks = articleGenerationService.getTasksByStatus(status); 91 + ArticleGenerationTask.TaskStatus taskStatus = ArticleGenerationTask.TaskStatus.fromCode(status);
  92 + List<ArticleGenerationTask> tasks = articleGenerationTaskService.findByStatus(taskStatus);
196 return Result.success("查询成功", tasks); 93 return Result.success("查询成功", tasks);
197 } catch (Exception e) { 94 } catch (Exception e) {
198 - log.error("根据任务状态查询文章生成任务失败, status: {}", status, e); 95 + log.error("根据状态获取文章生成任务列表失败, status: {}", status, e);
199 return Result.error("查询失败"); 96 return Result.error("查询失败");
200 } 97 }
201 } 98 }
202 -  
203 - @Operation(  
204 - summary = "获取进行中的任务",  
205 - description = "获取所有状态为进行中的文章生成任务"  
206 - )  
207 - @ApiResponses(value = {  
208 - @ApiResponse(responseCode = "200", description = "查询成功"),  
209 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
210 - })  
211 - @GetMapping("/running")  
212 - public Result<List<ArticleGenerationTask>> getRunningTasks(  
213 - @Parameter(description = "公司ID(可选)")  
214 - @RequestParam(required = false) Integer companyId  
215 - ) {  
216 - try {  
217 - List<ArticleGenerationTask> tasks = articleGenerationService.getRunningTasks(companyId);  
218 - return Result.success("查询成功", tasks);  
219 - } catch (Exception e) {  
220 - log.error("获取进行中的任务失败", e);  
221 - return Result.error("查询失败");  
222 - }  
223 - }  
224 -  
225 - @Operation(  
226 - summary = "获取最近完成的任务",  
227 - description = "获取最近完成的文章生成任务列表"  
228 - )  
229 - @ApiResponses(value = {  
230 - @ApiResponse(responseCode = "200", description = "查询成功"),  
231 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
232 - })  
233 - @GetMapping("/recent-completed")  
234 - public Result<List<ArticleGenerationTask>> getRecentCompletedTasks(  
235 - @Parameter(description = "公司ID(可选)")  
236 - @RequestParam(required = false) Integer companyId,  
237 -  
238 - @Parameter(description = "返回数量", example = "10")  
239 - @RequestParam(defaultValue = "10") int limit  
240 - ) {  
241 - try {  
242 - List<ArticleGenerationTask> tasks = articleGenerationService.getRecentCompletedTasks(companyId, limit);  
243 - return Result.success("查询成功", tasks);  
244 - } catch (Exception e) {  
245 - log.error("获取最近完成的任务失败", e);  
246 - return Result.error("查询失败");  
247 - }  
248 - }  
249 -  
250 - @Operation(  
251 - summary = "创建新的文章生成任务",  
252 - description = "创建一个新的文章生成任务,包含生成参数和配置"  
253 - )  
254 - @ApiResponses(value = {  
255 - @ApiResponse(responseCode = "200", description = "创建成功"),  
256 - @ApiResponse(responseCode = "400", description = "请求参数错误"),  
257 - @ApiResponse(responseCode = "429", description = "任务队列已满"),  
258 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
259 - })  
260 - @PostMapping  
261 - public Result<ArticleGenerationTask> createTask(  
262 - @Parameter(description = "文章生成任务信息", required = true)  
263 - @Valid @RequestBody ArticleGenerationTask task  
264 - ) {  
265 - try {  
266 - // 检查任务队列是否已满  
267 - if (articleGenerationService.isTaskQueueFull(task.getCompanyId())) {  
268 - return Result.error(ResultCode.TOO_MANY_REQUESTS, "任务队列已满,请稍后再试");  
269 - }  
270 -  
271 - ArticleGenerationTask savedTask = articleGenerationService.saveTask(task);  
272 - log.info("成功创建文章生成任务: {} (用户ID: {})", savedTask.getArticleTheme(), savedTask.getUserId());  
273 - return Result.success("创建成功", savedTask);  
274 - } catch (Exception e) {  
275 - log.error("创建文章生成任务失败", e);  
276 - return Result.error("创建失败");  
277 - }  
278 - }  
279 -  
280 - @Operation(  
281 - summary = "批量创建文章生成任务",  
282 - description = "批量创建多个文章生成任务"  
283 - )  
284 - @ApiResponses(value = {  
285 - @ApiResponse(responseCode = "200", description = "批量创建成功"),  
286 - @ApiResponse(responseCode = "400", description = "请求参数错误"),  
287 - @ApiResponse(responseCode = "429", description = "任务数量超限"),  
288 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
289 - })  
290 - @PostMapping("/batch")  
291 - public Result<List<ArticleGenerationTask>> batchCreateTasks(  
292 - @Parameter(description = "文章生成任务列表", required = true)  
293 - @Valid @RequestBody List<ArticleGenerationTask> tasks  
294 - ) {  
295 - try {  
296 - if (tasks.size() > 50) {  
297 - return Result.error(ResultCode.BAD_REQUEST, "单次批量创建任务数量不能超过50个");  
298 - }  
299 -  
300 - List<ArticleGenerationTask> savedTasks = articleGenerationService.batchSaveTasks(tasks);  
301 - log.info("成功批量创建文章生成任务,数量: {}", savedTasks.size());  
302 - return Result.success("批量创建成功", savedTasks);  
303 - } catch (Exception e) {  
304 - log.error("批量创建文章生成任务失败", e);  
305 - return Result.error("批量创建失败");  
306 - }  
307 - }  
308 -  
309 - @Operation(  
310 - summary = "更新文章生成任务",  
311 - description = "根据ID更新文章生成任务的详细信息"  
312 - )  
313 - @ApiResponses(value = {  
314 - @ApiResponse(responseCode = "200", description = "更新成功"),  
315 - @ApiResponse(responseCode = "400", description = "请求参数错误"),  
316 - @ApiResponse(responseCode = "404", description = "任务不存在"),  
317 - @ApiResponse(responseCode = "409", description = "任务状态不允许更新"),  
318 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
319 - }) 99 +
320 @PutMapping("/{id}") 100 @PutMapping("/{id}")
321 - public Result<ArticleGenerationTask> updateTask(  
322 - @Parameter(description = "任务ID", required = true, example = "1")  
323 - @PathVariable @NotNull Integer id,  
324 -  
325 - @Parameter(description = "更新的任务信息", required = true)  
326 - @Valid @RequestBody ArticleGenerationTask taskDetails  
327 - ) {  
328 - try {  
329 - return articleGenerationService.getTaskById(id)  
330 - .map(existingTask -> {  
331 - // 检查任务状态是否允许更新  
332 - if (existingTask.getStatus() == TaskStatus.COMPLETED) {  
333 - return Result.<ArticleGenerationTask>error(ResultCode.CONFLICT, "已完成的任务不允许更新");  
334 - }  
335 -  
336 - // 更新字段  
337 - existingTask.setArticleTheme(taskDetails.getArticleTheme());  
338 - existingTask.setStatus(taskDetails.getStatus());  
339 - existingTask.setProgress(taskDetails.getProgress());  
340 -  
341 - ArticleGenerationTask savedTask = articleGenerationService.saveTask(existingTask);  
342 - log.info("成功更新文章生成任务: {}", savedTask.getArticleTheme());  
343 - return Result.success("更新成功", savedTask);  
344 - })  
345 - .orElse(Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在"));  
346 - } catch (Exception e) {  
347 - log.error("更新文章生成任务失败, id: {}", id, e);  
348 - return Result.error("更新失败");  
349 - }  
350 - }  
351 -  
352 - @Operation(  
353 - summary = "启动文章生成任务",  
354 - description = "启动指定的文章生成任务"  
355 - )  
356 - @ApiResponses(value = {  
357 - @ApiResponse(responseCode = "200", description = "启动成功"),  
358 - @ApiResponse(responseCode = "404", description = "任务不存在"),  
359 - @ApiResponse(responseCode = "409", description = "任务状态不允许启动"),  
360 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
361 - })  
362 - @PostMapping("/{id}/start")  
363 - public Result<String> startTask(  
364 - @Parameter(description = "任务ID", required = true, example = "1")  
365 - @PathVariable @NotNull Integer id  
366 - ) {  
367 - try {  
368 - if (!articleGenerationService.existsById(id)) {  
369 - return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");  
370 - }  
371 -  
372 - boolean started = articleGenerationService.startTask(id);  
373 - if (started) {  
374 - log.info("成功启动文章生成任务, id: {}", id);  
375 - return Result.success("任务启动成功");  
376 - } else {  
377 - return Result.error(ResultCode.CONFLICT, "任务状态不允许启动");  
378 - }  
379 - } catch (Exception e) {  
380 - log.error("启动文章生成任务失败, id: {}", id, e);  
381 - return Result.error("启动失败");  
382 - }  
383 - }  
384 -  
385 - @Operation(  
386 - summary = "暂停文章生成任务",  
387 - description = "暂停正在运行的文章生成任务"  
388 - )  
389 - @ApiResponses(value = {  
390 - @ApiResponse(responseCode = "200", description = "暂停成功"),  
391 - @ApiResponse(responseCode = "404", description = "任务不存在"),  
392 - @ApiResponse(responseCode = "409", description = "任务状态不允许暂停"),  
393 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
394 - })  
395 - @PostMapping("/{id}/pause")  
396 - public Result<String> pauseTask(  
397 - @Parameter(description = "任务ID", required = true, example = "1")  
398 - @PathVariable @NotNull Integer id  
399 - ) {  
400 - try {  
401 - if (!articleGenerationService.existsById(id)) {  
402 - return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");  
403 - }  
404 -  
405 - boolean paused = articleGenerationService.pauseTask(id);  
406 - if (paused) {  
407 - log.info("成功暂停文章生成任务, id: {}", id);  
408 - return Result.success("任务暂停成功");  
409 - } else {  
410 - return Result.error(ResultCode.CONFLICT, "任务状态不允许暂停");  
411 - }  
412 - } catch (Exception e) {  
413 - log.error("暂停文章生成任务失败, id: {}", id, e);  
414 - return Result.error("暂停失败");  
415 - }  
416 - }  
417 -  
418 - @Operation(  
419 - summary = "取消文章生成任务",  
420 - description = "取消指定的文章生成任务"  
421 - )  
422 - @ApiResponses(value = {  
423 - @ApiResponse(responseCode = "200", description = "取消成功"),  
424 - @ApiResponse(responseCode = "404", description = "任务不存在"),  
425 - @ApiResponse(responseCode = "409", description = "任务状态不允许取消"),  
426 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
427 - })  
428 - @PostMapping("/{id}/cancel")  
429 - public Result<String> cancelTask(  
430 - @Parameter(description = "任务ID", required = true, example = "1")  
431 - @PathVariable @NotNull Integer id  
432 - ) { 101 + @Operation(summary = "更新文章生成任务", description = "更新文章生成任务信息")
  102 + public Result<ArticleGenerationTask> updateTask(@PathVariable Integer id, @RequestBody ArticleGenerationTask task) {
433 try { 103 try {
434 - if (!articleGenerationService.existsById(id)) {  
435 - return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");  
436 - }  
437 -  
438 - boolean cancelled = articleGenerationService.cancelTask(id);  
439 - if (cancelled) {  
440 - log.info("成功取消文章生成任务, id: {}", id);  
441 - return Result.success("任务取消成功");  
442 - } else {  
443 - return Result.error(ResultCode.CONFLICT, "任务状态不允许取消"); 104 + if (!articleGenerationTaskService.findById(id).isPresent()) {
  105 + return Result.error("任务不存在");
444 } 106 }
  107 + task.setId(id);
  108 + ArticleGenerationTask updatedTask = articleGenerationTaskService.save(task);
  109 + return Result.success("任务更新成功", updatedTask);
445 } catch (Exception e) { 110 } catch (Exception e) {
446 - log.error("取消文章生成任务失败, id: {}", id, e);  
447 - return Result.error("取消失败");  
448 - }  
449 - }  
450 -  
451 - @Operation(  
452 - summary = "重试失败的文章生成任务",  
453 - description = "重新启动失败的文章生成任务"  
454 - )  
455 - @ApiResponses(value = {  
456 - @ApiResponse(responseCode = "200", description = "重试成功"),  
457 - @ApiResponse(responseCode = "404", description = "任务不存在"),  
458 - @ApiResponse(responseCode = "409", description = "任务状态不允许重试"),  
459 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
460 - })  
461 - @PostMapping("/{id}/retry")  
462 - public Result<String> retryTask(  
463 - @Parameter(description = "任务ID", required = true, example = "1")  
464 - @PathVariable @NotNull Integer id  
465 - ) {  
466 - try {  
467 - if (!articleGenerationService.existsById(id)) {  
468 - return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");  
469 - }  
470 -  
471 - boolean retried = articleGenerationService.retryTask(id);  
472 - if (retried) {  
473 - log.info("成功重试文章生成任务, id: {}", id);  
474 - return Result.success("任务重试成功");  
475 - } else {  
476 - return Result.error(ResultCode.CONFLICT, "任务状态不允许重试");  
477 - }  
478 - } catch (Exception e) {  
479 - log.error("重试文章生成任务失败, id: {}", id, e);  
480 - return Result.error("重试失败"); 111 + log.error("更新文章生成任务失败, id: {}", id, e);
  112 + return Result.error("任务更新失败");
481 } 113 }
482 } 114 }
483 -  
484 - @Operation(  
485 - summary = "删除文章生成任务",  
486 - description = "根据ID删除文章生成任务记录(软删除)"  
487 - )  
488 - @ApiResponses(value = {  
489 - @ApiResponse(responseCode = "200", description = "删除成功"),  
490 - @ApiResponse(responseCode = "404", description = "任务不存在"),  
491 - @ApiResponse(responseCode = "409", description = "任务正在运行,无法删除"),  
492 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
493 - }) 115 +
494 @DeleteMapping("/{id}") 116 @DeleteMapping("/{id}")
495 - public Result<String> deleteTask(  
496 - @Parameter(description = "任务ID", required = true, example = "1")  
497 - @PathVariable @NotNull Integer id  
498 - ) { 117 + @Operation(summary = "删除文章生成任务", description = "删除指定ID的文章生成任务")
  118 + public Result<String> deleteTask(@PathVariable Integer id) {
499 try { 119 try {
500 - if (!articleGenerationService.existsById(id)) {  
501 - return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");  
502 - }  
503 -  
504 - // 检查任务是否正在运行  
505 - if (articleGenerationService.isTaskRunning(id)) {  
506 - return Result.error(ResultCode.CONFLICT, "任务正在运行,无法删除"); 120 + if (!articleGenerationTaskService.findById(id).isPresent()) {
  121 + return Result.error("任务不存在");
507 } 122 }
508 -  
509 - articleGenerationService.deleteTask(id);  
510 - log.info("成功删除文章生成任务, id: {}", id);  
511 - return Result.success("删除成功"); 123 + articleGenerationTaskService.deleteById(id);
  124 + return Result.success("任务删除成功");
512 } catch (Exception e) { 125 } catch (Exception e) {
513 log.error("删除文章生成任务失败, id: {}", id, e); 126 log.error("删除文章生成任务失败, id: {}", id, e);
514 - return Result.error("删除失败");  
515 - }  
516 - }  
517 -  
518 - @Operation(  
519 - summary = "获取任务执行日志",  
520 - description = "获取文章生成任务的执行日志信息"  
521 - )  
522 - @ApiResponses(value = {  
523 - @ApiResponse(responseCode = "200", description = "查询成功"),  
524 - @ApiResponse(responseCode = "404", description = "任务不存在"),  
525 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
526 - })  
527 - @GetMapping("/{id}/logs")  
528 - public Result<List<String>> getTaskLogs(  
529 - @Parameter(description = "任务ID", required = true, example = "1")  
530 - @PathVariable @NotNull Integer id,  
531 -  
532 - @Parameter(description = "日志级别(INFO/WARN/ERROR)")  
533 - @RequestParam(required = false) String level,  
534 -  
535 - @Parameter(description = "最大返回行数", example = "100")  
536 - @RequestParam(defaultValue = "100") int limit  
537 - ) {  
538 - try {  
539 - if (!articleGenerationService.existsById(id)) {  
540 - return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");  
541 - }  
542 -  
543 - List<String> logs = articleGenerationService.getTaskLogs(id, level, limit);  
544 - return Result.success("查询成功", logs);  
545 - } catch (Exception e) {  
546 - log.error("获取任务执行日志失败, id: {}", id, e);  
547 - return Result.error("查询失败");  
548 - }  
549 - }  
550 -  
551 - @Operation(  
552 - summary = "获取任务统计信息",  
553 - description = "获取文章生成任务相关的统计数据"  
554 - )  
555 - @ApiResponses(value = {  
556 - @ApiResponse(responseCode = "200", description = "统计成功"),  
557 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
558 - })  
559 - @GetMapping("/statistics")  
560 - public Result<TaskStatistics> getTaskStatistics(  
561 - @Parameter(description = "公司ID(可选)")  
562 - @RequestParam(required = false) Integer companyId,  
563 -  
564 - @Parameter(description = "统计时间范围(天数)", example = "30")  
565 - @RequestParam(defaultValue = "30") int days  
566 - ) {  
567 - try {  
568 - ArticleGenerationService.TaskStatistics serviceStats = articleGenerationService.getTaskStatistics(companyId, days);  
569 - TaskStatistics statistics = new TaskStatistics(  
570 - serviceStats.getTotalTasks(),  
571 - serviceStats.getPendingTasks(),  
572 - serviceStats.getRunningTasks(),  
573 - serviceStats.getCompletedTasks(),  
574 - serviceStats.getFailedTasks(),  
575 - serviceStats.getSuccessRate(),  
576 - serviceStats.getAverageExecutionTime()  
577 - );  
578 - return Result.success("统计成功", statistics);  
579 - } catch (Exception e) {  
580 - log.error("获取任务统计信息失败", e);  
581 - return Result.error("统计失败");  
582 - }  
583 - }  
584 -  
585 - @Operation(  
586 - summary = "清理已完成的任务",  
587 - description = "清理指定天数前完成的文章生成任务"  
588 - )  
589 - @ApiResponses(value = {  
590 - @ApiResponse(responseCode = "200", description = "清理成功"),  
591 - @ApiResponse(responseCode = "400", description = "请求参数错误"),  
592 - @ApiResponse(responseCode = "500", description = "服务器内部错误")  
593 - })  
594 - @DeleteMapping("/cleanup")  
595 - public Result<String> cleanupCompletedTasks(  
596 - @Parameter(description = "保留天数", required = true, example = "30")  
597 - @RequestParam @NotNull Integer retentionDays  
598 - ) {  
599 - try {  
600 - if (retentionDays < 1) {  
601 - return Result.error(ResultCode.BAD_REQUEST, "保留天数必须大于0");  
602 - }  
603 -  
604 - int cleanedCount = articleGenerationService.cleanupCompletedTasks(retentionDays);  
605 - log.info("清理已完成的任务成功,清理数量: {}", cleanedCount);  
606 - return Result.success(String.format("成功清理 %d 个已完成的任务", cleanedCount));  
607 - } catch (Exception e) {  
608 - log.error("清理已完成的任务失败", e);  
609 - return Result.error("清理失败");  
610 - }  
611 - }  
612 -  
613 - /**  
614 - * 任务统计信息DTO  
615 - */  
616 - public static class TaskStatistics {  
617 - private long totalTasks;  
618 - private long pendingTasks;  
619 - private long runningTasks;  
620 - private long completedTasks;  
621 - private long failedTasks;  
622 - private double successRate;  
623 - private double averageExecutionTime;  
624 -  
625 - // constructors, getters and setters  
626 - public TaskStatistics(long totalTasks, long pendingTasks, long runningTasks,  
627 - long completedTasks, long failedTasks, double successRate,  
628 - double averageExecutionTime) {  
629 - this.totalTasks = totalTasks;  
630 - this.pendingTasks = pendingTasks;  
631 - this.runningTasks = runningTasks;  
632 - this.completedTasks = completedTasks;  
633 - this.failedTasks = failedTasks;  
634 - this.successRate = successRate;  
635 - this.averageExecutionTime = averageExecutionTime; 127 + return Result.error("任务删除失败");
636 } 128 }
637 -  
638 - // getters and setters  
639 - public long getTotalTasks() { return totalTasks; }  
640 - public void setTotalTasks(long totalTasks) { this.totalTasks = totalTasks; }  
641 -  
642 - public long getPendingTasks() { return pendingTasks; }  
643 - public void setPendingTasks(long pendingTasks) { this.pendingTasks = pendingTasks; }  
644 -  
645 - public long getRunningTasks() { return runningTasks; }  
646 - public void setRunningTasks(long runningTasks) { this.runningTasks = runningTasks; }  
647 -  
648 - public long getCompletedTasks() { return completedTasks; }  
649 - public void setCompletedTasks(long completedTasks) { this.completedTasks = completedTasks; }  
650 -  
651 - public long getFailedTasks() { return failedTasks; }  
652 - public void setFailedTasks(long failedTasks) { this.failedTasks = failedTasks; }  
653 -  
654 - public double getSuccessRate() { return successRate; }  
655 - public void setSuccessRate(double successRate) { this.successRate = successRate; }  
656 -  
657 - public double getAverageExecutionTime() { return averageExecutionTime; }  
658 - public void setAverageExecutionTime(double averageExecutionTime) { this.averageExecutionTime = averageExecutionTime; }  
659 } 129 }
660 } 130 }
1 -package com.aigeo.article.entity;  
2 -  
3 -import com.aigeo.common.enums.AiTasteLevel;  
4 -import com.fasterxml.jackson.annotation.JsonFormat;  
5 -import lombok.AllArgsConstructor;  
6 -import lombok.Builder;  
7 -import lombok.Data;  
8 -import lombok.NoArgsConstructor;  
9 -import org.hibernate.annotations.CreationTimestamp;  
10 -import org.hibernate.annotations.UpdateTimestamp;  
11 -  
12 -import jakarta.persistence.*;  
13 -import jakarta.validation.constraints.NotBlank;  
14 -import jakarta.validation.constraints.NotNull;  
15 -import java.time.LocalDateTime;  
16 -  
17 -/**  
18 - * 文章生成配置实体类  
19 - * 对应数据库表:ai_article_generation_configs  
20 - *  
21 - * 用于存储文章生成的各种配置参数,包括:  
22 - * - AI写作风格和复杂度设置  
23 - * - 文章结构和长度配置  
24 - * - SEO优化参数设置  
25 - * - 输出格式和样式配置  
26 - *  
27 - * @author AIGEO Team  
28 - * @since 1.0.0  
29 - */  
30 -@Data  
31 -@Entity  
32 -@Builder  
33 -@NoArgsConstructor  
34 -@AllArgsConstructor  
35 -@Table(name = "ai_article_generation_configs")  
36 -public class ArticleGenerationConfig {  
37 -  
38 - /**  
39 - * 主键ID  
40 - */  
41 - @Id  
42 - @GeneratedValue(strategy = GenerationType.IDENTITY)  
43 - @Column(name = "id", nullable = false, updatable = false)  
44 - private Integer id;  
45 -  
46 - /**  
47 - * 公司ID(多租户字段)  
48 - */  
49 - @NotNull(message = "公司ID不能为空")  
50 - @Column(name = "company_id", nullable = false)  
51 - private Integer companyId;  
52 -  
53 - /**  
54 - * 配置名称  
55 - */  
56 - @NotBlank(message = "配置名称不能为空")  
57 - @Column(name = "config_name", nullable = false, length = 100)  
58 - private String configName;  
59 -  
60 - /**  
61 - * AI写作风格等级  
62 - */  
63 - @Enumerated(EnumType.STRING)  
64 - @Column(name = "ai_taste_level")  
65 - @Builder.Default  
66 - private AiTasteLevel aiTasteLevel = AiTasteLevel.JUNIOR_HIGH;  
67 -  
68 - /**  
69 - * 目标字数(范围的最小值)  
70 - */  
71 - @Column(name = "target_word_count_min")  
72 - @Builder.Default  
73 - private Integer targetWordCountMin = 800;  
74 -  
75 - /**  
76 - * 目标字数(范围的最大值)  
77 - */  
78 - @Column(name = "target_word_count_max")  
79 - @Builder.Default  
80 - private Integer targetWordCountMax = 1500;  
81 -  
82 - /**  
83 - * 是否包含FAQ部分  
84 - */  
85 - @Column(name = "include_faq")  
86 - @Builder.Default  
87 - private Boolean includeFaq = true;  
88 -  
89 - /**  
90 - * 是否生成结构化数据(Schema.org)  
91 - */  
92 - @Column(name = "generate_structured_data")  
93 - @Builder.Default  
94 - private Boolean generateStructuredData = true;  
95 -  
96 - /**  
97 - * 是否包含相关链接  
98 - */  
99 - @Column(name = "include_related_links")  
100 - @Builder.Default  
101 - private Boolean includeRelatedLinks = true;  
102 -  
103 - /**  
104 - * SEO标题模板  
105 - */  
106 - @Column(name = "seo_title_template", length = 500)  
107 - private String seoTitleTemplate;  
108 -  
109 - /**  
110 - * SEO描述模板  
111 - */  
112 - @Column(name = "seo_description_template", length = 500)  
113 - private String seoDescriptionTemplate;  
114 -  
115 - /**  
116 - * 文章标签模板(用逗号分隔)  
117 - */  
118 - @Column(name = "article_tags_template", length = 500)  
119 - private String articleTagsTemplate;  
120 -  
121 - /**  
122 - * 自定义提示词  
123 - */  
124 - @Column(name = "custom_prompt", columnDefinition = "TEXT")  
125 - private String customPrompt;  
126 -  
127 - /**  
128 - * 语言设置  
129 - */  
130 - @Column(name = "language", length = 10)  
131 - @Builder.Default  
132 - private String language = "zh-CN";  
133 -  
134 - /**  
135 - * 输出格式(markdown, html等)  
136 - */  
137 - @Column(name = "output_format", length = 20)  
138 - @Builder.Default  
139 - private String outputFormat = "markdown";  
140 -  
141 - /**  
142 - * 是否为默认配置  
143 - */  
144 - @Column(name = "is_default")  
145 - @Builder.Default  
146 - private Boolean isDefault = false;  
147 -  
148 - /**  
149 - * 是否启用  
150 - */  
151 - @Column(name = "is_active")  
152 - @Builder.Default  
153 - private Boolean isActive = true;  
154 -  
155 - /**  
156 - * 配置描述  
157 - */  
158 - @Column(name = "description")  
159 - private String description;  
160 -  
161 - /**  
162 - * 创建时间  
163 - */  
164 - @CreationTimestamp  
165 - @Column(name = "created_at", updatable = false)  
166 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
167 - private LocalDateTime createdAt;  
168 -  
169 - /**  
170 - * 更新时间  
171 - */  
172 - @UpdateTimestamp  
173 - @Column(name = "updated_at")  
174 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
175 - private LocalDateTime updatedAt;  
176 -  
177 - /**  
178 - * 实体创建前的处理  
179 - */  
180 - @PrePersist  
181 - protected void onCreate() {  
182 - if (isActive == null) isActive = true;  
183 - if (isDefault == null) isDefault = false;  
184 - if (includeFaq == null) includeFaq = true;  
185 - if (generateStructuredData == null) generateStructuredData = true;  
186 - if (includeRelatedLinks == null) includeRelatedLinks = true;  
187 - if (aiTasteLevel == null) aiTasteLevel = AiTasteLevel.JUNIOR_HIGH;  
188 - if (language == null) language = "zh-CN";  
189 - if (outputFormat == null) outputFormat = "markdown";  
190 - if (targetWordCountMin == null) targetWordCountMin = 800;  
191 - if (targetWordCountMax == null) targetWordCountMax = 1500;  
192 - }  
193 -  
194 - /**  
195 - * 获取目标字数范围描述  
196 - */  
197 - public String getWordCountRange() {  
198 - return targetWordCountMin + "-" + targetWordCountMax + "字";  
199 - }  
200 -  
201 - /**  
202 - * 检查是否为完整配置(包含所有必要参数)  
203 - */  
204 - public boolean isComplete() {  
205 - return configName != null && !configName.trim().isEmpty()  
206 - && aiTasteLevel != null  
207 - && targetWordCountMin != null && targetWordCountMin > 0  
208 - && targetWordCountMax != null && targetWordCountMax > targetWordCountMin;  
209 - }  
210 -  
211 - /**  
212 - * 获取配置的复杂度评分(1-5分)  
213 - */  
214 - public int getComplexityScore() {  
215 - int score = aiTasteLevel != null ? aiTasteLevel.getComplexityLevel() : 2;  
216 -  
217 - // 根据配置的复杂程度调整评分  
218 - if (Boolean.TRUE.equals(generateStructuredData)) score += 1;  
219 - if (Boolean.TRUE.equals(includeFaq)) score += 1;  
220 - if (customPrompt != null && !customPrompt.trim().isEmpty()) score += 1;  
221 -  
222 - return Math.min(score, 5); // 最高5分  
223 - }  
224 -}  
1 package com.aigeo.article.entity; 1 package com.aigeo.article.entity;
2 2
3 -import com.aigeo.common.enums.TaskStatus;  
4 -import com.aigeo.common.enums.AiTasteLevel;  
5 -import com.fasterxml.jackson.annotation.JsonFormat;  
6 -import lombok.AllArgsConstructor;  
7 -import lombok.Builder; 3 +import jakarta.persistence.*;
8 import lombok.Data; 4 import lombok.Data;
9 -import lombok.NoArgsConstructor;  
10 -import org.hibernate.annotations.CreationTimestamp;  
11 -import org.hibernate.annotations.UpdateTimestamp;  
12 5
13 -import jakarta.persistence.*;  
14 -import jakarta.validation.constraints.NotBlank;  
15 -import jakarta.validation.constraints.NotNull;  
16 import java.time.LocalDateTime; 6 import java.time.LocalDateTime;
17 7
18 /** 8 /**
19 * 文章生成任务实体类 9 * 文章生成任务实体类
20 - * 对应数据库表:ai_article_generation_tasks  
21 - *  
22 - * 用于存储AI文章生成任务信息,包括:  
23 - * - 任务配置和参数设置  
24 - * - 执行状态和进度跟踪  
25 - * - 参考资料和知识来源  
26 - * - 生成结果和错误信息  
27 - *  
28 - * @author AIGEO Team  
29 - * @since 1.0.0  
30 */ 10 */
31 @Data 11 @Data
32 @Entity 12 @Entity
33 -@Builder  
34 -@NoArgsConstructor  
35 -@AllArgsConstructor  
36 -@Table(name = "ai_article_generation_tasks", indexes = {  
37 - @Index(name = "idx_tasks_company_status", columnList = "company_id, status"),  
38 - @Index(name = "idx_tasks_user_id", columnList = "user_id"),  
39 - @Index(name = "idx_tasks_created_at", columnList = "created_at DESC")  
40 -}) 13 +@Table(name = "ai_article_generation_tasks")
41 public class ArticleGenerationTask { 14 public class ArticleGenerationTask {
42 -  
43 - /**  
44 - * 主键ID  
45 - */ 15 +
46 @Id 16 @Id
47 @GeneratedValue(strategy = GenerationType.IDENTITY) 17 @GeneratedValue(strategy = GenerationType.IDENTITY)
48 - @Column(name = "id", nullable = false, updatable = false)  
49 private Integer id; 18 private Integer id;
50 19
51 - /**  
52 - * 公司ID(多租户字段)  
53 - */  
54 - @NotNull(message = "公司ID不能为空")  
55 - @Column(name = "company_id", nullable = false) 20 + @Column(name = "company_id")
56 private Integer companyId; 21 private Integer companyId;
57 22
58 - /**  
59 - * 创建用户ID  
60 - */  
61 - @NotNull(message = "用户ID不能为空")  
62 - @Column(name = "user_id", nullable = false) 23 + @Column(name = "user_id")
63 private Integer userId; 24 private Integer userId;
64 25
65 - /**  
66 - * 生成配置ID  
67 - */  
68 @Column(name = "config_id") 26 @Column(name = "config_id")
69 private Integer configId; 27 private Integer configId;
70 28
71 - /**  
72 - * 关键词ID  
73 - */  
74 - @Column(name = "keyword_id")  
75 - private Integer keywordId;  
76 -  
77 - /**  
78 - * 文章主题  
79 - */  
80 - @NotBlank(message = "文章主题不能为空")  
81 - @Column(name = "article_theme", nullable = false, length = 500) 29 + @Column(name = "article_theme")
82 private String articleTheme; 30 private String articleTheme;
83 31
84 - /**  
85 - * 关联话题ID列表(逗号分隔)  
86 - */  
87 - @Column(name = "topic_ids", length = 500) 32 + @Column(name = "topic_ids")
88 private String topicIds; 33 private String topicIds;
89 34
90 - /**  
91 - * 参考资料URL列表(逗号分隔)  
92 - */  
93 - @Column(name = "reference_urls", columnDefinition = "TEXT") 35 + @Column(name = "reference_urls")
94 private String referenceUrls; 36 private String referenceUrls;
95 37
96 - /**  
97 - * 参考内容  
98 - */  
99 - @Column(name = "reference_content", columnDefinition = "LONGTEXT") 38 + @Column(name = "reference_content")
100 private String referenceContent; 39 private String referenceContent;
101 40
102 - /**  
103 - * AI写作风格等级  
104 - */  
105 - @Enumerated(EnumType.STRING)  
106 - @Column(name = "ai_taste_level")  
107 - @Builder.Default  
108 - private AiTasteLevel aiTasteLevel = AiTasteLevel.JUNIOR_HIGH;  
109 -  
110 - /**  
111 - * 任务状态  
112 - */  
113 @Enumerated(EnumType.STRING) 41 @Enumerated(EnumType.STRING)
114 @Column(name = "status") 42 @Column(name = "status")
115 - @Builder.Default  
116 - private TaskStatus status = TaskStatus.PENDING; 43 + private TaskStatus status;
117 44
118 - /**  
119 - * 任务进度(0-100)  
120 - */  
121 @Column(name = "progress") 45 @Column(name = "progress")
122 - @Builder.Default  
123 - private Integer progress = 0; 46 + private Byte progress;
124 47
125 - /**  
126 - * 错误信息  
127 - */  
128 - @Column(name = "error_message", length = 2000) 48 + @Column(name = "error_message")
129 private String errorMessage; 49 private String errorMessage;
130 50
131 - /**  
132 - * 生成的文章ID  
133 - */  
134 - @Column(name = "generated_article_id")  
135 - private Integer generatedArticleId;  
136 -  
137 - /**  
138 - * AI配置ID  
139 - */  
140 @Column(name = "dify_api_config_id") 51 @Column(name = "dify_api_config_id")
141 private Integer difyApiConfigId; 52 private Integer difyApiConfigId;
142 53
143 - /**  
144 - * 提示模板ID  
145 - */  
146 @Column(name = "prompt_template_id") 54 @Column(name = "prompt_template_id")
147 private Integer promptTemplateId; 55 private Integer promptTemplateId;
148 56
149 - /**  
150 - * 目标字数  
151 - */  
152 - @Column(name = "target_word_count")  
153 - @Builder.Default  
154 - private Integer targetWordCount = 1000;  
155 -  
156 - /**  
157 - * 生成语言  
158 - */  
159 - @Column(name = "language", length = 10)  
160 - @Builder.Default  
161 - private String language = "zh-CN";  
162 -  
163 - /**  
164 - * 是否包含FAQ  
165 - */  
166 - @Column(name = "include_faq")  
167 - @Builder.Default  
168 - private Boolean includeFaq = false;  
169 -  
170 - /**  
171 - * 是否生成SEO数据  
172 - */  
173 - @Column(name = "generate_seo")  
174 - @Builder.Default  
175 - private Boolean generateSeo = true;  
176 -  
177 - /**  
178 - * 执行时长(毫秒)  
179 - */  
180 - @Column(name = "execution_time_ms")  
181 - private Long executionTimeMs;  
182 -  
183 - /**  
184 - * 重试次数  
185 - */  
186 - @Column(name = "retry_count")  
187 - @Builder.Default  
188 - private Integer retryCount = 0;  
189 -  
190 - /**  
191 - * 最大重试次数  
192 - */  
193 - @Column(name = "max_retries")  
194 - @Builder.Default  
195 - private Integer maxRetries = 3;  
196 -  
197 - /**  
198 - * 任务优先级(1-10,数字越大优先级越高)  
199 - */  
200 - @Column(name = "priority")  
201 - @Builder.Default  
202 - private Integer priority = 5;  
203 -  
204 - /**  
205 - * 创建时间  
206 - */  
207 - @CreationTimestamp  
208 - @Column(name = "created_at", updatable = false)  
209 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 57 + @Column(name = "created_at")
210 private LocalDateTime createdAt; 58 private LocalDateTime createdAt;
211 59
212 - /**  
213 - * 开始执行时间  
214 - */  
215 - @Column(name = "started_at")  
216 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
217 - private LocalDateTime startedAt;  
218 -  
219 - /**  
220 - * 完成时间  
221 - */  
222 @Column(name = "completed_at") 60 @Column(name = "completed_at")
223 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
224 private LocalDateTime completedAt; 61 private LocalDateTime completedAt;
225 62
226 - /**  
227 - * 更新时间  
228 - */  
229 - @UpdateTimestamp  
230 - @Column(name = "updated_at")  
231 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
232 - private LocalDateTime updatedAt;  
233 -  
234 - /**  
235 - * 实体创建前的处理  
236 - */  
237 @PrePersist 63 @PrePersist
238 protected void onCreate() { 64 protected void onCreate() {
239 - if (status == null) status = TaskStatus.PENDING;  
240 - if (progress == null) progress = 0;  
241 - if (aiTasteLevel == null) aiTasteLevel = AiTasteLevel.JUNIOR_HIGH;  
242 - if (targetWordCount == null) targetWordCount = 1000;  
243 - if (language == null) language = "zh-CN";  
244 - if (includeFaq == null) includeFaq = false;  
245 - if (generateSeo == null) generateSeo = true;  
246 - if (retryCount == null) retryCount = 0;  
247 - if (maxRetries == null) maxRetries = 3;  
248 - if (priority == null) priority = 5;  
249 - }  
250 -  
251 - /**  
252 - * 检查任务是否已完成  
253 - */  
254 - public boolean isCompleted() {  
255 - return status == TaskStatus.COMPLETED || status == TaskStatus.FAILED || status == TaskStatus.CANCELLED;  
256 - }  
257 -  
258 - /**  
259 - * 检查任务是否正在运行  
260 - */  
261 - public boolean isRunning() {  
262 - return status == TaskStatus.PROCESSING;  
263 - }  
264 -  
265 - /**  
266 - * 检查是否可以重试  
267 - */  
268 - public boolean canRetry() {  
269 - return status == TaskStatus.FAILED && retryCount < maxRetries;  
270 - }  
271 -  
272 - /**  
273 - * 检查是否可以取消  
274 - */  
275 - public boolean isCancellable() {  
276 - return status == TaskStatus.PENDING || status == TaskStatus.PROCESSING;  
277 - }  
278 -  
279 - /**  
280 - * 开始任务执行  
281 - */  
282 - public void start() {  
283 - this.status = TaskStatus.PROCESSING;  
284 - this.startedAt = LocalDateTime.now();  
285 - this.progress = 0;  
286 - }  
287 -  
288 - /**  
289 - * 完成任务  
290 - */  
291 - public void complete(Integer articleId) {  
292 - this.status = TaskStatus.COMPLETED;  
293 - this.completedAt = LocalDateTime.now();  
294 - this.progress = 100;  
295 - this.generatedArticleId = articleId;  
296 -  
297 - if (startedAt != null) {  
298 - this.executionTimeMs = java.time.Duration.between(startedAt, completedAt).toMillis(); 65 + createdAt = LocalDateTime.now();
  66 + if (status == null) {
  67 + status = TaskStatus.PENDING;
299 } 68 }
300 - }  
301 -  
302 - /**  
303 - * 任务失败  
304 - */  
305 - public void fail(String errorMessage) {  
306 - this.status = TaskStatus.FAILED;  
307 - this.completedAt = LocalDateTime.now();  
308 - this.errorMessage = errorMessage;  
309 -  
310 - if (startedAt != null) {  
311 - this.executionTimeMs = java.time.Duration.between(startedAt, completedAt).toMillis(); 69 + if (progress == null) {
  70 + progress = 0;
312 } 71 }
313 } 72 }
314 73
315 /** 74 /**
316 - * 取消任务 75 + * 任务状态枚举
317 */ 76 */
318 - public void cancel(String reason) {  
319 - this.status = TaskStatus.CANCELLED;  
320 - this.completedAt = LocalDateTime.now();  
321 - this.errorMessage = "任务已取消: " + reason;  
322 - } 77 + public enum TaskStatus {
  78 + PENDING("pending"), // 待处理
  79 + PROCESSING("processing"), // 处理中
  80 + COMPLETED("completed"), // 已完成
  81 + FAILED("failed"); // 失败
323 82
324 - /**  
325 - * 增加重试次数  
326 - */  
327 - public void incrementRetryCount() {  
328 - this.retryCount = (retryCount == null ? 0 : retryCount) + 1;  
329 - } 83 + private final String code;
330 84
331 - /**  
332 - * 更新进度  
333 - */  
334 - public void updateProgress(Integer progress) {  
335 - if (progress >= 0 && progress <= 100) {  
336 - this.progress = progress; 85 + TaskStatus(String code) {
  86 + this.code = code;
337 } 87 }
338 - }  
339 88
340 - /**  
341 - * 获取任务执行时长(秒)  
342 - */  
343 - public Long getExecutionTimeSeconds() {  
344 - if (executionTimeMs == null) return null;  
345 - return executionTimeMs / 1000;  
346 - } 89 + public String getCode() {
  90 + return code;
  91 + }
347 92
348 - /**  
349 - * 获取任务优先级描述  
350 - */  
351 - public String getPriorityDescription() {  
352 - if (priority == null) return "普通";  
353 -  
354 - if (priority <= 3) return "低";  
355 - if (priority <= 7) return "普通";  
356 - return "高"; 93 + public static TaskStatus fromCode(String code) {
  94 + for (TaskStatus status : TaskStatus.values()) {
  95 + if (status.getCode().equals(code)) {
  96 + return status;
  97 + }
  98 + }
  99 + throw new IllegalArgumentException("未知的任务状态: " + code);
  100 + }
357 } 101 }
358 -} 102 +}
1 -package com.aigeo.article.entity;  
2 -  
3 -import com.fasterxml.jackson.annotation.JsonFormat;  
4 -import lombok.AllArgsConstructor;  
5 -import lombok.Builder;  
6 -import lombok.Data;  
7 -import lombok.NoArgsConstructor;  
8 -import org.hibernate.annotations.CreationTimestamp;  
9 -import org.hibernate.annotations.UpdateTimestamp;  
10 -  
11 -import jakarta.persistence.*;  
12 -import jakarta.validation.constraints.NotBlank;  
13 -import jakarta.validation.constraints.NotNull;  
14 -import java.time.LocalDateTime;  
15 -  
16 -/**  
17 - * 文章类型实体类  
18 - * 对应数据库表:ai_article_types  
19 - *  
20 - * 用于定义不同类型的文章分类,如:  
21 - * - 新闻资讯类文章  
22 - * - 产品介绍类文章  
23 - * - 教程指南类文章  
24 - * - SEO优化类文章  
25 - * - 行业分析类文章  
26 - *  
27 - * @author AIGEO Team  
28 - * @since 1.0.0  
29 - */  
30 -@Data  
31 -@Entity  
32 -@Builder  
33 -@NoArgsConstructor  
34 -@AllArgsConstructor  
35 -@Table(name = "ai_article_types", indexes = {  
36 - @Index(name = "uk_article_types_company_name", columnList = "company_id, name", unique = true)  
37 -})  
38 -public class ArticleType {  
39 -  
40 - /**  
41 - * 主键ID  
42 - */  
43 - @Id  
44 - @GeneratedValue(strategy = GenerationType.IDENTITY)  
45 - @Column(name = "id", nullable = false, updatable = false)  
46 - private Integer id;  
47 -  
48 - /**  
49 - * 公司ID(多租户字段)  
50 - */  
51 - @NotNull(message = "公司ID不能为空")  
52 - @Column(name = "company_id", nullable = false)  
53 - private Integer companyId;  
54 -  
55 - /**  
56 - * 文章类型名称  
57 - */  
58 - @NotBlank(message = "文章类型名称不能为空")  
59 - @Column(name = "name", nullable = false, length = 100)  
60 - private String name;  
61 -  
62 - /**  
63 - * 类型描述  
64 - */  
65 - @Column(name = "description", length = 500)  
66 - private String description;  
67 -  
68 - /**  
69 - * 类型标识代码(英文标识,用于程序识别)  
70 - */  
71 - @Column(name = "code", length = 50)  
72 - private String code;  
73 -  
74 - /**  
75 - * 默认标题模板  
76 - */  
77 - @Column(name = "default_title_template", length = 500)  
78 - private String defaultTitleTemplate;  
79 -  
80 - /**  
81 - * 默认内容模板  
82 - */  
83 - @Column(name = "default_content_template", columnDefinition = "TEXT")  
84 - private String defaultContentTemplate;  
85 -  
86 - /**  
87 - * 默认标签(逗号分隔)  
88 - */  
89 - @Column(name = "default_tags", length = 500)  
90 - private String defaultTags;  
91 -  
92 - /**  
93 - * 推荐字数范围(最小值)  
94 - */  
95 - @Column(name = "recommended_word_count_min")  
96 - @Builder.Default  
97 - private Integer recommendedWordCountMin = 800;  
98 -  
99 - /**  
100 - * 推荐字数范围(最大值)  
101 - */  
102 - @Column(name = "recommended_word_count_max")  
103 - @Builder.Default  
104 - private Integer recommendedWordCountMax = 1500;  
105 -  
106 - /**  
107 - * SEO权重(影响搜索引擎优化的重要性,1-10分)  
108 - */  
109 - @Column(name = "seo_weight")  
110 - @Builder.Default  
111 - private Integer seoWeight = 5;  
112 -  
113 - /**  
114 - * 是否需要FAQ部分  
115 - */  
116 - @Column(name = "requires_faq")  
117 - @Builder.Default  
118 - private Boolean requiresFaq = false;  
119 -  
120 - /**  
121 - * 是否需要结构化数据  
122 - */  
123 - @Column(name = "requires_structured_data")  
124 - @Builder.Default  
125 - private Boolean requiresStructuredData = false;  
126 -  
127 - /**  
128 - * 是否需要相关链接  
129 - */  
130 - @Column(name = "requires_related_links")  
131 - @Builder.Default  
132 - private Boolean requiresRelatedLinks = false;  
133 -  
134 - /**  
135 - * 排序序号(数字越小排序越靠前)  
136 - */  
137 - @Column(name = "sort_order")  
138 - @Builder.Default  
139 - private Integer sortOrder = 0;  
140 -  
141 - /**  
142 - * 是否启用  
143 - */  
144 - @Column(name = "is_active")  
145 - @Builder.Default  
146 - private Boolean isActive = true;  
147 -  
148 - /**  
149 - * 图标标识(用于前端显示)  
150 - */  
151 - @Column(name = "icon", length = 100)  
152 - private String icon;  
153 -  
154 - /**  
155 - * 颜色标识(十六进制颜色码)  
156 - */  
157 - @Column(name = "color", length = 7)  
158 - private String color;  
159 -  
160 - /**  
161 - * 使用次数统计  
162 - */  
163 - @Column(name = "usage_count")  
164 - @Builder.Default  
165 - private Integer usageCount = 0;  
166 -  
167 - /**  
168 - * 创建时间  
169 - */  
170 - @CreationTimestamp  
171 - @Column(name = "created_at", updatable = false)  
172 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
173 - private LocalDateTime createdAt;  
174 -  
175 - /**  
176 - * 更新时间  
177 - */  
178 - @UpdateTimestamp  
179 - @Column(name = "updated_at")  
180 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
181 - private LocalDateTime updatedAt;  
182 -  
183 - /**  
184 - * 实体创建前的处理  
185 - */  
186 - @PrePersist  
187 - protected void onCreate() {  
188 - if (isActive == null) isActive = true;  
189 - if (requiresFaq == null) requiresFaq = false;  
190 - if (requiresStructuredData == null) requiresStructuredData = false;  
191 - if (requiresRelatedLinks == null) requiresRelatedLinks = false;  
192 - if (sortOrder == null) sortOrder = 0;  
193 - if (usageCount == null) usageCount = 0;  
194 - if (seoWeight == null) seoWeight = 5;  
195 - if (recommendedWordCountMin == null) recommendedWordCountMin = 800;  
196 - if (recommendedWordCountMax == null) recommendedWordCountMax = 1500;  
197 -  
198 - // 自动生成code(如果为空)  
199 - if (code == null && name != null) {  
200 - this.code = generateCodeFromName(name);  
201 - }  
202 - }  
203 -  
204 - /**  
205 - * 从名称生成代码标识  
206 - * @param name 类型名称  
207 - * @return 代码标识  
208 - */  
209 - private String generateCodeFromName(String name) {  
210 - if (name == null) return null;  
211 -  
212 - return name.toLowerCase()  
213 - .replaceAll("[^a-zA-Z0-9\u4e00-\u9fa5]", "_") // 非字母数字中文替换为下划线  
214 - .replaceAll("_+", "_") // 多个下划线合并为一个  
215 - .replaceAll("^_|_$", ""); // 移除开头和结尾的下划线  
216 - }  
217 -  
218 - /**  
219 - * 获取推荐字数范围描述  
220 - */  
221 - public String getRecommendedWordCountRange() {  
222 - return recommendedWordCountMin + "-" + recommendedWordCountMax + "字";  
223 - }  
224 -  
225 - /**  
226 - * 检查是否为高SEO权重类型  
227 - */  
228 - public boolean isHighSeoWeight() {  
229 - return seoWeight != null && seoWeight >= 7;  
230 - }  
231 -  
232 - /**  
233 - * 检查是否为复杂类型(需要多种特殊元素)  
234 - */  
235 - public boolean isComplexType() {  
236 - int complexity = 0;  
237 - if (Boolean.TRUE.equals(requiresFaq)) complexity++;  
238 - if (Boolean.TRUE.equals(requiresStructuredData)) complexity++;  
239 - if (Boolean.TRUE.equals(requiresRelatedLinks)) complexity++;  
240 -  
241 - return complexity >= 2;  
242 - }  
243 -  
244 - /**  
245 - * 获取类型复杂度等级(1-5级)  
246 - */  
247 - public int getComplexityLevel() {  
248 - int level = 1; // 基础等级  
249 -  
250 - if (Boolean.TRUE.equals(requiresFaq)) level++;  
251 - if (Boolean.TRUE.equals(requiresStructuredData)) level++;  
252 - if (Boolean.TRUE.equals(requiresRelatedLinks)) level++;  
253 -  
254 - // 根据字数要求调整复杂度  
255 - if (recommendedWordCountMax != null && recommendedWordCountMax > 2000) level++;  
256 -  
257 - return Math.min(level, 5); // 最高5级  
258 - }  
259 -  
260 - /**  
261 - * 增加使用次数  
262 - */  
263 - public void incrementUsageCount() {  
264 - this.usageCount = (usageCount == null ? 0 : usageCount) + 1;  
265 - }  
266 -  
267 - /**  
268 - * 检查是否有完整的模板配置  
269 - */  
270 - public boolean hasCompleteTemplates() {  
271 - return defaultTitleTemplate != null && !defaultTitleTemplate.trim().isEmpty() &&  
272 - defaultContentTemplate != null && !defaultContentTemplate.trim().isEmpty();  
273 - }  
274 -  
275 - /**  
276 - * 获取类型优先级(基于SEO权重和使用次数)  
277 - */  
278 - public double getPriority() {  
279 - double seoWeight = this.seoWeight != null ? this.seoWeight : 5;  
280 - double usageWeight = this.usageCount != null ? Math.log(this.usageCount + 1) : 0;  
281 -  
282 - return seoWeight * 0.7 + usageWeight * 0.3; // SEO权重占70%,使用频率占30%  
283 - }  
284 -}  
1 package com.aigeo.article.entity; 1 package com.aigeo.article.entity;
2 2
3 -import com.aigeo.common.enums.ContentStatus;  
4 -import com.fasterxml.jackson.annotation.JsonFormat;  
5 -import lombok.AllArgsConstructor;  
6 -import lombok.Builder; 3 +import jakarta.persistence.*;
7 import lombok.Data; 4 import lombok.Data;
8 -import lombok.NoArgsConstructor;  
9 -import org.hibernate.annotations.CreationTimestamp;  
10 -import org.hibernate.annotations.UpdateTimestamp;  
11 5
12 -import jakarta.persistence.*;  
13 -import jakarta.validation.constraints.NotBlank;  
14 -import jakarta.validation.constraints.NotNull;  
15 import java.time.LocalDateTime; 6 import java.time.LocalDateTime;
16 7
17 /** 8 /**
18 - * 生成文章实体类  
19 - * 对应数据库表:ai_generated_articles  
20 - *  
21 - * 存储AI生成的文章内容,包括:  
22 - * - 文章标题、内容和摘要  
23 - * - SEO相关元数据  
24 - * - 文章状态和发布信息  
25 - * - 质量评分和统计数据  
26 - *  
27 - * @author AIGEO Team  
28 - * @since 1.0.0 9 + * 生成的文章实体类
29 */ 10 */
30 @Data 11 @Data
31 @Entity 12 @Entity
32 -@Builder  
33 -@NoArgsConstructor  
34 -@AllArgsConstructor  
35 -@Table(name = "ai_generated_articles", indexes = {  
36 - @Index(name = "idx_articles_company_status", columnList = "company_id, status"),  
37 - @Index(name = "idx_articles_task_id", columnList = "task_id"),  
38 - @Index(name = "idx_articles_slug", columnList = "slug")  
39 -}) 13 +@Table(name = "ai_generated_articles")
40 public class GeneratedArticle { 14 public class GeneratedArticle {
41 -  
42 - /**  
43 - * 主键ID  
44 - */ 15 +
45 @Id 16 @Id
46 @GeneratedValue(strategy = GenerationType.IDENTITY) 17 @GeneratedValue(strategy = GenerationType.IDENTITY)
47 - @Column(name = "id", nullable = false, updatable = false)  
48 private Integer id; 18 private Integer id;
49 19
50 - /**  
51 - * 公司ID(多租户字段)  
52 - */  
53 - @NotNull(message = "公司ID不能为空")  
54 - @Column(name = "company_id", nullable = false)  
55 - private Integer companyId;  
56 -  
57 - /**  
58 - * 关联的生成任务ID  
59 - */  
60 @Column(name = "task_id") 20 @Column(name = "task_id")
61 private Integer taskId; 21 private Integer taskId;
62 22
63 - /**  
64 - * 文章标题  
65 - */  
66 - @NotBlank(message = "文章标题不能为空")  
67 - @Column(name = "title", nullable = false, length = 500)  
68 - private String title; 23 + @Column(name = "company_id")
  24 + private Integer companyId;
69 25
70 - /**  
71 - * URL友好的标题(slug)  
72 - */  
73 - @Column(name = "slug", length = 500)  
74 - private String slug; 26 + @Column(name = "version")
  27 + private Integer version;
75 28
76 - /**  
77 - * 文章摘要/描述  
78 - */  
79 - @Column(name = "excerpt", length = 1000)  
80 - private String excerpt; 29 + @Column(name = "title")
  30 + private String title;
81 31
82 - /**  
83 - * 文章正文内容  
84 - */  
85 - @Column(name = "content", columnDefinition = "LONGTEXT") 32 + @Column(name = "content", columnDefinition = "TEXT")
86 private String content; 33 private String content;
87 34
88 - /**  
89 - * SEO标题(用于<title>标签)  
90 - */  
91 - @Column(name = "seo_title", length = 200)  
92 - private String seoTitle; 35 + @Column(name = "html_content", columnDefinition = "TEXT")
  36 + private String htmlContent;
93 37
94 - /**  
95 - * SEO描述(用于meta description)  
96 - */  
97 - @Column(name = "seo_description", length = 500)  
98 - private String seoDescription; 38 + @Column(name = "faq_section", columnDefinition = "TEXT")
  39 + private String faqSection;
99 40
100 - /**  
101 - * 文章标签(JSON数组或逗号分隔)  
102 - */  
103 - @Column(name = "tags", length = 1000)  
104 - private String tags; 41 + @Column(name = "structured_data", columnDefinition = "TEXT")
  42 + private String structuredData;
105 43
106 - /**  
107 - * 文章字数统计  
108 - */  
109 @Column(name = "word_count") 44 @Column(name = "word_count")
110 private Integer wordCount; 45 private Integer wordCount;
111 46
112 - /**  
113 - * 阅读时长估算(分钟)  
114 - */  
115 - @Column(name = "reading_time")  
116 - private Integer readingTime; 47 + @Column(name = "keyword_density", columnDefinition = "TEXT")
  48 + private String keywordDensity;
  49 +
  50 + @Column(name = "is_selected")
  51 + private Boolean isSelected;
117 52
118 - /**  
119 - * 文章状态  
120 - */  
121 @Enumerated(EnumType.STRING) 53 @Enumerated(EnumType.STRING)
122 @Column(name = "status") 54 @Column(name = "status")
123 - @Builder.Default  
124 - private ContentStatus status = ContentStatus.DRAFT;  
125 -  
126 - /**  
127 - * 质量评分(1-100分)  
128 - */  
129 - @Column(name = "quality_score")  
130 - private Integer qualityScore;  
131 -  
132 - /**  
133 - * AI置信度评分(0-1之间)  
134 - */  
135 - @Column(name = "ai_confidence")  
136 - private Double aiConfidence; 55 + private ArticleStatus status;
137 56
138 - /**  
139 - * 原创性评分(0-1之间,1表示完全原创)  
140 - */  
141 - @Column(name = "originality_score")  
142 - private Double originalityScore;  
143 -  
144 - /**  
145 - * SEO评分(1-100分)  
146 - */  
147 - @Column(name = "seo_score")  
148 - private Integer seoScore;  
149 -  
150 - /**  
151 - * 文章特色图片URL  
152 - */  
153 - @Column(name = "featured_image_url", length = 500)  
154 - private String featuredImageUrl;  
155 -  
156 - /**  
157 - * 发布时间(NULL表示未发布)  
158 - */  
159 - @Column(name = "published_at")  
160 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
161 - private LocalDateTime publishedAt;  
162 -  
163 - /**  
164 - * 最后编辑的用户ID  
165 - */  
166 - @Column(name = "last_edited_by")  
167 - private Integer lastEditedBy;  
168 -  
169 - /**  
170 - * 浏览次数统计  
171 - */  
172 - @Column(name = "view_count")  
173 - @Builder.Default  
174 - private Integer viewCount = 0;  
175 -  
176 - /**  
177 - * 喜欢次数统计  
178 - */  
179 - @Column(name = "like_count")  
180 - @Builder.Default  
181 - private Integer likeCount = 0;  
182 -  
183 - /**  
184 - * 分享次数统计  
185 - */  
186 - @Column(name = "share_count")  
187 - @Builder.Default  
188 - private Integer shareCount = 0;  
189 -  
190 - /**  
191 - * 结构化数据(Schema.org JSON-LD)  
192 - */  
193 - @Column(name = "structured_data", columnDefinition = "JSON")  
194 - private String structuredData;  
195 -  
196 - /**  
197 - * 元数据(JSON格式存储额外信息)  
198 - */  
199 - @Column(name = "metadata", columnDefinition = "JSON")  
200 - private String metadata;  
201 -  
202 - /**  
203 - * 创建时间  
204 - */  
205 - @CreationTimestamp  
206 - @Column(name = "created_at", updatable = false)  
207 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 57 + @Column(name = "created_at")
208 private LocalDateTime createdAt; 58 private LocalDateTime createdAt;
209 59
210 - /**  
211 - * 更新时间  
212 - */  
213 - @UpdateTimestamp  
214 @Column(name = "updated_at") 60 @Column(name = "updated_at")
215 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
216 private LocalDateTime updatedAt; 61 private LocalDateTime updatedAt;
217 62
218 - /**  
219 - * 实体创建前的处理  
220 - */  
221 @PrePersist 63 @PrePersist
222 protected void onCreate() { 64 protected void onCreate() {
223 - if (status == null) status = ContentStatus.DRAFT;  
224 - if (viewCount == null) viewCount = 0;  
225 - if (likeCount == null) likeCount = 0;  
226 - if (shareCount == null) shareCount = 0;  
227 -  
228 - // 生成slug  
229 - if (slug == null && title != null) {  
230 - this.slug = generateSlugFromTitle(title); 65 + createdAt = LocalDateTime.now();
  66 + updatedAt = LocalDateTime.now();
  67 + if (version == null) {
  68 + version = 1;
231 } 69 }
232 -  
233 - // 如果没有SEO标题,使用文章标题  
234 - if (seoTitle == null && title != null) {  
235 - this.seoTitle = title.length() > 60 ? title.substring(0, 60) + "..." : title; 70 + if (isSelected == null) {
  71 + isSelected = false;
236 } 72 }
237 -  
238 - // 估算阅读时长(按平均阅读速度250字/分钟计算)  
239 - if (readingTime == null && wordCount != null) {  
240 - this.readingTime = Math.max(1, (int) Math.ceil(wordCount / 250.0)); 73 + if (status == null) {
  74 + status = ArticleStatus.DRAFT;
241 } 75 }
242 } 76 }
243 77
244 - /**  
245 - * 从标题生成URL友好的slug  
246 - * @param title 文章标题  
247 - * @return URL友好的slug  
248 - */  
249 - private String generateSlugFromTitle(String title) {  
250 - if (title == null) return null;  
251 -  
252 - return title.toLowerCase()  
253 - .replaceAll("[^a-zA-Z0-9\u4e00-\u9fa5\\s-]", "") // 保留字母、数字、中文、空格和连字符  
254 - .replaceAll("\\s+", "-") // 空格替换为连字符  
255 - .replaceAll("-+", "-") // 多个连字符合并为一个  
256 - .replaceAll("^-|-$", ""); // 移除开头和结尾的连字符 78 + @PreUpdate
  79 + protected void onUpdate() {
  80 + updatedAt = LocalDateTime.now();
257 } 81 }
258 82
259 /** 83 /**
260 - * 检查文章是否已发布 84 + * 文章状态枚举
261 */ 85 */
262 - public boolean isPublished() {  
263 - return status == ContentStatus.PUBLISHED && publishedAt != null;  
264 - } 86 + public enum ArticleStatus {
  87 + DRAFT("draft"), // 草稿
  88 + REVIEW("review"), // 审核中
  89 + PUBLISHED("published"), // 已发布
  90 + ARCHIVED("archived"); // 已归档
265 91
266 - /**  
267 - * 检查文章是否可以发布  
268 - */  
269 - public boolean canPublish() {  
270 - return status == ContentStatus.APPROVED ||  
271 - status == ContentStatus.GENERATED ||  
272 - status == ContentStatus.COMPLETED;  
273 - } 92 + private final String code;
274 93
275 - /**  
276 - * 获取综合评分(质量+SEO+原创性的平均值)  
277 - */  
278 - public Double getOverallScore() {  
279 - int count = 0;  
280 - double total = 0.0;  
281 -  
282 - if (qualityScore != null) {  
283 - total += qualityScore;  
284 - count++;  
285 - }  
286 - if (seoScore != null) {  
287 - total += seoScore;  
288 - count++;  
289 - }  
290 - if (originalityScore != null) {  
291 - total += originalityScore * 100; // 转换为1-100分制  
292 - count++; 94 + ArticleStatus(String code) {
  95 + this.code = code;
293 } 96 }
294 -  
295 - return count > 0 ? total / count : null;  
296 - }  
297 -  
298 - /**  
299 - * 获取参与度评分(基于浏览、点赞、分享数据)  
300 - */  
301 - public Double getEngagementScore() {  
302 - if (viewCount == 0) return 0.0;  
303 -  
304 - // 简单的参与度计算:(点赞数*2 + 分享数*3) / 浏览数 * 100  
305 - return ((likeCount * 2.0 + shareCount * 3.0) / viewCount) * 100;  
306 - }  
307 97
308 - /**  
309 - * 增加浏览次数  
310 - */  
311 - public void incrementViewCount() {  
312 - this.viewCount = (viewCount == null ? 0 : viewCount) + 1;  
313 - }  
314 -  
315 - /**  
316 - * 增加点赞次数  
317 - */  
318 - public void incrementLikeCount() {  
319 - this.likeCount = (likeCount == null ? 0 : likeCount) + 1;  
320 - } 98 + public String getCode() {
  99 + return code;
  100 + }
321 101
322 - /**  
323 - * 增加分享次数  
324 - */  
325 - public void incrementShareCount() {  
326 - this.shareCount = (shareCount == null ? 0 : shareCount) + 1; 102 + public static ArticleStatus fromCode(String code) {
  103 + for (ArticleStatus status : ArticleStatus.values()) {
  104 + if (status.getCode().equals(code)) {
  105 + return status;
  106 + }
  107 + }
  108 + throw new IllegalArgumentException("未知的文章状态: " + code);
  109 + }
327 } 110 }
328 } 111 }
1 -package com.aigeo.article.entity;  
2 -  
3 -import com.fasterxml.jackson.annotation.JsonFormat;  
4 -import lombok.AllArgsConstructor;  
5 -import lombok.Builder;  
6 -import lombok.Data;  
7 -import lombok.NoArgsConstructor;  
8 -import org.hibernate.annotations.CreationTimestamp;  
9 -  
10 -import jakarta.persistence.*;  
11 -import jakarta.validation.constraints.NotBlank;  
12 -import jakarta.validation.constraints.NotNull;  
13 -import java.time.LocalDateTime;  
14 -  
15 -/**  
16 - * 任务参考资料实体类  
17 - * 对应数据库表:ai_task_references  
18 - *  
19 - * 存储文章生成任务的参考资料信息,包括:  
20 - * - 参考网站URL和标题  
21 - * - 参考内容摘要  
22 - * - 资料权重和可信度评分  
23 - * - 使用状态和备注信息  
24 - *  
25 - * @author AIGEO Team  
26 - * @since 1.0.0  
27 - */  
28 -@Data  
29 -@Entity  
30 -@Builder  
31 -@NoArgsConstructor  
32 -@AllArgsConstructor  
33 -@Table(name = "ai_task_references", indexes = {  
34 - @Index(name = "idx_task_refs_task_id", columnList = "task_id"),  
35 - @Index(name = "idx_task_refs_url", columnList = "reference_url")  
36 -})  
37 -public class TaskReference {  
38 -  
39 - /**  
40 - * 主键ID  
41 - */  
42 - @Id  
43 - @GeneratedValue(strategy = GenerationType.IDENTITY)  
44 - @Column(name = "id", nullable = false, updatable = false)  
45 - private Integer id;  
46 -  
47 - /**  
48 - * 关联的生成任务ID  
49 - */  
50 - @NotNull(message = "任务ID不能为空")  
51 - @Column(name = "task_id", nullable = false)  
52 - private Integer taskId;  
53 -  
54 - /**  
55 - * 参考资料URL  
56 - */  
57 - @NotBlank(message = "参考资料URL不能为空")  
58 - @Column(name = "reference_url", nullable = false, length = 500)  
59 - private String referenceUrl;  
60 -  
61 - /**  
62 - * 参考资料标题  
63 - */  
64 - @Column(name = "reference_title", length = 500)  
65 - private String referenceTitle;  
66 -  
67 - /**  
68 - * 参考内容摘要  
69 - */  
70 - @Column(name = "content_summary", length = 2000)  
71 - private String contentSummary;  
72 -  
73 - /**  
74 - * 资料来源域名  
75 - */  
76 - @Column(name = "source_domain", length = 100)  
77 - private String sourceDomain;  
78 -  
79 - /**  
80 - * 内容类型(如:article, blog, news, academic等)  
81 - */  
82 - @Column(name = "content_type", length = 50)  
83 - @Builder.Default  
84 - private String contentType = "article";  
85 -  
86 - /**  
87 - * 资料权重(影响生成结果的重要程度,1-10分)  
88 - */  
89 - @Column(name = "weight")  
90 - @Builder.Default  
91 - private Integer weight = 5;  
92 -  
93 - /**  
94 - * 可信度评分(1-10分)  
95 - */  
96 - @Column(name = "credibility_score")  
97 - @Builder.Default  
98 - private Integer credibilityScore = 5;  
99 -  
100 - /**  
101 - * 相关性评分(与目标话题的相关程度,1-10分)  
102 - */  
103 - @Column(name = "relevance_score")  
104 - @Builder.Default  
105 - private Integer relevanceScore = 5;  
106 -  
107 - /**  
108 - * 内容质量评分(1-10分)  
109 - */  
110 - @Column(name = "quality_score")  
111 - private Integer qualityScore;  
112 -  
113 - /**  
114 - * 发布时间(参考资料的原始发布时间)  
115 - */  
116 - @Column(name = "published_at")  
117 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
118 - private LocalDateTime publishedAt;  
119 -  
120 - /**  
121 - * 语言标识  
122 - */  
123 - @Column(name = "language", length = 10)  
124 - @Builder.Default  
125 - private String language = "zh-CN";  
126 -  
127 - /**  
128 - * 字数统计  
129 - */  
130 - @Column(name = "word_count")  
131 - private Integer wordCount;  
132 -  
133 - /**  
134 - * 是否已处理(是否已被AI分析处理)  
135 - */  
136 - @Column(name = "is_processed")  
137 - @Builder.Default  
138 - private Boolean isProcessed = false;  
139 -  
140 - /**  
141 - * 处理状态(pending, processing, completed, failed)  
142 - */  
143 - @Column(name = "processing_status", length = 20)  
144 - @Builder.Default  
145 - private String processingStatus = "pending";  
146 -  
147 - /**  
148 - * 使用状态(是否在生成中被实际使用)  
149 - */  
150 - @Column(name = "is_used")  
151 - @Builder.Default  
152 - private Boolean isUsed = false;  
153 -  
154 - /**  
155 - * 错误信息(处理失败时的错误描述)  
156 - */  
157 - @Column(name = "error_message", length = 1000)  
158 - private String errorMessage;  
159 -  
160 - /**  
161 - * 提取的关键词(JSON数组或逗号分隔)  
162 - */  
163 - @Column(name = "extracted_keywords", length = 1000)  
164 - private String extractedKeywords;  
165 -  
166 - /**  
167 - * 提取的实体(人名、地名、机构名等,JSON格式)  
168 - */  
169 - @Column(name = "extracted_entities", columnDefinition = "JSON")  
170 - private String extractedEntities;  
171 -  
172 - /**  
173 - * 内容分类标签  
174 - */  
175 - @Column(name = "content_tags", length = 500)  
176 - private String contentTags;  
177 -  
178 - /**  
179 - * 备注信息  
180 - */  
181 - @Column(name = "notes", length = 500)  
182 - private String notes;  
183 -  
184 - /**  
185 - * 元数据(JSON格式存储额外信息)  
186 - */  
187 - @Column(name = "metadata", columnDefinition = "JSON")  
188 - private String metadata;  
189 -  
190 - /**  
191 - * 创建时间  
192 - */  
193 - @CreationTimestamp  
194 - @Column(name = "created_at", updatable = false)  
195 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")  
196 - private LocalDateTime createdAt;  
197 -  
198 - /**  
199 - * 实体创建前的处理  
200 - */  
201 - @PrePersist  
202 - protected void onCreate() {  
203 - if (contentType == null) contentType = "article";  
204 - if (weight == null) weight = 5;  
205 - if (credibilityScore == null) credibilityScore = 5;  
206 - if (relevanceScore == null) relevanceScore = 5;  
207 - if (language == null) language = "zh-CN";  
208 - if (isProcessed == null) isProcessed = false;  
209 - if (processingStatus == null) processingStatus = "pending";  
210 - if (isUsed == null) isUsed = false;  
211 -  
212 - // 从URL提取域名  
213 - if (sourceDomain == null && referenceUrl != null) {  
214 - this.sourceDomain = extractDomainFromUrl(referenceUrl);  
215 - }  
216 - }  
217 -  
218 - /**  
219 - * 从URL提取域名  
220 - * @param url 完整URL  
221 - * @return 域名  
222 - */  
223 - private String extractDomainFromUrl(String url) {  
224 - try {  
225 - if (url.startsWith("http://")) {  
226 - url = url.substring(7);  
227 - } else if (url.startsWith("https://")) {  
228 - url = url.substring(8);  
229 - }  
230 -  
231 - int slashIndex = url.indexOf('/');  
232 - if (slashIndex != -1) {  
233 - url = url.substring(0, slashIndex);  
234 - }  
235 -  
236 - return url;  
237 - } catch (Exception e) {  
238 - return null;  
239 - }  
240 - }  
241 -  
242 - /**  
243 - * 检查是否为高质量参考资料  
244 - */  
245 - public boolean isHighQuality() {  
246 - return (credibilityScore != null && credibilityScore >= 7) &&  
247 - (relevanceScore != null && relevanceScore >= 7) &&  
248 - (qualityScore == null || qualityScore >= 7);  
249 - }  
250 -  
251 - /**  
252 - * 检查是否为权威来源  
253 - */  
254 - public boolean isAuthoritativeSource() {  
255 - if (sourceDomain == null) return false;  
256 -  
257 - // 常见权威域名后缀  
258 - return sourceDomain.endsWith(".edu") ||  
259 - sourceDomain.endsWith(".gov") ||  
260 - sourceDomain.endsWith(".org") ||  
261 - credibilityScore != null && credibilityScore >= 8;  
262 - }  
263 -  
264 - /**  
265 - * 获取综合评分(权重、可信度、相关性的加权平均)  
266 - */  
267 - public double getOverallScore() {  
268 - double weightScore = weight != null ? weight : 5;  
269 - double credibilityScore = this.credibilityScore != null ? this.credibilityScore : 5;  
270 - double relevanceScore = this.relevanceScore != null ? this.relevanceScore : 5;  
271 - double qualityScore = this.qualityScore != null ? this.qualityScore : 5;  
272 -  
273 - // 加权计算:相关性40%,可信度30%,质量20%,权重10%  
274 - return relevanceScore * 0.4 + credibilityScore * 0.3 + qualityScore * 0.2 + weightScore * 0.1;  
275 - }  
276 -  
277 - /**  
278 - * 检查内容是否为近期发布  
279 - */  
280 - public boolean isRecentContent() {  
281 - if (publishedAt == null) return false;  
282 -  
283 - LocalDateTime sixMonthsAgo = LocalDateTime.now().minusMonths(6);  
284 - return publishedAt.isAfter(sixMonthsAgo);  
285 - }  
286 -  
287 - /**  
288 - * 获取内容新鲜度评分(1-10分,越新分数越高)  
289 - */  
290 - public int getFreshnessScore() {  
291 - if (publishedAt == null) return 5; // 默认中等分数  
292 -  
293 - LocalDateTime now = LocalDateTime.now();  
294 - long daysDiff = java.time.Duration.between(publishedAt, now).toDays();  
295 -  
296 - if (daysDiff <= 7) return 10; // 一周内:10分  
297 - if (daysDiff <= 30) return 9; // 一月内:9分  
298 - if (daysDiff <= 90) return 8; // 三月内:8分  
299 - if (daysDiff <= 180) return 7; // 半年内:7分  
300 - if (daysDiff <= 365) return 6; // 一年内:6分  
301 - if (daysDiff <= 730) return 4; // 两年内:4分  
302 -  
303 - return 2; // 超过两年:2分  
304 - }  
305 -  
306 - /**  
307 - * 标记为已处理  
308 - */  
309 - public void markAsProcessed() {  
310 - this.isProcessed = true;  
311 - this.processingStatus = "completed";  
312 - }  
313 -  
314 - /**  
315 - * 标记处理失败  
316 - */  
317 - public void markAsFailed(String errorMessage) {  
318 - this.isProcessed = false;  
319 - this.processingStatus = "failed";  
320 - this.errorMessage = errorMessage;  
321 - }  
322 -  
323 - /**  
324 - * 标记为已使用  
325 - */  
326 - public void markAsUsed() {  
327 - this.isUsed = true;  
328 - }  
329 -}  
1 package com.aigeo.article.repository; 1 package com.aigeo.article.repository;
2 2
3 import com.aigeo.article.entity.ArticleGenerationTask; 3 import com.aigeo.article.entity.ArticleGenerationTask;
4 -import com.aigeo.common.enums.TaskStatus;  
5 -import com.aigeo.common.enums.AiTasteLevel;  
6 -import org.springframework.data.domain.Page;  
7 -import org.springframework.data.domain.Pageable;  
8 import org.springframework.data.jpa.repository.JpaRepository; 4 import org.springframework.data.jpa.repository.JpaRepository;
9 -import org.springframework.data.jpa.repository.Query;  
10 -import org.springframework.data.repository.query.Param;  
11 import org.springframework.stereotype.Repository; 5 import org.springframework.stereotype.Repository;
12 6
13 -import java.time.LocalDateTime;  
14 import java.util.List; 7 import java.util.List;
15 -import java.util.Optional;  
16 8
17 /** 9 /**
18 - * 文章生成任务数据访问层  
19 - * 对应数据库表:ai_article_generation_tasks  
20 - *  
21 - * 负责管理AI文章生成任务的数据访问操作,包括任务创建、状态跟踪、  
22 - * 进度监控和结果查询等功能  
23 - *  
24 - * @author AIGEO Team  
25 - * @since 1.0.0 10 + * 文章生成任务仓库接口
26 */ 11 */
27 @Repository 12 @Repository
28 public interface ArticleGenerationTaskRepository extends JpaRepository<ArticleGenerationTask, Integer> { 13 public interface ArticleGenerationTaskRepository extends JpaRepository<ArticleGenerationTask, Integer> {
29 14
30 /** 15 /**
31 - * 根据公司ID查找所有任务  
32 - * @param companyId 公司ID  
33 - * @return 该公司的所有文章生成任务 16 + * 根据公司ID查找任务列表
34 */ 17 */
35 List<ArticleGenerationTask> findByCompanyId(Integer companyId); 18 List<ArticleGenerationTask> findByCompanyId(Integer companyId);
36 19
37 /** 20 /**
38 - * 根据用户ID查找任务  
39 - * @param userId 用户ID  
40 - * @return 该用户创建的所有任务 21 + * 根据用户ID查找任务列表
41 */ 22 */
42 List<ArticleGenerationTask> findByUserId(Integer userId); 23 List<ArticleGenerationTask> findByUserId(Integer userId);
43 24
44 /** 25 /**
45 - * 根据状态查找任务  
46 - * @param status 任务状态  
47 - * @return 处于指定状态的所有任务 26 + * 根据状态查找任务列表
48 */ 27 */
49 - List<ArticleGenerationTask> findByStatus(TaskStatus status); 28 + List<ArticleGenerationTask> findByStatus(ArticleGenerationTask.TaskStatus status);
50 29
51 /** 30 /**
52 - * 根据公司ID和状态查找任务  
53 - * @param companyId 公司ID  
54 - * @param status 任务状态  
55 - * @return 匹配条件的任务列表 31 + * 根据公司ID和状态查找任务列表
56 */ 32 */
57 - List<ArticleGenerationTask> findByCompanyIdAndStatus(Integer companyId, TaskStatus status); 33 + List<ArticleGenerationTask> findByCompanyIdAndStatus(Integer companyId, ArticleGenerationTask.TaskStatus status);
58 34
59 /** 35 /**
60 - * 查找待处理的任务(按创建时间正序)  
61 - * @return 待处理的任务队列 36 + * 根据用户ID和状态查找任务列表
62 */ 37 */
63 - List<ArticleGenerationTask> findByStatusOrderByCreatedAtAsc(TaskStatus status);  
64 -  
65 - /**  
66 - * 分页查询公司任务(按创建时间倒序)  
67 - * @param companyId 公司ID  
68 - * @param pageable 分页参数  
69 - * @return 分页结果  
70 - */  
71 - Page<ArticleGenerationTask> findByCompanyIdOrderByCreatedAtDesc(Integer companyId, Pageable pageable);  
72 -  
73 - /**  
74 - * 根据关键词ID查找任务  
75 - * @param keywordId 关键词ID  
76 - * @return 基于该关键词的生成任务  
77 - */  
78 - List<ArticleGenerationTask> findByKeywordId(Integer keywordId);  
79 -  
80 - /**  
81 - * 根据AI风格等级查找任务  
82 - * @param companyId 公司ID  
83 - * @param aiTasteLevel AI风格等级  
84 - * @return 使用指定风格的任务  
85 - */  
86 - List<ArticleGenerationTask> findByCompanyIdAndAiTasteLevel(Integer companyId, AiTasteLevel aiTasteLevel);  
87 -  
88 - /**  
89 - * 查找指定时间范围内的任务  
90 - * @param companyId 公司ID  
91 - * @param startTime 开始时间  
92 - * @param endTime 结束时间  
93 - * @return 时间范围内的任务  
94 - */  
95 - List<ArticleGenerationTask> findByCompanyIdAndCreatedAtBetween(Integer companyId, LocalDateTime startTime, LocalDateTime endTime);  
96 -  
97 - /**  
98 - * 统计公司任务数量按状态分组  
99 - * @param companyId 公司ID  
100 - * @param status 任务状态  
101 - * @return 指定状态的任务数量  
102 - */  
103 - @Query("SELECT COUNT(t) FROM ArticleGenerationTask t WHERE t.companyId = :companyId AND t.status = :status")  
104 - long countByCompanyIdAndStatus(@Param("companyId") Integer companyId, @Param("status") TaskStatus status);  
105 -  
106 - /**  
107 - * 查找用户最近的任务  
108 - * @param userId 用户ID  
109 - * @param limit 限制数量  
110 - * @return 用户最近的任务  
111 - */  
112 - @Query("SELECT t FROM ArticleGenerationTask t WHERE t.userId = :userId ORDER BY t.createdAt DESC")  
113 - List<ArticleGenerationTask> findRecentTasksByUser(@Param("userId") Integer userId, Pageable pageable);  
114 -  
115 - /**  
116 - * 查找超时未完成的任务  
117 - * @param timeoutThreshold 超时时间阈值  
118 - * @return 超时的处理中任务  
119 - */  
120 - @Query("SELECT t FROM ArticleGenerationTask t WHERE t.status = 'PROCESSING' AND t.createdAt < :timeoutThreshold")  
121 - List<ArticleGenerationTask> findTimeoutTasks(@Param("timeoutThreshold") LocalDateTime timeoutThreshold);  
122 -  
123 - /**  
124 - * 根据公司ID和文章主题模糊搜索任务  
125 - * @param companyId 公司ID  
126 - * @param articleTheme 文章主题关键词  
127 - * @return 匹配的任务列表  
128 - */  
129 - List<ArticleGenerationTask> findByCompanyIdAndArticleThemeContainingIgnoreCase(Integer companyId, String articleTheme);  
130 -  
131 - /**  
132 - * 查找最近完成的任务  
133 - * @param companyId 公司ID(可选)  
134 - * @param status 任务状态  
135 - * @param pageable 分页参数  
136 - * @return 最近完成的任务  
137 - */  
138 - @Query("SELECT t FROM ArticleGenerationTask t WHERE (:companyId IS NULL OR t.companyId = :companyId) AND t.status = :status ORDER BY t.completedAt DESC")  
139 - List<ArticleGenerationTask> findRecentCompletedTasks(@Param("companyId") Integer companyId, @Param("status") TaskStatus status, Pageable pageable);  
140 -  
141 - /**  
142 - * 根据多个条件搜索任务  
143 - * @param companyId 公司ID  
144 - * @param userId 用户ID  
145 - * @param status 任务状态  
146 - * @param articleTheme 文章主题  
147 - * @param startTime 开始时间  
148 - * @param endTime 结束时间  
149 - * @param pageable 分页参数  
150 - * @return 分页搜索结果  
151 - */  
152 - @Query("SELECT t FROM ArticleGenerationTask t WHERE " +  
153 - "(:companyId IS NULL OR t.companyId = :companyId) AND " +  
154 - "(:userId IS NULL OR t.userId = :userId) AND " +  
155 - "(:status IS NULL OR t.status = :status) AND " +  
156 - "(:articleTheme IS NULL OR t.articleTheme LIKE %:articleTheme%) AND " +  
157 - "(:startTime IS NULL OR t.createdAt >= :startTime) AND " +  
158 - "(:endTime IS NULL OR t.createdAt <= :endTime)")  
159 - Page<ArticleGenerationTask> searchTasks(@Param("companyId") Integer companyId,  
160 - @Param("userId") Integer userId,  
161 - @Param("status") TaskStatus status,  
162 - @Param("articleTheme") String articleTheme,  
163 - @Param("startTime") LocalDateTime startTime,  
164 - @Param("endTime") LocalDateTime endTime,  
165 - Pageable pageable);  
166 -  
167 - /**  
168 - * 查找最近更新的任务  
169 - * @param companyId 公司ID  
170 - * @param since 起始时间  
171 - * @return 最近更新的任务  
172 - */  
173 - @Query("SELECT t FROM ArticleGenerationTask t WHERE (:companyId IS NULL OR t.companyId = :companyId) AND t.updatedAt >= :since ORDER BY t.updatedAt DESC")  
174 - List<ArticleGenerationTask> findByUpdatedAtAfterOrderByUpdatedAtDesc(@Param("companyId") Integer companyId, @Param("since") LocalDateTime since);  
175 -  
176 - /**  
177 - * 统计指定时间范围内的任务数量  
178 - * @param companyId 公司ID(可选)  
179 - * @param status 任务状态  
180 - * @param startTime 开始时间  
181 - * @param endTime 结束时间  
182 - * @return 任务数量  
183 - */  
184 - @Query("SELECT COUNT(t) FROM ArticleGenerationTask t WHERE " +  
185 - "(:companyId IS NULL OR t.companyId = :companyId) AND " +  
186 - "(:status IS NULL OR t.status = :status) AND " +  
187 - "t.createdAt BETWEEN :startTime AND :endTime")  
188 - long countByConditions(@Param("companyId") Integer companyId,  
189 - @Param("status") TaskStatus status,  
190 - @Param("startTime") LocalDateTime startTime,  
191 - @Param("endTime") LocalDateTime endTime);  
192 -  
193 - /**  
194 - * 计算平均执行时长  
195 - * @param companyId 公司ID(可选)  
196 - * @param startTime 开始时间  
197 - * @param endTime 结束时间  
198 - * @return 平均执行时长(毫秒)  
199 - */  
200 - @Query("SELECT AVG(t.executionTimeMs) FROM ArticleGenerationTask t WHERE " +  
201 - "(:companyId IS NULL OR t.companyId = :companyId) AND " +  
202 - "t.executionTimeMs IS NOT NULL AND " +  
203 - "t.createdAt BETWEEN :startTime AND :endTime")  
204 - Double getAverageExecutionTime(@Param("companyId") Integer companyId,  
205 - @Param("startTime") LocalDateTime startTime,  
206 - @Param("endTime") LocalDateTime endTime);  
207 -  
208 - /**  
209 - * 删除指定时间之前完成的任务  
210 - * @param completedBefore 完成时间阈值  
211 - * @param status 任务状态  
212 - * @return 删除的任务数量  
213 - */  
214 - @Query("DELETE FROM ArticleGenerationTask t WHERE t.status = :status AND t.completedAt < :completedBefore")  
215 - int deleteCompletedTasksBefore(@Param("status") TaskStatus status, @Param("completedBefore") LocalDateTime completedBefore);  
216 -  
217 - long countByCompanyId(Integer companyId);  
218 -} 38 + List<ArticleGenerationTask> findByUserIdAndStatus(Integer userId, ArticleGenerationTask.TaskStatus status);
  39 +}
1 -package com.aigeo.article.service;  
2 -  
3 -import com.aigeo.article.entity.ArticleGenerationTask;  
4 -import com.aigeo.article.repository.ArticleGenerationTaskRepository;  
5 -import com.aigeo.common.context.TenantContext;  
6 -import com.aigeo.common.enums.TaskStatus;  
7 -import com.aigeo.common.exception.BusinessException;  
8 -import com.aigeo.common.result.ResultCode;  
9 -import lombok.RequiredArgsConstructor;  
10 -import lombok.extern.slf4j.Slf4j;  
11 -import org.springframework.data.domain.Page;  
12 -import org.springframework.data.domain.Pageable;  
13 -import org.springframework.stereotype.Service;  
14 -import org.springframework.transaction.annotation.Transactional;  
15 -import org.springframework.util.StringUtils;  
16 -  
17 -import java.time.LocalDateTime;  
18 -import java.util.ArrayList;  
19 -import java.util.List;  
20 -import java.util.Optional;  
21 -import java.util.stream.Collectors;  
22 -import org.springframework.data.domain.PageRequest;  
23 -  
24 -/**  
25 - * 文章生成服务层  
26 - *  
27 - * 负责管理文章生成任务的业务逻辑,包括任务的创建、更新、查询、  
28 - * 状态跟踪、批量操作等功能。支持AI自动生成文章内容、  
29 - * 多种内容模板、发布状态管理等  
30 - *  
31 - * @author AIGEO Team  
32 - * @since 1.0.0  
33 - */  
34 -@Slf4j  
35 -@Service  
36 -@RequiredArgsConstructor  
37 -@Transactional(readOnly = true)  
38 -public class ArticleGenerationService {  
39 -  
40 - private final ArticleGenerationTaskRepository taskRepository;  
41 -  
42 - /**  
43 - * 获取所有文章生成任务  
44 - * @return 所有任务列表  
45 - */  
46 - public List<ArticleGenerationTask> getAllTasks() {  
47 - log.debug("获取所有文章生成任务");  
48 - return taskRepository.findAll();  
49 - }  
50 -  
51 - /**  
52 - * 根据公司ID获取文章生成任务列表  
53 - * @param companyId 公司ID  
54 - * @return 该公司的任务列表  
55 - * @throws BusinessException 当公司ID为空时抛出  
56 - */  
57 - public List<ArticleGenerationTask> getTasksByCompanyId(Integer companyId) {  
58 - log.debug("获取公司文章生成任务,companyId: {}", companyId);  
59 - if (companyId == null) {  
60 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");  
61 - }  
62 - return taskRepository.findByCompanyId(companyId);  
63 - }  
64 -  
65 - /**  
66 - * 根据用户ID获取文章生成任务列表  
67 - * @param userId 用户ID  
68 - * @return 该用户创建的任务列表  
69 - * @throws BusinessException 当用户ID为空时抛出  
70 - */  
71 - public List<ArticleGenerationTask> getTasksByUserId(Integer userId) {  
72 - log.debug("获取用户文章生成任务,userId: {}", userId);  
73 - if (userId == null) {  
74 - throw new BusinessException(ResultCode.PARAM_ERROR, "用户ID不能为空");  
75 - }  
76 - return taskRepository.findByUserId(userId);  
77 - }  
78 -  
79 - /**  
80 - * 根据任务状态获取文章生成任务列表  
81 - * @param status 任务状态  
82 - * @return 指定状态的任务列表  
83 - * @throws BusinessException 当状态为空时抛出  
84 - */  
85 - public List<ArticleGenerationTask> getTasksByStatus(TaskStatus status) {  
86 - log.debug("根据状态获取文章生成任务,status: {}", status);  
87 - if (status == null) {  
88 - throw new BusinessException(ResultCode.PARAM_ERROR, "任务状态不能为空");  
89 - }  
90 - return taskRepository.findByStatus(status);  
91 - }  
92 -  
93 - /**  
94 - * 根据公司ID和状态获取任务列表  
95 - * @param companyId 公司ID  
96 - * @param status 任务状态  
97 - * @return 匹配条件的任务列表  
98 - * @throws BusinessException 当参数为空时抛出  
99 - */  
100 - public List<ArticleGenerationTask> getTasksByCompanyIdAndStatus(Integer companyId, TaskStatus status) {  
101 - log.debug("根据公司和状态获取任务,companyId: {}, status: {}", companyId, status);  
102 - if (companyId == null || status == null) {  
103 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID和状态不能为空");  
104 - }  
105 - return taskRepository.findByCompanyIdAndStatus(companyId, status);  
106 - }  
107 -  
108 - /**  
109 - * 分页查询公司的文章生成任务  
110 - * @param companyId 公司ID  
111 - * @param pageable 分页参数  
112 - * @return 分页结果  
113 - * @throws BusinessException 当公司ID为空时抛出  
114 - */  
115 - public Page<ArticleGenerationTask> getTasksByCompanyIdWithPaging(Integer companyId, Pageable pageable) {  
116 - log.debug("分页查询公司文章生成任务,companyId: {}", companyId);  
117 - if (companyId == null) {  
118 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");  
119 - }  
120 - return taskRepository.findByCompanyIdOrderByCreatedAtDesc(companyId, pageable);  
121 - }  
122 -  
123 - /**  
124 - * 根据ID获取文章生成任务  
125 - * @param id 任务ID  
126 - * @return 任务对象的Optional包装  
127 - * @throws BusinessException 当ID为空时抛出  
128 - */  
129 - public Optional<ArticleGenerationTask> getTaskById(Integer id) {  
130 - log.debug("根据ID获取文章生成任务,id: {}", id);  
131 - if (id == null) {  
132 - throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID不能为空");  
133 - }  
134 - return taskRepository.findById(id);  
135 - }  
136 -  
137 - /**  
138 - * 根据文章主题模糊搜索任务  
139 - * @param companyId 公司ID  
140 - * @param articleTheme 文章主题关键词  
141 - * @return 匹配的任务列表  
142 - * @throws BusinessException 当参数为空时抛出  
143 - */  
144 - public List<ArticleGenerationTask> searchTasksByTitle(Integer companyId, String articleTheme) {  
145 - log.debug("根据文章主题搜索任务,companyId: {}, articleTheme: {}", companyId, articleTheme);  
146 - if (companyId == null || !StringUtils.hasText(articleTheme)) {  
147 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID和文章主题不能为空");  
148 - }  
149 - return taskRepository.findByCompanyIdAndArticleThemeContainingIgnoreCase(companyId, articleTheme);  
150 - }  
151 -  
152 - /**  
153 - * 保存文章生成任务  
154 - * @param task 任务对象  
155 - * @return 保存后的任务  
156 - * @throws BusinessException 当任务对象为空或保存失败时抛出  
157 - */  
158 - @Transactional  
159 - public ArticleGenerationTask saveTask(ArticleGenerationTask task) {  
160 - log.debug("保存文章生成任务,articleTheme: {}", task != null ? task.getArticleTheme() : null);  
161 - if (task == null) {  
162 - throw new BusinessException(ResultCode.PARAM_ERROR, "任务对象不能为空");  
163 - }  
164 -  
165 - // 设置当前租户ID  
166 - if (task.getCompanyId() == null) {  
167 - task.setCompanyId(TenantContext.getCurrentTenantId());  
168 - }  
169 -  
170 - // 新建任务时设置默认状态  
171 - if (task.getId() == null && task.getStatus() == null) {  
172 - task.setStatus(TaskStatus.PENDING);  
173 - }  
174 -  
175 - try {  
176 - ArticleGenerationTask savedTask = taskRepository.save(task);  
177 - log.info("文章生成任务保存成功,id: {}, articleTheme: {}", savedTask.getId(), savedTask.getArticleTheme());  
178 - return savedTask;  
179 - } catch (Exception e) {  
180 - log.error("文章生成任务保存失败", e);  
181 - throw new BusinessException(ResultCode.SYSTEM_ERROR, "任务保存失败");  
182 - }  
183 - }  
184 -  
185 - /**  
186 - * 批量保存文章生成任务  
187 - * @param tasks 任务列表  
188 - * @return 保存后的任务列表  
189 - * @throws BusinessException 当任务列表为空或保存失败时抛出  
190 - */  
191 - @Transactional  
192 - public List<ArticleGenerationTask> batchSaveTasks(List<ArticleGenerationTask> tasks) {  
193 - log.debug("批量保存文章生成任务,数量: {}", tasks != null ? tasks.size() : 0);  
194 - if (tasks == null || tasks.isEmpty()) {  
195 - throw new BusinessException(ResultCode.PARAM_ERROR, "任务列表不能为空");  
196 - }  
197 -  
198 - Integer companyId = TenantContext.getCurrentTenantId();  
199 - for (ArticleGenerationTask task : tasks) {  
200 - if (task.getCompanyId() == null) {  
201 - task.setCompanyId(companyId);  
202 - }  
203 - if (task.getStatus() == null) {  
204 - task.setStatus(TaskStatus.PENDING);  
205 - }  
206 - }  
207 -  
208 - try {  
209 - List<ArticleGenerationTask> savedTasks = taskRepository.saveAll(tasks);  
210 - log.info("批量保存文章生成任务成功,数量: {}", savedTasks.size());  
211 - return savedTasks;  
212 - } catch (Exception e) {  
213 - log.error("批量保存文章生成任务失败", e);  
214 - throw new BusinessException(ResultCode.SYSTEM_ERROR, "批量保存任务失败");  
215 - }  
216 - }  
217 -  
218 - /**  
219 - * 更新任务状态  
220 - * @param id 任务ID  
221 - * @param status 新状态  
222 - * @return 更新后的任务  
223 - * @throws BusinessException 当参数为空或任务不存在时抛出  
224 - */  
225 - @Transactional  
226 - public ArticleGenerationTask updateTaskStatus(Integer id, TaskStatus status) {  
227 - log.debug("更新任务状态,id: {}, status: {}", id, status);  
228 - if (id == null || status == null) {  
229 - throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID和状态不能为空");  
230 - }  
231 -  
232 - ArticleGenerationTask task = taskRepository.findById(id)  
233 - .orElseThrow(() -> new BusinessException(ResultCode.DATA_NOT_FOUND, "任务不存在"));  
234 -  
235 - task.setStatus(status);  
236 - task.setUpdatedAt(LocalDateTime.now());  
237 -  
238 - try {  
239 - ArticleGenerationTask updatedTask = taskRepository.save(task);  
240 - log.info("任务状态更新成功,id: {}, status: {}", id, status);  
241 - return updatedTask;  
242 - } catch (Exception e) {  
243 - log.error("任务状态更新失败", e);  
244 - throw new BusinessException(ResultCode.SYSTEM_ERROR, "任务状态更新失败");  
245 - }  
246 - }  
247 -  
248 - /**  
249 - * 更新任务进度  
250 - * @param id 任务ID  
251 - * @param progress 进度百分比(0-100)  
252 - * @return 更新后的任务  
253 - * @throws BusinessException 当参数无效或任务不存在时抛出  
254 - */  
255 - @Transactional  
256 - public ArticleGenerationTask updateTaskProgress(Integer id, Integer progress) {  
257 - log.debug("更新任务进度,id: {}, progress: {}", id, progress);  
258 - if (id == null || progress == null || progress < 0 || progress > 100) {  
259 - throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID不能为空,进度必须在0-100之间");  
260 - }  
261 -  
262 - ArticleGenerationTask task = taskRepository.findById(id)  
263 - .orElseThrow(() -> new BusinessException(ResultCode.DATA_NOT_FOUND, "任务不存在"));  
264 -  
265 - task.setProgress(progress);  
266 - task.setUpdatedAt(LocalDateTime.now());  
267 -  
268 - try {  
269 - ArticleGenerationTask updatedTask = taskRepository.save(task);  
270 - log.info("任务进度更新成功,id: {}, progress: {}", id, progress);  
271 - return updatedTask;  
272 - } catch (Exception e) {  
273 - log.error("任务进度更新失败", e);  
274 - throw new BusinessException(ResultCode.SYSTEM_ERROR, "任务进度更新失败");  
275 - }  
276 - }  
277 -  
278 - /**  
279 - * 删除文章生成任务  
280 - * @param id 任务ID  
281 - * @throws BusinessException 当ID为空或任务不存在时抛出  
282 - */  
283 - @Transactional  
284 - public void deleteTask(Integer id) {  
285 - log.debug("删除文章生成任务,id: {}", id);  
286 - if (id == null) {  
287 - throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID不能为空");  
288 - }  
289 -  
290 - if (!taskRepository.existsById(id)) {  
291 - throw new BusinessException(ResultCode.DATA_NOT_FOUND, "任务不存在");  
292 - }  
293 -  
294 - try {  
295 - taskRepository.deleteById(id);  
296 - log.info("文章生成任务删除成功,id: {}", id);  
297 - } catch (Exception e) {  
298 - log.error("文章生成任务删除失败", e);  
299 - throw new BusinessException(ResultCode.SYSTEM_ERROR, "任务删除失败");  
300 - }  
301 - }  
302 -  
303 - /**  
304 - * 批量删除文章生成任务  
305 - * @param ids 任务ID列表  
306 - * @throws BusinessException 当ID列表为空时抛出  
307 - */  
308 - @Transactional  
309 - public void batchDeleteTasks(List<Integer> ids) {  
310 - log.debug("批量删除文章生成任务,数量: {}", ids != null ? ids.size() : 0);  
311 - if (ids == null || ids.isEmpty()) {  
312 - throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID列表不能为空");  
313 - }  
314 -  
315 - try {  
316 - taskRepository.deleteAllById(ids);  
317 - log.info("批量删除文章生成任务成功,数量: {}", ids.size());  
318 - } catch (Exception e) {  
319 - log.error("批量删除文章生成任务失败", e);  
320 - throw new BusinessException(ResultCode.SYSTEM_ERROR, "批量删除任务失败");  
321 - }  
322 - }  
323 -  
324 - /**  
325 - * 统计公司任务总数  
326 - * @param companyId 公司ID  
327 - * @return 任务总数  
328 - * @throws BusinessException 当公司ID为空时抛出  
329 - */  
330 - public long countByCompanyId(Integer companyId) {  
331 - log.debug("统计公司任务总数,companyId: {}", companyId);  
332 - if (companyId == null) {  
333 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");  
334 - }  
335 - return taskRepository.countByCompanyId(companyId);  
336 - }  
337 -  
338 - /**  
339 - * 统计公司指定状态的任务数量  
340 - * @param companyId 公司ID  
341 - * @param status 任务状态  
342 - * @return 任务数量  
343 - * @throws BusinessException 当参数为空时抛出  
344 - */  
345 - public long countByCompanyIdAndStatus(Integer companyId, TaskStatus status) {  
346 - log.debug("统计公司指定状态任务数量,companyId: {}, status: {}", companyId, status);  
347 - if (companyId == null || status == null) {  
348 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID和状态不能为空");  
349 - }  
350 - return taskRepository.countByCompanyIdAndStatus(companyId, status);  
351 - }  
352 -  
353 - /**  
354 - * 查找最近更新的任务  
355 - * @param companyId 公司ID  
356 - * @param since 起始时间  
357 - * @return 最近更新的任务列表  
358 - * @throws BusinessException 当参数为空时抛出  
359 - */  
360 - public List<ArticleGenerationTask> getRecentlyUpdatedTasks(Integer companyId, LocalDateTime since) {  
361 - log.debug("查找最近更新的任务,companyId: {}, since: {}", companyId, since);  
362 - if (companyId == null || since == null) {  
363 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID和时间不能为空");  
364 - }  
365 - return taskRepository.findByUpdatedAtAfterOrderByUpdatedAtDesc(companyId, since);  
366 - }  
367 -  
368 - /**  
369 - * 查找待处理的任务  
370 - * @param companyId 公司ID  
371 - * @return 待处理任务列表  
372 - * @throws BusinessException 当公司ID为空时抛出  
373 - */  
374 - public List<ArticleGenerationTask> getPendingTasks(Integer companyId) {  
375 - log.debug("查找待处理任务,companyId: {}", companyId);  
376 - if (companyId == null) {  
377 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");  
378 - }  
379 - return taskRepository.findByCompanyIdAndStatus(companyId, TaskStatus.PENDING);  
380 - }  
381 -  
382 - /**  
383 - * 查找正在处理的任务  
384 - * @param companyId 公司ID  
385 - * @return 正在处理的任务列表  
386 - * @throws BusinessException 当公司ID为空时抛出  
387 - */  
388 - public List<ArticleGenerationTask> getRunningTasks(Integer companyId) {  
389 - log.debug("查找正在处理任务,companyId: {}", companyId);  
390 - if (companyId == null) {  
391 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");  
392 - }  
393 - return taskRepository.findByCompanyIdAndStatus(companyId, TaskStatus.PROCESSING);  
394 - }  
395 -  
396 - /**  
397 - * 查找已完成的任务  
398 - * @param companyId 公司ID  
399 - * @return 已完成任务列表  
400 - * @throws BusinessException 当公司ID为空时抛出  
401 - */  
402 - public List<ArticleGenerationTask> getCompletedTasks(Integer companyId) {  
403 - log.debug("查找已完成任务,companyId: {}", companyId);  
404 - if (companyId == null) {  
405 - throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");  
406 - }  
407 - return taskRepository.findByCompanyIdAndStatus(companyId, TaskStatus.COMPLETED);  
408 - }  
409 -  
410 - /**  
411 - * 多条件搜索任务(分页)  
412 - * @param companyId 公司ID  
413 - * @param userId 用户ID  
414 - * @param status 任务状态  
415 - * @param articleTheme 文章主题  
416 - * @param startTime 开始时间  
417 - * @param endTime 结束时间  
418 - * @param pageable 分页参数  
419 - * @return 搜索结果分页数据  
420 - */  
421 - public Page<ArticleGenerationTask> searchTasks(Integer companyId, Integer userId,  
422 - TaskStatus status, String articleTheme,  
423 - LocalDateTime startTime, LocalDateTime endTime,  
424 - Pageable pageable) {  
425 - log.debug("多条件搜索任务,companyId: {}, userId: {}, status: {}", companyId, userId, status);  
426 - return taskRepository.searchTasks(companyId, userId, status, articleTheme, startTime, endTime, pageable);  
427 - }  
428 -  
429 - /**  
430 - * 检查任务队列是否已满  
431 - * @param companyId 公司ID  
432 - * @return 是否已满  
433 - */  
434 - public boolean isTaskQueueFull(Integer companyId) {  
435 - log.debug("检查任务队列是否已满,companyId: {}", companyId);  
436 - long pendingCount = taskRepository.countByCompanyIdAndStatus(companyId, TaskStatus.PENDING);  
437 - long processingCount = taskRepository.countByCompanyIdAndStatus(companyId, TaskStatus.PROCESSING);  
438 -  
439 - // 假设每个公司最大允许同时进行50个任务  
440 - return (pendingCount + processingCount) >= 50;  
441 - }  
442 -  
443 - /**  
444 - * 获取最近完成的任务  
445 - * @param companyId 公司ID(可选)  
446 - * @param limit 返回数量限制  
447 - * @return 最近完成的任务列表  
448 - */  
449 - public List<ArticleGenerationTask> getRecentCompletedTasks(Integer companyId, int limit) {  
450 - log.debug("获取最近完成的任务,companyId: {}, limit: {}", companyId, limit);  
451 - Pageable pageable = PageRequest.of(0, limit);  
452 - return taskRepository.findRecentCompletedTasks(companyId, TaskStatus.COMPLETED, pageable);  
453 - }  
454 -  
455 - /**  
456 - * 检查任务是否存在  
457 - * @param id 任务ID  
458 - * @return 是否存在  
459 - */  
460 - public boolean existsById(Integer id) {  
461 - log.debug("检查任务是否存在,id: {}", id);  
462 - if (id == null) return false;  
463 - return taskRepository.existsById(id);  
464 - }  
465 -  
466 - /**  
467 - * 启动任务  
468 - * @param id 任务ID  
469 - * @return 是否启动成功  
470 - */  
471 - @Transactional  
472 - public boolean startTask(Integer id) {  
473 - log.debug("启动任务,id: {}", id);  
474 - if (!taskRepository.existsById(id)) {  
475 - return false;  
476 - }  
477 -  
478 - Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);  
479 - if (taskOpt.isPresent()) {  
480 - ArticleGenerationTask task = taskOpt.get();  
481 - if (task.getStatus() == TaskStatus.PENDING || task.getStatus() == TaskStatus.FAILED) {  
482 - task.start();  
483 - taskRepository.save(task);  
484 - log.info("任务启动成功,id: {}", id);  
485 - return true;  
486 - }  
487 - }  
488 - return false;  
489 - }  
490 -  
491 - /**  
492 - * 暂停任务  
493 - * @param id 任务ID  
494 - * @return 是否暂停成功  
495 - */  
496 - @Transactional  
497 - public boolean pauseTask(Integer id) {  
498 - log.debug("暂停任务,id: {}", id);  
499 - Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);  
500 - if (taskOpt.isPresent()) {  
501 - ArticleGenerationTask task = taskOpt.get();  
502 - if (task.getStatus() == TaskStatus.PROCESSING) {  
503 - task.setStatus(TaskStatus.PAUSED);  
504 - taskRepository.save(task);  
505 - log.info("任务暂停成功,id: {}", id);  
506 - return true;  
507 - }  
508 - }  
509 - return false;  
510 - }  
511 -  
512 - /**  
513 - * 取消任务  
514 - * @param id 任务ID  
515 - * @return 是否取消成功  
516 - */  
517 - @Transactional  
518 - public boolean cancelTask(Integer id) {  
519 - log.debug("取消任务,id: {}", id);  
520 - Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);  
521 - if (taskOpt.isPresent()) {  
522 - ArticleGenerationTask task = taskOpt.get();  
523 - if (task.isCancellable()) {  
524 - task.cancel("用户手动取消");  
525 - taskRepository.save(task);  
526 - log.info("任务取消成功,id: {}", id);  
527 - return true;  
528 - }  
529 - }  
530 - return false;  
531 - }  
532 -  
533 - /**  
534 - * 重试任务  
535 - * @param id 任务ID  
536 - * @return 是否重试成功  
537 - */  
538 - @Transactional  
539 - public boolean retryTask(Integer id) {  
540 - log.debug("重试任务,id: {}", id);  
541 - Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);  
542 - if (taskOpt.isPresent()) {  
543 - ArticleGenerationTask task = taskOpt.get();  
544 - if (task.canRetry()) {  
545 - task.incrementRetryCount();  
546 - task.setStatus(TaskStatus.PENDING);  
547 - task.setErrorMessage(null);  
548 - taskRepository.save(task);  
549 - log.info("任务重试成功,id: {}", id);  
550 - return true;  
551 - }  
552 - }  
553 - return false;  
554 - }  
555 -  
556 - /**  
557 - * 检查任务是否正在运行  
558 - * @param id 任务ID  
559 - * @return 是否正在运行  
560 - */  
561 - public boolean isTaskRunning(Integer id) {  
562 - log.debug("检查任务是否正在运行,id: {}", id);  
563 - Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);  
564 - return taskOpt.map(ArticleGenerationTask::isRunning).orElse(false);  
565 - }  
566 -  
567 -  
568 - /**  
569 - * 获取任务执行日志  
570 - * @param id 任务ID  
571 - * @param level 日志级别  
572 - * @param limit 返回数量限制  
573 - * @return 日志列表  
574 - */  
575 - public List<String> getTaskLogs(Integer id, String level, int limit) {  
576 - log.debug("获取任务执行日志,id: {}, level: {}, limit: {}", id, level, limit);  
577 -  
578 - // 这里应该实现从日志系统获取任务相关的日志  
579 - // 暂时返回模拟数据  
580 - List<String> logs = new ArrayList<>();  
581 - logs.add("任务创建成功");  
582 - logs.add("开始执行任务");  
583 - logs.add("正在处理中...");  
584 -  
585 - return logs.stream().limit(limit).collect(Collectors.toList());  
586 - }  
587 -  
588 - /**  
589 - * 获取任务统计信息  
590 - * @param companyId 公司ID(可选)  
591 - * @param days 统计时间范围(天数)  
592 - * @return 统计信息  
593 - */  
594 - public TaskStatistics getTaskStatistics(Integer companyId, int days) {  
595 - log.debug("获取任务统计信息,companyId: {}, days: {}", companyId, days);  
596 -  
597 - LocalDateTime startTime = LocalDateTime.now().minusDays(days);  
598 - LocalDateTime endTime = LocalDateTime.now();  
599 -  
600 - long totalTasks = taskRepository.countByConditions(companyId, null, startTime, endTime);  
601 - long pendingTasks = taskRepository.countByConditions(companyId, TaskStatus.PENDING, startTime, endTime);  
602 - long runningTasks = taskRepository.countByConditions(companyId, TaskStatus.PROCESSING, startTime, endTime);  
603 - long completedTasks = taskRepository.countByConditions(companyId, TaskStatus.COMPLETED, startTime, endTime);  
604 - long failedTasks = taskRepository.countByConditions(companyId, TaskStatus.FAILED, startTime, endTime);  
605 -  
606 - double successRate = totalTasks > 0 ? (completedTasks * 100.0 / totalTasks) : 0.0;  
607 - Double averageExecutionTime = taskRepository.getAverageExecutionTime(companyId, startTime, endTime);  
608 -  
609 - return new TaskStatistics(  
610 - totalTasks, pendingTasks, runningTasks, completedTasks, failedTasks,  
611 - successRate, averageExecutionTime != null ? averageExecutionTime : 0.0  
612 - );  
613 - }  
614 -  
615 - /**  
616 - * 任务统计信息数据结构  
617 - */  
618 - public static class TaskStatistics {  
619 - private final long totalTasks;  
620 - private final long pendingTasks;  
621 - private final long runningTasks;  
622 - private final long completedTasks;  
623 - private final long failedTasks;  
624 - private final double successRate;  
625 - private final double averageExecutionTime;  
626 -  
627 - public TaskStatistics(long totalTasks, long pendingTasks, long runningTasks,  
628 - long completedTasks, long failedTasks, double successRate,  
629 - double averageExecutionTime) {  
630 - this.totalTasks = totalTasks;  
631 - this.pendingTasks = pendingTasks;  
632 - this.runningTasks = runningTasks;  
633 - this.completedTasks = completedTasks;  
634 - this.failedTasks = failedTasks;  
635 - this.successRate = successRate;  
636 - this.averageExecutionTime = averageExecutionTime;  
637 - }  
638 -  
639 - // Getters  
640 - public long getTotalTasks() { return totalTasks; }  
641 - public long getPendingTasks() { return pendingTasks; }  
642 - public long getRunningTasks() { return runningTasks; }  
643 - public long getCompletedTasks() { return completedTasks; }  
644 - public long getFailedTasks() { return failedTasks; }  
645 - public double getSuccessRate() { return successRate; }  
646 - public double getAverageExecutionTime() { return averageExecutionTime; }  
647 - }  
648 -  
649 - /**  
650 - * 清理已完成的任务  
651 - * @param retentionDays 保留天数  
652 - * @return 清理的任务数量  
653 - */  
654 - @Transactional  
655 - public int cleanupCompletedTasks(int retentionDays) {  
656 - log.debug("清理已完成的任务,保留天数: {}", retentionDays);  
657 -  
658 - LocalDateTime cutoffDate = LocalDateTime.now().minusDays(retentionDays);  
659 - int deletedCount = taskRepository.deleteCompletedTasksBefore(TaskStatus.COMPLETED, cutoffDate);  
660 -  
661 - log.info("清理已完成的任务成功,删除数量: {}", deletedCount);  
662 - return deletedCount;  
663 - }  
664 -}