正在显示
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 | -} |
-
请 注册 或 登录 后发表评论