作者 徐宝林

初始化项目0908

要显示太多修改。

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

... ... @@ -23,12 +23,19 @@
<knife4j.version>4.4.0</knife4j.version>
<fastjson2.version>2.0.43</fastjson2.version>
<hutool.version>5.8.25</hutool.version>
<jwt.version>4.4.0</jwt.version>
<jwt.version>0.12.3</jwt.version>
<redisson.version>3.25.2</redisson.version>
<jsqlparser.version>4.9</jsqlparser.version>
<jsqlparser.version>4.7</jsqlparser.version>
<springdoc.version>2.6.0</springdoc.version>
</properties>
<dependencies>
<!-- Jackson JSR310依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
... ... @@ -92,6 +99,13 @@
<version>${knife4j.version}</version>
</dependency>
<!-- SpringDoc OpenAPI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
... ... @@ -110,18 +124,18 @@
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>
... ... @@ -151,6 +165,28 @@
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<!-- javax.annotation compatibility for older libraries -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<!-- JSR305 annotations for javax.annotation.meta.When -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<!-- SpotBugs annotations (替代JSR305) -->
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.7.3</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
... ... @@ -173,46 +209,17 @@
<optional>true</optional>
</dependency>
<!-- Test Dependencies -->
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<source>17</source>
<target>17</target>
<fork>true</fork>
<executable>F:/IntelliJ IDEA/jdk-17.0.12_windows-x64_bin/jdk-17.0.12/bin/javac.exe</executable>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
... ... @@ -226,5 +233,4 @@
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
... ...
... ... @@ -2,44 +2,16 @@ package com.aigeo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.context.annotation.ComponentScan;
/**
* AIGEO AI内容生成平台主启动类
*
* @author AIGEO Team
* @since 1.0.0
*/
@SpringBootApplication
@EnableScheduling
@EnableAsync
@EnableTransactionManagement
@ComponentScan(basePackages = {"com.aigeo"})
@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"})
public class AigeoApplication {
private static final Logger log = LoggerFactory.getLogger(AigeoApplication.class);
public static void main(String[] args) {
SpringApplication.run(AigeoApplication.class, args);
log.info("\n========================================");
log.info(" AIGEO AI Content Platform Started!");
log.info(" API Documentation: /doc.html");
log.info("========================================\n");
}
/**
* 提供一个空的ddlApplicationRunner bean以防止Spring Boot自动配置创建的bean类型不匹配
*/
@Bean
public ApplicationRunner ddlApplicationRunner() {
return args -> {
System.out.println("DDL Application Runner executed");
};
}
}
}
\ No newline at end of file
... ...
... ... @@ -2,138 +2,116 @@ package com.aigeo.ai.controller;
import com.aigeo.ai.entity.DifyApiConfig;
import com.aigeo.ai.service.DifyApiConfigService;
import com.aigeo.common.result.Result;
import com.aigeo.common.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Optional;
/**
* AI配置管理控制器
*
* @author AIGEO Team
* @since 1.0.0
* AI配置控制器
*/
@Tag(name = "AI配置管理", description = "Dify API配置管理接口")
@Slf4j
@RestController
@RequestMapping("/api/ai-configs")
@RequiredArgsConstructor
@Tag(name = "AI配置管理", description = "AI配置相关接口")
public class DifyApiConfigController {
private final DifyApiConfigService difyApiConfigService;
@Operation(summary = "获取所有配置", description = "获取所有AI配置列表")
@GetMapping
public Result<List<DifyApiConfig>> getAllConfigs() {
log.debug("获取所有AI配置");
List<DifyApiConfig> configs = difyApiConfigService.getAllConfigs();
return Result.success(configs);
}
@Operation(summary = "分页查询配置", description = "分页查询AI配置列表")
@GetMapping("/list")
public Result<Page<DifyApiConfig>> listConfigs(
@Parameter(description = "页码") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "页大小") @RequestParam(defaultValue = "10") int size) {
log.debug("分页查询AI配置,page: {}, size: {}", page, size);
Pageable pageable = PageRequest.of(page, size);
Page<DifyApiConfig> configs = difyApiConfigService.findAll(pageable);
return Result.success(configs);
@Autowired
private DifyApiConfigService difyApiConfigService;
@PostMapping
@Operation(summary = "创建AI配置", description = "创建新的AI配置")
public Result<DifyApiConfig> createConfig(@RequestBody DifyApiConfig config) {
try {
DifyApiConfig savedConfig = difyApiConfigService.save(config);
return Result.success("配置创建成功", savedConfig);
} catch (Exception e) {
log.error("创建AI配置失败", e);
return Result.error("配置创建失败");
}
}
@Operation(summary = "根据ID获取配置", description = "根据配置ID获取单个AI配置")
@GetMapping("/{id}")
public Result<DifyApiConfig> getConfigById(
@Parameter(description = "配置ID") @PathVariable @NotNull Integer id) {
log.debug("获取AI配置,id: {}", id);
Optional<DifyApiConfig> config = difyApiConfigService.getConfigById(id);
if (config.isPresent()) {
return Result.success(config.get());
} else {
return Result.error(404, "配置不存在");
@Operation(summary = "获取AI配置详情", description = "根据ID获取AI配置详情")
public Result<DifyApiConfig> getConfigById(@PathVariable Integer id) {
try {
return difyApiConfigService.findById(id)
.map(config -> Result.success("查询成功", config))
.orElse(Result.error("配置不存在"));
} catch (Exception e) {
log.error("获取AI配置详情失败, id: {}", id, e);
return Result.error("查询失败");
}
}
@Operation(summary = "根据公司ID获取配置", description = "获取指定公司的AI配置列表")
@GetMapping("/company/{companyId}")
public Result<List<DifyApiConfig>> getConfigsByCompanyId(
@Parameter(description = "公司ID") @PathVariable @NotNull Integer companyId) {
log.debug("获取公司AI配置,companyId: {}", companyId);
List<DifyApiConfig> configs = difyApiConfigService.getConfigsByCompanyId(companyId);
return Result.success(configs);
}
@Operation(summary = "获取共享配置", description = "获取所有共享的AI配置")
@GetMapping("/shared")
public Result<List<DifyApiConfig>> getSharedConfigs() {
log.debug("获取共享AI配置");
List<DifyApiConfig> configs = difyApiConfigService.getSharedConfigs();
return Result.success(configs);
@GetMapping
@Operation(summary = "获取AI配置列表", description = "获取所有AI配置列表")
public Result<List<DifyApiConfig>> getAllConfigs() {
try {
List<DifyApiConfig> configs = difyApiConfigService.findAll();
return Result.success("查询成功", configs);
} catch (Exception e) {
log.error("获取AI配置列表失败", e);
return Result.error("查询失败");
}
}
@Operation(summary = "获取启用的配置", description = "获取所有启用状态的AI配置")
@GetMapping("/active")
public Result<List<DifyApiConfig>> getActiveConfigs() {
log.debug("获取启用的AI配置");
List<DifyApiConfig> configs = difyApiConfigService.getActiveConfigs();
return Result.success(configs);
@GetMapping("/company/{companyId}")
@Operation(summary = "根据公司ID获取AI配置列表", description = "根据公司ID获取AI配置列表")
public Result<List<DifyApiConfig>> getConfigsByCompanyId(@PathVariable Integer companyId) {
try {
List<DifyApiConfig> configs = difyApiConfigService.findByCompanyId(companyId);
return Result.success("查询成功", configs);
} catch (Exception e) {
log.error("根据公司ID获取AI配置列表失败, companyId: {}", companyId, e);
return Result.error("查询失败");
}
}
@Operation(summary = "创建配置", description = "创建新的AI配置")
@PostMapping
public Result<DifyApiConfig> createConfig(
@Parameter(description = "配置信息") @Valid @RequestBody DifyApiConfig config) {
log.info("创建AI配置,name: {}", config.getName());
DifyApiConfig createdConfig = difyApiConfigService.saveConfig(config);
return Result.success(createdConfig);
@GetMapping("/company/{companyId}/active")
@Operation(summary = "根据公司ID获取启用的AI配置列表", description = "根据公司ID获取启用的AI配置列表")
public Result<List<DifyApiConfig>> getActiveConfigsByCompanyId(@PathVariable Integer companyId) {
try {
List<DifyApiConfig> configs = difyApiConfigService.findActiveByCompanyId(companyId);
return Result.success("查询成功", configs);
} catch (Exception e) {
log.error("根据公司ID获取启用的AI配置列表失败, companyId: {}", companyId, e);
return Result.error("查询失败");
}
}
@Operation(summary = "更新配置", description = "更新指定的AI配置")
@PutMapping("/{id}")
public Result<DifyApiConfig> updateConfig(
@Parameter(description = "配置ID") @PathVariable @NotNull Integer id,
@Parameter(description = "配置信息") @Valid @RequestBody DifyApiConfig configDetails) {
log.info("更新AI配置,id: {}, name: {}", id, configDetails.getName());
DifyApiConfig updatedConfig = difyApiConfigService.updateConfig(id, configDetails);
return Result.success(updatedConfig);
@Operation(summary = "更新AI配置", description = "更新AI配置信息")
public Result<DifyApiConfig> updateConfig(@PathVariable Integer id, @RequestBody DifyApiConfig config) {
try {
if (!difyApiConfigService.findById(id).isPresent()) {
return Result.error("配置不存在");
}
config.setId(id);
DifyApiConfig updatedConfig = difyApiConfigService.save(config);
return Result.success("配置更新成功", updatedConfig);
} catch (Exception e) {
log.error("更新AI配置失败, id: {}", id, e);
return Result.error("配置更新失败");
}
}
@Operation(summary = "删除配置", description = "删除指定的AI配置")
@DeleteMapping("/{id}")
public Result<Void> deleteConfig(
@Parameter(description = "配置ID") @PathVariable @NotNull Integer id) {
log.info("删除AI配置,id: {}", id);
difyApiConfigService.deleteConfig(id);
return Result.success();
}
@Operation(summary = "测试配置", description = "测试AI配置的连接性")
@PostMapping("/{id}/test")
public Result<Boolean> testConfig(
@Parameter(description = "配置ID") @PathVariable @NotNull Integer id) {
log.info("测试AI配置连接,id: {}", id);
boolean testResult = difyApiConfigService.testConnection(id);
return Result.success(testResult);
}
@Operation(summary = "启用/禁用配置", description = "启用或禁用指定的AI配置")
@PutMapping("/{id}/toggle")
public Result<DifyApiConfig> toggleConfig(
@Parameter(description = "配置ID") @PathVariable @NotNull Integer id,
@Parameter(description = "是否启用") @RequestParam boolean active) {
log.info("切换AI配置状态,id: {}, active: {}", id, active);
DifyApiConfig config = difyApiConfigService.toggleActive(id, active);
return Result.success(config);
@Operation(summary = "删除AI配置", description = "删除指定ID的AI配置")
public Result<String> deleteConfig(@PathVariable Integer id) {
try {
if (!difyApiConfigService.findById(id).isPresent()) {
return Result.error("配置不存在");
}
difyApiConfigService.deleteById(id);
return Result.success("配置删除成功");
} catch (Exception e) {
log.error("删除AI配置失败, id: {}", id, e);
return Result.error("配置删除失败");
}
}
}
}
\ No newline at end of file
... ...
// ai/controller/PromptTemplateController.java
package com.aigeo.ai.controller;
import com.aigeo.ai.entity.PromptTemplate;
import com.aigeo.ai.service.PromptTemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/prompt-templates")
public class PromptTemplateController {
@Autowired
private PromptTemplateService promptTemplateService;
@GetMapping
public List<PromptTemplate> getAllTemplates() {
return promptTemplateService.getAllTemplates();
}
@GetMapping("/{id}")
public ResponseEntity<PromptTemplate> getTemplateById(@PathVariable Integer id) {
Optional<PromptTemplate> template = promptTemplateService.getTemplateById(id);
return template.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/company/{companyId}")
public List<PromptTemplate> getTemplatesByCompanyId(@PathVariable Integer companyId) {
return promptTemplateService.getTemplatesByCompanyId(companyId);
}
@GetMapping("/system")
public List<PromptTemplate> getSystemTemplates() {
return promptTemplateService.getSystemTemplates();
}
@GetMapping("/active")
public List<PromptTemplate> getActiveTemplates() {
return promptTemplateService.getActiveTemplates();
}
@GetMapping("/company/{companyId}/active")
public List<PromptTemplate> getActiveTemplatesByCompanyId(@PathVariable Integer companyId) {
return promptTemplateService.getActiveTemplatesByCompanyId(companyId);
}
@PostMapping
public PromptTemplate createTemplate(@RequestBody PromptTemplate template) {
return promptTemplateService.saveTemplate(template);
}
@PutMapping("/{id}")
public ResponseEntity<PromptTemplate> updateTemplate(@PathVariable Integer id,
@RequestBody PromptTemplate templateDetails) {
Optional<PromptTemplate> template = promptTemplateService.getTemplateById(id);
if (template.isPresent()) {
PromptTemplate updatedTemplate = template.get();
updatedTemplate.setName(templateDetails.getName());
updatedTemplate.setDescription(templateDetails.getDescription());
updatedTemplate.setLanguage(templateDetails.getLanguage());
updatedTemplate.setContent(templateDetails.getContent());
updatedTemplate.setVariables(templateDetails.getVariables());
updatedTemplate.setIsActive(templateDetails.getIsActive());
PromptTemplate savedTemplate = promptTemplateService.saveTemplate(updatedTemplate);
return ResponseEntity.ok(savedTemplate);
} else {
return ResponseEntity.notFound().build();
}
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTemplate(@PathVariable Integer id) {
promptTemplateService.deleteTemplate(id);
return ResponseEntity.noContent().build();
}
}
// ai/controller/UploadedFileController.java
package com.aigeo.ai.controller;
import com.aigeo.ai.entity.UploadedFile;
import com.aigeo.common.enums.FileStatus;
import com.aigeo.common.enums.FileType;
import com.aigeo.ai.service.UploadedFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/uploaded-files")
public class UploadedFileController {
@Autowired
private UploadedFileService uploadedFileService;
@GetMapping
public List<UploadedFile> getAllFiles() {
return uploadedFileService.getAllFiles();
}
@GetMapping("/{id}")
public ResponseEntity<UploadedFile> getFileById(@PathVariable Integer id) {
Optional<UploadedFile> file = uploadedFileService.getFileById(id);
return file.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/company/{companyId}")
public List<UploadedFile> getFilesByCompanyId(@PathVariable Integer companyId) {
return uploadedFileService.getFilesByCompanyId(companyId);
}
@GetMapping("/user/{userId}")
public List<UploadedFile> getFilesByUserId(@PathVariable Integer userId) {
return uploadedFileService.getFilesByUserId(userId);
}
@GetMapping("/company/{companyId}/user/{userId}")
public List<UploadedFile> getFilesByCompanyIdAndUserId(@PathVariable Integer companyId,
@PathVariable Integer userId) {
return uploadedFileService.getFilesByCompanyIdAndUserId(companyId, userId);
}
@GetMapping("/type/{fileType}")
public List<UploadedFile> getFilesByFileType(@PathVariable FileType fileType) {
return uploadedFileService.getFilesByFileType(fileType);
}
@GetMapping("/status/{status}")
public List<UploadedFile> getFilesByStatus(@PathVariable FileStatus status) {
return uploadedFileService.getFilesByStatus(status);
}
@GetMapping("/company/{companyId}/status/{status}")
public List<UploadedFile> getFilesByCompanyIdAndStatus(@PathVariable Integer companyId,
@PathVariable FileStatus status) {
return uploadedFileService.getFilesByCompanyIdAndStatus(companyId, status);
}
@PostMapping
public UploadedFile createFile(@RequestBody UploadedFile file) {
return uploadedFileService.saveFile(file);
}
@PutMapping("/{id}")
public ResponseEntity<UploadedFile> updateFile(@PathVariable Integer id,
@RequestBody UploadedFile fileDetails) {
Optional<UploadedFile> file = uploadedFileService.getFileById(id);
if (file.isPresent()) {
UploadedFile updatedFile = file.get();
updatedFile.setFileName(fileDetails.getFileName());
updatedFile.setFilePath(fileDetails.getFilePath());
updatedFile.setFileType(fileDetails.getFileType());
updatedFile.setFileSize(fileDetails.getFileSize());
updatedFile.setMimeType(fileDetails.getMimeType());
updatedFile.setStatus(fileDetails.getStatus());
UploadedFile savedFile = uploadedFileService.saveFile(updatedFile);
return ResponseEntity.ok(savedFile);
} else {
return ResponseEntity.notFound().build();
}
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteFile(@PathVariable Integer id) {
uploadedFileService.deleteFile(id);
return ResponseEntity.noContent().build();
}
}
// ai/entity/DifyApiConfig.java
package com.aigeo.ai.entity;
import org.hibernate.annotations.JdbcTypeCode;
import java.sql.Types;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import com.aigeo.common.enums.AiProvider;
import java.time.LocalDateTime;
import java.util.Date;
import java.time.LocalDateTime;
/**
* DifyApiConfig类是一个实体类,用于映射数据库中的ai_dify_api_configs表。
* 该类存储了Dify API的配置信息,包括提供商、API密钥、模型参数等。
*
* @author AIGEO Team
* @since 1.0.0
* AI配置实体类
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "ai_dify_api_configs")
public class DifyApiConfig {
/**
* 主键ID,采用自增策略
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
/**
* 公司ID,用于区分不同公司的配置
*/
@Column(name = "company_id")
private Integer companyId;
/**
* AI服务提供商,使用枚举类型存储
*/
@Enumerated(EnumType.STRING)
private AiProvider provider;
@Column(name = "provider")
private Provider provider;
/**
* 配置名称,不能为空
*/
@NotBlank(message = "配置名称不能为空")
@Column(nullable = false)
@Column(name = "name", nullable = false)
private String name;
/**
* API的基础URL
*/
@Column(name = "base_url")
private String baseUrl;
/**
* API密钥,用于身份验证
*/
@Column(name = "api_key")
private String apiKey;
/**
* 模型名称,指定使用的具体模型
*/
@Column(name = "model_name")
private String modelName;
/**
* 温度参数,控制输出的随机性
*/
@Column(name = "temperature")
private Double temperature;
/**
* Top P参数,控制词汇采样的概率范围
*/
@Column(name = "top_p")
private Double topP;
/**
* 最大令牌数,限制生成内容的长度
*/
@Column(name = "max_tokens")
private Integer maxTokens;
/**
* 请求头信息,以JSON格式存储
*/
@Column(name = "request_headers")
private String requestHeaders;
/**
* 是否激活该配置
*/
@Column(name = "is_active")
private Boolean isActive;
/**
* 创建时间,使用时间戳类型
*/
@Column(name = "created_at")
private LocalDateTime createdAt;
/**
* 更新时间,使用时间戳类型
*/
@Column(name = "updated_at")
private LocalDateTime updatedAt;
/**
* 创建时间设置,新建实体时自动设置
*/
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
... ... @@ -127,11 +66,39 @@ public class DifyApiConfig {
}
}
/**
* 更新时间设置,实体更新时自动设置
*/
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
/**
* AI提供方枚举
*/
public enum Provider {
DIFY("dify"),
OPENAI("openai"),
ANTHROPIC("anthropic"),
GOOGLE("google"),
AZURE_OPENAI("azure_openai"),
OTHER("other");
private final String code;
Provider(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public static Provider fromCode(String code) {
for (Provider provider : Provider.values()) {
if (provider.getCode().equals(code)) {
return provider;
}
}
throw new IllegalArgumentException("未知的AI提供方: " + code);
}
}
}
\ No newline at end of file
... ...
// ai/entity/PromptTemplate.java
package com.aigeo.ai.entity;
import org.hibernate.annotations.JdbcTypeCode;
import java.sql.Types;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
/**
* 提示模板实体类
* 对应数据库表:ai_prompt_templates
*
* 存储AI生成内容时使用的提示词模板,包括:
* - 模板内容和变量定义
* - 语言和适用场景配置
* - 使用统计和版本管理
*
* @author AIGEO Team
* @since 1.0.0
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "ai_prompt_templates", indexes = {
@Index(name = "idx_prompt_templates_company", columnList = "company_id"),
@Index(name = "idx_prompt_templates_active", columnList = "is_active"),
@Index(name = "idx_prompt_templates_language", columnList = "language")
})
public class PromptTemplate {
/**
* 主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Integer id;
/**
* 公司ID(多租户字段)
*/
@NotNull(message = "公司ID不能为空")
@Column(name = "company_id", nullable = false)
private Integer companyId;
/**
* 模板名称
*/
@NotBlank(message = "模板名称不能为空")
@Column(name = "name", nullable = false, length = 200)
private String name;
/**
* 模板描述
*/
@Column(name = "description", length = 500)
private String description;
/**
* 模板语言
*/
@Column(name = "language", length = 10)
@Builder.Default
private String language = "zh-CN";
/**
* 模板内容
*/
@NotBlank(message = "模板内容不能为空")
@Column(name = "content", nullable = false, columnDefinition = "TEXT")
private String content;
/**
* 模板变量(JSON格式存储变量定义)
*/
@Column(name = "variables", columnDefinition = "JSON")
private String variables;
/**
* 模板类型(如:article, product, seo等)
*/
@Column(name = "template_type", length = 50)
@Builder.Default
private String templateType = "article";
/**
* 使用次数统计
*/
@Column(name = "usage_count")
@Builder.Default
private Integer usageCount = 0;
/**
* 是否激活
*/
@Column(name = "is_active")
@Builder.Default
private Boolean isActive = true;
/**
* 创建时间
*/
@CreationTimestamp
@Column(name = "created_at", updatable = false)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(name = "updated_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
/**
* 实体创建前的处理
*/
@PrePersist
protected void onCreate() {
if (isActive == null) isActive = true;
if (language == null) language = "zh-CN";
if (templateType == null) templateType = "article";
if (usageCount == null) usageCount = 0;
}
/**
* 检查是否为激活状态
*/
public boolean isActive() {
return Boolean.TRUE.equals(isActive);
}
/**
* 增加使用次数
*/
public void incrementUsageCount() {
this.usageCount = (usageCount == null ? 0 : usageCount) + 1;
}
/**
* 检查是否为热门模板(使用次数超过阈值)
*/
public boolean isPopularTemplate() {
return usageCount != null && usageCount >= 50;
}
/**
* 获取模板的复杂度评级(基于内容长度和变量数量)
*/
public String getComplexityLevel() {
if (content == null) return "SIMPLE";
int contentLength = content.length();
int variableCount = (variables != null && !variables.trim().isEmpty()) ?
variables.split(",").length : 0;
if (contentLength > 2000 || variableCount > 10) {
return "COMPLEX";
} else if (contentLength > 500 || variableCount > 3) {
return "MEDIUM";
} else {
return "SIMPLE";
}
}
/**
* 检查是否包含指定变量
*/
public boolean hasVariable(String variableName) {
if (variables == null || variables.trim().isEmpty()) {
return false;
}
return variables.contains("\"" + variableName + "\"") ||
variables.contains(variableName);
}
}
\ No newline at end of file
// ai/entity/UploadedFile.java
package com.aigeo.ai.entity;
import org.hibernate.annotations.JdbcTypeCode;
import java.sql.Types;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import com.aigeo.common.enums.FileType;
import com.aigeo.common.enums.FileStatus;
import java.time.LocalDateTime;
/**
* 上传文件实体类,对应数据库表 ai_uploaded_files
*
* 管理系统中上传的文件信息,包括文件元数据、存储路径、
* 文件类型、大小、校验和等信息,支持多租户和版本控制
*
* @author AIGEO Team
* @since 1.0.0
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "ai_uploaded_files", indexes = {
@Index(name = "idx_uploaded_files_company", columnList = "company_id"),
@Index(name = "idx_uploaded_files_user", columnList = "user_id"),
@Index(name = "idx_uploaded_files_type", columnList = "file_type"),
@Index(name = "idx_uploaded_files_status", columnList = "status"),
@Index(name = "idx_uploaded_files_created", columnList = "created_at")
})
public class UploadedFile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
/**
* 公司ID,多租户标识
*/
@NotNull(message = "公司ID不能为空")
@Column(name = "company_id", nullable = false)
private Integer companyId;
/**
* 用户ID,文件上传者
*/
@NotNull(message = "用户ID不能为空")
@Column(name = "user_id", nullable = false)
private Integer userId;
/**
* 原始文件名
*/
@NotBlank(message = "文件名不能为空")
@Column(name = "file_name", nullable = false, length = 255)
private String fileName;
/**
* 文件存储路径
*/
@NotBlank(message = "文件路径不能为空")
@Column(name = "file_path", nullable = false, length = 500)
private String filePath;
/**
* 文件类型枚举
*/
@NotNull(message = "文件类型不能为空")
@Enumerated(EnumType.STRING)
@Column(name = "file_type", nullable = false, length = 50)
private FileType fileType;
/**
* 文件大小,单位字节
*/
@Column(name = "file_size")
private Long fileSize;
/**
* MIME类型
*/
@Column(name = "mime_type", length = 100)
private String mimeType;
/**
* 文件校验和,用于文件完整性验证
*/
@Column(length = 128)
private String checksum;
/**
* 文件版本号,支持文件版本管理
*/
@Builder.Default
private Integer version = 1;
/**
* 文件状态
*/
@Enumerated(EnumType.STRING)
@Column(length = 20)
@Builder.Default
private FileStatus status = FileStatus.UPLOADED;
/**
* 创建时间
*/
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
/**
* 创建时间自动设置
*/
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
/**
* 获取文件大小的可读格式
* @return 格式化的文件大小字符串
*/
public String getFormattedFileSize() {
if (fileSize == null) return "未知";
if (fileSize < 1024) return fileSize + " B";
if (fileSize < 1024 * 1024) return String.format("%.1f KB", fileSize / 1024.0);
if (fileSize < 1024 * 1024 * 1024) return String.format("%.1f MB", fileSize / (1024.0 * 1024.0));
return String.format("%.1f GB", fileSize / (1024.0 * 1024.0 * 1024.0));
}
/**
* 检查文件是否为图片类型
* @return 是否为图片
*/
public boolean isImage() {
return fileType == FileType.IMAGE ||
(mimeType != null && mimeType.startsWith("image/"));
}
/**
* 检查文件是否为文档类型
* @return 是否为文档
*/
public boolean isDocument() {
return fileType == FileType.DOCUMENT ||
(mimeType != null && (mimeType.contains("pdf") ||
mimeType.contains("word") ||
mimeType.contains("text")));
}
}
// ai/repository/DifyApiConfigRepository.java
package com.aigeo.ai.repository;
import com.aigeo.ai.entity.DifyApiConfig;
... ... @@ -7,9 +6,29 @@ import org.springframework.stereotype.Repository;
import java.util.List;
/**
* AI配置仓库接口
*/
@Repository
public interface DifyApiConfigRepository extends JpaRepository<DifyApiConfig, Integer> {
/**
* 根据公司ID查找配置列表
*/
List<DifyApiConfig> findByCompanyId(Integer companyId);
List<DifyApiConfig> findByCompanyIdIsNull(); // 共享配置
List<DifyApiConfig> findByIsActiveTrue();
}
/**
* 根据公司ID和启用状态查找配置列表
*/
List<DifyApiConfig> findByCompanyIdAndIsActiveTrue(Integer companyId);
/**
* 根据提供方查找配置列表
*/
List<DifyApiConfig> findByProvider(DifyApiConfig.Provider provider);
/**
* 根据提供方和启用状态查找配置列表
*/
List<DifyApiConfig> findByProviderAndIsActiveTrue(DifyApiConfig.Provider provider);
}
\ No newline at end of file
... ...
// ai/repository/PromptTemplateRepository.java
package com.aigeo.ai.repository;
import com.aigeo.ai.entity.PromptTemplate;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface PromptTemplateRepository extends JpaRepository<PromptTemplate, Integer> {
List<PromptTemplate> findByCompanyId(Integer companyId);
List<PromptTemplate> findByCompanyIdIsNull(); // 系统模板
List<PromptTemplate> findByIsActiveTrue();
List<PromptTemplate> findByCompanyIdAndIsActiveTrue(Integer companyId);
}
// ai/repository/UploadedFileRepository.java
package com.aigeo.ai.repository;
import com.aigeo.ai.entity.UploadedFile;
import com.aigeo.common.enums.FileStatus;
import com.aigeo.common.enums.FileType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UploadedFileRepository extends JpaRepository<UploadedFile, Integer> {
List<UploadedFile> findByCompanyId(Integer companyId);
List<UploadedFile> findByUserId(Integer userId);
List<UploadedFile> findByCompanyIdAndUserId(Integer companyId, Integer userId);
List<UploadedFile> findByFileType(FileType fileType);
List<UploadedFile> findByStatus(FileStatus status);
List<UploadedFile> findByCompanyIdAndStatus(Integer companyId, FileStatus status);
}
// ai/service/DifyApiConfigService.java
package com.aigeo.ai.service;
import com.aigeo.ai.entity.DifyApiConfig;
import com.aigeo.ai.repository.DifyApiConfigRepository;
import com.aigeo.common.exception.BusinessException;
import com.aigeo.common.result.ResultCode;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
/**
* Dify API配置服务类
*
* 负责管理AI配置的业务逻辑,包括配置的CRUD操作、
* 连接测试、状态管理等功能,支持多租户和权限控制
*
* @author AIGEO Team
* @since 1.0.0
* AI配置服务接口
*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class DifyApiConfigService {
private final DifyApiConfigRepository difyApiConfigRepository;
public interface DifyApiConfigService {
/**
* 获取所有配置
* @return 所有配置列表
* 保存配置
*/
public List<DifyApiConfig> getAllConfigs() {
log.debug("获取所有AI配置");
return difyApiConfigRepository.findAll();
}
DifyApiConfig save(DifyApiConfig config);
/**
* 根据公司ID获取配置列表
* @param companyId 公司ID
* @return 该公司的配置列表
* @throws BusinessException 当公司ID为空时抛出
* 根据ID查找配置
*/
public List<DifyApiConfig> getConfigsByCompanyId(Integer companyId) {
log.debug("根据公司ID获取AI配置,companyId: {}", companyId);
if (companyId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");
}
return difyApiConfigRepository.findByCompanyId(companyId);
}
public List<DifyApiConfig> getSharedConfigs() {
return difyApiConfigRepository.findByCompanyIdIsNull();
}
public List<DifyApiConfig> getActiveConfigs() {
return difyApiConfigRepository.findByIsActiveTrue();
}
public Optional<DifyApiConfig> getConfigById(Integer id) {
return difyApiConfigRepository.findById(id);
}
public DifyApiConfig saveConfig(DifyApiConfig config) {
return difyApiConfigRepository.save(config);
}
public void deleteConfig(Integer id) {
difyApiConfigRepository.deleteById(id);
}
public Page<DifyApiConfig> findAll(Pageable pageable) {
return null;
}
public DifyApiConfig updateConfig(@NotNull Integer id, @Valid DifyApiConfig configDetails) {
return configDetails;
}
Optional<DifyApiConfig> findById(Integer id);
/**
* 测试API配置的连接性
* @param id 配置ID
* @return 连接是否成功
* 根据公司ID查找配置列表
*/
public boolean testConnection(@NotNull Integer id) {
Optional<DifyApiConfig> configOpt = difyApiConfigRepository.findById(id);
if (!configOpt.isPresent()) {
return false;
}
DifyApiConfig config = configOpt.get();
if (config.getApiKey() == null || config.getBaseUrl() == null) {
return false;
}
// 这里应该实现具体的连接测试逻辑
// 比如发送HTTP请求到API端点进行验证
// 暂时返回true表示测试通过
return true;
}
List<DifyApiConfig> findByCompanyId(Integer companyId);
/**
* 根据公司ID和启用状态查找配置列表
*/
List<DifyApiConfig> findActiveByCompanyId(Integer companyId);
/**
* 根据提供方查找配置列表
*/
List<DifyApiConfig> findByProvider(DifyApiConfig.Provider provider);
/**
* 根据提供方和启用状态查找配置列表
*/
List<DifyApiConfig> findActiveByProvider(DifyApiConfig.Provider provider);
/**
* 查找所有配置
*/
List<DifyApiConfig> findAll();
/**
* 切换配置的激活状态
* @param id 配置ID
* @param active 目标状态
* @return 更新后的配置对象
* @throws BusinessException 当配置不存在时抛出
* 删除配置
*/
@Transactional
public DifyApiConfig toggleActive(@NotNull Integer id, boolean active) {
Optional<DifyApiConfig> configOpt = difyApiConfigRepository.findById(id);
if (!configOpt.isPresent()) {
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "配置不存在");
}
DifyApiConfig config = configOpt.get();
config.setIsActive(active);
return difyApiConfigRepository.save(config);
}
}
void deleteById(Integer id);
}
\ No newline at end of file
... ...
// ai/service/PromptTemplateService.java
package com.aigeo.ai.service;
import com.aigeo.ai.entity.PromptTemplate;
import com.aigeo.ai.repository.PromptTemplateRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class PromptTemplateService {
@Autowired
private PromptTemplateRepository promptTemplateRepository;
public List<PromptTemplate> getAllTemplates() {
return promptTemplateRepository.findAll();
}
public List<PromptTemplate> getTemplatesByCompanyId(Integer companyId) {
return promptTemplateRepository.findByCompanyId(companyId);
}
public List<PromptTemplate> getSystemTemplates() {
return promptTemplateRepository.findByCompanyIdIsNull();
}
public List<PromptTemplate> getActiveTemplates() {
return promptTemplateRepository.findByIsActiveTrue();
}
public List<PromptTemplate> getActiveTemplatesByCompanyId(Integer companyId) {
return promptTemplateRepository.findByCompanyIdAndIsActiveTrue(companyId);
}
public Optional<PromptTemplate> getTemplateById(Integer id) {
return promptTemplateRepository.findById(id);
}
public PromptTemplate saveTemplate(PromptTemplate template) {
return promptTemplateRepository.save(template);
}
public void deleteTemplate(Integer id) {
promptTemplateRepository.deleteById(id);
}
}
// ai/service/UploadedFileService.java
package com.aigeo.ai.service;
import com.aigeo.ai.entity.UploadedFile;
import com.aigeo.common.enums.FileStatus;
import com.aigeo.common.enums.FileType;
import com.aigeo.ai.repository.UploadedFileRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UploadedFileService {
@Autowired
private UploadedFileRepository uploadedFileRepository;
public List<UploadedFile> getAllFiles() {
return uploadedFileRepository.findAll();
}
public List<UploadedFile> getFilesByCompanyId(Integer companyId) {
return uploadedFileRepository.findByCompanyId(companyId);
}
public List<UploadedFile> getFilesByUserId(Integer userId) {
return uploadedFileRepository.findByUserId(userId);
}
public List<UploadedFile> getFilesByCompanyIdAndUserId(Integer companyId, Integer userId) {
return uploadedFileRepository.findByCompanyIdAndUserId(companyId, userId);
}
public List<UploadedFile> getFilesByFileType(FileType fileType) {
return uploadedFileRepository.findByFileType(fileType);
}
public List<UploadedFile> getFilesByStatus(FileStatus status) {
return uploadedFileRepository.findByStatus(status);
}
public List<UploadedFile> getFilesByCompanyIdAndStatus(Integer companyId, FileStatus status) {
return uploadedFileRepository.findByCompanyIdAndStatus(companyId, status);
}
public Optional<UploadedFile> getFileById(Integer id) {
return uploadedFileRepository.findById(id);
}
public UploadedFile saveFile(UploadedFile file) {
return uploadedFileRepository.save(file);
}
public void deleteFile(Integer id) {
uploadedFileRepository.deleteById(id);
}
}
package com.aigeo.article.controller;
import com.aigeo.article.entity.ArticleGenerationTask;
import com.aigeo.article.service.ArticleGenerationService;
import com.aigeo.common.enums.TaskStatus;
import com.aigeo.common.result.Result;
import com.aigeo.common.result.ResultCode;
import com.aigeo.article.service.ArticleGenerationTaskService;
import com.aigeo.common.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* 文章生成任务管理控制器
*
* @author AIGEO Team
* @since 1.0.0
* 文章生成任务控制器
*/
@Tag(name = "文章生成任务管理", description = "AI文章生成任务管理接口,支持任务的创建、查询、更新和删除操作")
@Slf4j
@RestController
@RequestMapping("/api/article-tasks")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "文章生成任务管理", description = "文章生成任务相关接口")
public class ArticleGenerationTaskController {
private final ArticleGenerationService articleGenerationService;
@Operation(
summary = "分页查询文章生成任务列表",
description = "支持按任务状态、用户、公司等条件分页查询文章生成任务列表,默认按创建时间倒序排列"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping("/list")
public Result<Page<ArticleGenerationTask>> listTasks(
@Parameter(description = "页码,从0开始", example = "0")
@RequestParam(defaultValue = "0") int page,
@Parameter(description = "页大小,默认10条", example = "10")
@RequestParam(defaultValue = "10") int size,
@Parameter(description = "公司ID(可选)")
@RequestParam(required = false) Integer companyId,
@Parameter(description = "用户ID(可选)")
@RequestParam(required = false) Integer userId,
@Parameter(description = "任务状态")
@RequestParam(required = false) TaskStatus status,
@Parameter(description = "文章主题(模糊查询)")
@RequestParam(required = false) String articleTheme,
@Parameter(description = "开始时间(格式:yyyy-MM-ddTHH:mm:ss)")
@RequestParam(required = false) String startTime,
@Parameter(description = "结束时间(格式:yyyy-MM-ddTHH:mm:ss)")
@RequestParam(required = false) String endTime
) {
@Autowired
private ArticleGenerationTaskService articleGenerationTaskService;
@PostMapping
@Operation(summary = "创建文章生成任务", description = "创建新的文章生成任务")
public Result<ArticleGenerationTask> createTask(@RequestBody ArticleGenerationTask task) {
try {
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt"));
LocalDateTime start = startTime != null ? LocalDateTime.parse(startTime) : null;
LocalDateTime end = endTime != null ? LocalDateTime.parse(endTime) : null;
Page<ArticleGenerationTask> tasks = articleGenerationService.searchTasks(
companyId, userId, status, articleTheme, start, end, pageable
);
return Result.success("查询成功", tasks);
ArticleGenerationTask savedTask = articleGenerationTaskService.save(task);
return Result.success("任务创建成功", savedTask);
} catch (Exception e) {
log.error("分页查询文章生成任务列表失败", e);
return Result.error("查询失败");
log.error("创建文章生成任务失败", e);
return Result.error("任务创建失败");
}
}
@Operation(
summary = "获取所有文章生成任务(简化版)",
description = "获取所有文章生成任务的基本信息,用于下拉选择等场景"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping
public Result<List<ArticleGenerationTask>> getAllTasks() {
@GetMapping("/{id}")
@Operation(summary = "获取文章生成任务详情", description = "根据ID获取文章生成任务详情")
public Result<ArticleGenerationTask> getTaskById(@PathVariable Integer id) {
try {
List<ArticleGenerationTask> tasks = articleGenerationService.getAllTasks();
return Result.success("查询成功", tasks);
return articleGenerationTaskService.findById(id)
.map(task -> Result.success("查询成功", task))
.orElse(Result.error("任务不存在"));
} catch (Exception e) {
log.error("获取所有文章生成任务失败", e);
log.error("获取文章生成任务详情失败, id: {}", id, e);
return Result.error("查询失败");
}
}
@Operation(
summary = "根据ID查询文章生成任务详情",
description = "通过任务ID获取文章生成任务的详细信息"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "404", description = "任务不存在"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping("/{id}")
public Result<ArticleGenerationTask> getTaskById(
@Parameter(description = "任务ID", required = true, example = "1")
@PathVariable @NotNull Integer id
) {
@GetMapping
@Operation(summary = "获取文章生成任务列表", description = "获取所有文章生成任务列表")
public Result<List<ArticleGenerationTask>> getAllTasks() {
try {
return articleGenerationService.getTaskById(id)
.map(task -> Result.success("查询成功", task))
.orElse(Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在"));
List<ArticleGenerationTask> tasks = articleGenerationTaskService.findAll();
return Result.success("查询成功", tasks);
} catch (Exception e) {
log.error("根据ID查询文章生成任务详情失败, id: {}", id, e);
log.error("获取文章生成任务列表失败", e);
return Result.error("查询失败");
}
}
@Operation(
summary = "根据公司ID查询文章生成任务",
description = "获取指定公司下的所有文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "404", description = "公司不存在"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping("/company/{companyId}")
public Result<List<ArticleGenerationTask>> getTasksByCompanyId(
@Parameter(description = "公司ID", required = true, example = "1")
@PathVariable @NotNull Integer companyId
) {
@Operation(summary = "根据公司ID获取文章生成任务列表", description = "根据公司ID获取文章生成任务列表")
public Result<List<ArticleGenerationTask>> getTasksByCompanyId(@PathVariable Integer companyId) {
try {
List<ArticleGenerationTask> tasks = articleGenerationService.getTasksByCompanyId(companyId);
List<ArticleGenerationTask> tasks = articleGenerationTaskService.findByCompanyId(companyId);
return Result.success("查询成功", tasks);
} catch (Exception e) {
log.error("根据公司ID查询文章生成任务失败, companyId: {}", companyId, e);
log.error("根据公司ID获取文章生成任务列表失败, companyId: {}", companyId, e);
return Result.error("查询失败");
}
}
@Operation(
summary = "根据用户ID查询文章生成任务",
description = "获取指定用户创建的所有文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "404", description = "用户不存在"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping("/user/{userId}")
public Result<List<ArticleGenerationTask>> getTasksByUserId(
@Parameter(description = "用户ID", required = true, example = "1")
@PathVariable @NotNull Integer userId
) {
@Operation(summary = "根据用户ID获取文章生成任务列表", description = "根据用户ID获取文章生成任务列表")
public Result<List<ArticleGenerationTask>> getTasksByUserId(@PathVariable Integer userId) {
try {
List<ArticleGenerationTask> tasks = articleGenerationService.getTasksByUserId(userId);
List<ArticleGenerationTask> tasks = articleGenerationTaskService.findByUserId(userId);
return Result.success("查询成功", tasks);
} catch (Exception e) {
log.error("根据用户ID查询文章生成任务失败, userId: {}", userId, e);
log.error("根据用户ID获取文章生成任务列表失败, userId: {}", userId, e);
return Result.error("查询失败");
}
}
@Operation(
summary = "根据任务状态查询文章生成任务",
description = "获取指定状态的所有文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping("/status/{status}")
public Result<List<ArticleGenerationTask>> getTasksByStatus(
@Parameter(description = "任务状态", required = true)
@PathVariable @NotNull TaskStatus status
) {
@Operation(summary = "根据状态获取文章生成任务列表", description = "根据状态获取文章生成任务列表")
public Result<List<ArticleGenerationTask>> getTasksByStatus(@PathVariable String status) {
try {
List<ArticleGenerationTask> tasks = articleGenerationService.getTasksByStatus(status);
ArticleGenerationTask.TaskStatus taskStatus = ArticleGenerationTask.TaskStatus.fromCode(status);
List<ArticleGenerationTask> tasks = articleGenerationTaskService.findByStatus(taskStatus);
return Result.success("查询成功", tasks);
} catch (Exception e) {
log.error("根据任务状态查询文章生成任务失败, status: {}", status, e);
log.error("根据状态获取文章生成任务列表失败, status: {}", status, e);
return Result.error("查询失败");
}
}
@Operation(
summary = "获取进行中的任务",
description = "获取所有状态为进行中的文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping("/running")
public Result<List<ArticleGenerationTask>> getRunningTasks(
@Parameter(description = "公司ID(可选)")
@RequestParam(required = false) Integer companyId
) {
try {
List<ArticleGenerationTask> tasks = articleGenerationService.getRunningTasks(companyId);
return Result.success("查询成功", tasks);
} catch (Exception e) {
log.error("获取进行中的任务失败", e);
return Result.error("查询失败");
}
}
@Operation(
summary = "获取最近完成的任务",
description = "获取最近完成的文章生成任务列表"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping("/recent-completed")
public Result<List<ArticleGenerationTask>> getRecentCompletedTasks(
@Parameter(description = "公司ID(可选)")
@RequestParam(required = false) Integer companyId,
@Parameter(description = "返回数量", example = "10")
@RequestParam(defaultValue = "10") int limit
) {
try {
List<ArticleGenerationTask> tasks = articleGenerationService.getRecentCompletedTasks(companyId, limit);
return Result.success("查询成功", tasks);
} catch (Exception e) {
log.error("获取最近完成的任务失败", e);
return Result.error("查询失败");
}
}
@Operation(
summary = "创建新的文章生成任务",
description = "创建一个新的文章生成任务,包含生成参数和配置"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "创建成功"),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "429", description = "任务队列已满"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@PostMapping
public Result<ArticleGenerationTask> createTask(
@Parameter(description = "文章生成任务信息", required = true)
@Valid @RequestBody ArticleGenerationTask task
) {
try {
// 检查任务队列是否已满
if (articleGenerationService.isTaskQueueFull(task.getCompanyId())) {
return Result.error(ResultCode.TOO_MANY_REQUESTS, "任务队列已满,请稍后再试");
}
ArticleGenerationTask savedTask = articleGenerationService.saveTask(task);
log.info("成功创建文章生成任务: {} (用户ID: {})", savedTask.getArticleTheme(), savedTask.getUserId());
return Result.success("创建成功", savedTask);
} catch (Exception e) {
log.error("创建文章生成任务失败", e);
return Result.error("创建失败");
}
}
@Operation(
summary = "批量创建文章生成任务",
description = "批量创建多个文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "批量创建成功"),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "429", description = "任务数量超限"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@PostMapping("/batch")
public Result<List<ArticleGenerationTask>> batchCreateTasks(
@Parameter(description = "文章生成任务列表", required = true)
@Valid @RequestBody List<ArticleGenerationTask> tasks
) {
try {
if (tasks.size() > 50) {
return Result.error(ResultCode.BAD_REQUEST, "单次批量创建任务数量不能超过50个");
}
List<ArticleGenerationTask> savedTasks = articleGenerationService.batchSaveTasks(tasks);
log.info("成功批量创建文章生成任务,数量: {}", savedTasks.size());
return Result.success("批量创建成功", savedTasks);
} catch (Exception e) {
log.error("批量创建文章生成任务失败", e);
return Result.error("批量创建失败");
}
}
@Operation(
summary = "更新文章生成任务",
description = "根据ID更新文章生成任务的详细信息"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "更新成功"),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "404", description = "任务不存在"),
@ApiResponse(responseCode = "409", description = "任务状态不允许更新"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@PutMapping("/{id}")
public Result<ArticleGenerationTask> updateTask(
@Parameter(description = "任务ID", required = true, example = "1")
@PathVariable @NotNull Integer id,
@Parameter(description = "更新的任务信息", required = true)
@Valid @RequestBody ArticleGenerationTask taskDetails
) {
try {
return articleGenerationService.getTaskById(id)
.map(existingTask -> {
// 检查任务状态是否允许更新
if (existingTask.getStatus() == TaskStatus.COMPLETED) {
return Result.<ArticleGenerationTask>error(ResultCode.CONFLICT, "已完成的任务不允许更新");
}
// 更新字段
existingTask.setArticleTheme(taskDetails.getArticleTheme());
existingTask.setStatus(taskDetails.getStatus());
existingTask.setProgress(taskDetails.getProgress());
ArticleGenerationTask savedTask = articleGenerationService.saveTask(existingTask);
log.info("成功更新文章生成任务: {}", savedTask.getArticleTheme());
return Result.success("更新成功", savedTask);
})
.orElse(Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在"));
} catch (Exception e) {
log.error("更新文章生成任务失败, id: {}", id, e);
return Result.error("更新失败");
}
}
@Operation(
summary = "启动文章生成任务",
description = "启动指定的文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "启动成功"),
@ApiResponse(responseCode = "404", description = "任务不存在"),
@ApiResponse(responseCode = "409", description = "任务状态不允许启动"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@PostMapping("/{id}/start")
public Result<String> startTask(
@Parameter(description = "任务ID", required = true, example = "1")
@PathVariable @NotNull Integer id
) {
try {
if (!articleGenerationService.existsById(id)) {
return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");
}
boolean started = articleGenerationService.startTask(id);
if (started) {
log.info("成功启动文章生成任务, id: {}", id);
return Result.success("任务启动成功");
} else {
return Result.error(ResultCode.CONFLICT, "任务状态不允许启动");
}
} catch (Exception e) {
log.error("启动文章生成任务失败, id: {}", id, e);
return Result.error("启动失败");
}
}
@Operation(
summary = "暂停文章生成任务",
description = "暂停正在运行的文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "暂停成功"),
@ApiResponse(responseCode = "404", description = "任务不存在"),
@ApiResponse(responseCode = "409", description = "任务状态不允许暂停"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@PostMapping("/{id}/pause")
public Result<String> pauseTask(
@Parameter(description = "任务ID", required = true, example = "1")
@PathVariable @NotNull Integer id
) {
try {
if (!articleGenerationService.existsById(id)) {
return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");
}
boolean paused = articleGenerationService.pauseTask(id);
if (paused) {
log.info("成功暂停文章生成任务, id: {}", id);
return Result.success("任务暂停成功");
} else {
return Result.error(ResultCode.CONFLICT, "任务状态不允许暂停");
}
} catch (Exception e) {
log.error("暂停文章生成任务失败, id: {}", id, e);
return Result.error("暂停失败");
}
}
@Operation(
summary = "取消文章生成任务",
description = "取消指定的文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "取消成功"),
@ApiResponse(responseCode = "404", description = "任务不存在"),
@ApiResponse(responseCode = "409", description = "任务状态不允许取消"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@PostMapping("/{id}/cancel")
public Result<String> cancelTask(
@Parameter(description = "任务ID", required = true, example = "1")
@PathVariable @NotNull Integer id
) {
@Operation(summary = "更新文章生成任务", description = "更新文章生成任务信息")
public Result<ArticleGenerationTask> updateTask(@PathVariable Integer id, @RequestBody ArticleGenerationTask task) {
try {
if (!articleGenerationService.existsById(id)) {
return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");
}
boolean cancelled = articleGenerationService.cancelTask(id);
if (cancelled) {
log.info("成功取消文章生成任务, id: {}", id);
return Result.success("任务取消成功");
} else {
return Result.error(ResultCode.CONFLICT, "任务状态不允许取消");
if (!articleGenerationTaskService.findById(id).isPresent()) {
return Result.error("任务不存在");
}
task.setId(id);
ArticleGenerationTask updatedTask = articleGenerationTaskService.save(task);
return Result.success("任务更新成功", updatedTask);
} catch (Exception e) {
log.error("取消文章生成任务失败, id: {}", id, e);
return Result.error("取消失败");
}
}
@Operation(
summary = "重试失败的文章生成任务",
description = "重新启动失败的文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "重试成功"),
@ApiResponse(responseCode = "404", description = "任务不存在"),
@ApiResponse(responseCode = "409", description = "任务状态不允许重试"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@PostMapping("/{id}/retry")
public Result<String> retryTask(
@Parameter(description = "任务ID", required = true, example = "1")
@PathVariable @NotNull Integer id
) {
try {
if (!articleGenerationService.existsById(id)) {
return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");
}
boolean retried = articleGenerationService.retryTask(id);
if (retried) {
log.info("成功重试文章生成任务, id: {}", id);
return Result.success("任务重试成功");
} else {
return Result.error(ResultCode.CONFLICT, "任务状态不允许重试");
}
} catch (Exception e) {
log.error("重试文章生成任务失败, id: {}", id, e);
return Result.error("重试失败");
log.error("更新文章生成任务失败, id: {}", id, e);
return Result.error("任务更新失败");
}
}
@Operation(
summary = "删除文章生成任务",
description = "根据ID删除文章生成任务记录(软删除)"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "删除成功"),
@ApiResponse(responseCode = "404", description = "任务不存在"),
@ApiResponse(responseCode = "409", description = "任务正在运行,无法删除"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@DeleteMapping("/{id}")
public Result<String> deleteTask(
@Parameter(description = "任务ID", required = true, example = "1")
@PathVariable @NotNull Integer id
) {
@Operation(summary = "删除文章生成任务", description = "删除指定ID的文章生成任务")
public Result<String> deleteTask(@PathVariable Integer id) {
try {
if (!articleGenerationService.existsById(id)) {
return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");
}
// 检查任务是否正在运行
if (articleGenerationService.isTaskRunning(id)) {
return Result.error(ResultCode.CONFLICT, "任务正在运行,无法删除");
if (!articleGenerationTaskService.findById(id).isPresent()) {
return Result.error("任务不存在");
}
articleGenerationService.deleteTask(id);
log.info("成功删除文章生成任务, id: {}", id);
return Result.success("删除成功");
articleGenerationTaskService.deleteById(id);
return Result.success("任务删除成功");
} catch (Exception e) {
log.error("删除文章生成任务失败, id: {}", id, e);
return Result.error("删除失败");
}
}
@Operation(
summary = "获取任务执行日志",
description = "获取文章生成任务的执行日志信息"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "404", description = "任务不存在"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping("/{id}/logs")
public Result<List<String>> getTaskLogs(
@Parameter(description = "任务ID", required = true, example = "1")
@PathVariable @NotNull Integer id,
@Parameter(description = "日志级别(INFO/WARN/ERROR)")
@RequestParam(required = false) String level,
@Parameter(description = "最大返回行数", example = "100")
@RequestParam(defaultValue = "100") int limit
) {
try {
if (!articleGenerationService.existsById(id)) {
return Result.error(ResultCode.DATA_NOT_FOUND, "任务不存在");
}
List<String> logs = articleGenerationService.getTaskLogs(id, level, limit);
return Result.success("查询成功", logs);
} catch (Exception e) {
log.error("获取任务执行日志失败, id: {}", id, e);
return Result.error("查询失败");
}
}
@Operation(
summary = "获取任务统计信息",
description = "获取文章生成任务相关的统计数据"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "统计成功"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@GetMapping("/statistics")
public Result<TaskStatistics> getTaskStatistics(
@Parameter(description = "公司ID(可选)")
@RequestParam(required = false) Integer companyId,
@Parameter(description = "统计时间范围(天数)", example = "30")
@RequestParam(defaultValue = "30") int days
) {
try {
ArticleGenerationService.TaskStatistics serviceStats = articleGenerationService.getTaskStatistics(companyId, days);
TaskStatistics statistics = new TaskStatistics(
serviceStats.getTotalTasks(),
serviceStats.getPendingTasks(),
serviceStats.getRunningTasks(),
serviceStats.getCompletedTasks(),
serviceStats.getFailedTasks(),
serviceStats.getSuccessRate(),
serviceStats.getAverageExecutionTime()
);
return Result.success("统计成功", statistics);
} catch (Exception e) {
log.error("获取任务统计信息失败", e);
return Result.error("统计失败");
}
}
@Operation(
summary = "清理已完成的任务",
description = "清理指定天数前完成的文章生成任务"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "清理成功"),
@ApiResponse(responseCode = "400", description = "请求参数错误"),
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
@DeleteMapping("/cleanup")
public Result<String> cleanupCompletedTasks(
@Parameter(description = "保留天数", required = true, example = "30")
@RequestParam @NotNull Integer retentionDays
) {
try {
if (retentionDays < 1) {
return Result.error(ResultCode.BAD_REQUEST, "保留天数必须大于0");
}
int cleanedCount = articleGenerationService.cleanupCompletedTasks(retentionDays);
log.info("清理已完成的任务成功,清理数量: {}", cleanedCount);
return Result.success(String.format("成功清理 %d 个已完成的任务", cleanedCount));
} catch (Exception e) {
log.error("清理已完成的任务失败", e);
return Result.error("清理失败");
}
}
/**
* 任务统计信息DTO
*/
public static class TaskStatistics {
private long totalTasks;
private long pendingTasks;
private long runningTasks;
private long completedTasks;
private long failedTasks;
private double successRate;
private double averageExecutionTime;
// constructors, getters and setters
public TaskStatistics(long totalTasks, long pendingTasks, long runningTasks,
long completedTasks, long failedTasks, double successRate,
double averageExecutionTime) {
this.totalTasks = totalTasks;
this.pendingTasks = pendingTasks;
this.runningTasks = runningTasks;
this.completedTasks = completedTasks;
this.failedTasks = failedTasks;
this.successRate = successRate;
this.averageExecutionTime = averageExecutionTime;
return Result.error("任务删除失败");
}
// getters and setters
public long getTotalTasks() { return totalTasks; }
public void setTotalTasks(long totalTasks) { this.totalTasks = totalTasks; }
public long getPendingTasks() { return pendingTasks; }
public void setPendingTasks(long pendingTasks) { this.pendingTasks = pendingTasks; }
public long getRunningTasks() { return runningTasks; }
public void setRunningTasks(long runningTasks) { this.runningTasks = runningTasks; }
public long getCompletedTasks() { return completedTasks; }
public void setCompletedTasks(long completedTasks) { this.completedTasks = completedTasks; }
public long getFailedTasks() { return failedTasks; }
public void setFailedTasks(long failedTasks) { this.failedTasks = failedTasks; }
public double getSuccessRate() { return successRate; }
public void setSuccessRate(double successRate) { this.successRate = successRate; }
public double getAverageExecutionTime() { return averageExecutionTime; }
public void setAverageExecutionTime(double averageExecutionTime) { this.averageExecutionTime = averageExecutionTime; }
}
}
\ No newline at end of file
... ...
package com.aigeo.article.entity;
import com.aigeo.common.enums.AiTasteLevel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 文章生成配置实体类
* 对应数据库表:ai_article_generation_configs
*
* 用于存储文章生成的各种配置参数,包括:
* - AI写作风格和复杂度设置
* - 文章结构和长度配置
* - SEO优化参数设置
* - 输出格式和样式配置
*
* @author AIGEO Team
* @since 1.0.0
*/
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ai_article_generation_configs")
public class ArticleGenerationConfig {
/**
* 主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Integer id;
/**
* 公司ID(多租户字段)
*/
@NotNull(message = "公司ID不能为空")
@Column(name = "company_id", nullable = false)
private Integer companyId;
/**
* 配置名称
*/
@NotBlank(message = "配置名称不能为空")
@Column(name = "config_name", nullable = false, length = 100)
private String configName;
/**
* AI写作风格等级
*/
@Enumerated(EnumType.STRING)
@Column(name = "ai_taste_level")
@Builder.Default
private AiTasteLevel aiTasteLevel = AiTasteLevel.JUNIOR_HIGH;
/**
* 目标字数(范围的最小值)
*/
@Column(name = "target_word_count_min")
@Builder.Default
private Integer targetWordCountMin = 800;
/**
* 目标字数(范围的最大值)
*/
@Column(name = "target_word_count_max")
@Builder.Default
private Integer targetWordCountMax = 1500;
/**
* 是否包含FAQ部分
*/
@Column(name = "include_faq")
@Builder.Default
private Boolean includeFaq = true;
/**
* 是否生成结构化数据(Schema.org)
*/
@Column(name = "generate_structured_data")
@Builder.Default
private Boolean generateStructuredData = true;
/**
* 是否包含相关链接
*/
@Column(name = "include_related_links")
@Builder.Default
private Boolean includeRelatedLinks = true;
/**
* SEO标题模板
*/
@Column(name = "seo_title_template", length = 500)
private String seoTitleTemplate;
/**
* SEO描述模板
*/
@Column(name = "seo_description_template", length = 500)
private String seoDescriptionTemplate;
/**
* 文章标签模板(用逗号分隔)
*/
@Column(name = "article_tags_template", length = 500)
private String articleTagsTemplate;
/**
* 自定义提示词
*/
@Column(name = "custom_prompt", columnDefinition = "TEXT")
private String customPrompt;
/**
* 语言设置
*/
@Column(name = "language", length = 10)
@Builder.Default
private String language = "zh-CN";
/**
* 输出格式(markdown, html等)
*/
@Column(name = "output_format", length = 20)
@Builder.Default
private String outputFormat = "markdown";
/**
* 是否为默认配置
*/
@Column(name = "is_default")
@Builder.Default
private Boolean isDefault = false;
/**
* 是否启用
*/
@Column(name = "is_active")
@Builder.Default
private Boolean isActive = true;
/**
* 配置描述
*/
@Column(name = "description")
private String description;
/**
* 创建时间
*/
@CreationTimestamp
@Column(name = "created_at", updatable = false)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(name = "updated_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
/**
* 实体创建前的处理
*/
@PrePersist
protected void onCreate() {
if (isActive == null) isActive = true;
if (isDefault == null) isDefault = false;
if (includeFaq == null) includeFaq = true;
if (generateStructuredData == null) generateStructuredData = true;
if (includeRelatedLinks == null) includeRelatedLinks = true;
if (aiTasteLevel == null) aiTasteLevel = AiTasteLevel.JUNIOR_HIGH;
if (language == null) language = "zh-CN";
if (outputFormat == null) outputFormat = "markdown";
if (targetWordCountMin == null) targetWordCountMin = 800;
if (targetWordCountMax == null) targetWordCountMax = 1500;
}
/**
* 获取目标字数范围描述
*/
public String getWordCountRange() {
return targetWordCountMin + "-" + targetWordCountMax + "字";
}
/**
* 检查是否为完整配置(包含所有必要参数)
*/
public boolean isComplete() {
return configName != null && !configName.trim().isEmpty()
&& aiTasteLevel != null
&& targetWordCountMin != null && targetWordCountMin > 0
&& targetWordCountMax != null && targetWordCountMax > targetWordCountMin;
}
/**
* 获取配置的复杂度评分(1-5分)
*/
public int getComplexityScore() {
int score = aiTasteLevel != null ? aiTasteLevel.getComplexityLevel() : 2;
// 根据配置的复杂程度调整评分
if (Boolean.TRUE.equals(generateStructuredData)) score += 1;
if (Boolean.TRUE.equals(includeFaq)) score += 1;
if (customPrompt != null && !customPrompt.trim().isEmpty()) score += 1;
return Math.min(score, 5); // 最高5分
}
}
\ No newline at end of file
package com.aigeo.article.entity;
import com.aigeo.common.enums.TaskStatus;
import com.aigeo.common.enums.AiTasteLevel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 文章生成任务实体类
* 对应数据库表:ai_article_generation_tasks
*
* 用于存储AI文章生成任务信息,包括:
* - 任务配置和参数设置
* - 执行状态和进度跟踪
* - 参考资料和知识来源
* - 生成结果和错误信息
*
* @author AIGEO Team
* @since 1.0.0
*/
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ai_article_generation_tasks", indexes = {
@Index(name = "idx_tasks_company_status", columnList = "company_id, status"),
@Index(name = "idx_tasks_user_id", columnList = "user_id"),
@Index(name = "idx_tasks_created_at", columnList = "created_at DESC")
})
@Table(name = "ai_article_generation_tasks")
public class ArticleGenerationTask {
/**
* 主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Integer id;
/**
* 公司ID(多租户字段)
*/
@NotNull(message = "公司ID不能为空")
@Column(name = "company_id", nullable = false)
@Column(name = "company_id")
private Integer companyId;
/**
* 创建用户ID
*/
@NotNull(message = "用户ID不能为空")
@Column(name = "user_id", nullable = false)
@Column(name = "user_id")
private Integer userId;
/**
* 生成配置ID
*/
@Column(name = "config_id")
private Integer configId;
/**
* 关键词ID
*/
@Column(name = "keyword_id")
private Integer keywordId;
/**
* 文章主题
*/
@NotBlank(message = "文章主题不能为空")
@Column(name = "article_theme", nullable = false, length = 500)
@Column(name = "article_theme")
private String articleTheme;
/**
* 关联话题ID列表(逗号分隔)
*/
@Column(name = "topic_ids", length = 500)
@Column(name = "topic_ids")
private String topicIds;
/**
* 参考资料URL列表(逗号分隔)
*/
@Column(name = "reference_urls", columnDefinition = "TEXT")
@Column(name = "reference_urls")
private String referenceUrls;
/**
* 参考内容
*/
@Column(name = "reference_content", columnDefinition = "LONGTEXT")
@Column(name = "reference_content")
private String referenceContent;
/**
* AI写作风格等级
*/
@Enumerated(EnumType.STRING)
@Column(name = "ai_taste_level")
@Builder.Default
private AiTasteLevel aiTasteLevel = AiTasteLevel.JUNIOR_HIGH;
/**
* 任务状态
*/
@Enumerated(EnumType.STRING)
@Column(name = "status")
@Builder.Default
private TaskStatus status = TaskStatus.PENDING;
private TaskStatus status;
/**
* 任务进度(0-100)
*/
@Column(name = "progress")
@Builder.Default
private Integer progress = 0;
private Byte progress;
/**
* 错误信息
*/
@Column(name = "error_message", length = 2000)
@Column(name = "error_message")
private String errorMessage;
/**
* 生成的文章ID
*/
@Column(name = "generated_article_id")
private Integer generatedArticleId;
/**
* AI配置ID
*/
@Column(name = "dify_api_config_id")
private Integer difyApiConfigId;
/**
* 提示模板ID
*/
@Column(name = "prompt_template_id")
private Integer promptTemplateId;
/**
* 目标字数
*/
@Column(name = "target_word_count")
@Builder.Default
private Integer targetWordCount = 1000;
/**
* 生成语言
*/
@Column(name = "language", length = 10)
@Builder.Default
private String language = "zh-CN";
/**
* 是否包含FAQ
*/
@Column(name = "include_faq")
@Builder.Default
private Boolean includeFaq = false;
/**
* 是否生成SEO数据
*/
@Column(name = "generate_seo")
@Builder.Default
private Boolean generateSeo = true;
/**
* 执行时长(毫秒)
*/
@Column(name = "execution_time_ms")
private Long executionTimeMs;
/**
* 重试次数
*/
@Column(name = "retry_count")
@Builder.Default
private Integer retryCount = 0;
/**
* 最大重试次数
*/
@Column(name = "max_retries")
@Builder.Default
private Integer maxRetries = 3;
/**
* 任务优先级(1-10,数字越大优先级越高)
*/
@Column(name = "priority")
@Builder.Default
private Integer priority = 5;
/**
* 创建时间
*/
@CreationTimestamp
@Column(name = "created_at", updatable = false)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Column(name = "created_at")
private LocalDateTime createdAt;
/**
* 开始执行时间
*/
@Column(name = "started_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startedAt;
/**
* 完成时间
*/
@Column(name = "completed_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime completedAt;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(name = "updated_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
/**
* 实体创建前的处理
*/
@PrePersist
protected void onCreate() {
if (status == null) status = TaskStatus.PENDING;
if (progress == null) progress = 0;
if (aiTasteLevel == null) aiTasteLevel = AiTasteLevel.JUNIOR_HIGH;
if (targetWordCount == null) targetWordCount = 1000;
if (language == null) language = "zh-CN";
if (includeFaq == null) includeFaq = false;
if (generateSeo == null) generateSeo = true;
if (retryCount == null) retryCount = 0;
if (maxRetries == null) maxRetries = 3;
if (priority == null) priority = 5;
}
/**
* 检查任务是否已完成
*/
public boolean isCompleted() {
return status == TaskStatus.COMPLETED || status == TaskStatus.FAILED || status == TaskStatus.CANCELLED;
}
/**
* 检查任务是否正在运行
*/
public boolean isRunning() {
return status == TaskStatus.PROCESSING;
}
/**
* 检查是否可以重试
*/
public boolean canRetry() {
return status == TaskStatus.FAILED && retryCount < maxRetries;
}
/**
* 检查是否可以取消
*/
public boolean isCancellable() {
return status == TaskStatus.PENDING || status == TaskStatus.PROCESSING;
}
/**
* 开始任务执行
*/
public void start() {
this.status = TaskStatus.PROCESSING;
this.startedAt = LocalDateTime.now();
this.progress = 0;
}
/**
* 完成任务
*/
public void complete(Integer articleId) {
this.status = TaskStatus.COMPLETED;
this.completedAt = LocalDateTime.now();
this.progress = 100;
this.generatedArticleId = articleId;
if (startedAt != null) {
this.executionTimeMs = java.time.Duration.between(startedAt, completedAt).toMillis();
createdAt = LocalDateTime.now();
if (status == null) {
status = TaskStatus.PENDING;
}
}
/**
* 任务失败
*/
public void fail(String errorMessage) {
this.status = TaskStatus.FAILED;
this.completedAt = LocalDateTime.now();
this.errorMessage = errorMessage;
if (startedAt != null) {
this.executionTimeMs = java.time.Duration.between(startedAt, completedAt).toMillis();
if (progress == null) {
progress = 0;
}
}
/**
* 取消任务
* 任务状态枚举
*/
public void cancel(String reason) {
this.status = TaskStatus.CANCELLED;
this.completedAt = LocalDateTime.now();
this.errorMessage = "任务已取消: " + reason;
}
public enum TaskStatus {
PENDING("pending"), // 待处理
PROCESSING("processing"), // 处理中
COMPLETED("completed"), // 已完成
FAILED("failed"); // 失败
/**
* 增加重试次数
*/
public void incrementRetryCount() {
this.retryCount = (retryCount == null ? 0 : retryCount) + 1;
}
private final String code;
/**
* 更新进度
*/
public void updateProgress(Integer progress) {
if (progress >= 0 && progress <= 100) {
this.progress = progress;
TaskStatus(String code) {
this.code = code;
}
}
/**
* 获取任务执行时长(秒)
*/
public Long getExecutionTimeSeconds() {
if (executionTimeMs == null) return null;
return executionTimeMs / 1000;
}
public String getCode() {
return code;
}
/**
* 获取任务优先级描述
*/
public String getPriorityDescription() {
if (priority == null) return "普通";
if (priority <= 3) return "低";
if (priority <= 7) return "普通";
return "高";
public static TaskStatus fromCode(String code) {
for (TaskStatus status : TaskStatus.values()) {
if (status.getCode().equals(code)) {
return status;
}
}
throw new IllegalArgumentException("未知的任务状态: " + code);
}
}
}
}
\ No newline at end of file
... ...
package com.aigeo.article.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 文章类型实体类
* 对应数据库表:ai_article_types
*
* 用于定义不同类型的文章分类,如:
* - 新闻资讯类文章
* - 产品介绍类文章
* - 教程指南类文章
* - SEO优化类文章
* - 行业分析类文章
*
* @author AIGEO Team
* @since 1.0.0
*/
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ai_article_types", indexes = {
@Index(name = "uk_article_types_company_name", columnList = "company_id, name", unique = true)
})
public class ArticleType {
/**
* 主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Integer id;
/**
* 公司ID(多租户字段)
*/
@NotNull(message = "公司ID不能为空")
@Column(name = "company_id", nullable = false)
private Integer companyId;
/**
* 文章类型名称
*/
@NotBlank(message = "文章类型名称不能为空")
@Column(name = "name", nullable = false, length = 100)
private String name;
/**
* 类型描述
*/
@Column(name = "description", length = 500)
private String description;
/**
* 类型标识代码(英文标识,用于程序识别)
*/
@Column(name = "code", length = 50)
private String code;
/**
* 默认标题模板
*/
@Column(name = "default_title_template", length = 500)
private String defaultTitleTemplate;
/**
* 默认内容模板
*/
@Column(name = "default_content_template", columnDefinition = "TEXT")
private String defaultContentTemplate;
/**
* 默认标签(逗号分隔)
*/
@Column(name = "default_tags", length = 500)
private String defaultTags;
/**
* 推荐字数范围(最小值)
*/
@Column(name = "recommended_word_count_min")
@Builder.Default
private Integer recommendedWordCountMin = 800;
/**
* 推荐字数范围(最大值)
*/
@Column(name = "recommended_word_count_max")
@Builder.Default
private Integer recommendedWordCountMax = 1500;
/**
* SEO权重(影响搜索引擎优化的重要性,1-10分)
*/
@Column(name = "seo_weight")
@Builder.Default
private Integer seoWeight = 5;
/**
* 是否需要FAQ部分
*/
@Column(name = "requires_faq")
@Builder.Default
private Boolean requiresFaq = false;
/**
* 是否需要结构化数据
*/
@Column(name = "requires_structured_data")
@Builder.Default
private Boolean requiresStructuredData = false;
/**
* 是否需要相关链接
*/
@Column(name = "requires_related_links")
@Builder.Default
private Boolean requiresRelatedLinks = false;
/**
* 排序序号(数字越小排序越靠前)
*/
@Column(name = "sort_order")
@Builder.Default
private Integer sortOrder = 0;
/**
* 是否启用
*/
@Column(name = "is_active")
@Builder.Default
private Boolean isActive = true;
/**
* 图标标识(用于前端显示)
*/
@Column(name = "icon", length = 100)
private String icon;
/**
* 颜色标识(十六进制颜色码)
*/
@Column(name = "color", length = 7)
private String color;
/**
* 使用次数统计
*/
@Column(name = "usage_count")
@Builder.Default
private Integer usageCount = 0;
/**
* 创建时间
*/
@CreationTimestamp
@Column(name = "created_at", updatable = false)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(name = "updated_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
/**
* 实体创建前的处理
*/
@PrePersist
protected void onCreate() {
if (isActive == null) isActive = true;
if (requiresFaq == null) requiresFaq = false;
if (requiresStructuredData == null) requiresStructuredData = false;
if (requiresRelatedLinks == null) requiresRelatedLinks = false;
if (sortOrder == null) sortOrder = 0;
if (usageCount == null) usageCount = 0;
if (seoWeight == null) seoWeight = 5;
if (recommendedWordCountMin == null) recommendedWordCountMin = 800;
if (recommendedWordCountMax == null) recommendedWordCountMax = 1500;
// 自动生成code(如果为空)
if (code == null && name != null) {
this.code = generateCodeFromName(name);
}
}
/**
* 从名称生成代码标识
* @param name 类型名称
* @return 代码标识
*/
private String generateCodeFromName(String name) {
if (name == null) return null;
return name.toLowerCase()
.replaceAll("[^a-zA-Z0-9\u4e00-\u9fa5]", "_") // 非字母数字中文替换为下划线
.replaceAll("_+", "_") // 多个下划线合并为一个
.replaceAll("^_|_$", ""); // 移除开头和结尾的下划线
}
/**
* 获取推荐字数范围描述
*/
public String getRecommendedWordCountRange() {
return recommendedWordCountMin + "-" + recommendedWordCountMax + "字";
}
/**
* 检查是否为高SEO权重类型
*/
public boolean isHighSeoWeight() {
return seoWeight != null && seoWeight >= 7;
}
/**
* 检查是否为复杂类型(需要多种特殊元素)
*/
public boolean isComplexType() {
int complexity = 0;
if (Boolean.TRUE.equals(requiresFaq)) complexity++;
if (Boolean.TRUE.equals(requiresStructuredData)) complexity++;
if (Boolean.TRUE.equals(requiresRelatedLinks)) complexity++;
return complexity >= 2;
}
/**
* 获取类型复杂度等级(1-5级)
*/
public int getComplexityLevel() {
int level = 1; // 基础等级
if (Boolean.TRUE.equals(requiresFaq)) level++;
if (Boolean.TRUE.equals(requiresStructuredData)) level++;
if (Boolean.TRUE.equals(requiresRelatedLinks)) level++;
// 根据字数要求调整复杂度
if (recommendedWordCountMax != null && recommendedWordCountMax > 2000) level++;
return Math.min(level, 5); // 最高5级
}
/**
* 增加使用次数
*/
public void incrementUsageCount() {
this.usageCount = (usageCount == null ? 0 : usageCount) + 1;
}
/**
* 检查是否有完整的模板配置
*/
public boolean hasCompleteTemplates() {
return defaultTitleTemplate != null && !defaultTitleTemplate.trim().isEmpty() &&
defaultContentTemplate != null && !defaultContentTemplate.trim().isEmpty();
}
/**
* 获取类型优先级(基于SEO权重和使用次数)
*/
public double getPriority() {
double seoWeight = this.seoWeight != null ? this.seoWeight : 5;
double usageWeight = this.usageCount != null ? Math.log(this.usageCount + 1) : 0;
return seoWeight * 0.7 + usageWeight * 0.3; // SEO权重占70%,使用频率占30%
}
}
\ No newline at end of file
package com.aigeo.article.entity;
import com.aigeo.common.enums.ContentStatus;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 生成文章实体类
* 对应数据库表:ai_generated_articles
*
* 存储AI生成的文章内容,包括:
* - 文章标题、内容和摘要
* - SEO相关元数据
* - 文章状态和发布信息
* - 质量评分和统计数据
*
* @author AIGEO Team
* @since 1.0.0
* 生成的文章实体类
*/
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ai_generated_articles", indexes = {
@Index(name = "idx_articles_company_status", columnList = "company_id, status"),
@Index(name = "idx_articles_task_id", columnList = "task_id"),
@Index(name = "idx_articles_slug", columnList = "slug")
})
@Table(name = "ai_generated_articles")
public class GeneratedArticle {
/**
* 主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Integer id;
/**
* 公司ID(多租户字段)
*/
@NotNull(message = "公司ID不能为空")
@Column(name = "company_id", nullable = false)
private Integer companyId;
/**
* 关联的生成任务ID
*/
@Column(name = "task_id")
private Integer taskId;
/**
* 文章标题
*/
@NotBlank(message = "文章标题不能为空")
@Column(name = "title", nullable = false, length = 500)
private String title;
@Column(name = "company_id")
private Integer companyId;
/**
* URL友好的标题(slug)
*/
@Column(name = "slug", length = 500)
private String slug;
@Column(name = "version")
private Integer version;
/**
* 文章摘要/描述
*/
@Column(name = "excerpt", length = 1000)
private String excerpt;
@Column(name = "title")
private String title;
/**
* 文章正文内容
*/
@Column(name = "content", columnDefinition = "LONGTEXT")
@Column(name = "content", columnDefinition = "TEXT")
private String content;
/**
* SEO标题(用于<title>标签)
*/
@Column(name = "seo_title", length = 200)
private String seoTitle;
@Column(name = "html_content", columnDefinition = "TEXT")
private String htmlContent;
/**
* SEO描述(用于meta description)
*/
@Column(name = "seo_description", length = 500)
private String seoDescription;
@Column(name = "faq_section", columnDefinition = "TEXT")
private String faqSection;
/**
* 文章标签(JSON数组或逗号分隔)
*/
@Column(name = "tags", length = 1000)
private String tags;
@Column(name = "structured_data", columnDefinition = "TEXT")
private String structuredData;
/**
* 文章字数统计
*/
@Column(name = "word_count")
private Integer wordCount;
/**
* 阅读时长估算(分钟)
*/
@Column(name = "reading_time")
private Integer readingTime;
@Column(name = "keyword_density", columnDefinition = "TEXT")
private String keywordDensity;
@Column(name = "is_selected")
private Boolean isSelected;
/**
* 文章状态
*/
@Enumerated(EnumType.STRING)
@Column(name = "status")
@Builder.Default
private ContentStatus status = ContentStatus.DRAFT;
/**
* 质量评分(1-100分)
*/
@Column(name = "quality_score")
private Integer qualityScore;
/**
* AI置信度评分(0-1之间)
*/
@Column(name = "ai_confidence")
private Double aiConfidence;
private ArticleStatus status;
/**
* 原创性评分(0-1之间,1表示完全原创)
*/
@Column(name = "originality_score")
private Double originalityScore;
/**
* SEO评分(1-100分)
*/
@Column(name = "seo_score")
private Integer seoScore;
/**
* 文章特色图片URL
*/
@Column(name = "featured_image_url", length = 500)
private String featuredImageUrl;
/**
* 发布时间(NULL表示未发布)
*/
@Column(name = "published_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime publishedAt;
/**
* 最后编辑的用户ID
*/
@Column(name = "last_edited_by")
private Integer lastEditedBy;
/**
* 浏览次数统计
*/
@Column(name = "view_count")
@Builder.Default
private Integer viewCount = 0;
/**
* 喜欢次数统计
*/
@Column(name = "like_count")
@Builder.Default
private Integer likeCount = 0;
/**
* 分享次数统计
*/
@Column(name = "share_count")
@Builder.Default
private Integer shareCount = 0;
/**
* 结构化数据(Schema.org JSON-LD)
*/
@Column(name = "structured_data", columnDefinition = "JSON")
private String structuredData;
/**
* 元数据(JSON格式存储额外信息)
*/
@Column(name = "metadata", columnDefinition = "JSON")
private String metadata;
/**
* 创建时间
*/
@CreationTimestamp
@Column(name = "created_at", updatable = false)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Column(name = "created_at")
private LocalDateTime createdAt;
/**
* 更新时间
*/
@UpdateTimestamp
@Column(name = "updated_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
/**
* 实体创建前的处理
*/
@PrePersist
protected void onCreate() {
if (status == null) status = ContentStatus.DRAFT;
if (viewCount == null) viewCount = 0;
if (likeCount == null) likeCount = 0;
if (shareCount == null) shareCount = 0;
// 生成slug
if (slug == null && title != null) {
this.slug = generateSlugFromTitle(title);
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
if (version == null) {
version = 1;
}
// 如果没有SEO标题,使用文章标题
if (seoTitle == null && title != null) {
this.seoTitle = title.length() > 60 ? title.substring(0, 60) + "..." : title;
if (isSelected == null) {
isSelected = false;
}
// 估算阅读时长(按平均阅读速度250字/分钟计算)
if (readingTime == null && wordCount != null) {
this.readingTime = Math.max(1, (int) Math.ceil(wordCount / 250.0));
if (status == null) {
status = ArticleStatus.DRAFT;
}
}
/**
* 从标题生成URL友好的slug
* @param title 文章标题
* @return URL友好的slug
*/
private String generateSlugFromTitle(String title) {
if (title == null) return null;
return title.toLowerCase()
.replaceAll("[^a-zA-Z0-9\u4e00-\u9fa5\\s-]", "") // 保留字母、数字、中文、空格和连字符
.replaceAll("\\s+", "-") // 空格替换为连字符
.replaceAll("-+", "-") // 多个连字符合并为一个
.replaceAll("^-|-$", ""); // 移除开头和结尾的连字符
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
/**
* 检查文章是否已发布
* 文章状态枚举
*/
public boolean isPublished() {
return status == ContentStatus.PUBLISHED && publishedAt != null;
}
public enum ArticleStatus {
DRAFT("draft"), // 草稿
REVIEW("review"), // 审核中
PUBLISHED("published"), // 已发布
ARCHIVED("archived"); // 已归档
/**
* 检查文章是否可以发布
*/
public boolean canPublish() {
return status == ContentStatus.APPROVED ||
status == ContentStatus.GENERATED ||
status == ContentStatus.COMPLETED;
}
private final String code;
/**
* 获取综合评分(质量+SEO+原创性的平均值)
*/
public Double getOverallScore() {
int count = 0;
double total = 0.0;
if (qualityScore != null) {
total += qualityScore;
count++;
}
if (seoScore != null) {
total += seoScore;
count++;
}
if (originalityScore != null) {
total += originalityScore * 100; // 转换为1-100分制
count++;
ArticleStatus(String code) {
this.code = code;
}
return count > 0 ? total / count : null;
}
/**
* 获取参与度评分(基于浏览、点赞、分享数据)
*/
public Double getEngagementScore() {
if (viewCount == 0) return 0.0;
// 简单的参与度计算:(点赞数*2 + 分享数*3) / 浏览数 * 100
return ((likeCount * 2.0 + shareCount * 3.0) / viewCount) * 100;
}
/**
* 增加浏览次数
*/
public void incrementViewCount() {
this.viewCount = (viewCount == null ? 0 : viewCount) + 1;
}
/**
* 增加点赞次数
*/
public void incrementLikeCount() {
this.likeCount = (likeCount == null ? 0 : likeCount) + 1;
}
public String getCode() {
return code;
}
/**
* 增加分享次数
*/
public void incrementShareCount() {
this.shareCount = (shareCount == null ? 0 : shareCount) + 1;
public static ArticleStatus fromCode(String code) {
for (ArticleStatus status : ArticleStatus.values()) {
if (status.getCode().equals(code)) {
return status;
}
}
throw new IllegalArgumentException("未知的文章状态: " + code);
}
}
}
\ No newline at end of file
... ...
package com.aigeo.article.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 任务参考资料实体类
* 对应数据库表:ai_task_references
*
* 存储文章生成任务的参考资料信息,包括:
* - 参考网站URL和标题
* - 参考内容摘要
* - 资料权重和可信度评分
* - 使用状态和备注信息
*
* @author AIGEO Team
* @since 1.0.0
*/
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ai_task_references", indexes = {
@Index(name = "idx_task_refs_task_id", columnList = "task_id"),
@Index(name = "idx_task_refs_url", columnList = "reference_url")
})
public class TaskReference {
/**
* 主键ID
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Integer id;
/**
* 关联的生成任务ID
*/
@NotNull(message = "任务ID不能为空")
@Column(name = "task_id", nullable = false)
private Integer taskId;
/**
* 参考资料URL
*/
@NotBlank(message = "参考资料URL不能为空")
@Column(name = "reference_url", nullable = false, length = 500)
private String referenceUrl;
/**
* 参考资料标题
*/
@Column(name = "reference_title", length = 500)
private String referenceTitle;
/**
* 参考内容摘要
*/
@Column(name = "content_summary", length = 2000)
private String contentSummary;
/**
* 资料来源域名
*/
@Column(name = "source_domain", length = 100)
private String sourceDomain;
/**
* 内容类型(如:article, blog, news, academic等)
*/
@Column(name = "content_type", length = 50)
@Builder.Default
private String contentType = "article";
/**
* 资料权重(影响生成结果的重要程度,1-10分)
*/
@Column(name = "weight")
@Builder.Default
private Integer weight = 5;
/**
* 可信度评分(1-10分)
*/
@Column(name = "credibility_score")
@Builder.Default
private Integer credibilityScore = 5;
/**
* 相关性评分(与目标话题的相关程度,1-10分)
*/
@Column(name = "relevance_score")
@Builder.Default
private Integer relevanceScore = 5;
/**
* 内容质量评分(1-10分)
*/
@Column(name = "quality_score")
private Integer qualityScore;
/**
* 发布时间(参考资料的原始发布时间)
*/
@Column(name = "published_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime publishedAt;
/**
* 语言标识
*/
@Column(name = "language", length = 10)
@Builder.Default
private String language = "zh-CN";
/**
* 字数统计
*/
@Column(name = "word_count")
private Integer wordCount;
/**
* 是否已处理(是否已被AI分析处理)
*/
@Column(name = "is_processed")
@Builder.Default
private Boolean isProcessed = false;
/**
* 处理状态(pending, processing, completed, failed)
*/
@Column(name = "processing_status", length = 20)
@Builder.Default
private String processingStatus = "pending";
/**
* 使用状态(是否在生成中被实际使用)
*/
@Column(name = "is_used")
@Builder.Default
private Boolean isUsed = false;
/**
* 错误信息(处理失败时的错误描述)
*/
@Column(name = "error_message", length = 1000)
private String errorMessage;
/**
* 提取的关键词(JSON数组或逗号分隔)
*/
@Column(name = "extracted_keywords", length = 1000)
private String extractedKeywords;
/**
* 提取的实体(人名、地名、机构名等,JSON格式)
*/
@Column(name = "extracted_entities", columnDefinition = "JSON")
private String extractedEntities;
/**
* 内容分类标签
*/
@Column(name = "content_tags", length = 500)
private String contentTags;
/**
* 备注信息
*/
@Column(name = "notes", length = 500)
private String notes;
/**
* 元数据(JSON格式存储额外信息)
*/
@Column(name = "metadata", columnDefinition = "JSON")
private String metadata;
/**
* 创建时间
*/
@CreationTimestamp
@Column(name = "created_at", updatable = false)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
/**
* 实体创建前的处理
*/
@PrePersist
protected void onCreate() {
if (contentType == null) contentType = "article";
if (weight == null) weight = 5;
if (credibilityScore == null) credibilityScore = 5;
if (relevanceScore == null) relevanceScore = 5;
if (language == null) language = "zh-CN";
if (isProcessed == null) isProcessed = false;
if (processingStatus == null) processingStatus = "pending";
if (isUsed == null) isUsed = false;
// 从URL提取域名
if (sourceDomain == null && referenceUrl != null) {
this.sourceDomain = extractDomainFromUrl(referenceUrl);
}
}
/**
* 从URL提取域名
* @param url 完整URL
* @return 域名
*/
private String extractDomainFromUrl(String url) {
try {
if (url.startsWith("http://")) {
url = url.substring(7);
} else if (url.startsWith("https://")) {
url = url.substring(8);
}
int slashIndex = url.indexOf('/');
if (slashIndex != -1) {
url = url.substring(0, slashIndex);
}
return url;
} catch (Exception e) {
return null;
}
}
/**
* 检查是否为高质量参考资料
*/
public boolean isHighQuality() {
return (credibilityScore != null && credibilityScore >= 7) &&
(relevanceScore != null && relevanceScore >= 7) &&
(qualityScore == null || qualityScore >= 7);
}
/**
* 检查是否为权威来源
*/
public boolean isAuthoritativeSource() {
if (sourceDomain == null) return false;
// 常见权威域名后缀
return sourceDomain.endsWith(".edu") ||
sourceDomain.endsWith(".gov") ||
sourceDomain.endsWith(".org") ||
credibilityScore != null && credibilityScore >= 8;
}
/**
* 获取综合评分(权重、可信度、相关性的加权平均)
*/
public double getOverallScore() {
double weightScore = weight != null ? weight : 5;
double credibilityScore = this.credibilityScore != null ? this.credibilityScore : 5;
double relevanceScore = this.relevanceScore != null ? this.relevanceScore : 5;
double qualityScore = this.qualityScore != null ? this.qualityScore : 5;
// 加权计算:相关性40%,可信度30%,质量20%,权重10%
return relevanceScore * 0.4 + credibilityScore * 0.3 + qualityScore * 0.2 + weightScore * 0.1;
}
/**
* 检查内容是否为近期发布
*/
public boolean isRecentContent() {
if (publishedAt == null) return false;
LocalDateTime sixMonthsAgo = LocalDateTime.now().minusMonths(6);
return publishedAt.isAfter(sixMonthsAgo);
}
/**
* 获取内容新鲜度评分(1-10分,越新分数越高)
*/
public int getFreshnessScore() {
if (publishedAt == null) return 5; // 默认中等分数
LocalDateTime now = LocalDateTime.now();
long daysDiff = java.time.Duration.between(publishedAt, now).toDays();
if (daysDiff <= 7) return 10; // 一周内:10分
if (daysDiff <= 30) return 9; // 一月内:9分
if (daysDiff <= 90) return 8; // 三月内:8分
if (daysDiff <= 180) return 7; // 半年内:7分
if (daysDiff <= 365) return 6; // 一年内:6分
if (daysDiff <= 730) return 4; // 两年内:4分
return 2; // 超过两年:2分
}
/**
* 标记为已处理
*/
public void markAsProcessed() {
this.isProcessed = true;
this.processingStatus = "completed";
}
/**
* 标记处理失败
*/
public void markAsFailed(String errorMessage) {
this.isProcessed = false;
this.processingStatus = "failed";
this.errorMessage = errorMessage;
}
/**
* 标记为已使用
*/
public void markAsUsed() {
this.isUsed = true;
}
}
\ No newline at end of file
package com.aigeo.article.repository;
import com.aigeo.article.entity.ArticleGenerationTask;
import com.aigeo.common.enums.TaskStatus;
import com.aigeo.common.enums.AiTasteLevel;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
/**
* 文章生成任务数据访问层
* 对应数据库表:ai_article_generation_tasks
*
* 负责管理AI文章生成任务的数据访问操作,包括任务创建、状态跟踪、
* 进度监控和结果查询等功能
*
* @author AIGEO Team
* @since 1.0.0
* 文章生成任务仓库接口
*/
@Repository
public interface ArticleGenerationTaskRepository extends JpaRepository<ArticleGenerationTask, Integer> {
/**
* 根据公司ID查找所有任务
* @param companyId 公司ID
* @return 该公司的所有文章生成任务
* 根据公司ID查找任务列表
*/
List<ArticleGenerationTask> findByCompanyId(Integer companyId);
/**
* 根据用户ID查找任务
* @param userId 用户ID
* @return 该用户创建的所有任务
* 根据用户ID查找任务列表
*/
List<ArticleGenerationTask> findByUserId(Integer userId);
/**
* 根据状态查找任务
* @param status 任务状态
* @return 处于指定状态的所有任务
* 根据状态查找任务列表
*/
List<ArticleGenerationTask> findByStatus(TaskStatus status);
List<ArticleGenerationTask> findByStatus(ArticleGenerationTask.TaskStatus status);
/**
* 根据公司ID和状态查找任务
* @param companyId 公司ID
* @param status 任务状态
* @return 匹配条件的任务列表
* 根据公司ID和状态查找任务列表
*/
List<ArticleGenerationTask> findByCompanyIdAndStatus(Integer companyId, TaskStatus status);
List<ArticleGenerationTask> findByCompanyIdAndStatus(Integer companyId, ArticleGenerationTask.TaskStatus status);
/**
* 查找待处理的任务(按创建时间正序)
* @return 待处理的任务队列
* 根据用户ID和状态查找任务列表
*/
List<ArticleGenerationTask> findByStatusOrderByCreatedAtAsc(TaskStatus status);
/**
* 分页查询公司任务(按创建时间倒序)
* @param companyId 公司ID
* @param pageable 分页参数
* @return 分页结果
*/
Page<ArticleGenerationTask> findByCompanyIdOrderByCreatedAtDesc(Integer companyId, Pageable pageable);
/**
* 根据关键词ID查找任务
* @param keywordId 关键词ID
* @return 基于该关键词的生成任务
*/
List<ArticleGenerationTask> findByKeywordId(Integer keywordId);
/**
* 根据AI风格等级查找任务
* @param companyId 公司ID
* @param aiTasteLevel AI风格等级
* @return 使用指定风格的任务
*/
List<ArticleGenerationTask> findByCompanyIdAndAiTasteLevel(Integer companyId, AiTasteLevel aiTasteLevel);
/**
* 查找指定时间范围内的任务
* @param companyId 公司ID
* @param startTime 开始时间
* @param endTime 结束时间
* @return 时间范围内的任务
*/
List<ArticleGenerationTask> findByCompanyIdAndCreatedAtBetween(Integer companyId, LocalDateTime startTime, LocalDateTime endTime);
/**
* 统计公司任务数量按状态分组
* @param companyId 公司ID
* @param status 任务状态
* @return 指定状态的任务数量
*/
@Query("SELECT COUNT(t) FROM ArticleGenerationTask t WHERE t.companyId = :companyId AND t.status = :status")
long countByCompanyIdAndStatus(@Param("companyId") Integer companyId, @Param("status") TaskStatus status);
/**
* 查找用户最近的任务
* @param userId 用户ID
* @param limit 限制数量
* @return 用户最近的任务
*/
@Query("SELECT t FROM ArticleGenerationTask t WHERE t.userId = :userId ORDER BY t.createdAt DESC")
List<ArticleGenerationTask> findRecentTasksByUser(@Param("userId") Integer userId, Pageable pageable);
/**
* 查找超时未完成的任务
* @param timeoutThreshold 超时时间阈值
* @return 超时的处理中任务
*/
@Query("SELECT t FROM ArticleGenerationTask t WHERE t.status = 'PROCESSING' AND t.createdAt < :timeoutThreshold")
List<ArticleGenerationTask> findTimeoutTasks(@Param("timeoutThreshold") LocalDateTime timeoutThreshold);
/**
* 根据公司ID和文章主题模糊搜索任务
* @param companyId 公司ID
* @param articleTheme 文章主题关键词
* @return 匹配的任务列表
*/
List<ArticleGenerationTask> findByCompanyIdAndArticleThemeContainingIgnoreCase(Integer companyId, String articleTheme);
/**
* 查找最近完成的任务
* @param companyId 公司ID(可选)
* @param status 任务状态
* @param pageable 分页参数
* @return 最近完成的任务
*/
@Query("SELECT t FROM ArticleGenerationTask t WHERE (:companyId IS NULL OR t.companyId = :companyId) AND t.status = :status ORDER BY t.completedAt DESC")
List<ArticleGenerationTask> findRecentCompletedTasks(@Param("companyId") Integer companyId, @Param("status") TaskStatus status, Pageable pageable);
/**
* 根据多个条件搜索任务
* @param companyId 公司ID
* @param userId 用户ID
* @param status 任务状态
* @param articleTheme 文章主题
* @param startTime 开始时间
* @param endTime 结束时间
* @param pageable 分页参数
* @return 分页搜索结果
*/
@Query("SELECT t FROM ArticleGenerationTask t WHERE " +
"(:companyId IS NULL OR t.companyId = :companyId) AND " +
"(:userId IS NULL OR t.userId = :userId) AND " +
"(:status IS NULL OR t.status = :status) AND " +
"(:articleTheme IS NULL OR t.articleTheme LIKE %:articleTheme%) AND " +
"(:startTime IS NULL OR t.createdAt >= :startTime) AND " +
"(:endTime IS NULL OR t.createdAt <= :endTime)")
Page<ArticleGenerationTask> searchTasks(@Param("companyId") Integer companyId,
@Param("userId") Integer userId,
@Param("status") TaskStatus status,
@Param("articleTheme") String articleTheme,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime,
Pageable pageable);
/**
* 查找最近更新的任务
* @param companyId 公司ID
* @param since 起始时间
* @return 最近更新的任务
*/
@Query("SELECT t FROM ArticleGenerationTask t WHERE (:companyId IS NULL OR t.companyId = :companyId) AND t.updatedAt >= :since ORDER BY t.updatedAt DESC")
List<ArticleGenerationTask> findByUpdatedAtAfterOrderByUpdatedAtDesc(@Param("companyId") Integer companyId, @Param("since") LocalDateTime since);
/**
* 统计指定时间范围内的任务数量
* @param companyId 公司ID(可选)
* @param status 任务状态
* @param startTime 开始时间
* @param endTime 结束时间
* @return 任务数量
*/
@Query("SELECT COUNT(t) FROM ArticleGenerationTask t WHERE " +
"(:companyId IS NULL OR t.companyId = :companyId) AND " +
"(:status IS NULL OR t.status = :status) AND " +
"t.createdAt BETWEEN :startTime AND :endTime")
long countByConditions(@Param("companyId") Integer companyId,
@Param("status") TaskStatus status,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
/**
* 计算平均执行时长
* @param companyId 公司ID(可选)
* @param startTime 开始时间
* @param endTime 结束时间
* @return 平均执行时长(毫秒)
*/
@Query("SELECT AVG(t.executionTimeMs) FROM ArticleGenerationTask t WHERE " +
"(:companyId IS NULL OR t.companyId = :companyId) AND " +
"t.executionTimeMs IS NOT NULL AND " +
"t.createdAt BETWEEN :startTime AND :endTime")
Double getAverageExecutionTime(@Param("companyId") Integer companyId,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
/**
* 删除指定时间之前完成的任务
* @param completedBefore 完成时间阈值
* @param status 任务状态
* @return 删除的任务数量
*/
@Query("DELETE FROM ArticleGenerationTask t WHERE t.status = :status AND t.completedAt < :completedBefore")
int deleteCompletedTasksBefore(@Param("status") TaskStatus status, @Param("completedBefore") LocalDateTime completedBefore);
long countByCompanyId(Integer companyId);
}
List<ArticleGenerationTask> findByUserIdAndStatus(Integer userId, ArticleGenerationTask.TaskStatus status);
}
\ No newline at end of file
... ...
package com.aigeo.article.service;
import com.aigeo.article.entity.ArticleGenerationTask;
import com.aigeo.article.repository.ArticleGenerationTaskRepository;
import com.aigeo.common.context.TenantContext;
import com.aigeo.common.enums.TaskStatus;
import com.aigeo.common.exception.BusinessException;
import com.aigeo.common.result.ResultCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.data.domain.PageRequest;
/**
* 文章生成服务层
*
* 负责管理文章生成任务的业务逻辑,包括任务的创建、更新、查询、
* 状态跟踪、批量操作等功能。支持AI自动生成文章内容、
* 多种内容模板、发布状态管理等
*
* @author AIGEO Team
* @since 1.0.0
*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ArticleGenerationService {
private final ArticleGenerationTaskRepository taskRepository;
/**
* 获取所有文章生成任务
* @return 所有任务列表
*/
public List<ArticleGenerationTask> getAllTasks() {
log.debug("获取所有文章生成任务");
return taskRepository.findAll();
}
/**
* 根据公司ID获取文章生成任务列表
* @param companyId 公司ID
* @return 该公司的任务列表
* @throws BusinessException 当公司ID为空时抛出
*/
public List<ArticleGenerationTask> getTasksByCompanyId(Integer companyId) {
log.debug("获取公司文章生成任务,companyId: {}", companyId);
if (companyId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");
}
return taskRepository.findByCompanyId(companyId);
}
/**
* 根据用户ID获取文章生成任务列表
* @param userId 用户ID
* @return 该用户创建的任务列表
* @throws BusinessException 当用户ID为空时抛出
*/
public List<ArticleGenerationTask> getTasksByUserId(Integer userId) {
log.debug("获取用户文章生成任务,userId: {}", userId);
if (userId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "用户ID不能为空");
}
return taskRepository.findByUserId(userId);
}
/**
* 根据任务状态获取文章生成任务列表
* @param status 任务状态
* @return 指定状态的任务列表
* @throws BusinessException 当状态为空时抛出
*/
public List<ArticleGenerationTask> getTasksByStatus(TaskStatus status) {
log.debug("根据状态获取文章生成任务,status: {}", status);
if (status == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务状态不能为空");
}
return taskRepository.findByStatus(status);
}
/**
* 根据公司ID和状态获取任务列表
* @param companyId 公司ID
* @param status 任务状态
* @return 匹配条件的任务列表
* @throws BusinessException 当参数为空时抛出
*/
public List<ArticleGenerationTask> getTasksByCompanyIdAndStatus(Integer companyId, TaskStatus status) {
log.debug("根据公司和状态获取任务,companyId: {}, status: {}", companyId, status);
if (companyId == null || status == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID和状态不能为空");
}
return taskRepository.findByCompanyIdAndStatus(companyId, status);
}
/**
* 分页查询公司的文章生成任务
* @param companyId 公司ID
* @param pageable 分页参数
* @return 分页结果
* @throws BusinessException 当公司ID为空时抛出
*/
public Page<ArticleGenerationTask> getTasksByCompanyIdWithPaging(Integer companyId, Pageable pageable) {
log.debug("分页查询公司文章生成任务,companyId: {}", companyId);
if (companyId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");
}
return taskRepository.findByCompanyIdOrderByCreatedAtDesc(companyId, pageable);
}
/**
* 根据ID获取文章生成任务
* @param id 任务ID
* @return 任务对象的Optional包装
* @throws BusinessException 当ID为空时抛出
*/
public Optional<ArticleGenerationTask> getTaskById(Integer id) {
log.debug("根据ID获取文章生成任务,id: {}", id);
if (id == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID不能为空");
}
return taskRepository.findById(id);
}
/**
* 根据文章主题模糊搜索任务
* @param companyId 公司ID
* @param articleTheme 文章主题关键词
* @return 匹配的任务列表
* @throws BusinessException 当参数为空时抛出
*/
public List<ArticleGenerationTask> searchTasksByTitle(Integer companyId, String articleTheme) {
log.debug("根据文章主题搜索任务,companyId: {}, articleTheme: {}", companyId, articleTheme);
if (companyId == null || !StringUtils.hasText(articleTheme)) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID和文章主题不能为空");
}
return taskRepository.findByCompanyIdAndArticleThemeContainingIgnoreCase(companyId, articleTheme);
}
/**
* 保存文章生成任务
* @param task 任务对象
* @return 保存后的任务
* @throws BusinessException 当任务对象为空或保存失败时抛出
*/
@Transactional
public ArticleGenerationTask saveTask(ArticleGenerationTask task) {
log.debug("保存文章生成任务,articleTheme: {}", task != null ? task.getArticleTheme() : null);
if (task == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务对象不能为空");
}
// 设置当前租户ID
if (task.getCompanyId() == null) {
task.setCompanyId(TenantContext.getCurrentTenantId());
}
// 新建任务时设置默认状态
if (task.getId() == null && task.getStatus() == null) {
task.setStatus(TaskStatus.PENDING);
}
try {
ArticleGenerationTask savedTask = taskRepository.save(task);
log.info("文章生成任务保存成功,id: {}, articleTheme: {}", savedTask.getId(), savedTask.getArticleTheme());
return savedTask;
} catch (Exception e) {
log.error("文章生成任务保存失败", e);
throw new BusinessException(ResultCode.SYSTEM_ERROR, "任务保存失败");
}
}
/**
* 批量保存文章生成任务
* @param tasks 任务列表
* @return 保存后的任务列表
* @throws BusinessException 当任务列表为空或保存失败时抛出
*/
@Transactional
public List<ArticleGenerationTask> batchSaveTasks(List<ArticleGenerationTask> tasks) {
log.debug("批量保存文章生成任务,数量: {}", tasks != null ? tasks.size() : 0);
if (tasks == null || tasks.isEmpty()) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务列表不能为空");
}
Integer companyId = TenantContext.getCurrentTenantId();
for (ArticleGenerationTask task : tasks) {
if (task.getCompanyId() == null) {
task.setCompanyId(companyId);
}
if (task.getStatus() == null) {
task.setStatus(TaskStatus.PENDING);
}
}
try {
List<ArticleGenerationTask> savedTasks = taskRepository.saveAll(tasks);
log.info("批量保存文章生成任务成功,数量: {}", savedTasks.size());
return savedTasks;
} catch (Exception e) {
log.error("批量保存文章生成任务失败", e);
throw new BusinessException(ResultCode.SYSTEM_ERROR, "批量保存任务失败");
}
}
/**
* 更新任务状态
* @param id 任务ID
* @param status 新状态
* @return 更新后的任务
* @throws BusinessException 当参数为空或任务不存在时抛出
*/
@Transactional
public ArticleGenerationTask updateTaskStatus(Integer id, TaskStatus status) {
log.debug("更新任务状态,id: {}, status: {}", id, status);
if (id == null || status == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID和状态不能为空");
}
ArticleGenerationTask task = taskRepository.findById(id)
.orElseThrow(() -> new BusinessException(ResultCode.DATA_NOT_FOUND, "任务不存在"));
task.setStatus(status);
task.setUpdatedAt(LocalDateTime.now());
try {
ArticleGenerationTask updatedTask = taskRepository.save(task);
log.info("任务状态更新成功,id: {}, status: {}", id, status);
return updatedTask;
} catch (Exception e) {
log.error("任务状态更新失败", e);
throw new BusinessException(ResultCode.SYSTEM_ERROR, "任务状态更新失败");
}
}
/**
* 更新任务进度
* @param id 任务ID
* @param progress 进度百分比(0-100)
* @return 更新后的任务
* @throws BusinessException 当参数无效或任务不存在时抛出
*/
@Transactional
public ArticleGenerationTask updateTaskProgress(Integer id, Integer progress) {
log.debug("更新任务进度,id: {}, progress: {}", id, progress);
if (id == null || progress == null || progress < 0 || progress > 100) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID不能为空,进度必须在0-100之间");
}
ArticleGenerationTask task = taskRepository.findById(id)
.orElseThrow(() -> new BusinessException(ResultCode.DATA_NOT_FOUND, "任务不存在"));
task.setProgress(progress);
task.setUpdatedAt(LocalDateTime.now());
try {
ArticleGenerationTask updatedTask = taskRepository.save(task);
log.info("任务进度更新成功,id: {}, progress: {}", id, progress);
return updatedTask;
} catch (Exception e) {
log.error("任务进度更新失败", e);
throw new BusinessException(ResultCode.SYSTEM_ERROR, "任务进度更新失败");
}
}
/**
* 删除文章生成任务
* @param id 任务ID
* @throws BusinessException 当ID为空或任务不存在时抛出
*/
@Transactional
public void deleteTask(Integer id) {
log.debug("删除文章生成任务,id: {}", id);
if (id == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID不能为空");
}
if (!taskRepository.existsById(id)) {
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "任务不存在");
}
try {
taskRepository.deleteById(id);
log.info("文章生成任务删除成功,id: {}", id);
} catch (Exception e) {
log.error("文章生成任务删除失败", e);
throw new BusinessException(ResultCode.SYSTEM_ERROR, "任务删除失败");
}
}
/**
* 批量删除文章生成任务
* @param ids 任务ID列表
* @throws BusinessException 当ID列表为空时抛出
*/
@Transactional
public void batchDeleteTasks(List<Integer> ids) {
log.debug("批量删除文章生成任务,数量: {}", ids != null ? ids.size() : 0);
if (ids == null || ids.isEmpty()) {
throw new BusinessException(ResultCode.PARAM_ERROR, "任务ID列表不能为空");
}
try {
taskRepository.deleteAllById(ids);
log.info("批量删除文章生成任务成功,数量: {}", ids.size());
} catch (Exception e) {
log.error("批量删除文章生成任务失败", e);
throw new BusinessException(ResultCode.SYSTEM_ERROR, "批量删除任务失败");
}
}
/**
* 统计公司任务总数
* @param companyId 公司ID
* @return 任务总数
* @throws BusinessException 当公司ID为空时抛出
*/
public long countByCompanyId(Integer companyId) {
log.debug("统计公司任务总数,companyId: {}", companyId);
if (companyId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");
}
return taskRepository.countByCompanyId(companyId);
}
/**
* 统计公司指定状态的任务数量
* @param companyId 公司ID
* @param status 任务状态
* @return 任务数量
* @throws BusinessException 当参数为空时抛出
*/
public long countByCompanyIdAndStatus(Integer companyId, TaskStatus status) {
log.debug("统计公司指定状态任务数量,companyId: {}, status: {}", companyId, status);
if (companyId == null || status == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID和状态不能为空");
}
return taskRepository.countByCompanyIdAndStatus(companyId, status);
}
/**
* 查找最近更新的任务
* @param companyId 公司ID
* @param since 起始时间
* @return 最近更新的任务列表
* @throws BusinessException 当参数为空时抛出
*/
public List<ArticleGenerationTask> getRecentlyUpdatedTasks(Integer companyId, LocalDateTime since) {
log.debug("查找最近更新的任务,companyId: {}, since: {}", companyId, since);
if (companyId == null || since == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID和时间不能为空");
}
return taskRepository.findByUpdatedAtAfterOrderByUpdatedAtDesc(companyId, since);
}
/**
* 查找待处理的任务
* @param companyId 公司ID
* @return 待处理任务列表
* @throws BusinessException 当公司ID为空时抛出
*/
public List<ArticleGenerationTask> getPendingTasks(Integer companyId) {
log.debug("查找待处理任务,companyId: {}", companyId);
if (companyId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");
}
return taskRepository.findByCompanyIdAndStatus(companyId, TaskStatus.PENDING);
}
/**
* 查找正在处理的任务
* @param companyId 公司ID
* @return 正在处理的任务列表
* @throws BusinessException 当公司ID为空时抛出
*/
public List<ArticleGenerationTask> getRunningTasks(Integer companyId) {
log.debug("查找正在处理任务,companyId: {}", companyId);
if (companyId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");
}
return taskRepository.findByCompanyIdAndStatus(companyId, TaskStatus.PROCESSING);
}
/**
* 查找已完成的任务
* @param companyId 公司ID
* @return 已完成任务列表
* @throws BusinessException 当公司ID为空时抛出
*/
public List<ArticleGenerationTask> getCompletedTasks(Integer companyId) {
log.debug("查找已完成任务,companyId: {}", companyId);
if (companyId == null) {
throw new BusinessException(ResultCode.PARAM_ERROR, "公司ID不能为空");
}
return taskRepository.findByCompanyIdAndStatus(companyId, TaskStatus.COMPLETED);
}
/**
* 多条件搜索任务(分页)
* @param companyId 公司ID
* @param userId 用户ID
* @param status 任务状态
* @param articleTheme 文章主题
* @param startTime 开始时间
* @param endTime 结束时间
* @param pageable 分页参数
* @return 搜索结果分页数据
*/
public Page<ArticleGenerationTask> searchTasks(Integer companyId, Integer userId,
TaskStatus status, String articleTheme,
LocalDateTime startTime, LocalDateTime endTime,
Pageable pageable) {
log.debug("多条件搜索任务,companyId: {}, userId: {}, status: {}", companyId, userId, status);
return taskRepository.searchTasks(companyId, userId, status, articleTheme, startTime, endTime, pageable);
}
/**
* 检查任务队列是否已满
* @param companyId 公司ID
* @return 是否已满
*/
public boolean isTaskQueueFull(Integer companyId) {
log.debug("检查任务队列是否已满,companyId: {}", companyId);
long pendingCount = taskRepository.countByCompanyIdAndStatus(companyId, TaskStatus.PENDING);
long processingCount = taskRepository.countByCompanyIdAndStatus(companyId, TaskStatus.PROCESSING);
// 假设每个公司最大允许同时进行50个任务
return (pendingCount + processingCount) >= 50;
}
/**
* 获取最近完成的任务
* @param companyId 公司ID(可选)
* @param limit 返回数量限制
* @return 最近完成的任务列表
*/
public List<ArticleGenerationTask> getRecentCompletedTasks(Integer companyId, int limit) {
log.debug("获取最近完成的任务,companyId: {}, limit: {}", companyId, limit);
Pageable pageable = PageRequest.of(0, limit);
return taskRepository.findRecentCompletedTasks(companyId, TaskStatus.COMPLETED, pageable);
}
/**
* 检查任务是否存在
* @param id 任务ID
* @return 是否存在
*/
public boolean existsById(Integer id) {
log.debug("检查任务是否存在,id: {}", id);
if (id == null) return false;
return taskRepository.existsById(id);
}
/**
* 启动任务
* @param id 任务ID
* @return 是否启动成功
*/
@Transactional
public boolean startTask(Integer id) {
log.debug("启动任务,id: {}", id);
if (!taskRepository.existsById(id)) {
return false;
}
Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);
if (taskOpt.isPresent()) {
ArticleGenerationTask task = taskOpt.get();
if (task.getStatus() == TaskStatus.PENDING || task.getStatus() == TaskStatus.FAILED) {
task.start();
taskRepository.save(task);
log.info("任务启动成功,id: {}", id);
return true;
}
}
return false;
}
/**
* 暂停任务
* @param id 任务ID
* @return 是否暂停成功
*/
@Transactional
public boolean pauseTask(Integer id) {
log.debug("暂停任务,id: {}", id);
Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);
if (taskOpt.isPresent()) {
ArticleGenerationTask task = taskOpt.get();
if (task.getStatus() == TaskStatus.PROCESSING) {
task.setStatus(TaskStatus.PAUSED);
taskRepository.save(task);
log.info("任务暂停成功,id: {}", id);
return true;
}
}
return false;
}
/**
* 取消任务
* @param id 任务ID
* @return 是否取消成功
*/
@Transactional
public boolean cancelTask(Integer id) {
log.debug("取消任务,id: {}", id);
Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);
if (taskOpt.isPresent()) {
ArticleGenerationTask task = taskOpt.get();
if (task.isCancellable()) {
task.cancel("用户手动取消");
taskRepository.save(task);
log.info("任务取消成功,id: {}", id);
return true;
}
}
return false;
}
/**
* 重试任务
* @param id 任务ID
* @return 是否重试成功
*/
@Transactional
public boolean retryTask(Integer id) {
log.debug("重试任务,id: {}", id);
Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);
if (taskOpt.isPresent()) {
ArticleGenerationTask task = taskOpt.get();
if (task.canRetry()) {
task.incrementRetryCount();
task.setStatus(TaskStatus.PENDING);
task.setErrorMessage(null);
taskRepository.save(task);
log.info("任务重试成功,id: {}", id);
return true;
}
}
return false;
}
/**
* 检查任务是否正在运行
* @param id 任务ID
* @return 是否正在运行
*/
public boolean isTaskRunning(Integer id) {
log.debug("检查任务是否正在运行,id: {}", id);
Optional<ArticleGenerationTask> taskOpt = taskRepository.findById(id);
return taskOpt.map(ArticleGenerationTask::isRunning).orElse(false);
}
/**
* 获取任务执行日志
* @param id 任务ID
* @param level 日志级别
* @param limit 返回数量限制
* @return 日志列表
*/
public List<String> getTaskLogs(Integer id, String level, int limit) {
log.debug("获取任务执行日志,id: {}, level: {}, limit: {}", id, level, limit);
// 这里应该实现从日志系统获取任务相关的日志
// 暂时返回模拟数据
List<String> logs = new ArrayList<>();
logs.add("任务创建成功");
logs.add("开始执行任务");
logs.add("正在处理中...");
return logs.stream().limit(limit).collect(Collectors.toList());
}
/**
* 获取任务统计信息
* @param companyId 公司ID(可选)
* @param days 统计时间范围(天数)
* @return 统计信息
*/
public TaskStatistics getTaskStatistics(Integer companyId, int days) {
log.debug("获取任务统计信息,companyId: {}, days: {}", companyId, days);
LocalDateTime startTime = LocalDateTime.now().minusDays(days);
LocalDateTime endTime = LocalDateTime.now();
long totalTasks = taskRepository.countByConditions(companyId, null, startTime, endTime);
long pendingTasks = taskRepository.countByConditions(companyId, TaskStatus.PENDING, startTime, endTime);
long runningTasks = taskRepository.countByConditions(companyId, TaskStatus.PROCESSING, startTime, endTime);
long completedTasks = taskRepository.countByConditions(companyId, TaskStatus.COMPLETED, startTime, endTime);
long failedTasks = taskRepository.countByConditions(companyId, TaskStatus.FAILED, startTime, endTime);
double successRate = totalTasks > 0 ? (completedTasks * 100.0 / totalTasks) : 0.0;
Double averageExecutionTime = taskRepository.getAverageExecutionTime(companyId, startTime, endTime);
return new TaskStatistics(
totalTasks, pendingTasks, runningTasks, completedTasks, failedTasks,
successRate, averageExecutionTime != null ? averageExecutionTime : 0.0
);
}
/**
* 任务统计信息数据结构
*/
public static class TaskStatistics {
private final long totalTasks;
private final long pendingTasks;
private final long runningTasks;
private final long completedTasks;
private final long failedTasks;
private final double successRate;
private final double averageExecutionTime;
public TaskStatistics(long totalTasks, long pendingTasks, long runningTasks,
long completedTasks, long failedTasks, double successRate,
double averageExecutionTime) {
this.totalTasks = totalTasks;
this.pendingTasks = pendingTasks;
this.runningTasks = runningTasks;
this.completedTasks = completedTasks;
this.failedTasks = failedTasks;
this.successRate = successRate;
this.averageExecutionTime = averageExecutionTime;
}
// Getters
public long getTotalTasks() { return totalTasks; }
public long getPendingTasks() { return pendingTasks; }
public long getRunningTasks() { return runningTasks; }
public long getCompletedTasks() { return completedTasks; }
public long getFailedTasks() { return failedTasks; }
public double getSuccessRate() { return successRate; }
public double getAverageExecutionTime() { return averageExecutionTime; }
}
/**
* 清理已完成的任务
* @param retentionDays 保留天数
* @return 清理的任务数量
*/
@Transactional
public int cleanupCompletedTasks(int retentionDays) {
log.debug("清理已完成的任务,保留天数: {}", retentionDays);
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(retentionDays);
int deletedCount = taskRepository.deleteCompletedTasksBefore(TaskStatus.COMPLETED, cutoffDate);
log.info("清理已完成的任务成功,删除数量: {}", deletedCount);
return deletedCount;
}
}
\ No newline at end of file