正在显示
23 个修改的文件
包含
357 行增加
和
3842 行删除
@@ -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 | 22 | ||
34 | - private final DifyApiConfigService difyApiConfigService; | 23 | + @Autowired |
24 | + private DifyApiConfigService difyApiConfigService; | ||
35 | 25 | ||
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); | 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("配置创建失败"); | ||
42 | } | 35 | } |
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); | ||
53 | } | 36 | } |
54 | 37 | ||
55 | - @Operation(summary = "根据ID获取配置", description = "根据配置ID获取单个AI配置") | ||
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 | 50 | ||
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); | 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("查询失败"); | ||
75 | } | 60 | } |
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); | ||
83 | } | 61 | } |
84 | 62 | ||
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); | 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 | 74 | ||
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); | 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 | 86 | ||
102 | - @Operation(summary = "更新配置", description = "更新指定的AI配置") | ||
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 | 102 | ||
112 | - @Operation(summary = "删除配置", description = "删除指定的AI配置") | ||
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(); | 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("配置不存在"); | ||
119 | } | 109 | } |
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); | 110 | + difyApiConfigService.deleteById(id); |
111 | + return Result.success("配置删除成功"); | ||
112 | + } catch (Exception e) { | ||
113 | + log.error("删除AI配置失败, id: {}", id, e); | ||
114 | + return Result.error("配置删除失败"); | ||
128 | } | 115 | } |
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); | ||
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 | } |
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 | + } | ||
137 | } | 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(); | 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); | ||
15 | } | 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; | 11 | +public interface DifyApiConfigService { |
37 | 12 | ||
38 | /** | 13 | /** |
39 | - * 获取所有配置 | ||
40 | - * @return 所有配置列表 | 14 | + * 保存配置 |
41 | */ | 15 | */ |
42 | - public List<DifyApiConfig> getAllConfigs() { | ||
43 | - log.debug("获取所有AI配置"); | ||
44 | - return difyApiConfigRepository.findAll(); | ||
45 | - } | 16 | + DifyApiConfig save(DifyApiConfig config); |
46 | 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 | - } | 21 | + Optional<DifyApiConfig> findById(Integer id); |
72 | 22 | ||
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 | - } | 23 | + /** |
24 | + * 根据公司ID查找配置列表 | ||
25 | + */ | ||
26 | + List<DifyApiConfig> findByCompanyId(Integer companyId); | ||
88 | 27 | ||
89 | /** | 28 | /** |
90 | - * 测试API配置的连接性 | ||
91 | - * @param id 配置ID | ||
92 | - * @return 连接是否成功 | 29 | + * 根据公司ID和启用状态查找配置列表 |
93 | */ | 30 | */ |
94 | - public boolean testConnection(@NotNull Integer id) { | ||
95 | - Optional<DifyApiConfig> configOpt = difyApiConfigRepository.findById(id); | ||
96 | - if (!configOpt.isPresent()) { | ||
97 | - return false; | ||
98 | - } | 31 | + List<DifyApiConfig> findActiveByCompanyId(Integer companyId); |
99 | 32 | ||
100 | - DifyApiConfig config = configOpt.get(); | ||
101 | - if (config.getApiKey() == null || config.getBaseUrl() == null) { | ||
102 | - return false; | ||
103 | - } | 33 | + /** |
34 | + * 根据提供方查找配置列表 | ||
35 | + */ | ||
36 | + List<DifyApiConfig> findByProvider(DifyApiConfig.Provider provider); | ||
104 | 37 | ||
105 | - // 这里应该实现具体的连接测试逻辑 | ||
106 | - // 比如发送HTTP请求到API端点进行验证 | ||
107 | - // 暂时返回true表示测试通过 | ||
108 | - return true; | ||
109 | - } | 38 | + /** |
39 | + * 根据提供方和启用状态查找配置列表 | ||
40 | + */ | ||
41 | + List<DifyApiConfig> findActiveByProvider(DifyApiConfig.Provider provider); | ||
110 | 42 | ||
111 | /** | 43 | /** |
112 | - * 切换配置的激活状态 | ||
113 | - * @param id 配置ID | ||
114 | - * @param active 目标状态 | ||
115 | - * @return 更新后的配置对象 | ||
116 | - * @throws BusinessException 当配置不存在时抛出 | 44 | + * 查找所有配置 |
117 | */ | 45 | */ |
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 | - } | 46 | + List<DifyApiConfig> findAll(); |
124 | 47 | ||
125 | - DifyApiConfig config = configOpt.get(); | ||
126 | - config.setIsActive(active); | ||
127 | - return difyApiConfigRepository.save(config); | ||
128 | - } | 48 | + /** |
49 | + * 删除配置 | ||
50 | + */ | ||
51 | + void deleteById(Integer id); | ||
129 | } | 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 | 22 | ||
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 | - ) { | ||
76 | - 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); | ||
85 | - } catch (Exception e) { | ||
86 | - log.error("分页查询文章生成任务列表失败", e); | ||
87 | - return Result.error("查询失败"); | ||
88 | - } | ||
89 | - } | 23 | + @Autowired |
24 | + private ArticleGenerationTaskService articleGenerationTaskService; | ||
90 | 25 | ||
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() { | 26 | + @PostMapping |
27 | + @Operation(summary = "创建文章生成任务", description = "创建新的文章生成任务") | ||
28 | + public Result<ArticleGenerationTask> createTask(@RequestBody ArticleGenerationTask task) { | ||
101 | try { | 29 | try { |
102 | - List<ArticleGenerationTask> tasks = articleGenerationService.getAllTasks(); | ||
103 | - return Result.success("查询成功", tasks); | 30 | + ArticleGenerationTask savedTask = articleGenerationTaskService.save(task); |
31 | + return Result.success("任务创建成功", savedTask); | ||
104 | } catch (Exception e) { | 32 | } catch (Exception e) { |
105 | - log.error("获取所有文章生成任务失败", e); | ||
106 | - return Result.error("查询失败"); | 33 | + log.error("创建文章生成任务失败", e); |
34 | + return Result.error("任务创建失败"); | ||
107 | } | 35 | } |
108 | } | 36 | } |
109 | 37 | ||
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}") | 38 | @GetMapping("/{id}") |
120 | - public Result<ArticleGenerationTask> getTaskById( | ||
121 | - @Parameter(description = "任务ID", required = true, example = "1") | ||
122 | - @PathVariable @NotNull Integer id | ||
123 | - ) { | 39 | + @Operation(summary = "获取文章生成任务详情", description = "根据ID获取文章生成任务详情") |
40 | + public Result<ArticleGenerationTask> getTaskById(@PathVariable Integer id) { | ||
124 | try { | 41 | try { |
125 | - return articleGenerationService.getTaskById(id) | 42 | + return articleGenerationTaskService.findById(id) |
126 | .map(task -> Result.success("查询成功", task)) | 43 | .map(task -> Result.success("查询成功", task)) |
127 | - .orElse(Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在")); | ||
128 | - } catch (Exception e) { | ||
129 | - log.error("根据ID查询文章生成任务详情失败, id: {}", id, e); | ||
130 | - return Result.error("查询失败"); | ||
131 | - } | ||
132 | - } | ||
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 | - }) | ||
143 | - @GetMapping("/company/{companyId}") | ||
144 | - public Result<List<ArticleGenerationTask>> getTasksByCompanyId( | ||
145 | - @Parameter(description = "公司ID", required = true, example = "1") | ||
146 | - @PathVariable @NotNull Integer companyId | ||
147 | - ) { | ||
148 | - try { | ||
149 | - List<ArticleGenerationTask> tasks = articleGenerationService.getTasksByCompanyId(companyId); | ||
150 | - return Result.success("查询成功", tasks); | 44 | + .orElse(Result.error("任务不存在")); |
151 | } catch (Exception e) { | 45 | } catch (Exception e) { |
152 | - log.error("根据公司ID查询文章生成任务失败, companyId: {}", companyId, e); | 46 | + log.error("获取文章生成任务详情失败, id: {}", id, e); |
153 | return Result.error("查询失败"); | 47 | return Result.error("查询失败"); |
154 | } | 48 | } |
155 | } | 49 | } |
156 | 50 | ||
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 | - }) | ||
166 | - @GetMapping("/user/{userId}") | ||
167 | - public Result<List<ArticleGenerationTask>> getTasksByUserId( | ||
168 | - @Parameter(description = "用户ID", required = true, example = "1") | ||
169 | - @PathVariable @NotNull Integer userId | ||
170 | - ) { | 51 | + @GetMapping |
52 | + @Operation(summary = "获取文章生成任务列表", description = "获取所有文章生成任务列表") | ||
53 | + public Result<List<ArticleGenerationTask>> getAllTasks() { | ||
171 | try { | 54 | try { |
172 | - List<ArticleGenerationTask> tasks = articleGenerationService.getTasksByUserId(userId); | 55 | + List<ArticleGenerationTask> tasks = articleGenerationTaskService.findAll(); |
173 | return Result.success("查询成功", tasks); | 56 | return Result.success("查询成功", tasks); |
174 | } catch (Exception e) { | 57 | } catch (Exception e) { |
175 | - log.error("根据用户ID查询文章生成任务失败, userId: {}", userId, e); | 58 | + log.error("获取文章生成任务列表失败", e); |
176 | return Result.error("查询失败"); | 59 | return Result.error("查询失败"); |
177 | } | 60 | } |
178 | } | 61 | } |
179 | 62 | ||
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 | - }) | ||
189 | - @GetMapping("/status/{status}") | ||
190 | - public Result<List<ArticleGenerationTask>> getTasksByStatus( | ||
191 | - @Parameter(description = "任务状态", required = true) | ||
192 | - @PathVariable @NotNull TaskStatus status | ||
193 | - ) { | 63 | + @GetMapping("/company/{companyId}") |
64 | + @Operation(summary = "根据公司ID获取文章生成任务列表", description = "根据公司ID获取文章生成任务列表") | ||
65 | + public Result<List<ArticleGenerationTask>> getTasksByCompanyId(@PathVariable Integer companyId) { | ||
194 | try { | 66 | try { |
195 | - List<ArticleGenerationTask> tasks = articleGenerationService.getTasksByStatus(status); | 67 | + List<ArticleGenerationTask> tasks = articleGenerationTaskService.findByCompanyId(companyId); |
196 | return Result.success("查询成功", tasks); | 68 | return Result.success("查询成功", tasks); |
197 | } catch (Exception e) { | 69 | } catch (Exception e) { |
198 | - log.error("根据任务状态查询文章生成任务失败, status: {}", status, e); | 70 | + log.error("根据公司ID获取文章生成任务列表失败, companyId: {}", companyId, e); |
199 | return Result.error("查询失败"); | 71 | return Result.error("查询失败"); |
200 | } | 72 | } |
201 | } | 73 | } |
202 | 74 | ||
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 | - ) { | 75 | + @GetMapping("/user/{userId}") |
76 | + @Operation(summary = "根据用户ID获取文章生成任务列表", description = "根据用户ID获取文章生成任务列表") | ||
77 | + public Result<List<ArticleGenerationTask>> getTasksByUserId(@PathVariable Integer userId) { | ||
216 | try { | 78 | try { |
217 | - List<ArticleGenerationTask> tasks = articleGenerationService.getRunningTasks(companyId); | 79 | + List<ArticleGenerationTask> tasks = articleGenerationTaskService.findByUserId(userId); |
218 | return Result.success("查询成功", tasks); | 80 | return Result.success("查询成功", tasks); |
219 | } catch (Exception e) { | 81 | } catch (Exception e) { |
220 | - log.error("获取进行中的任务失败", e); | 82 | + log.error("根据用户ID获取文章生成任务列表失败, userId: {}", userId, e); |
221 | return Result.error("查询失败"); | 83 | return Result.error("查询失败"); |
222 | } | 84 | } |
223 | } | 85 | } |
224 | 86 | ||
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 | - ) { | 87 | + @GetMapping("/status/{status}") |
88 | + @Operation(summary = "根据状态获取文章生成任务列表", description = "根据状态获取文章生成任务列表") | ||
89 | + public Result<List<ArticleGenerationTask>> getTasksByStatus(@PathVariable String status) { | ||
241 | try { | 90 | try { |
242 | - List<ArticleGenerationTask> tasks = articleGenerationService.getRecentCompletedTasks(companyId, limit); | 91 | + ArticleGenerationTask.TaskStatus taskStatus = ArticleGenerationTask.TaskStatus.fromCode(status); |
92 | + List<ArticleGenerationTask> tasks = articleGenerationTaskService.findByStatus(taskStatus); | ||
243 | return Result.success("查询成功", tasks); | 93 | return Result.success("查询成功", tasks); |
244 | } catch (Exception e) { | 94 | } catch (Exception e) { |
245 | - log.error("获取最近完成的任务失败", e); | 95 | + log.error("根据状态获取文章生成任务列表失败, status: {}", status, e); |
246 | return Result.error("查询失败"); | 96 | return Result.error("查询失败"); |
247 | } | 97 | } |
248 | } | 98 | } |
249 | 99 | ||
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 | - }) | ||
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 | - ) { | 101 | + @Operation(summary = "更新文章生成任务", description = "更新文章生成任务信息") |
102 | + public Result<ArticleGenerationTask> updateTask(@PathVariable Integer id, @RequestBody ArticleGenerationTask task) { | ||
328 | try { | 103 | try { |
329 | - return articleGenerationService.getTaskById(id) | ||
330 | - .map(existingTask -> { | ||
331 | - // 检查任务状态是否允许更新 | ||
332 | - if (existingTask.getStatus() == TaskStatus.COMPLETED) { | ||
333 | - return Result.<ArticleGenerationTask>error(ResultCode.CONFLICT, "已完成的任务不允许更新"); | 104 | + if (!articleGenerationTaskService.findById(id).isPresent()) { |
105 | + return Result.error("任务不存在"); | ||
334 | } | 106 | } |
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, "任务不存在")); | 107 | + task.setId(id); |
108 | + ArticleGenerationTask updatedTask = articleGenerationTaskService.save(task); | ||
109 | + return Result.success("任务更新成功", updatedTask); | ||
346 | } catch (Exception e) { | 110 | } catch (Exception e) { |
347 | log.error("更新文章生成任务失败, id: {}", id, e); | 111 | 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 | - ) { | ||
433 | - 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, "任务状态不允许取消"); | ||
444 | - } | ||
445 | - } 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("重试失败"); | 112 | + return Result.error("任务更新失败"); |
481 | } | 113 | } |
482 | } | 114 | } |
483 | 115 | ||
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 | - }) | ||
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 | 15 | ||
43 | - /** | ||
44 | - * 主键ID | ||
45 | - */ | ||
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; | 65 | + createdAt = LocalDateTime.now(); |
66 | + if (status == null) { | ||
67 | + status = TaskStatus.PENDING; | ||
286 | } | 68 | } |
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(); | 69 | + if (progress == null) { |
70 | + progress = 0; | ||
299 | } | 71 | } |
300 | } | 72 | } |
301 | 73 | ||
302 | /** | 74 | /** |
303 | - * 任务失败 | 75 | + * 任务状态枚举 |
304 | */ | 76 | */ |
305 | - public void fail(String errorMessage) { | ||
306 | - this.status = TaskStatus.FAILED; | ||
307 | - this.completedAt = LocalDateTime.now(); | ||
308 | - this.errorMessage = errorMessage; | 77 | + public enum TaskStatus { |
78 | + PENDING("pending"), // 待处理 | ||
79 | + PROCESSING("processing"), // 处理中 | ||
80 | + COMPLETED("completed"), // 已完成 | ||
81 | + FAILED("failed"); // 失败 | ||
309 | 82 | ||
310 | - if (startedAt != null) { | ||
311 | - this.executionTimeMs = java.time.Duration.between(startedAt, completedAt).toMillis(); | ||
312 | - } | ||
313 | - } | 83 | + private final String code; |
314 | 84 | ||
315 | - /** | ||
316 | - * 取消任务 | ||
317 | - */ | ||
318 | - public void cancel(String reason) { | ||
319 | - this.status = TaskStatus.CANCELLED; | ||
320 | - this.completedAt = LocalDateTime.now(); | ||
321 | - this.errorMessage = "任务已取消: " + reason; | 85 | + TaskStatus(String code) { |
86 | + this.code = code; | ||
322 | } | 87 | } |
323 | 88 | ||
324 | - /** | ||
325 | - * 增加重试次数 | ||
326 | - */ | ||
327 | - public void incrementRetryCount() { | ||
328 | - this.retryCount = (retryCount == null ? 0 : retryCount) + 1; | 89 | + public String getCode() { |
90 | + return code; | ||
329 | } | 91 | } |
330 | 92 | ||
331 | - /** | ||
332 | - * 更新进度 | ||
333 | - */ | ||
334 | - public void updateProgress(Integer progress) { | ||
335 | - if (progress >= 0 && progress <= 100) { | ||
336 | - this.progress = progress; | 93 | + public static TaskStatus fromCode(String code) { |
94 | + for (TaskStatus status : TaskStatus.values()) { | ||
95 | + if (status.getCode().equals(code)) { | ||
96 | + return status; | ||
337 | } | 97 | } |
338 | } | 98 | } |
339 | - | ||
340 | - /** | ||
341 | - * 获取任务执行时长(秒) | ||
342 | - */ | ||
343 | - public Long getExecutionTimeSeconds() { | ||
344 | - if (executionTimeMs == null) return null; | ||
345 | - return executionTimeMs / 1000; | 99 | + throw new IllegalArgumentException("未知的任务状态: " + code); |
346 | } | 100 | } |
347 | - | ||
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 "高"; | ||
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 | 15 | ||
42 | - /** | ||
43 | - * 主键ID | ||
44 | - */ | ||
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; | ||
137 | - | ||
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; | 55 | + private ArticleStatus status; |
162 | 56 | ||
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 | - } | ||
274 | - | ||
275 | - /** | ||
276 | - * 获取综合评分(质量+SEO+原创性的平均值) | ||
277 | - */ | ||
278 | - public Double getOverallScore() { | ||
279 | - int count = 0; | ||
280 | - double total = 0.0; | 92 | + private final String code; |
281 | 93 | ||
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 | 97 | ||
295 | - return count > 0 ? total / count : null; | 98 | + public String getCode() { |
99 | + return code; | ||
296 | } | 100 | } |
297 | 101 | ||
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; | 102 | + public static ArticleStatus fromCode(String code) { |
103 | + for (ArticleStatus status : ArticleStatus.values()) { | ||
104 | + if (status.getCode().equals(code)) { | ||
105 | + return status; | ||
306 | } | 106 | } |
307 | - | ||
308 | - /** | ||
309 | - * 增加浏览次数 | ||
310 | - */ | ||
311 | - public void incrementViewCount() { | ||
312 | - this.viewCount = (viewCount == null ? 0 : viewCount) + 1; | ||
313 | } | 107 | } |
314 | - | ||
315 | - /** | ||
316 | - * 增加点赞次数 | ||
317 | - */ | ||
318 | - public void incrementLikeCount() { | ||
319 | - this.likeCount = (likeCount == null ? 0 : likeCount) + 1; | 108 | + throw new IllegalArgumentException("未知的文章状态: " + code); |
320 | } | 109 | } |
321 | - | ||
322 | - /** | ||
323 | - * 增加分享次数 | ||
324 | - */ | ||
325 | - public void incrementShareCount() { | ||
326 | - this.shareCount = (shareCount == null ? 0 : shareCount) + 1; | ||
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); | 38 | + List<ArticleGenerationTask> findByUserIdAndStatus(Integer userId, ArticleGenerationTask.TaskStatus status); |
218 | } | 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 | -} |
-
请 注册 或 登录 后发表评论