作者 徐宝林

初始化项目0908

正在显示 90 个修改的文件 包含 8158 行增加0 行删除
  1 +# AIGEO - AI内容生成平台
  2 +
  3 +AIGEO是一个基于人工智能技术的内容生成平台,旨在帮助企业用户高效生成各种类型的内容,包括文章、落地页和网站。
  4 +
  5 +## 功能特性
  6 +
  7 +- **AI文章生成** - 基于关键词和主题自动生成高质量文章
  8 +- **AI落地页生成** - 根据业务需求自动生成营销落地页
  9 +- **AI网站生成** - 一键生成企业官网或电商网站
  10 +- **多租户架构** - 支持多企业用户独立使用
  11 +- **权限管理** - 完善的用户角色和权限控制
  12 +- **订阅计费** - 灵活的订阅计划和计费系统
  13 +- **内容发布** - 支持将生成内容发布到多种平台
  14 +- **SEO优化** - 自动生成SEO友好的内容结构
  15 +
  16 +## 技术栈
  17 +
  18 +- **后端**: Spring Boot 3.3.3
  19 +- **数据库**: MySQL 8.0
  20 +- **ORM框架**: JPA/Hibernate
  21 +- **安全框架**: Spring Security + JWT
  22 +- **缓存**: Redis
  23 +- **API文档**: Knife4j (OpenAPI 3)
  24 +- **任务调度**: Quartz
  25 +- **构建工具**: Maven
  26 +- **其他**: Lombok, Hutool, FastJSON2
  27 +
  28 +## 系统要求
  29 +
  30 +- JDK 17+
  31 +- MySQL 8.0+
  32 +- Redis
  33 +- Maven 3.8+
  34 +
  35 +## 快速开始
  36 +
  37 +### 1. 克隆项目
  38 +
  39 +```bash
  40 +git clone <repository-url>
  41 +cd aigeo
  42 +```
  43 +
  44 +### 2. 数据库配置
  45 +
  46 +在 `src/main/resources/application.yml` 中配置数据库连接:
  47 +
  48 +```yaml
  49 +spring:
  50 + datasource:
  51 + url: jdbc:mysql://localhost:3306/aigeo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
  52 + username: root
  53 + password: your_password
  54 +```
  55 +
  56 +### 3. Redis配置
  57 +
  58 +在 `src/main/resources/application.yml` 中配置Redis连接:
  59 +
  60 +```yaml
  61 +spring:
  62 + data:
  63 + redis:
  64 + host: localhost
  65 + port: 6379
  66 +```
  67 +
  68 +### 4. 构建和运行
  69 +
  70 +```bash
  71 +# 构建项目
  72 +mvn clean package
  73 +
  74 +# 运行项目
  75 +mvn spring-boot:run
  76 +
  77 +# 或者运行打包后的JAR文件
  78 +java -jar target/aigeo-1.0.0.jar
  79 +```
  80 +
  81 +## API文档
  82 +
  83 +项目启动后,访问以下地址查看API文档:
  84 +
  85 +- Knife4j UI: http://localhost:8080/api/doc.html
  86 +- OpenAPI JSON: http://localhost:8080/api/v3/api-docs
  87 +
  88 +## 项目结构
  89 +
  90 +```
  91 +src/main/java/com/aigeo
  92 +├── AigeoApplication.java # 应用启动类
  93 +├── ai/ # AI配置模块
  94 +│ ├── controller/
  95 +│ ├── dto/
  96 +│ ├── entity/
  97 +│ ├── repository/
  98 +│ └── service/
  99 +├── article/ # AI文章生成模块
  100 +│ ├── controller/
  101 +│ ├── dto/
  102 +│ ├── entity/
  103 +│ ├── repository/
  104 +│ └── service/
  105 +├── auth/ # 认证模块
  106 +│ └── dto/
  107 +├── common/ # 公共组件
  108 +│ ├── enums/ # 枚举类
  109 +│ ├── exception/ # 异常处理
  110 +│ └── config/ # 配置类
  111 +├── company/ # 公司与用户模块
  112 +│ ├── controller/
  113 +│ ├── dto/
  114 +│ ├── entity/
  115 +│ ├── repository/
  116 +│ └── service/
  117 +├── config/ # 配置类
  118 +├── controller/ # 控制器层(旧结构,待迁移)
  119 +├── entity/ # 实体类(旧结构,待迁移)
  120 +├── exception/ # 异常处理
  121 +├── landingpage/ # 落地页生成模块
  122 +│ ├── controller/
  123 +│ ├── dto/
  124 +│ ├── entity/
  125 +│ ├── repository/
  126 +│ └── service/
  127 +├── repository/ # 数据访问层(旧结构,待迁移)
  128 +├── service/ # 业务逻辑层(旧结构,待迁移)
  129 +├── util/ # 工具类
  130 +└── website/ # 网站构建模块(待实现)
  131 +
  132 +src/main/resources/
  133 +├── application.yml # 应用配置
  134 +├── schema.sql # 数据库表结构
  135 +├── data.sql # 初始化数据
  136 +```
  137 +
  138 +## 数据库设计
  139 +
  140 +系统包含100+张数据表,主要模块包括:
  141 +
  142 +1. **核心模块** - 公司、用户、权限、订阅
  143 +2. **AI功能模块** - 文章生成、落地页生成、网站生成
  144 +3. **内容管理模块** - 关键词、话题、文章、页面
  145 +4. **发布系统模块** - 平台配置、发布任务、发布记录
  146 +5. **统计分析模块** - 使用统计、活动日志
  147 +
  148 +## 开发规范
  149 +
  150 +- 使用Lombok简化实体类代码
  151 +- 遵循RESTful API设计规范
  152 +- 使用JPA注解进行ORM映射
  153 +- 采用分层架构设计(Controller-Service-Repository)
  154 +- 统一异常处理和响应格式
  155 +
  156 +## 模块迁移计划
  157 +
  158 +当前项目正在从扁平化结构迁移到模块化结构,迁移计划如下:
  159 +
  160 +1. **已完成**:
  161 + - 创建模块化目录结构
  162 + - 创建DTO类
  163 + - 创建枚举类
  164 +
  165 +2. **进行中**:
  166 + - 迁移Controller类到对应模块
  167 + - 迁移Entity类到对应模块
  168 + - 迁移Repository类到对应模块
  169 + - 迁移Service类到对应模块
  170 +
  171 +3. **待完成**:
  172 + - 实现website模块
  173 + - 完善各模块间的交互
  174 + - 添加单元测试
  175 +
  176 +## 部署
  177 +
  178 +### 生产环境部署
  179 +
  180 +```bash
  181 +# 构建生产包
  182 +mvn clean package -Pprod
  183 +
  184 +# 运行
  185 +java -jar target/aigeo-1.0.0.jar --spring.profiles.active=prod
  186 +```
  187 +
  188 +### Docker部署(可选)
  189 +
  190 +```bash
  191 +# 构建Docker镜像
  192 +docker build -t aigeo:latest .
  193 +
  194 +# 运行容器
  195 +docker run -d -p 8080:8080 aigeo:latest
  196 +```
  197 +
  198 +## 贡献
  199 +
  200 +欢迎提交Issue和Pull Request来改进项目。
  201 +
  202 +## 许可证
  203 +
  204 +本项目仅供学习和参考使用。
此 diff 太大无法显示。
  1 +SET NAMES utf8mb4;
  2 +SET FOREIGN_KEY_CHECKS = 0;
  3 +
  4 +-- ====================================================================================================
  5 +-- 1) 核心:公司、用户、权限、订阅
  6 +-- ====================================================================================================
  7 +
  8 +-- 公司表(多租户根)
  9 +-- 存储企业客户的基本信息和订阅状态。
  10 +CREATE TABLE `ai_companies` (
  11 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '公司主键ID',
  12 + `name` VARCHAR(255) NOT NULL COMMENT '公司名称',
  13 + `domain` VARCHAR(100) DEFAULT NULL COMMENT '公司域名,用于多租户访问',
  14 + `status` ENUM('active','suspended','trial') DEFAULT 'trial' COMMENT '公司状态',
  15 + `trial_expiry_date` DATE DEFAULT NULL COMMENT '试用到期日',
  16 + `default_settings` JSON DEFAULT NULL COMMENT '企业默认设置(JSON)',
  17 + `billing_email` VARCHAR(100) DEFAULT NULL COMMENT '账单邮箱',
  18 + `contact_phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
  19 + `address` TEXT DEFAULT NULL COMMENT '公司地址',
  20 + `logo_url` VARCHAR(255) DEFAULT NULL COMMENT '公司Logo URL',
  21 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  22 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  23 + PRIMARY KEY (`id`),
  24 + UNIQUE KEY `uk_companies_domain` (`domain`)
  25 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='公司表(多租户根)';
  26 +
  27 +-- 用户表
  28 +-- 存储用户信息,并关联到所属公司。
  29 +CREATE TABLE `ai_users` (
  30 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '用户主键ID',
  31 + `company_id` INT NOT NULL COMMENT '所属公司ID(外键)',
  32 + `username` VARCHAR(50) NOT NULL COMMENT '登录用户名',
  33 + `email` VARCHAR(100) NOT NULL COMMENT '用户邮箱',
  34 + `password_hash` VARCHAR(255) NOT NULL COMMENT '密码哈希',
  35 + `full_name` VARCHAR(100) DEFAULT NULL COMMENT '用户全名',
  36 + `avatar_url` VARCHAR(255) DEFAULT NULL COMMENT '用户头像URL',
  37 + `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号',
  38 + `role` ENUM('admin','manager','editor','viewer') DEFAULT 'editor' COMMENT '用户角色',
  39 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用(1启用/0禁用)',
  40 + `last_login` TIMESTAMP NULL DEFAULT NULL COMMENT '最近登录时间',
  41 + `last_password_change` TIMESTAMP NULL DEFAULT NULL COMMENT '上次修改密码时间',
  42 + `failed_login_attempts` INT DEFAULT 0 COMMENT '登录失败次数',
  43 + `locked_until` TIMESTAMP NULL DEFAULT NULL COMMENT '锁定截止时间',
  44 + `timezone` VARCHAR(50) DEFAULT 'Asia/Shanghai' COMMENT '用户时区',
  45 + `preferences` JSON DEFAULT NULL COMMENT '用户个性化设置',
  46 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  47 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  48 + PRIMARY KEY (`id`),
  49 + UNIQUE KEY `uk_users_username` (`username`),
  50 + UNIQUE KEY `uk_users_email` (`email`),
  51 + KEY `idx_users_company` (`company_id`),
  52 + KEY `idx_users_company_role` (`company_id`, `role`),
  53 + KEY `idx_users_active` (`is_active`),
  54 + CONSTRAINT `fk_user_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE
  55 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
  56 +
  57 +-- 角色表 (增强权限控制)
  58 +-- 允许公司自定义角色及其权限。
  59 +CREATE TABLE `ai_roles` (
  60 + `id` INT NOT NULL AUTO_INCREMENT,
  61 + `company_id` INT NOT NULL COMMENT '所属公司',
  62 + `name` VARCHAR(50) NOT NULL COMMENT '角色名称(如 admin, editor)',
  63 + `description` VARCHAR(255) DEFAULT NULL,
  64 + `permissions` JSON DEFAULT NULL COMMENT '该角色拥有的权限列表(JSON)',
  65 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  66 + PRIMARY KEY (`id`),
  67 + UNIQUE KEY `uk_roles_company_name` (`company_id`, `name`),
  68 + CONSTRAINT `fk_role_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE
  69 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  70 +
  71 +-- 用户-角色关联表 (支持多角色)
  72 +-- 一个用户可以拥有多个角色。
  73 +CREATE TABLE `ai_user_roles` (
  74 + `user_id` INT NOT NULL,
  75 + `role_id` INT NOT NULL,
  76 + PRIMARY KEY (`user_id`, `role_id`),
  77 + FOREIGN KEY (user_id) REFERENCES ai_users(id) ON DELETE CASCADE,
  78 + FOREIGN KEY (role_id) REFERENCES ai_roles(id) ON DELETE CASCADE
  79 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  80 +
  81 +-- 审计日志表
  82 +-- 记录用户的关键操作,用于安全审计和追踪。
  83 +CREATE TABLE `ai_audit_logs` (
  84 + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '审计日志主键ID',
  85 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  86 + `user_id` INT NOT NULL COMMENT '操作用户ID(外键)',
  87 + `action` VARCHAR(100) NOT NULL COMMENT '操作类型(create/update/delete/publish等)',
  88 + `target_table` VARCHAR(100) NOT NULL COMMENT '被操作表名',
  89 + `target_id` INT NOT NULL COMMENT '被操作记录ID',
  90 + `details` JSON DEFAULT NULL COMMENT '操作详情(JSON,可记录前后值)',
  91 + `ip_address` VARCHAR(45) DEFAULT NULL COMMENT '客户端IP',
  92 + `user_agent` TEXT DEFAULT NULL COMMENT '浏览器信息',
  93 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  94 + PRIMARY KEY (`id`),
  95 + KEY `idx_audit_company` (`company_id`),
  96 + KEY `idx_audit_user` (`user_id`),
  97 + CONSTRAINT `fk_audit_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  98 + CONSTRAINT `fk_audit_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`)
  99 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作审计日志';
  100 +
  101 +-- ====================================================================================================
  102 +-- 2) 订阅与计费系统
  103 +-- ====================================================================================================
  104 +
  105 +-- 订阅计划定义表
  106 +-- 定义可用的订阅计划及其基本属性。
  107 +CREATE TABLE `ai_subscription_plans` (
  108 + `id` INT NOT NULL AUTO_INCREMENT,
  109 + `plan_key` VARCHAR(50) NOT NULL COMMENT '计划标识符(如 free, basic, premium)',
  110 + `name` VARCHAR(100) NOT NULL COMMENT '计划显示名称',
  111 + `description` TEXT DEFAULT NULL COMMENT '计划描述',
  112 + `price_monthly` DECIMAL(10,2) DEFAULT 0.00 COMMENT '月费价格',
  113 + `price_yearly` DECIMAL(10,2) DEFAULT 0.00 COMMENT '年费价格',
  114 + `max_users` INT DEFAULT 1 COMMENT '最大用户数',
  115 + `max_storage_mb` INT DEFAULT 100 COMMENT '最大存储空间(MB)',
  116 + `max_api_calls_per_day` INT DEFAULT 1000 COMMENT '每日API调用限制',
  117 + `features` JSON DEFAULT NULL COMMENT '包含的功能列表(JSON)',
  118 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  119 + `sort_order` INT DEFAULT 0 COMMENT '排序权重',
  120 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  121 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  122 + PRIMARY KEY (`id`),
  123 + UNIQUE KEY `uk_plans_plan_key` (`plan_key`),
  124 + KEY `idx_plans_active` (`is_active`)
  125 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订阅计划定义表';
  126 +
  127 +-- 公司订阅记录表
  128 +-- 记录每个公司的订阅历史和当前状态。
  129 +CREATE TABLE `ai_company_subscriptions` (
  130 + `id` INT NOT NULL AUTO_INCREMENT,
  131 + `company_id` INT NOT NULL COMMENT '公司ID',
  132 + `plan_id` INT NOT NULL COMMENT '订阅计划ID',
  133 + `plan_key` VARCHAR(50) NOT NULL COMMENT '冗余字段:计划标识符',
  134 + `subscription_type` ENUM('monthly','yearly') DEFAULT 'monthly' COMMENT '订阅类型',
  135 + `status` ENUM('active','cancelled','expired','suspended') DEFAULT 'active' COMMENT '订阅状态',
  136 + `start_date` DATE NOT NULL COMMENT '订阅开始日期',
  137 + `end_date` DATE DEFAULT NULL COMMENT '订阅结束日期',
  138 + `next_billing_date` DATE DEFAULT NULL COMMENT '下次计费日期',
  139 + `trial_start_date` DATE DEFAULT NULL COMMENT '试用开始日期',
  140 + `trial_end_date` DATE DEFAULT NULL COMMENT '试用结束日期',
  141 + `amount` DECIMAL(10,2) DEFAULT 0.00 COMMENT '订阅金额',
  142 + `payment_method` VARCHAR(50) DEFAULT NULL COMMENT '支付方式',
  143 + `payment_status` ENUM('pending','paid','failed','refunded') DEFAULT 'pending' COMMENT '支付状态',
  144 + `auto_renew` TINYINT(1) DEFAULT 1 COMMENT '是否自动续费',
  145 + `cancel_reason` TEXT DEFAULT NULL COMMENT '取消原因',
  146 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  147 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  148 + PRIMARY KEY (`id`),
  149 + KEY `idx_subscriptions_company` (`company_id`),
  150 + KEY `idx_subscriptions_status` (`status`),
  151 + KEY `idx_subscriptions_next_billing` (`next_billing_date`),
  152 + CONSTRAINT `fk_subscription_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE,
  153 + CONSTRAINT `fk_subscription_plan` FOREIGN KEY (`plan_id`) REFERENCES `ai_subscription_plans` (`id`)
  154 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司订阅记录表';
  155 +
  156 +-- 订阅支付记录表
  157 +-- 记录具体的支付交易。
  158 +CREATE TABLE `ai_subscription_payments` (
  159 + `id` INT NOT NULL AUTO_INCREMENT,
  160 + `subscription_id` INT NOT NULL COMMENT '订阅ID',
  161 + `company_id` INT NOT NULL COMMENT '公司ID',
  162 + `amount` DECIMAL(10,2) NOT NULL COMMENT '支付金额',
  163 + `currency` VARCHAR(3) DEFAULT 'CNY' COMMENT '货币类型',
  164 + `payment_method` VARCHAR(50) DEFAULT NULL COMMENT '支付方式(如 alipay, wechat, stripe)',
  165 + `transaction_id` VARCHAR(255) DEFAULT NULL COMMENT '交易ID',
  166 + `payment_status` ENUM('pending','success','failed','refunded') DEFAULT 'pending' COMMENT '支付状态',
  167 + `payment_date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '支付时间',
  168 + `period_start` DATE DEFAULT NULL COMMENT '计费周期开始日期',
  169 + `period_end` DATE DEFAULT NULL COMMENT '计费周期结束日期',
  170 + `invoice_url` VARCHAR(500) DEFAULT NULL COMMENT '发票URL',
  171 + `failure_reason` TEXT DEFAULT NULL COMMENT '失败原因',
  172 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  173 + PRIMARY KEY (`id`),
  174 + KEY `idx_payments_company` (`company_id`),
  175 + KEY `idx_payments_subscription` (`subscription_id`),
  176 + KEY `idx_payments_status` (`payment_status`),
  177 + CONSTRAINT `fk_payment_subscription` FOREIGN KEY (`subscription_id`) REFERENCES `ai_company_subscriptions` (`id`),
  178 + CONSTRAINT `fk_payment_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`)
  179 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订阅支付记录表';
  180 +
  181 +-- ====================================================================================================
  182 +-- 3) AI功能模块与权限控制
  183 +-- ====================================================================================================
  184 +
  185 +-- AI功能模块定义表
  186 +-- 定义系统提供的所有AI功能模块。
  187 +CREATE TABLE `ai_features` (
  188 + `id` INT NOT NULL AUTO_INCREMENT,
  189 + `feature_key` VARCHAR(50) NOT NULL COMMENT '功能标识符 (如 ai_copywriting, ai_landing_page)',
  190 + `name` VARCHAR(100) NOT NULL COMMENT '功能名称',
  191 + `description` TEXT DEFAULT NULL COMMENT '功能描述',
  192 + `category` VARCHAR(50) DEFAULT NULL COMMENT '功能分类(如 content, marketing, website)',
  193 + `is_premium` TINYINT(1) DEFAULT 0 COMMENT '是否为高级功能',
  194 + `sort_order` INT DEFAULT 0 COMMENT '排序权重',
  195 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  196 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  197 + PRIMARY KEY (`id`),
  198 + UNIQUE KEY `uk_features_key` (`feature_key`),
  199 + KEY `idx_features_category` (`category`),
  200 + KEY `idx_features_active` (`is_active`)
  201 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI功能模块定义表';
  202 +
  203 +-- 订阅计划功能权限表
  204 +-- 定义每个订阅计划对各AI功能的访问权限和使用限制。
  205 +CREATE TABLE `ai_plan_features` (
  206 + `id` INT NOT NULL AUTO_INCREMENT,
  207 + `plan_id` INT NOT NULL COMMENT '订阅计划ID',
  208 + `feature_id` INT NOT NULL COMMENT '功能ID',
  209 + `is_allowed` TINYINT(1) DEFAULT 1 COMMENT '是否允许使用',
  210 + `usage_limit` INT DEFAULT NULL COMMENT '使用限制(如每月次数,NULL为无限制)',
  211 + `limit_period` ENUM('daily','monthly','yearly','total') DEFAULT 'monthly' COMMENT '限制周期',
  212 + `custom_config` JSON DEFAULT NULL COMMENT '自定义配置(JSON)',
  213 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  214 + PRIMARY KEY (`id`),
  215 + UNIQUE KEY `uk_plan_feature` (`plan_id`, `feature_id`),
  216 + CONSTRAINT `fk_planfeature_plan` FOREIGN KEY (`plan_id`) REFERENCES `ai_subscription_plans` (`id`) ON DELETE CASCADE,
  217 + CONSTRAINT `fk_planfeature_feature` FOREIGN KEY (`feature_id`) REFERENCES `ai_features` (`id`) ON DELETE CASCADE
  218 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订阅计划功能权限表';
  219 +
  220 +-- 功能使用记录表
  221 +-- 记录用户对公司功能的具体使用情况,用于计费和分析。
  222 +CREATE TABLE `ai_feature_usage` (
  223 + `id` BIGINT NOT NULL AUTO_INCREMENT,
  224 + `company_id` INT NOT NULL COMMENT '公司ID',
  225 + `user_id` INT DEFAULT NULL COMMENT '用户ID(可选)',
  226 + `feature_id` INT NOT NULL COMMENT '功能ID',
  227 + `usage_type` VARCHAR(50) DEFAULT NULL COMMENT '使用类型(如 generate, export, analyze)',
  228 + `usage_count` INT DEFAULT 1 COMMENT '使用次数',
  229 + `related_resource_id` VARCHAR(100) DEFAULT NULL COMMENT '相关资源ID(如文章ID、页面ID)',
  230 + `ip_address` VARCHAR(45) DEFAULT NULL COMMENT '客户端IP',
  231 + `user_agent` TEXT DEFAULT NULL COMMENT '用户代理',
  232 + `metadata` JSON DEFAULT NULL COMMENT '额外元数据',
  233 + `used_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '使用时间',
  234 + PRIMARY KEY (`id`),
  235 + KEY `idx_usage_company_feature` (`company_id`, `feature_id`),
  236 + KEY `idx_usage_feature_date` (`feature_id`, `used_at`),
  237 + KEY `idx_usage_user` (`user_id`),
  238 + CONSTRAINT `fk_usage_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE,
  239 + CONSTRAINT `fk_usage_feature` FOREIGN KEY (`feature_id`) REFERENCES `ai_features` (`id`)
  240 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='功能使用记录表';
  241 +
  242 +-- 功能使用统计表(用于快速查询)
  243 +-- 按日统计功能使用量,提高查询效率。
  244 +CREATE TABLE `ai_feature_usage_stats` (
  245 + `id` INT NOT NULL AUTO_INCREMENT,
  246 + `company_id` INT NOT NULL COMMENT '公司ID',
  247 + `feature_id` INT NOT NULL COMMENT '功能ID',
  248 + `stat_date` DATE NOT NULL COMMENT '统计日期',
  249 + `usage_count` INT DEFAULT 0 COMMENT '当日使用次数',
  250 + `last_used_at` TIMESTAMP DEFAULT NULL COMMENT '最后使用时间',
  251 + PRIMARY KEY (`id`),
  252 + UNIQUE KEY `uk_stats_company_feature_date` (`company_id`, `feature_id`, `stat_date`),
  253 + KEY `idx_stats_company` (`company_id`),
  254 + KEY `idx_stats_feature` (`feature_id`),
  255 + CONSTRAINT `fk_stats_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE,
  256 + CONSTRAINT `fk_stats_feature` FOREIGN KEY (`feature_id`) REFERENCES `ai_features` (`id`) ON DELETE CASCADE
  257 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='功能使用统计表';
  258 +
  259 +-- ====================================================================================================
  260 +-- 4) AI内容生成核心:模型、Prompt、文件
  261 +-- ====================================================================================================
  262 +
  263 +-- AI服务配置表
  264 +-- 存储连接到不同AI服务(如Dify, OpenAI)的配置信息。
  265 +CREATE TABLE `ai_dify_api_configs` (
  266 + `id` INT NOT NULL AUTO_INCREMENT COMMENT 'AI配置主键ID',
  267 + `company_id` INT DEFAULT NULL COMMENT '公司ID(NULL 表示共享/通用)',
  268 + `provider` ENUM('dify','openai','anthropic','google','azure_openai','other') DEFAULT 'dify' COMMENT 'AI 提供方',
  269 + `name` VARCHAR(100) NOT NULL COMMENT '配置名称(便于识别)',
  270 + `base_url` VARCHAR(255) DEFAULT NULL COMMENT 'API 基础地址(可选)',
  271 + `api_key` VARCHAR(255) DEFAULT NULL COMMENT 'API Key/Token',
  272 + `model_name` VARCHAR(100) DEFAULT NULL COMMENT '模型名称',
  273 + `temperature` DECIMAL(3,2) DEFAULT 0.70 COMMENT '默认温度值',
  274 + `top_p` DECIMAL(3,2) DEFAULT 1.00 COMMENT 'TopP 值',
  275 + `max_tokens` INT DEFAULT 2048 COMMENT '最大生成 token 数',
  276 + `request_headers` JSON DEFAULT NULL COMMENT '额外请求头(JSON)',
  277 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  278 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  279 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  280 + PRIMARY KEY (`id`),
  281 + KEY `idx_dify_company` (`company_id`)
  282 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI 服务配置(Dify/OpenAI 等)';
  283 +
  284 +-- Prompt 模板表
  285 +-- 存储可复用的Prompt模板。
  286 +CREATE TABLE `ai_prompt_templates` (
  287 + `id` INT NOT NULL AUTO_INCREMENT COMMENT 'Prompt 模板主键ID',
  288 + `company_id` INT DEFAULT NULL COMMENT '公司ID(NULL 表示系统模板)',
  289 + `name` VARCHAR(100) NOT NULL COMMENT '模板名称',
  290 + `description` VARCHAR(255) DEFAULT NULL COMMENT '模板描述',
  291 + `language` VARCHAR(20) DEFAULT 'zh' COMMENT '默认语言编码(en/zh 等)',
  292 + `content` LONGTEXT NOT NULL COMMENT '模板内容(可含变量占位符)',
  293 + `variables` JSON DEFAULT NULL COMMENT '变量说明(JSON)',
  294 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  295 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  296 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  297 + PRIMARY KEY (`id`),
  298 + KEY `idx_prompt_company` (`company_id`)
  299 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='Prompt 模板表';
  300 +
  301 +-- 上传文件表
  302 +-- 管理用户上传的文件,如知识库、图片、视频等。
  303 +CREATE TABLE `ai_uploaded_files` (
  304 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '上传文件主键ID',
  305 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  306 + `user_id` INT NOT NULL COMMENT '上传者用户ID(外键)',
  307 + `file_name` VARCHAR(255) NOT NULL COMMENT '原始文件名',
  308 + `file_path` VARCHAR(500) NOT NULL COMMENT '服务器存储路径或外部URL',
  309 + `file_type` ENUM('knowledge','image','video','document','other') NOT NULL COMMENT '文件类型',
  310 + `file_size` BIGINT DEFAULT 0 COMMENT '文件大小(字节)',
  311 + `mime_type` VARCHAR(100) DEFAULT NULL COMMENT 'MIME 类型',
  312 + `checksum` VARCHAR(64) DEFAULT NULL COMMENT '校验和(可选)',
  313 + `version` INT DEFAULT 1 COMMENT '版本号(用于版本控制)',
  314 + `status` ENUM('active','archived','deleted') DEFAULT 'active' COMMENT '文件状态',
  315 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
  316 + PRIMARY KEY (`id`),
  317 + KEY `idx_files_company` (`company_id`),
  318 + KEY `idx_files_user` (`user_id`),
  319 + CONSTRAINT `fk_file_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  320 + CONSTRAINT `fk_file_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`)
  321 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='上传文件表(知识库/媒体等)';
  322 +
  323 +-- ====================================================================================================
  324 +-- 5) AI内容生成:文章
  325 +-- ====================================================================================================
  326 +
  327 +-- 文章类型表
  328 +-- 分类文章,如产品介绍、新闻稿等。
  329 +CREATE TABLE `ai_article_types` (
  330 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章类型主键ID',
  331 + `name` VARCHAR(50) NOT NULL COMMENT '文章类型名称(如产品介绍)',
  332 + `description` VARCHAR(255) DEFAULT NULL COMMENT '类型说明',
  333 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  334 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  335 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  336 + PRIMARY KEY (`id`),
  337 + UNIQUE KEY `uk_article_types_name` (`name`)
  338 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章类型表';
  339 +
  340 +-- 文章生成配置表
  341 +-- 存储文章生成的参数和偏好设置。
  342 +CREATE TABLE `ai_article_generation_configs` (
  343 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章生成配置主键ID',
  344 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  345 + `name` VARCHAR(100) NOT NULL COMMENT '配置名称',
  346 + `platform_id` INT DEFAULT NULL COMMENT '适用发布平台ID(可选)',
  347 + `article_type_id` INT DEFAULT NULL COMMENT '文章类型ID(外键)',
  348 + `writing_language` VARCHAR(20) DEFAULT 'en' COMMENT '写作语言(en/zh)',
  349 + `remove_ai_tone` TINYINT(1) DEFAULT 1 COMMENT '是否去除AI味道',
  350 + `ai_taste_level` ENUM('colloquial','junior_high','senior_high','professional') DEFAULT 'junior_high' COMMENT 'AI风格等级',
  351 + `article_length_min` INT DEFAULT 800 COMMENT '最小长度(字符)',
  352 + `article_length_max` INT DEFAULT 1500 COMMENT '最大长度(字符)',
  353 + `auto_seo_optimization` TINYINT(1) DEFAULT 1 COMMENT '自动SEO优化',
  354 + `keyword_density_min` DECIMAL(5,2) DEFAULT 1.00 COMMENT '关键词密度下限(%)',
  355 + `keyword_density_max` DECIMAL(5,2) DEFAULT 2.00 COMMENT '关键词密度上限(%)',
  356 + `auto_geo_optimization` TINYINT(1) DEFAULT 1 COMMENT '是否自动GEO优化',
  357 + `auto_structured_data` TINYINT(1) DEFAULT 1 COMMENT '是否自动生成结构化数据',
  358 + `multi_version_count` INT DEFAULT 3 COMMENT '生成版本数量',
  359 + `extra_options` JSON DEFAULT NULL COMMENT '额外选项(JSON)',
  360 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  361 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  362 + PRIMARY KEY (`id`),
  363 + KEY `idx_config_company` (`company_id`),
  364 + KEY `idx_config_platform` (`platform_id`),
  365 + KEY `idx_config_article_type` (`article_type_id`),
  366 + CONSTRAINT `fk_config_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  367 + CONSTRAINT `fk_config_platform` FOREIGN KEY (`platform_id`) REFERENCES `ai_publishing_platforms` (`id`),
  368 + CONSTRAINT `fk_config_article_type` FOREIGN KEY (`article_type_id`) REFERENCES `ai_article_types` (`id`)
  369 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI 文章生成配置';
  370 +
  371 +-- 文章生成任务表
  372 +-- 记录每次文章生成的请求和状态。
  373 +CREATE TABLE `ai_article_generation_tasks` (
  374 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章生成任务主键ID',
  375 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  376 + `user_id` INT NOT NULL COMMENT '发起用户ID(外键)',
  377 + `config_id` INT DEFAULT NULL COMMENT '使用的生成配置ID(外键)',
  378 + `article_theme` VARCHAR(255) DEFAULT NULL COMMENT '文章主题/标题(输入)',
  379 + `topic_ids` TEXT DEFAULT NULL COMMENT '所选话题ID列表(逗号分隔)',
  380 + `reference_urls` TEXT DEFAULT NULL COMMENT '参考URL列表(逗号分隔)',
  381 + `reference_content` LONGTEXT DEFAULT NULL COMMENT '高度参考链接抓取到的内容摘要',
  382 + `status` ENUM('pending','processing','completed','failed') DEFAULT 'pending' COMMENT '任务状态',
  383 + `progress` TINYINT DEFAULT 0 COMMENT '进度(0-100)',
  384 + `error_message` TEXT DEFAULT NULL COMMENT '错误信息',
  385 + `dify_api_config_id` INT DEFAULT NULL COMMENT '调用的AI配置ID(外键)',
  386 + `prompt_template_id` INT DEFAULT NULL COMMENT '使用的 Prompt 模板 ID(外键)',
  387 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  388 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
  389 + PRIMARY KEY (`id`),
  390 + KEY `idx_task_company_status` (`company_id`,`status`),
  391 + KEY `idx_task_user` (`user_id`),
  392 + KEY `idx_task_config` (`config_id`),
  393 + CONSTRAINT `fk_task_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  394 + CONSTRAINT `fk_task_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`),
  395 + CONSTRAINT `fk_task_config` FOREIGN KEY (`config_id`) REFERENCES `ai_article_generation_configs` (`id`),
  396 + CONSTRAINT `fk_task_ai_config` FOREIGN KEY (`dify_api_config_id`) REFERENCES `ai_dify_api_configs` (`id`),
  397 + CONSTRAINT `fk_task_prompt_template` FOREIGN KEY (`prompt_template_id`) REFERENCES `ai_prompt_templates` (`id`)
  398 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章生成任务';
  399 +
  400 +-- 生成的文章表(多版本支持)
  401 +-- 存储AI生成的最终文章内容。
  402 +CREATE TABLE `ai_generated_articles` (
  403 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '生成文章主键ID',
  404 + `task_id` INT DEFAULT NULL COMMENT '来源生成任务ID(外键,可空)',
  405 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  406 + `version` INT DEFAULT 1 COMMENT '文章版本号(用于多版本)',
  407 + `title` VARCHAR(255) DEFAULT NULL COMMENT '文章标题',
  408 + `content` LONGTEXT DEFAULT NULL COMMENT '文章纯文本内容(不含HTML)',
  409 + `html_content` LONGTEXT DEFAULT NULL COMMENT '文章HTML格式内容(含排版)',
  410 + `faq_section` JSON DEFAULT NULL COMMENT 'FAQ 部分(JSON)',
  411 + `structured_data` JSON DEFAULT NULL COMMENT '结构化数据 JSON-LD(全文)',
  412 + `word_count` INT DEFAULT 0 COMMENT '文章字数统计',
  413 + `keyword_density` JSON DEFAULT NULL COMMENT '关键词密度分析结果(JSON)',
  414 + `is_selected` TINYINT(1) DEFAULT 0 COMMENT '是否被选为最终版本(1是)',
  415 + `status` ENUM('draft','approved','archived','deleted') DEFAULT 'draft' COMMENT '文章状态',
  416 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  417 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  418 + PRIMARY KEY (`id`),
  419 + KEY `idx_articles_company_status` (`company_id`,`status`),
  420 + KEY `idx_articles_task` (`task_id`),
  421 + CONSTRAINT `fk_article_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`),
  422 + CONSTRAINT `fk_article_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`)
  423 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='生成的文章表(多版本支持)';
  424 +
  425 +-- 文章FAQ列表
  426 +-- 存储文章的FAQ部分。
  427 +CREATE TABLE `ai_article_faqs` (
  428 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章 FAQ 主键ID',
  429 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  430 + `question` VARCHAR(255) NOT NULL COMMENT 'FAQ 问题',
  431 + `answer` TEXT NOT NULL COMMENT 'FAQ 回答',
  432 + PRIMARY KEY (`id`),
  433 + KEY `idx_article_faqs_article` (`article_id`),
  434 + CONSTRAINT `fk_faq_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE
  435 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章 FAQ 列表';
  436 +
  437 +-- 文章结构化数据
  438 +-- 存储文章的结构化数据(JSON-LD)。
  439 +CREATE TABLE `ai_article_structured_data` (
  440 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '结构化数据主键ID',
  441 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  442 + `json_ld` JSON NOT NULL COMMENT 'JSON-LD 结构化数据内容',
  443 + PRIMARY KEY (`id`),
  444 + KEY `idx_structured_article` (`article_id`),
  445 + CONSTRAINT `fk_structured_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE
  446 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章结构化数据(单独表便于管理/更新)';
  447 +
  448 +-- 文章媒体资源
  449 +-- 存储文章关联的图片、视频等媒体。
  450 +CREATE TABLE `ai_article_media` (
  451 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章媒体资源主键ID',
  452 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  453 + `media_type` ENUM('image','video','audio') NOT NULL COMMENT '媒体类型',
  454 + `url` VARCHAR(500) NOT NULL COMMENT '媒体链接或存储路径',
  455 + `prompt` TEXT DEFAULT NULL COMMENT '生成提示语(AI 配图时记录)',
  456 + `source_type` ENUM('ai_generated','user_provided','external') DEFAULT 'external' COMMENT '资源来源类型',
  457 + `alt_text` VARCHAR(255) DEFAULT NULL COMMENT '图片替代文本(SEO)',
  458 + `caption` VARCHAR(255) DEFAULT NULL COMMENT '图片说明/标题',
  459 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  460 + PRIMARY KEY (`id`),
  461 + KEY `idx_media_article` (`article_id`),
  462 + CONSTRAINT `fk_media_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE
  463 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章媒体资源(多张图片/多视频)';
  464 +
  465 +-- 文章多语言翻译表
  466 +-- 存储文章的不同语言版本。
  467 +CREATE TABLE `ai_article_translations` (
  468 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章翻译主键ID',
  469 + `article_id` INT NOT NULL COMMENT '原文章ID(外键)',
  470 + `language` VARCHAR(20) NOT NULL COMMENT '翻译目标语言(如 en/zh)',
  471 + `title` VARCHAR(255) DEFAULT NULL COMMENT '译文标题',
  472 + `content` LONGTEXT DEFAULT NULL COMMENT '译文纯文本内容',
  473 + `html_content` LONGTEXT DEFAULT NULL COMMENT '译文 HTML 内容',
  474 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  475 + PRIMARY KEY (`id`),
  476 + UNIQUE KEY `uk_article_language` (`article_id`,`language`),
  477 + KEY `idx_translations_article` (`article_id`),
  478 + CONSTRAINT `fk_translation_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE
  479 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章多语言翻译表';
  480 +
  481 +-- ====================================================================================================
  482 +-- 6) AI内容生成:落地页
  483 +-- ====================================================================================================
  484 +
  485 +-- 落地页模板表
  486 +-- 存储可用的落地页布局模板。
  487 +CREATE TABLE `ai_landing_page_templates` (
  488 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '落地页模板主键ID',
  489 + `name` VARCHAR(100) NOT NULL COMMENT '模板名称(如:单栏布局)',
  490 + `code` VARCHAR(50) NOT NULL COMMENT '模板代码(如:single-column)',
  491 + `description` VARCHAR(255) DEFAULT NULL COMMENT '模板描述',
  492 + `preview_image_url` VARCHAR(255) DEFAULT NULL COMMENT '预览图URL',
  493 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  494 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  495 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  496 + PRIMARY KEY (`id`),
  497 + UNIQUE KEY `uk_lp_templates_code` (`code`)
  498 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='落地页布局与设计模板';
  499 +
  500 +-- 落地页项目表
  501 +-- 代表一个完整的落地页创建流程。
  502 +CREATE TABLE `ai_landing_page_projects` (
  503 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '落地页项目主键ID',
  504 + `company_id` INT NOT NULL COMMENT '所属公司ID(外键)',
  505 + `user_id` INT NOT NULL COMMENT '创建者用户ID(外键)',
  506 + `name` VARCHAR(255) NOT NULL COMMENT '落地页项目名称(用于内部识别)',
  507 + `status` ENUM('draft','configuring','generated','published','archived') DEFAULT 'draft' COMMENT '项目状态',
  508 + `last_step_completed` INT DEFAULT 0 COMMENT '最后完成的步骤号',
  509 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  510 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  511 + PRIMARY KEY (`id`),
  512 + KEY `idx_lp_projects_company` (`company_id`),
  513 + KEY `idx_lp_projects_user` (`user_id`),
  514 + CONSTRAINT `fk_lp_project_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  515 + CONSTRAINT `fk_lp_project_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`)
  516 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI落地页构建项目表';
  517 +
  518 +-- 落地页各步骤配置数据
  519 +-- 存储落地页构建过程中用户输入的所有配置信息。
  520 +CREATE TABLE `ai_landing_page_step_configs` (
  521 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '配置主键ID',
  522 + `project_id` INT NOT NULL COMMENT '落地页项目ID(外键)',
  523 + -- Step 1: 目标用户
  524 + `target_audience_desc` TEXT COMMENT '目标用户描述',
  525 + `user_pain_points` TEXT COMMENT '用户痛点(可JSON或换行分隔)',
  526 + `user_expectations` TEXT COMMENT '用户期望结果',
  527 + `age_groups` VARCHAR(255) DEFAULT NULL COMMENT '年龄段(逗号分隔)',
  528 + `gender_preference` ENUM('male','female','balanced') DEFAULT 'balanced' COMMENT '性别倾向',
  529 + `behavior_characteristics` VARCHAR(255) DEFAULT NULL COMMENT '用户行为特征(逗号分隔)',
  530 + `decision_making_styles` VARCHAR(255) DEFAULT NULL COMMENT '用户决策方式(逗号分隔)',
  531 + -- Step 2: 落地页目标
  532 + `industry_primary` VARCHAR(100) DEFAULT NULL COMMENT '一级行业',
  533 + `industry_secondary` VARCHAR(100) DEFAULT NULL COMMENT '二级行业',
  534 + `industry_tertiary` VARCHAR(100) DEFAULT NULL COMMENT '子分类',
  535 + `marketing_goal` VARCHAR(50) DEFAULT NULL COMMENT '营销目标(lead-collection, product-sales等)',
  536 + -- Step 3: 风格与配色
  537 + `design_style` VARCHAR(50) DEFAULT NULL COMMENT '设计风格(modern, professional等)',
  538 + `primary_color` VARCHAR(20) DEFAULT NULL COMMENT '主色调',
  539 + `accent_color` VARCHAR(20) DEFAULT NULL COMMENT '辅助色调',
  540 + `template_id` INT DEFAULT NULL COMMENT '布局模板ID(外键)',
  541 + -- Step 4: 核心卖点
  542 + `unique_value_proposition` TEXT COMMENT '独特价值主张',
  543 + `core_advantages` JSON DEFAULT NULL COMMENT '核心优势列表(JSON数组)',
  544 + `primary_keyword` VARCHAR(255) DEFAULT NULL COMMENT '主要关键词',
  545 + `secondary_keywords` JSON DEFAULT NULL COMMENT '次要关键词列表(JSON数组)',
  546 + -- Step 5: 页面内容
  547 + `content_generation_type` ENUM('ai','custom','upload') DEFAULT 'ai' COMMENT '内容生成方式',
  548 + `company_name` VARCHAR(255) DEFAULT NULL COMMENT '公司全称',
  549 + `brand_name` VARCHAR(255) DEFAULT NULL COMMENT '品牌名称',
  550 + `company_description` TEXT COMMENT '公司介绍',
  551 + `video_url` VARCHAR(500) DEFAULT NULL COMMENT '视频链接',
  552 + `logo_file_id` INT DEFAULT NULL COMMENT '公司Logo文件ID(关联ai_uploaded_files)',
  553 + `contact_info` JSON DEFAULT NULL COMMENT '联系信息(地址、电话、邮箱、工作时间)',
  554 + `social_media_links` JSON DEFAULT NULL COMMENT '社交媒体链接(JSON数组)',
  555 + -- Step 6: CTA
  556 + `primary_cta_text` VARCHAR(100) DEFAULT NULL COMMENT '主要CTA按钮文案',
  557 + `secondary_cta_texts` JSON DEFAULT NULL COMMENT '次要CTA按钮文案(JSON数组)',
  558 + -- Step 7: 信任元素
  559 + `testimonials` JSON DEFAULT NULL COMMENT '客户评价/推荐语(JSON数组,含姓名、职位、内容)',
  560 + `social_proofs` JSON DEFAULT NULL COMMENT '社会证明数据(JSON数组,含标签、数值)',
  561 + -- Step 8: 表单字段
  562 + `form_fields` JSON DEFAULT NULL COMMENT '表单字段配置(JSON对象,key为字段名,value为是否启用)',
  563 + -- Step 9: 生成与部署
  564 + `page_title` VARCHAR(255) DEFAULT NULL COMMENT '页面SEO标题',
  565 + `page_description` TEXT COMMENT '页面SEO描述',
  566 + `ga_tracking_code` TEXT COMMENT '谷歌广告跟踪代码',
  567 + `deployment_method` ENUM('ftp','link') DEFAULT 'link' COMMENT '部署方式',
  568 + `deployment_config` JSON DEFAULT NULL COMMENT '部署配置(FTP信息或子域名)',
  569 + `pricing_plan` VARCHAR(50) DEFAULT NULL COMMENT '选择的套餐(basic, pro, enterprise)',
  570 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  571 + PRIMARY KEY (`id`),
  572 + UNIQUE KEY `uk_lp_config_project` (`project_id`),
  573 + CONSTRAINT `fk_lp_config_project` FOREIGN KEY (`project_id`) REFERENCES `ai_landing_page_projects` (`id`) ON DELETE CASCADE,
  574 + CONSTRAINT `fk_lp_config_template` FOREIGN KEY (`template_id`) REFERENCES `ai_landing_page_templates` (`id`)
  575 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='落地页各步骤配置数据';
  576 +
  577 +-- 落地页AI生成任务表
  578 +-- 记录落地页生成的请求和状态。
  579 +CREATE TABLE `ai_landing_page_generation_tasks` (
  580 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务主键ID',
  581 + `project_id` INT NOT NULL COMMENT '落地页项目ID (外键)',
  582 + `user_id` INT NOT NULL COMMENT '发起用户ID (外键)',
  583 + `status` ENUM('pending','processing','completed','failed') DEFAULT 'pending' COMMENT '任务状态',
  584 + `progress` TINYINT DEFAULT 0 COMMENT '进度 (0-100)',
  585 + `dify_api_config_id` INT DEFAULT NULL COMMENT '调用的AI配置ID (外键, 关联 ai_dify_api_configs)',
  586 + `prompt_template_id` INT DEFAULT NULL COMMENT '使用的Prompt模板ID (外键, 关联 ai_prompt_templates)',
  587 + `final_prompt_snapshot` LONGTEXT DEFAULT NULL COMMENT '发送给AI的最终Prompt快照(用于调试和记录)',
  588 + `error_message` TEXT DEFAULT NULL COMMENT '错误信息',
  589 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  590 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
  591 + PRIMARY KEY (`id`),
  592 + KEY `idx_lp_task_project` (`project_id`),
  593 + KEY `idx_lp_task_status` (`status`),
  594 + CONSTRAINT `fk_lp_task_project` FOREIGN KEY (`project_id`) REFERENCES `ai_landing_page_projects` (`id`),
  595 + CONSTRAINT `fk_lp_task_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`),
  596 + CONSTRAINT `fk_lp_task_ai_config` FOREIGN KEY (`dify_api_config_id`) REFERENCES `ai_dify_api_configs` (`id`),
  597 + CONSTRAINT `fk_lp_task_prompt_template` FOREIGN KEY (`prompt_template_id`) REFERENCES `ai_prompt_templates` (`id`)
  598 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='落地页AI生成任务表';
  599 +
  600 +-- 最终生成的落地页(支持多版本)
  601 +-- 存储AI生成的最终落地页HTML代码。
  602 +CREATE TABLE `ai_generated_landing_pages` (
  603 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '生成落地页主键ID',
  604 + `project_id` INT NOT NULL COMMENT '来源项目ID(外键)',
  605 + `version_code` VARCHAR(20) NOT NULL DEFAULT 'A' COMMENT '版本标识(用于A/B测试,如A, B)',
  606 + `html_content` LONGTEXT COMMENT '落地页HTML内容',
  607 + `status` ENUM('draft','final','published') DEFAULT 'draft' COMMENT '版本状态',
  608 + `publish_url` VARCHAR(500) DEFAULT NULL COMMENT '发布后的URL(使用link方式时)',
  609 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间',
  610 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  611 + PRIMARY KEY (`id`),
  612 + UNIQUE KEY `uk_lp_project_version` (`project_id`, `version_code`),
  613 + CONSTRAINT `fk_lp_page_project` FOREIGN KEY (`project_id`) REFERENCES `ai_landing_page_projects` (`id`)
  614 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='最终生成的落地页(支持多版本)';
  615 +
  616 +-- 落地页项目关联的媒体资产
  617 +-- 存储落地页项目中使用的图片、Logo等文件。
  618 +CREATE TABLE `ai_landing_page_assets` (
  619 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '资产主键ID',
  620 + `project_id` INT NOT NULL COMMENT '落地页项目ID(外键)',
  621 + `asset_type` ENUM('product_image','certification_logo','testimonial_avatar') NOT NULL COMMENT '资产类型',
  622 + `uploaded_file_id` INT NOT NULL COMMENT '上传文件ID(外键)',
  623 + `related_data` JSON DEFAULT NULL COMMENT '相关数据(如关联的产品名或评价人)',
  624 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  625 + PRIMARY KEY (`id`),
  626 + KEY `idx_lp_assets_project` (`project_id`),
  627 + CONSTRAINT `fk_lp_asset_project` FOREIGN KEY (`project_id`) REFERENCES `ai_landing_page_projects` (`id`),
  628 + CONSTRAINT `fk_lp_asset_file` FOREIGN KEY (`uploaded_file_id`) REFERENCES `ai_uploaded_files` (`id`)
  629 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='落地页项目关联的媒体资产';
  630 +
  631 +-- ====================================================================================================
  632 +-- 7) AI内容生成:网站 (简化版)
  633 +-- ====================================================================================================
  634 +
  635 +-- AI网站构建项目总表
  636 +-- 代表一个完整的网站创建流程。
  637 +CREATE TABLE `ai_website_projects` (
  638 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '网站项目主键ID',
  639 + `company_id` INT NOT NULL COMMENT '所属公司ID (外键)',
  640 + `user_id` INT NOT NULL COMMENT '创建者用户ID (外键)',
  641 + `project_name` VARCHAR(255) NOT NULL COMMENT '项目名称 (用于内部识别)',
  642 + `site_name` VARCHAR(255) DEFAULT NULL COMMENT '最终生成的网站名称',
  643 + `status` ENUM('draft','configuring','generating','completed','published') DEFAULT 'draft' COMMENT '项目状态',
  644 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  645 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  646 + PRIMARY KEY (`id`),
  647 + KEY `idx_website_projects_company` (`company_id`),
  648 + CONSTRAINT `fk_website_project_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  649 + CONSTRAINT `fk_website_project_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`)
  650 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI网站构建项目总表';
  651 +
  652 +-- AI网站构建器配置(存储站点地图)
  653 +-- 存储网站的全局配置和站点地图结构。
  654 +CREATE TABLE `ai_website_build_configs` (
  655 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '配置主键ID',
  656 + `project_id` INT NOT NULL COMMENT '网站项目ID (外键)',
  657 + `website_type` VARCHAR(50) DEFAULT NULL COMMENT '网站类型 (business, portfolio等)',
  658 + `site_identity` JSON DEFAULT NULL COMMENT '网站身份信息 (名称, 标语, 描述, 关键词)',
  659 + `design_preferences` JSON DEFAULT NULL COMMENT '设计偏好 (风格, 主色调)',
  660 + `sitemap_structure` JSON DEFAULT NULL COMMENT '站点地图结构定义 (JSON, 描述栏目层级和类型)',
  661 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  662 + PRIMARY KEY (`id`),
  663 + UNIQUE KEY `uk_website_config_project` (`project_id`),
  664 + CONSTRAINT `fk_website_config_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`) ON DELETE CASCADE
  665 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI网站构建器配置(存储站点地图)';
  666 +
  667 +-- 网站栏目结构表 (支持无限层级)
  668 +-- 定义网站的栏目结构。
  669 +CREATE TABLE `ai_website_channels` (
  670 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '网站栏目主键ID',
  671 + `project_id` INT NOT NULL COMMENT '所属网站项目ID (外键)',
  672 + `parent_id` INT DEFAULT NULL COMMENT '父栏目ID (用于实现层级结构)',
  673 + `name` VARCHAR(100) NOT NULL COMMENT '栏目名称 (如: 公司介绍, 新闻中心)',
  674 + `path` VARCHAR(100) NOT NULL COMMENT 'URL路径 (如: /about, /news)',
  675 + `channel_type` ENUM('single_page', 'article_list', 'product_list', 'custom') NOT NULL COMMENT '栏目类型',
  676 + `display_order` INT DEFAULT 0 COMMENT '显示顺序',
  677 + `is_visible_in_nav` TINYINT(1) DEFAULT 1 COMMENT '是否在主导航中可见',
  678 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  679 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  680 + PRIMARY KEY (`id`),
  681 + KEY `idx_channel_project` (`project_id`),
  682 + KEY `idx_channel_parent` (`parent_id`),
  683 + CONSTRAINT `fk_channel_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  684 + CONSTRAINT `fk_channel_parent` FOREIGN KEY (`parent_id`) REFERENCES `ai_website_channels` (`id`)
  685 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='网站栏目结构表 (支持无限层级)';
  686 +
  687 +-- 网站文章内容表
  688 +-- 存储网站栏目下的文章内容。
  689 +CREATE TABLE `ai_website_articles` (
  690 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章主键ID',
  691 + `project_id` INT NOT NULL COMMENT '所属网站项目ID (外键)',
  692 + `channel_id` INT NOT NULL COMMENT '所属文章栏目ID (外键)',
  693 + `title` VARCHAR(255) NOT NULL COMMENT '文章标题',
  694 + `summary` TEXT DEFAULT NULL COMMENT '文章摘要',
  695 + `content` LONGTEXT COMMENT '文章主体内容 (Markdown或HTML)',
  696 + `status` ENUM('draft', 'published') DEFAULT 'draft' COMMENT '发布状态',
  697 + `published_at` TIMESTAMP NULL DEFAULT NULL COMMENT '发布时间',
  698 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  699 + PRIMARY KEY (`id`),
  700 + KEY `idx_article_project_channel` (`project_id`, `channel_id`),
  701 + CONSTRAINT `fk_website_article_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  702 + CONSTRAINT `fk_website_article_channel` FOREIGN KEY (`channel_id`) REFERENCES `ai_website_channels` (`id`)
  703 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='网站文章内容表';
  704 +
  705 +-- 网站产品内容表
  706 +-- 存储网站栏目下的产品内容。
  707 +CREATE TABLE `ai_website_products` (
  708 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '产品主键ID',
  709 + `project_id` INT NOT NULL COMMENT '所属网站项目ID (外键)',
  710 + `channel_id` INT NOT NULL COMMENT '所属产品栏目ID (外键)',
  711 + `name` VARCHAR(255) NOT NULL COMMENT '产品名称',
  712 + `description` LONGTEXT COMMENT '产品详细描述',
  713 + `specifications` JSON DEFAULT NULL COMMENT '产品规格 (JSON格式)',
  714 + `main_image_url` VARCHAR(255) DEFAULT NULL COMMENT '产品主图URL',
  715 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  716 + PRIMARY KEY (`id`),
  717 + KEY `idx_product_project_channel` (`project_id`, `channel_id`),
  718 + CONSTRAINT `fk_website_product_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  719 + CONSTRAINT `fk_website_product_channel` FOREIGN KEY (`channel_id`) REFERENCES `ai_website_channels` (`id`)
  720 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='网站产品内容表';
  721 +
  722 +-- 网站AI生成任务表(支持父子任务)
  723 +-- 记录网站生成的请求和状态。
  724 +CREATE TABLE `ai_website_generation_tasks` (
  725 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务主键ID',
  726 + `project_id` INT NOT NULL COMMENT '网站项目ID (外键)',
  727 + `user_id` INT NOT NULL COMMENT '发起用户ID (外键)',
  728 + `parent_task_id` INT DEFAULT NULL COMMENT '父任务ID (用于父子任务)',
  729 + `task_type` ENUM('full_site', 'generate_shell', 'generate_page_content', 'regenerate_channel') NOT NULL COMMENT '任务类型',
  730 + `target_id` INT DEFAULT NULL COMMENT '任务目标ID (如特定页面或栏目ID)',
  731 + `status` ENUM('pending','processing','completed','failed', 'waiting_for_children') DEFAULT 'pending' COMMENT '任务状态',
  732 + `dify_api_config_id` INT DEFAULT NULL COMMENT '调用的AI配置ID (外键)',
  733 + `config_snapshot` JSON DEFAULT NULL COMMENT '生成时刻的配置快照',
  734 + `error_message` TEXT DEFAULT NULL COMMENT '错误信息',
  735 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  736 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
  737 + PRIMARY KEY (`id`),
  738 + KEY `idx_website_task_project` (`project_id`),
  739 + KEY `idx_website_task_parent` (`parent_task_id`),
  740 + CONSTRAINT `fk_website_task_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  741 + CONSTRAINT `fk_website_task_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`),
  742 + CONSTRAINT `fk_website_task_parent` FOREIGN KEY (`parent_task_id`) REFERENCES `ai_website_generation_tasks` (`id`),
  743 + CONSTRAINT `fk_website_task_ai_config` FOREIGN KEY (`dify_api_config_id`) REFERENCES `ai_dify_api_configs` (`id`)
  744 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='网站AI生成任务表(支持父子任务)';
  745 +
  746 +-- 生成的网站页面(映射到栏目或内容项)
  747 +-- 存储AI生成的最终网站页面HTML代码。
  748 +CREATE TABLE `ai_generated_website_pages` (
  749 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '生成页面主键ID',
  750 + `project_id` INT NOT NULL COMMENT '来源网站项目ID (外键)',
  751 + `task_id` INT NOT NULL COMMENT '来源生成任务ID (外键)',
  752 + `pageable_type` VARCHAR(100) NOT NULL COMMENT '关联类型 (Channel, Article, Product)',
  753 + `pageable_id` INT NOT NULL COMMENT '关联类型ID',
  754 + `file_name` VARCHAR(100) NOT NULL COMMENT '文件名 (如: index.html, news/article-1.html)',
  755 + `html_content` LONGTEXT COMMENT '页面的完整HTML内容',
  756 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间',
  757 + PRIMARY KEY (`id`),
  758 + KEY `idx_pageable` (`pageable_type`, `pageable_id`),
  759 + CONSTRAINT `fk_website_page_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  760 + CONSTRAINT `fk_website_page_task` FOREIGN KEY (`task_id`) REFERENCES `ai_website_generation_tasks` (`id`)
  761 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='生成的网站页面(映射到栏目或内容项)';
  762 +
  763 +-- ====================================================================================================
  764 +-- 8) 内容发布系统
  765 +-- ====================================================================================================
  766 +
  767 +-- 目标网站类型表
  768 +-- 对目标平台进行大类划分。
  769 +CREATE TABLE `ai_publishing_platform_types` (
  770 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '平台类型主键ID',
  771 + `name` VARCHAR(50) NOT NULL COMMENT '平台类型名称(如 Blog, Social, Video)',
  772 + `description` VARCHAR(255) DEFAULT NULL COMMENT '类型描述',
  773 + `icon` VARCHAR(100) DEFAULT NULL COMMENT '图标标识',
  774 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  775 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  776 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  777 + PRIMARY KEY (`id`),
  778 + UNIQUE KEY `uk_platform_types_name` (`name`)
  779 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='目标网站类型表';
  780 +
  781 +-- 目标网站表
  782 +-- 定义支持发布的目标平台及其API配置模板。
  783 +CREATE TABLE `ai_publishing_platforms` (
  784 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '目标网站主键ID',
  785 + `type_id` INT NOT NULL COMMENT '目标网站类型ID(外键)',
  786 + `name` VARCHAR(100) NOT NULL COMMENT '目标网站名称(如 WordPress, LinkedIn, 小红书)',
  787 + `code` VARCHAR(50) NOT NULL COMMENT '目标网站代码(唯一)',
  788 + `description` VARCHAR(255) DEFAULT NULL COMMENT '目标网站说明',
  789 + `icon` VARCHAR(100) DEFAULT NULL COMMENT '目标网站图标',
  790 + `auth_type` ENUM('oauth','api_key','basic','custom') NOT NULL COMMENT '认证类型',
  791 + `api_config_template` JSON DEFAULT NULL COMMENT 'API 配置模板(JSON schema)',
  792 + `character_limit` INT DEFAULT NULL COMMENT '目标网站字符限制(如微博)',
  793 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  794 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  795 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  796 + PRIMARY KEY (`id`),
  797 + UNIQUE KEY `uk_platforms_code` (`code`),
  798 + KEY `idx_platforms_type` (`type_id`),
  799 + CONSTRAINT `fk_platform_type` FOREIGN KEY (`type_id`) REFERENCES `ai_publishing_platform_types` (`id`)
  800 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='目标网站表';
  801 +
  802 +-- 企业在目标网站的配置(API key/Token等)
  803 +-- 存储公司对特定平台的授权信息。
  804 +CREATE TABLE `ai_company_platform_configs` (
  805 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '企业平台配置主键ID',
  806 + `company_id` INT NOT NULL COMMENT '授权公司ID(外键)',
  807 + `platform_id` INT NOT NULL COMMENT '目标网站ID(外键)',
  808 + `config_data` JSON NOT NULL COMMENT '目标网站API配置信息(如 token、client_id)',
  809 + `account_name` VARCHAR(100) DEFAULT NULL COMMENT '企业在该目标网站的账号名/展示名',
  810 + `is_enabled` TINYINT(1) DEFAULT 1 COMMENT '是否启用该配置',
  811 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  812 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  813 + PRIMARY KEY (`id`),
  814 + UNIQUE KEY `uk_company_platform` (`company_id`,`platform_id`),
  815 + KEY `idx_company_platform_company` (`company_id`),
  816 + KEY `idx_company_platform_platform` (`platform_id`),
  817 + CONSTRAINT `fk_company_platform_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE,
  818 + CONSTRAINT `fk_company_platform_platform` FOREIGN KEY (`platform_id`) REFERENCES `ai_publishing_platforms` (`id`)
  819 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='企业在目标网站的配置(API key/Token等)';
  820 +
  821 +-- 定时发布任务表
  822 +-- 管理计划在未来某个时间点执行的发布任务。
  823 +CREATE TABLE `ai_scheduled_publishing_tasks` (
  824 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '定时发布任务主键ID',
  825 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  826 + `publishable_type` VARCHAR(50) DEFAULT 'article' COMMENT '发布内容类型(article, landing_page)',
  827 + `publishable_id` INT NOT NULL COMMENT '发布内容ID',
  828 + `article_id` INT DEFAULT NULL COMMENT '要发布的文章ID(外键) - 兼容旧版',
  829 + `platform_config_id` INT NOT NULL COMMENT '使用的企业平台配置ID(外键)',
  830 + `schedule_time` TIMESTAMP NOT NULL COMMENT '计划发布时间',
  831 + `status` ENUM('scheduled','processing','success','failed','cancelled') DEFAULT 'scheduled' COMMENT '任务状态',
  832 + `retry_count` INT DEFAULT 0 COMMENT '已重试次数',
  833 + `max_retries` INT DEFAULT 3 COMMENT '最大重试次数',
  834 + `last_attempt_at` TIMESTAMP NULL DEFAULT NULL COMMENT '最近一次尝试执行时间',
  835 + `error_message` TEXT DEFAULT NULL COMMENT '错误信息(失败时记录)',
  836 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  837 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  838 + PRIMARY KEY (`id`),
  839 + KEY `idx_schedule_company_time` (`company_id`, `schedule_time`),
  840 + KEY `idx_schedule_status` (`status`),
  841 + CONSTRAINT `fk_schedule_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  842 + CONSTRAINT `fk_schedule_platform_config` FOREIGN KEY (`platform_config_id`) REFERENCES `ai_company_platform_configs` (`id`)
  843 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时发布任务表';
  844 +
  845 +-- 目标网站发布记录表(执行日志)
  846 +-- 记录所有发布操作的执行结果。
  847 +CREATE TABLE `ai_publishing_records` (
  848 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '发布记录主键ID',
  849 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  850 + `publishable_type` VARCHAR(50) DEFAULT 'article' COMMENT '发布内容类型(article, landing_page)',
  851 + `publishable_id` INT NOT NULL COMMENT '发布内容ID',
  852 + `article_id` INT DEFAULT NULL COMMENT '文章ID(外键) - 兼容旧版',
  853 + `platform_config_id` INT NOT NULL COMMENT '目标网站配置ID(外键)',
  854 + `scheduled_task_id` INT DEFAULT NULL COMMENT '来源的定时任务ID(如果是定时发布则关联)',
  855 + `status` ENUM('scheduled','published','failed') DEFAULT 'scheduled' COMMENT '发布记录状态',
  856 + `publish_url` VARCHAR(500) DEFAULT NULL COMMENT '发布后目标网站返回的文章URL',
  857 + `external_post_id` VARCHAR(255) DEFAULT NULL COMMENT '目标网站返回的外部帖文/文章ID',
  858 + `publish_time` TIMESTAMP NULL DEFAULT NULL COMMENT '实际发布时间',
  859 + `error_message` TEXT DEFAULT NULL COMMENT '失败原因(若失败)',
  860 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
  861 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
  862 + PRIMARY KEY (`id`),
  863 + KEY `idx_publishing_company_time` (`company_id`,`publish_time`),
  864 + KEY `idx_publishing_status` (`status`),
  865 + CONSTRAINT `fk_publishing_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  866 + CONSTRAINT `fk_publishing_platform_config` FOREIGN KEY (`platform_config_id`) REFERENCES `ai_company_platform_configs` (`id`),
  867 + CONSTRAINT `fk_publishing_scheduled_task` FOREIGN KEY (`scheduled_task_id`) REFERENCES `ai_scheduled_publishing_tasks` (`id`)
  868 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='目标网站发布记录表(执行日志)';
  869 +
  870 +-- ====================================================================================================
  871 +-- 9) 关键词与话题管理 (SEO/内容规划)
  872 +-- ====================================================================================================
  873 +
  874 +-- 关键词表
  875 +-- 管理用于内容生成和SEO的关键词。
  876 +CREATE TABLE `ai_keywords` (
  877 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '关键词主键ID',
  878 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  879 + `keyword` VARCHAR(255) NOT NULL COMMENT '关键词文本',
  880 + `search_volume` INT DEFAULT 0 COMMENT '搜索量估算',
  881 + `cpc` DECIMAL(10,2) DEFAULT 0.00 COMMENT '估计每次点击成本',
  882 + `difficulty` TINYINT DEFAULT 0 COMMENT '关键词难度评分(0-100)',
  883 + `source` ENUM('baidu','google','manual','expansion','import') DEFAULT 'manual' COMMENT '来源类型',
  884 + `status` ENUM('pending','planned','generated','published') DEFAULT 'pending' COMMENT '关键词状态',
  885 + `tags` VARCHAR(255) DEFAULT NULL COMMENT '关键词标签,逗号分隔',
  886 + `usage_count` INT DEFAULT 0 COMMENT '关键词被文章使用次数',
  887 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  888 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  889 + PRIMARY KEY (`id`),
  890 + UNIQUE KEY `uk_company_keyword` (`company_id`,`keyword`),
  891 + KEY `idx_keywords_company_status` (`company_id`,`status`),
  892 + CONSTRAINT `fk_keyword_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`)
  893 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='关键词表';
  894 +
  895 +-- 关键词扩展表(保存 related_searches)
  896 +-- 存储关键词的扩展词(如相关搜索词)。
  897 +CREATE TABLE `ai_keyword_expansions` (
  898 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '关键词扩展主键ID',
  899 + `keyword_id` INT NOT NULL COMMENT '原始关键词ID(外键)',
  900 + `related_keyword` VARCHAR(255) NOT NULL COMMENT '扩展关键词(来自 related_searches 等)',
  901 + `source` ENUM('baidu','google','import') DEFAULT 'google' COMMENT '扩展来源',
  902 + `raw_response` JSON DEFAULT NULL COMMENT 'API 原始返回(便于溯源)',
  903 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  904 + PRIMARY KEY (`id`),
  905 + KEY `idx_keyword_exp_keyword` (`keyword_id`),
  906 + CONSTRAINT `fk_keyword_expansion_keyword` FOREIGN KEY (`keyword_id`) REFERENCES `ai_keywords` (`id`) ON DELETE CASCADE
  907 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='关键词扩展表(保存 related_searches)';
  908 +
  909 +-- 话题表
  910 +-- 管理更宽泛的内容主题。
  911 +CREATE TABLE `ai_topics` (
  912 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '话题主键ID',
  913 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  914 + `source_task_id` INT DEFAULT NULL COMMENT '来源的抓取任务ID(外键,可空)',
  915 + `title` VARCHAR(255) NOT NULL COMMENT '话题标题',
  916 + `description` TEXT DEFAULT NULL COMMENT '话题描述或摘要',
  917 + `source_url` VARCHAR(500) DEFAULT NULL COMMENT '原始来源链接(可选)',
  918 + `status` ENUM('raw','curated','rejected') DEFAULT 'raw' COMMENT '话题状态',
  919 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  920 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  921 + PRIMARY KEY (`id`),
  922 + KEY `idx_topics_company_status` (`company_id`,`status`),
  923 + KEY `idx_topics_source_task` (`source_task_id`),
  924 + CONSTRAINT `fk_topic_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  925 + CONSTRAINT `fk_topic_source_task` FOREIGN KEY (`source_task_id`) REFERENCES `ai_search_sources_tasks` (`id`)
  926 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='话题表';
  927 +
  928 +-- 话题与关键词的多对多映射
  929 +-- 建立话题和关键词之间的关联。
  930 +CREATE TABLE `ai_topic_keywords` (
  931 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '话题-关键词关联主键ID',
  932 + `topic_id` INT NOT NULL COMMENT '话题ID(外键)',
  933 + `keyword_id` INT NOT NULL COMMENT '关键词ID(外键)',
  934 + PRIMARY KEY (`id`),
  935 + UNIQUE KEY `uk_topic_keyword` (`topic_id`,`keyword_id`),
  936 + KEY `idx_topic_keywords_topic` (`topic_id`),
  937 + KEY `idx_topic_keywords_keyword` (`keyword_id`),
  938 + CONSTRAINT `fk_topic_keyword_topic` FOREIGN KEY (`topic_id`) REFERENCES `ai_topics` (`id`) ON DELETE CASCADE,
  939 + CONSTRAINT `fk_topic_keyword_keyword` FOREIGN KEY (`keyword_id`) REFERENCES `ai_keywords` (`id`) ON DELETE CASCADE
  940 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='话题与关键词的多对多映射';
  941 +
  942 +-- AI数据源配置表
  943 +-- 配置用于抓取话题的外部数据源(如SERP API)。
  944 +CREATE TABLE `ai_search_sources_api` (
  945 + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '数据源配置主键ID',
  946 + `name` VARCHAR(100) NOT NULL COMMENT '数据来源名称 (如 百度前10、Google also ask)',
  947 + `source_type` ENUM('serp_api', 'rss', 'custom_api', 'web_scraping', 'database') NOT NULL COMMENT '来源类型',
  948 + `api_config` JSON NOT NULL COMMENT 'API 配置',
  949 + `result_parser` JSON NOT NULL COMMENT '结果解析规则',
  950 + `is_active` BOOLEAN DEFAULT TRUE COMMENT '是否启用',
  951 + `priority` TINYINT UNSIGNED DEFAULT 10 COMMENT '优先级 (1-100, 数字越小优先级越高)',
  952 + `rate_limit` JSON COMMENT '限流配置 {"requests": 100, "period": 3600}',
  953 + `health_check` JSON COMMENT '健康检查配置',
  954 + `metadata` JSON COMMENT '元数据 (描述、版本等)',
  955 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  956 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  957 + PRIMARY KEY (`id`),
  958 + INDEX `idx_source_name` (`name`),
  959 + INDEX `idx_source_type` (`source_type`),
  960 + INDEX `idx_source_active` (`is_active`),
  961 + INDEX `idx_source_priority` (`priority`)
  962 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI数据源配置表';
  963 +
  964 +-- AI数据源抓取任务表
  965 +-- 记录从外部数据源抓取话题的任务。
  966 +CREATE TABLE `ai_search_sources_tasks` (
  967 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '话题抓取任务主键ID',
  968 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  969 + `user_id` INT NOT NULL COMMENT '发起任务的用户ID(外键)',
  970 + `topic_source_id` INT NOT NULL COMMENT '话题来源配置ID(外键)',
  971 + `keywords` TEXT DEFAULT NULL COMMENT '用于本次抓取的关键词快照(逗号分隔)',
  972 + `topic_id` INT DEFAULT NULL COMMENT '关联的话题ID(外键,可空)',
  973 + `keyword_id` INT DEFAULT NULL COMMENT '关联的关键词ID(外键,可空)',
  974 + `status` ENUM('pending','processing','completed','failed') DEFAULT 'pending' COMMENT '任务状态',
  975 + `result_count` INT DEFAULT 0 COMMENT '抓取结果数量',
  976 + `raw_response` JSON DEFAULT NULL COMMENT 'API 原始返回(JSON)',
  977 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  978 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '任务完成时间',
  979 + PRIMARY KEY (`id`),
  980 + KEY `idx_topic_fetch_company` (`company_id`),
  981 + KEY `idx_topic_fetch_source` (`topic_source_id`),
  982 + KEY `idx_topic_fetch_status` (`status`),
  983 + CONSTRAINT `fk_search_task_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  984 + CONSTRAINT `fk_search_task_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`),
  985 + CONSTRAINT `fk_search_task_source` FOREIGN KEY (`topic_source_id`) REFERENCES `ai_search_sources_api` (`id`)
  986 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI数据源抓取任务表';
  987 +
  988 +-- ====================================================================================================
  989 +-- 10) 任务关联表 (连接生成任务与资源)
  990 +-- ====================================================================================================
  991 +
  992 +-- 任务参考链接明细(可标注高度参考)
  993 +-- 存储文章生成任务中使用的参考链接。
  994 +CREATE TABLE `ai_task_references` (
  995 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务参考链接主键ID',
  996 + `task_id` INT NOT NULL COMMENT '对应任务ID(外键)',
  997 + `url` VARCHAR(500) NOT NULL COMMENT '参考链接URL',
  998 + `title` VARCHAR(255) DEFAULT NULL COMMENT '页面标题(可选)',
  999 + `source_engine` VARCHAR(50) DEFAULT NULL COMMENT '来源引擎(google/baidu/zhihu等)',
  1000 + `position` INT DEFAULT NULL COMMENT '在搜索结果中的排名位置',
  1001 + `is_high_reference` TINYINT(1) DEFAULT 0 COMMENT '是否标记为高度参考(1是)',
  1002 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  1003 + PRIMARY KEY (`id`),
  1004 + KEY `idx_task_references_task` (`task_id`),
  1005 + CONSTRAINT `fk_task_reference_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`) ON DELETE CASCADE
  1006 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='任务参考链接明细(可标注高度参考)';
  1007 +
  1008 +-- 生成任务与关键词映射(规划用)
  1009 +-- 将生成任务与计划使用的关键词关联。
  1010 +CREATE TABLE `ai_task_keywords` (
  1011 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务-关键词关联主键ID',
  1012 + `task_id` INT NOT NULL COMMENT '生成任务ID(外键)',
  1013 + `keyword_id` INT NOT NULL COMMENT '关键词ID(外键)',
  1014 + `is_primary` TINYINT(1) DEFAULT 0 COMMENT '是否主关键词',
  1015 + PRIMARY KEY (`id`),
  1016 + UNIQUE KEY `uk_task_keyword` (`task_id`,`keyword_id`),
  1017 + KEY `idx_task_keywords_task` (`task_id`),
  1018 + KEY `idx_task_keywords_keyword` (`keyword_id`),
  1019 + CONSTRAINT `fk_task_keyword_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`) ON DELETE CASCADE,
  1020 + CONSTRAINT `fk_task_keyword_keyword` FOREIGN KEY (`keyword_id`) REFERENCES `ai_keywords` (`id`) ON DELETE CASCADE
  1021 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='生成任务与关键词映射(规划用)';
  1022 +
  1023 +-- 生成任务与话题映射
  1024 +-- 将生成任务与相关话题关联。
  1025 +CREATE TABLE `ai_task_topics` (
  1026 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务-话题关联主键ID',
  1027 + `task_id` INT NOT NULL COMMENT '生成任务ID(外键)',
  1028 + `topic_id` INT NOT NULL COMMENT '话题ID(外键)',
  1029 + PRIMARY KEY (`id`),
  1030 + UNIQUE KEY `uk_task_topic` (`task_id`,`topic_id`),
  1031 + KEY `idx_task_topics_task` (`task_id`),
  1032 + KEY `idx_task_topics_topic` (`topic_id`),
  1033 + CONSTRAINT `fk_task_topic_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`) ON DELETE CASCADE,
  1034 + CONSTRAINT `fk_task_topic_topic` FOREIGN KEY (`topic_id`) REFERENCES `ai_topics` (`id`) ON DELETE CASCADE
  1035 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='生成任务与话题映射';
  1036 +
  1037 +-- 任务知识库来源(公司库或上传的解析内容)
  1038 +-- 指定生成任务使用的知识来源。
  1039 +CREATE TABLE `ai_task_knowledge_sources` (
  1040 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务知识来源主键ID',
  1041 + `task_id` INT NOT NULL COMMENT '任务ID(外键)',
  1042 + `knowledge_type` ENUM('company','uploaded') NOT NULL COMMENT '知识来源类型(公司知识库/上传文件)',
  1043 + `knowledge_id` INT DEFAULT NULL COMMENT '当 knowledge_type=uploaded 时,关联 ai_uploaded_files.id',
  1044 + `content` LONGTEXT DEFAULT NULL COMMENT '若上传文件被解析,存解析后的文本内容',
  1045 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  1046 + PRIMARY KEY (`id`),
  1047 + KEY `idx_task_knowledge_task` (`task_id`),
  1048 + KEY `idx_task_knowledge_knowledge` (`knowledge_id`),
  1049 + CONSTRAINT `fk_task_knowledge_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`) ON DELETE CASCADE,
  1050 + CONSTRAINT `fk_task_knowledge_file` FOREIGN KEY (`knowledge_id`) REFERENCES `ai_uploaded_files` (`id`)
  1051 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='任务知识库来源(公司库或上传的解析内容)';
  1052 +
  1053 +-- 文章与关键词关联(实际使用)
  1054 +-- 将最终生成的文章与实际使用的关键词关联。
  1055 +CREATE TABLE `ai_article_keywords` (
  1056 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章关键词关联主键ID',
  1057 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  1058 + `keyword_id` INT NOT NULL COMMENT '关键词ID(外键)',
  1059 + `is_primary` TINYINT(1) DEFAULT 0 COMMENT '是否主关键词(1主/0辅)',
  1060 + PRIMARY KEY (`id`),
  1061 + UNIQUE KEY `uk_article_keyword` (`article_id`,`keyword_id`),
  1062 + KEY `idx_article_keywords_article` (`article_id`),
  1063 + KEY `idx_article_keywords_keyword` (`keyword_id`),
  1064 + CONSTRAINT `fk_article_keyword_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE,
  1065 + CONSTRAINT `fk_article_keyword_keyword` FOREIGN KEY (`keyword_id`) REFERENCES `ai_keywords` (`id`) ON DELETE CASCADE
  1066 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章与关键词关联(实际使用)';
  1067 +
  1068 +-- 文章与话题关联
  1069 +-- 将最终生成的文章与相关话题关联。
  1070 +CREATE TABLE `ai_article_topics` (
  1071 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章话题关联主键ID',
  1072 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  1073 + `topic_id` INT NOT NULL COMMENT '话题ID(外键)',
  1074 + PRIMARY KEY (`id`),
  1075 + UNIQUE KEY `uk_article_topic` (`article_id`,`topic_id`),
  1076 + KEY `idx_article_topics_article` (`article_id`),
  1077 + KEY `idx_article_topics_topic` (`topic_id`),
  1078 + CONSTRAINT `fk_article_topic_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE,
  1079 + CONSTRAINT `fk_article_topic_topic` FOREIGN KEY (`topic_id`) REFERENCES `ai_topics` (`id`) ON DELETE CASCADE
  1080 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章与话题关联';
  1081 +
  1082 +-- ====================================================================================================
  1083 +-- 11) 统计与活动日志
  1084 +-- ====================================================================================================
  1085 +
  1086 +-- 用户活动日志(审计/分析)
  1087 +-- 记录用户的常规活动,用于行为分析。
  1088 +CREATE TABLE `ai_user_activity_logs` (
  1089 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '用户活动日志主键ID',
  1090 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  1091 + `user_id` INT NOT NULL COMMENT '用户ID(外键)',
  1092 + `activity_type` VARCHAR(50) NOT NULL COMMENT '活动类型(如 login/create_article)',
  1093 + `activity_details` JSON DEFAULT NULL COMMENT '活动详情(JSON)',
  1094 + `ip_address` VARCHAR(45) DEFAULT NULL COMMENT '操作IP',
  1095 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  1096 + PRIMARY KEY (`id`),
  1097 + KEY `idx_activity_company_user` (`company_id`,`user_id`),
  1098 + KEY `idx_activity_type` (`activity_type`),
  1099 + CONSTRAINT `fk_activity_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  1100 + CONSTRAINT `fk_activity_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`)
  1101 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户活动日志(审计/分析)';
  1102 +
  1103 +-- 公司使用统计
  1104 +-- 统计公司的资源使用情况,用于计费和容量管理。
  1105 +CREATE TABLE `ai_company_usage_stats` (
  1106 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '公司用量统计主键ID',
  1107 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  1108 + `stat_date` DATE NOT NULL COMMENT '统计日期',
  1109 + `article_count` INT DEFAULT 0 COMMENT '当日生成文章数量',
  1110 + `ai_token_usage` BIGINT DEFAULT 0 COMMENT '当日 AI 令牌使用量',
  1111 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  1112 + PRIMARY KEY (`id`),
  1113 + UNIQUE KEY `uk_company_date` (`company_id`,`stat_date`),
  1114 + KEY `idx_usage_company` (`company_id`),
  1115 + CONSTRAINT `fk_usage_stats_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`)
  1116 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='公司使用统计';
  1117 +
  1118 +SET FOREIGN_KEY_CHECKS = 1;
  1 +package com.aigeo.ai.controller;
  2 +
  3 +import com.aigeo.ai.entity.Feature;
  4 +import com.aigeo.ai.service.FeatureService;
  5 +import com.aigeo.common.Result;
  6 +import com.aigeo.common.ResultPage;
  7 +import io.swagger.v3.oas.annotations.Operation;
  8 +import io.swagger.v3.oas.annotations.tags.Tag;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.springframework.beans.factory.annotation.Autowired;
  11 +import org.springframework.data.domain.Page;
  12 +import org.springframework.data.domain.PageRequest;
  13 +import org.springframework.data.domain.Pageable;
  14 +import org.springframework.web.bind.annotation.*;
  15 +
  16 +import java.util.List;
  17 +
  18 +/**
  19 + * AI功能模块控制器
  20 + */
  21 +@Slf4j
  22 +@RestController
  23 +@RequestMapping("/api/features")
  24 +@Tag(name = "AI功能管理", description = "AI功能模块相关接口")
  25 +public class FeatureController {
  26 +
  27 + @Autowired
  28 + private FeatureService featureService;
  29 +
  30 + @PostMapping
  31 + @Operation(summary = "创建AI功能", description = "创建新的AI功能")
  32 + public Result<Feature> createFeature(@RequestBody Feature feature) {
  33 + try {
  34 + Feature savedFeature = featureService.save(feature);
  35 + return Result.success("功能创建成功", savedFeature);
  36 + } catch (Exception e) {
  37 + log.error("创建AI功能失败", e);
  38 + return Result.error("功能创建失败");
  39 + }
  40 + }
  41 +
  42 + @GetMapping("/{id}")
  43 + @Operation(summary = "获取AI功能详情", description = "根据ID获取AI功能详情")
  44 + public Result<Feature> getFeatureById(@PathVariable Integer id) {
  45 + try {
  46 + return featureService.findById(id)
  47 + .map(feature -> Result.success("查询成功", feature))
  48 + .orElse(Result.error("功能不存在"));
  49 + } catch (Exception e) {
  50 + log.error("获取AI功能详情失败, id: {}", id, e);
  51 + return Result.error("查询失败");
  52 + }
  53 + }
  54 +
  55 + @GetMapping
  56 + @Operation(summary = "获取AI功能列表", description = "获取所有AI功能列表")
  57 + public Result<List<Feature>> getAllFeatures() {
  58 + try {
  59 + List<Feature> features = featureService.findAll();
  60 + return Result.success("查询成功", features);
  61 + } catch (Exception e) {
  62 + log.error("获取AI功能列表失败", e);
  63 + return Result.error("查询失败");
  64 + }
  65 + }
  66 +
  67 + @GetMapping("/active")
  68 + @Operation(summary = "获取启用的AI功能列表", description = "获取所有启用的AI功能列表")
  69 + public Result<List<Feature>> getActiveFeatures() {
  70 + try {
  71 + List<Feature> features = featureService.findActiveFeatures();
  72 + return Result.success("查询成功", features);
  73 + } catch (Exception e) {
  74 + log.error("获取启用的AI功能列表失败", e);
  75 + return Result.error("查询失败");
  76 + }
  77 + }
  78 +
  79 + @GetMapping("/category/{category}")
  80 + @Operation(summary = "根据分类获取AI功能列表", description = "根据分类获取AI功能列表")
  81 + public Result<List<Feature>> getFeaturesByCategory(@PathVariable String category) {
  82 + try {
  83 + List<Feature> features = featureService.findByCategory(category);
  84 + return Result.success("查询成功", features);
  85 + } catch (Exception e) {
  86 + log.error("根据分类获取AI功能列表失败, category: {}", category, e);
  87 + return Result.error("查询失败");
  88 + }
  89 + }
  90 +
  91 + @PutMapping("/{id}")
  92 + @Operation(summary = "更新AI功能", description = "更新AI功能信息")
  93 + public Result<Feature> updateFeature(@PathVariable Integer id, @RequestBody Feature feature) {
  94 + try {
  95 + if (!featureService.findById(id).isPresent()) {
  96 + return Result.error("功能不存在");
  97 + }
  98 + feature.setId(id);
  99 + Feature updatedFeature = featureService.save(feature);
  100 + return Result.success("功能更新成功", updatedFeature);
  101 + } catch (Exception e) {
  102 + log.error("更新AI功能失败, id: {}", id, e);
  103 + return Result.error("功能更新失败");
  104 + }
  105 + }
  106 +
  107 + @DeleteMapping("/{id}")
  108 + @Operation(summary = "删除AI功能", description = "删除指定ID的AI功能")
  109 + public Result<String> deleteFeature(@PathVariable Integer id) {
  110 + try {
  111 + if (!featureService.findById(id).isPresent()) {
  112 + return Result.error("功能不存在");
  113 + }
  114 + featureService.deleteById(id);
  115 + return Result.success("功能删除成功");
  116 + } catch (Exception e) {
  117 + log.error("删除AI功能失败, id: {}", id, e);
  118 + return Result.error("功能删除失败");
  119 + }
  120 + }
  121 +}
  1 +/**
  2 + * AI模块控制器包
  3 + */
  4 +package com.aigeo.ai.controller;
  1 +package com.aigeo.ai.dto;
  2 +
  3 +import com.aigeo.ai.entity.DifyApiConfig;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import org.springframework.beans.BeanUtils;
  7 +
  8 +/**
  9 + * Dify API配置DTO
  10 + */
  11 +@Data
  12 +@Schema(description = "Dify API配置DTO")
  13 +public class DifyApiConfigDTO {
  14 +
  15 + @Schema(description = "配置ID")
  16 + private Integer id;
  17 +
  18 + @Schema(description = "公司ID")
  19 + private Integer companyId;
  20 +
  21 + @Schema(description = "配置名称")
  22 + private String name;
  23 +
  24 + @Schema(description = "API Key")
  25 + private String apiKey;
  26 +
  27 + @Schema(description = "API地址")
  28 + private String apiUrl;
  29 +
  30 + @Schema(description = "是否启用")
  31 + private Boolean isActive;
  32 +
  33 + @Schema(description = "备注")
  34 + private String remark;
  35 +
  36 + /**
  37 + * 将DTO转换为实体类
  38 + */
  39 + public DifyApiConfig toEntity() {
  40 + DifyApiConfig entity = new DifyApiConfig();
  41 + BeanUtils.copyProperties(this, entity);
  42 + return entity;
  43 + }
  44 +
  45 + /**
  46 + * 将实体类转换为DTO
  47 + */
  48 + public static DifyApiConfigDTO fromEntity(DifyApiConfig entity) {
  49 + DifyApiConfigDTO dto = new DifyApiConfigDTO();
  50 + BeanUtils.copyProperties(entity, dto);
  51 + return dto;
  52 + }
  53 +}
  1 +package com.aigeo.ai.dto;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * AI功能模块DTO
  10 + */
  11 +@Data
  12 +@Schema(description = "AI功能模块数据传输对象")
  13 +public class FeatureDTO {
  14 +
  15 + @Schema(description = "功能ID")
  16 + private Integer id;
  17 +
  18 + @Schema(description = "功能标识符", example = "ai_article")
  19 + private String featureKey;
  20 +
  21 + @Schema(description = "功能名称", example = "AI文章生成")
  22 + private String name;
  23 +
  24 + @Schema(description = "功能描述", example = "基于关键词和主题自动生成高质量文章")
  25 + private String description;
  26 +
  27 + @Schema(description = "功能分类", example = "content")
  28 + private String category;
  29 +
  30 + @Schema(description = "是否为高级功能")
  31 + private Boolean isPremium;
  32 +
  33 + @Schema(description = "排序权重")
  34 + private Integer sortOrder;
  35 +
  36 + @Schema(description = "是否启用")
  37 + private Boolean isActive;
  38 +
  39 + @Schema(description = "创建时间")
  40 + private LocalDateTime createdAt;
  41 +}
  1 +package com.aigeo.ai.entity;
  2 +
  3 +import jakarta.persistence.*;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * AI功能模块实体类
  10 + */
  11 +@Data
  12 +@Entity
  13 +@Table(name = "ai_features")
  14 +public class Feature {
  15 +
  16 + @Id
  17 + @GeneratedValue(strategy = GenerationType.IDENTITY)
  18 + private Integer id;
  19 +
  20 + @Column(name = "feature_key", nullable = false, unique = true)
  21 + private String featureKey;
  22 +
  23 + @Column(name = "name", nullable = false)
  24 + private String name;
  25 +
  26 + @Column(name = "description")
  27 + private String description;
  28 +
  29 + @Column(name = "category")
  30 + private String category;
  31 +
  32 + @Column(name = "is_premium")
  33 + private Boolean isPremium;
  34 +
  35 + @Column(name = "sort_order")
  36 + private Integer sortOrder;
  37 +
  38 + @Column(name = "is_active")
  39 + private Boolean isActive;
  40 +
  41 + @Column(name = "created_at")
  42 + private LocalDateTime createdAt;
  43 +
  44 + @Column(name = "updated_at")
  45 + private LocalDateTime updatedAt;
  46 +
  47 + @PrePersist
  48 + protected void onCreate() {
  49 + createdAt = LocalDateTime.now();
  50 + updatedAt = LocalDateTime.now();
  51 + if (isActive == null) {
  52 + isActive = true;
  53 + }
  54 + }
  55 +
  56 + @PreUpdate
  57 + protected void onUpdate() {
  58 + updatedAt = LocalDateTime.now();
  59 + }
  60 +}
  1 +/**
  2 + * AI模块实体包
  3 + */
  4 +package com.aigeo.ai.entity;
  1 +package com.aigeo.ai.repository;
  2 +
  3 +import com.aigeo.ai.entity.Feature;
  4 +import org.springframework.data.jpa.repository.JpaRepository;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.List;
  8 +import java.util.Optional;
  9 +
  10 +/**
  11 + * AI功能模块仓库接口
  12 + */
  13 +@Repository
  14 +public interface FeatureRepository extends JpaRepository<Feature, Integer> {
  15 +
  16 + /**
  17 + * 根据功能标识符查找功能
  18 + */
  19 + Optional<Feature> findByFeatureKey(String featureKey);
  20 +
  21 + /**
  22 + * 根据分类查找功能列表
  23 + */
  24 + List<Feature> findByCategory(String category);
  25 +
  26 + /**
  27 + * 查找启用的功能列表
  28 + */
  29 + List<Feature> findByIsActiveTrue();
  30 +
  31 + /**
  32 + * 根据分类和启用状态查找功能列表
  33 + */
  34 + List<Feature> findByCategoryAndIsActiveTrue(String category);
  35 +}
  1 +/**
  2 + * AI模块数据访问包
  3 + */
  4 +package com.aigeo.ai.repository;
  1 +package com.aigeo.ai.service;
  2 +
  3 +import com.aigeo.ai.entity.Feature;
  4 +
  5 +import java.util.List;
  6 +import java.util.Optional;
  7 +
  8 +/**
  9 + * AI功能模块服务接口
  10 + */
  11 +public interface FeatureService {
  12 +
  13 + /**
  14 + * 保存功能
  15 + */
  16 + Feature save(Feature feature);
  17 +
  18 + /**
  19 + * 根据ID查找功能
  20 + */
  21 + Optional<Feature> findById(Integer id);
  22 +
  23 + /**
  24 + * 根据功能标识符查找功能
  25 + */
  26 + Optional<Feature> findByFeatureKey(String featureKey);
  27 +
  28 + /**
  29 + * 查找所有功能
  30 + */
  31 + List<Feature> findAll();
  32 +
  33 + /**
  34 + * 查找启用的功能列表
  35 + */
  36 + List<Feature> findActiveFeatures();
  37 +
  38 + /**
  39 + * 根据分类查找功能列表
  40 + */
  41 + List<Feature> findByCategory(String category);
  42 +
  43 + /**
  44 + * 根据分类和启用状态查找功能列表
  45 + */
  46 + List<Feature> findByCategoryAndIsActiveTrue(String category);
  47 +
  48 + /**
  49 + * 删除功能
  50 + */
  51 + void deleteById(Integer id);
  52 +}
  1 +package com.aigeo.ai.service.impl;
  2 +
  3 +import com.aigeo.ai.entity.DifyApiConfig;
  4 +import com.aigeo.ai.repository.DifyApiConfigRepository;
  5 +import com.aigeo.ai.service.DifyApiConfigService;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * AI配置服务实现类
  14 + */
  15 +@Service
  16 +public class DifyApiConfigServiceImpl implements DifyApiConfigService {
  17 +
  18 + @Autowired
  19 + private DifyApiConfigRepository difyApiConfigRepository;
  20 +
  21 + @Override
  22 + public DifyApiConfig save(DifyApiConfig config) {
  23 + return difyApiConfigRepository.save(config);
  24 + }
  25 +
  26 + @Override
  27 + public Optional<DifyApiConfig> findById(Integer id) {
  28 + return difyApiConfigRepository.findById(id);
  29 + }
  30 +
  31 + @Override
  32 + public List<DifyApiConfig> findByCompanyId(Integer companyId) {
  33 + return difyApiConfigRepository.findByCompanyId(companyId);
  34 + }
  35 +
  36 + @Override
  37 + public List<DifyApiConfig> findActiveByCompanyId(Integer companyId) {
  38 + return difyApiConfigRepository.findByCompanyIdAndIsActiveTrue(companyId);
  39 + }
  40 +
  41 + @Override
  42 + public List<DifyApiConfig> findByProvider(DifyApiConfig.Provider provider) {
  43 + return difyApiConfigRepository.findByProvider(provider);
  44 + }
  45 +
  46 + @Override
  47 + public List<DifyApiConfig> findActiveByProvider(DifyApiConfig.Provider provider) {
  48 + return difyApiConfigRepository.findByProviderAndIsActiveTrue(provider);
  49 + }
  50 +
  51 + @Override
  52 + public List<DifyApiConfig> findAll() {
  53 + return difyApiConfigRepository.findAll();
  54 + }
  55 +
  56 + @Override
  57 + public void deleteById(Integer id) {
  58 + difyApiConfigRepository.deleteById(id);
  59 + }
  60 +}
  1 +package com.aigeo.ai.service.impl;
  2 +
  3 +import com.aigeo.ai.entity.Feature;
  4 +import com.aigeo.ai.repository.FeatureRepository;
  5 +import com.aigeo.ai.service.FeatureService;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * AI功能模块服务实现类
  14 + */
  15 +@Service
  16 +public class FeatureServiceImpl implements FeatureService {
  17 +
  18 + @Autowired
  19 + private FeatureRepository featureRepository;
  20 +
  21 + @Override
  22 + public Feature save(Feature feature) {
  23 + return featureRepository.save(feature);
  24 + }
  25 +
  26 + @Override
  27 + public Optional<Feature> findById(Integer id) {
  28 + return featureRepository.findById(id);
  29 + }
  30 +
  31 + @Override
  32 + public Optional<Feature> findByFeatureKey(String featureKey) {
  33 + return featureRepository.findByFeatureKey(featureKey);
  34 + }
  35 +
  36 + @Override
  37 + public List<Feature> findAll() {
  38 + return featureRepository.findAll();
  39 + }
  40 +
  41 + @Override
  42 + public List<Feature> findActiveFeatures() {
  43 + return featureRepository.findByIsActiveTrue();
  44 + }
  45 +
  46 + @Override
  47 + public List<Feature> findByCategory(String category) {
  48 + return featureRepository.findByCategory(category);
  49 + }
  50 +
  51 + @Override
  52 + public List<Feature> findByCategoryAndIsActiveTrue(String category) {
  53 + return featureRepository.findByCategoryAndIsActiveTrue(category);
  54 + }
  55 +
  56 + @Override
  57 + public void deleteById(Integer id) {
  58 + featureRepository.deleteById(id);
  59 + }
  60 +}
  1 +/**
  2 + * AI模块业务逻辑包
  3 + */
  4 +package com.aigeo.ai.service;
  1 +package com.aigeo.article.controller;
  2 +
  3 +import com.aigeo.article.entity.GeneratedArticle;
  4 +import com.aigeo.article.service.GeneratedArticleService;
  5 +import com.aigeo.common.Result;
  6 +import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.springframework.beans.factory.annotation.Autowired;
  10 +import org.springframework.web.bind.annotation.*;
  11 +
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * 生成的文章控制器
  16 + */
  17 +@Slf4j
  18 +@RestController
  19 +@RequestMapping("/api/articles")
  20 +@Tag(name = "生成文章管理", description = "AI生成文章相关接口")
  21 +public class GeneratedArticleController {
  22 +
  23 + @Autowired
  24 + private GeneratedArticleService generatedArticleService;
  25 +
  26 + @PostMapping
  27 + @Operation(summary = "创建生成文章", description = "创建新的生成文章")
  28 + public Result<GeneratedArticle> createArticle(@RequestBody GeneratedArticle article) {
  29 + try {
  30 + GeneratedArticle savedArticle = generatedArticleService.save(article);
  31 + return Result.success("文章创建成功", savedArticle);
  32 + } catch (Exception e) {
  33 + log.error("创建生成文章失败", e);
  34 + return Result.error("文章创建失败");
  35 + }
  36 + }
  37 +
  38 + @GetMapping("/{id}")
  39 + @Operation(summary = "获取生成文章详情", description = "根据ID获取生成文章详情")
  40 + public Result<GeneratedArticle> getArticleById(@PathVariable Integer id) {
  41 + try {
  42 + return generatedArticleService.findById(id)
  43 + .map(article -> Result.success("查询成功", article))
  44 + .orElse(Result.error("文章不存在"));
  45 + } catch (Exception e) {
  46 + log.error("获取生成文章详情失败, id: {}", id, e);
  47 + return Result.error("查询失败");
  48 + }
  49 + }
  50 +
  51 + @GetMapping
  52 + @Operation(summary = "获取生成文章列表", description = "获取所有生成文章列表")
  53 + public Result<List<GeneratedArticle>> getAllArticles() {
  54 + try {
  55 + List<GeneratedArticle> articles = generatedArticleService.findAll();
  56 + return Result.success("查询成功", articles);
  57 + } catch (Exception e) {
  58 + log.error("获取生成文章列表失败", e);
  59 + return Result.error("查询失败");
  60 + }
  61 + }
  62 +
  63 + @GetMapping("/task/{taskId}")
  64 + @Operation(summary = "根据任务ID获取生成文章列表", description = "根据任务ID获取生成文章列表")
  65 + public Result<List<GeneratedArticle>> getArticlesByTaskId(@PathVariable Integer taskId) {
  66 + try {
  67 + List<GeneratedArticle> articles = generatedArticleService.findByTaskId(taskId);
  68 + return Result.success("查询成功", articles);
  69 + } catch (Exception e) {
  70 + log.error("根据任务ID获取生成文章列表失败, taskId: {}", taskId, e);
  71 + return Result.error("查询失败");
  72 + }
  73 + }
  74 +
  75 + @GetMapping("/company/{companyId}")
  76 + @Operation(summary = "根据公司ID获取生成文章列表", description = "根据公司ID获取生成文章列表")
  77 + public Result<List<GeneratedArticle>> getArticlesByCompanyId(@PathVariable Integer companyId) {
  78 + try {
  79 + List<GeneratedArticle> articles = generatedArticleService.findByCompanyId(companyId);
  80 + return Result.success("查询成功", articles);
  81 + } catch (Exception e) {
  82 + log.error("根据公司ID获取生成文章列表失败, companyId: {}", companyId, e);
  83 + return Result.error("查询失败");
  84 + }
  85 + }
  86 +
  87 + @GetMapping("/status/{status}")
  88 + @Operation(summary = "根据状态获取生成文章列表", description = "根据状态获取生成文章列表")
  89 + public Result<List<GeneratedArticle>> getArticlesByStatus(@PathVariable String status) {
  90 + try {
  91 + GeneratedArticle.ArticleStatus articleStatus = GeneratedArticle.ArticleStatus.fromCode(status);
  92 + List<GeneratedArticle> articles = generatedArticleService.findByStatus(articleStatus);
  93 + return Result.success("查询成功", articles);
  94 + } catch (Exception e) {
  95 + log.error("根据状态获取生成文章列表失败, status: {}", status, e);
  96 + return Result.error("查询失败");
  97 + }
  98 + }
  99 +
  100 + @PutMapping("/{id}")
  101 + @Operation(summary = "更新生成文章", description = "更新生成文章信息")
  102 + public Result<GeneratedArticle> updateArticle(@PathVariable Integer id, @RequestBody GeneratedArticle article) {
  103 + try {
  104 + if (!generatedArticleService.findById(id).isPresent()) {
  105 + return Result.error("文章不存在");
  106 + }
  107 + article.setId(id);
  108 + GeneratedArticle updatedArticle = generatedArticleService.save(article);
  109 + return Result.success("文章更新成功", updatedArticle);
  110 + } catch (Exception e) {
  111 + log.error("更新生成文章失败, id: {}", id, e);
  112 + return Result.error("文章更新失败");
  113 + }
  114 + }
  115 +
  116 + @DeleteMapping("/{id}")
  117 + @Operation(summary = "删除生成文章", description = "删除指定ID的生成文章")
  118 + public Result<String> deleteArticle(@PathVariable Integer id) {
  119 + try {
  120 + if (!generatedArticleService.findById(id).isPresent()) {
  121 + return Result.error("文章不存在");
  122 + }
  123 + generatedArticleService.deleteById(id);
  124 + return Result.success("文章删除成功");
  125 + } catch (Exception e) {
  126 + log.error("删除生成文章失败, id: {}", id, e);
  127 + return Result.error("文章删除失败");
  128 + }
  129 + }
  130 +}
  1 +/**
  2 + * 文章模块控制器包
  3 + */
  4 +package com.aigeo.article.controller;
  1 +package com.aigeo.article.dto;
  2 +
  3 +import com.aigeo.article.entity.ArticleGenerationTask;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import org.springframework.beans.BeanUtils;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +/**
  11 + * 文章生成任务DTO
  12 + */
  13 +@Data
  14 +@Schema(description = "文章生成任务DTO")
  15 +public class ArticleGenerationTaskDTO {
  16 +
  17 + @Schema(description = "任务ID")
  18 + private Integer id;
  19 +
  20 + @Schema(description = "公司ID")
  21 + private Integer companyId;
  22 +
  23 + @Schema(description = "任务名称")
  24 + private String name;
  25 +
  26 + @Schema(description = "主题")
  27 + private String topic;
  28 +
  29 + @Schema(description = "关键词")
  30 + private String keywords;
  31 +
  32 + @Schema(description = "文章数量")
  33 + private Integer articleCount;
  34 +
  35 + @Schema(description = "平台类型")
  36 + private String platformType;
  37 +
  38 + @Schema(description = "状态")
  39 + private String status;
  40 +
  41 + @Schema(description = "创建时间")
  42 + private LocalDateTime createdAt;
  43 +
  44 + @Schema(description = "更新时间")
  45 + private LocalDateTime updatedAt;
  46 +
  47 + /**
  48 + * 将DTO转换为实体类
  49 + */
  50 + public ArticleGenerationTask toEntity() {
  51 + ArticleGenerationTask entity = new ArticleGenerationTask();
  52 + BeanUtils.copyProperties(this, entity);
  53 + return entity;
  54 + }
  55 +
  56 + /**
  57 + * 将实体类转换为DTO
  58 + */
  59 + public static ArticleGenerationTaskDTO fromEntity(ArticleGenerationTask entity) {
  60 + ArticleGenerationTaskDTO dto = new ArticleGenerationTaskDTO();
  61 + BeanUtils.copyProperties(entity, dto);
  62 + return dto;
  63 + }
  64 +}
  1 +package com.aigeo.article.dto;
  2 +
  3 +import com.aigeo.article.entity.GeneratedArticle;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import org.springframework.beans.BeanUtils;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +/**
  11 + * 生成文章DTO
  12 + */
  13 +@Data
  14 +@Schema(description = "生成文章DTO")
  15 +public class GeneratedArticleDTO {
  16 +
  17 + @Schema(description = "文章ID")
  18 + private Integer id;
  19 +
  20 + @Schema(description = "任务ID")
  21 + private Integer taskId;
  22 +
  23 + @Schema(description = "公司ID")
  24 + private Integer companyId;
  25 +
  26 + @Schema(description = "标题")
  27 + private String title;
  28 +
  29 + @Schema(description = "内容")
  30 + private String content;
  31 +
  32 + @Schema(description = "状态")
  33 + private String status;
  34 +
  35 + @Schema(description = "创建时间")
  36 + private LocalDateTime createdAt;
  37 +
  38 + @Schema(description = "更新时间")
  39 + private LocalDateTime updatedAt;
  40 +
  41 + /**
  42 + * 将DTO转换为实体类
  43 + */
  44 + public GeneratedArticle toEntity() {
  45 + GeneratedArticle entity = new GeneratedArticle();
  46 + BeanUtils.copyProperties(this, entity);
  47 + return entity;
  48 + }
  49 +
  50 + /**
  51 + * 将实体类转换为DTO
  52 + */
  53 + public static GeneratedArticleDTO fromEntity(GeneratedArticle entity) {
  54 + GeneratedArticleDTO dto = new GeneratedArticleDTO();
  55 + BeanUtils.copyProperties(entity, dto);
  56 + return dto;
  57 + }
  58 +}
  1 +/**
  2 + * 文章模块实体包
  3 + */
  4 +package com.aigeo.article.entity;
  1 +package com.aigeo.article.repository;
  2 +
  3 +import com.aigeo.article.entity.GeneratedArticle;
  4 +import org.springframework.data.jpa.repository.JpaRepository;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * 生成的文章仓库接口
  11 + */
  12 +@Repository
  13 +public interface GeneratedArticleRepository extends JpaRepository<GeneratedArticle, Integer> {
  14 +
  15 + /**
  16 + * 根据任务ID查找文章列表
  17 + */
  18 + List<GeneratedArticle> findByTaskId(Integer taskId);
  19 +
  20 + /**
  21 + * 根据公司ID查找文章列表
  22 + */
  23 + List<GeneratedArticle> findByCompanyId(Integer companyId);
  24 +
  25 + /**
  26 + * 根据任务ID和版本查找文章
  27 + */
  28 + GeneratedArticle findByTaskIdAndVersion(Integer taskId, Integer version);
  29 +
  30 + /**
  31 + * 根据任务ID查找选定的文章
  32 + */
  33 + GeneratedArticle findByTaskIdAndIsSelectedTrue(Integer taskId);
  34 +
  35 + /**
  36 + * 根据状态查找文章列表
  37 + */
  38 + List<GeneratedArticle> findByStatus(GeneratedArticle.ArticleStatus status);
  39 +
  40 + /**
  41 + * 根据公司ID和状态查找文章列表
  42 + */
  43 + List<GeneratedArticle> findByCompanyIdAndStatus(Integer companyId, GeneratedArticle.ArticleStatus status);
  44 +}
  1 +/**
  2 + * 文章模块数据访问包
  3 + */
  4 +package com.aigeo.article.repository;
  1 +package com.aigeo.article.service;
  2 +
  3 +import com.aigeo.article.entity.ArticleGenerationTask;
  4 +
  5 +import java.util.List;
  6 +import java.util.Optional;
  7 +
  8 +/**
  9 + * 文章生成任务服务接口
  10 + */
  11 +public interface ArticleGenerationTaskService {
  12 +
  13 + /**
  14 + * 保存任务
  15 + */
  16 + ArticleGenerationTask save(ArticleGenerationTask task);
  17 +
  18 + /**
  19 + * 根据ID查找任务
  20 + */
  21 + Optional<ArticleGenerationTask> findById(Integer id);
  22 +
  23 + /**
  24 + * 根据公司ID查找任务列表
  25 + */
  26 + List<ArticleGenerationTask> findByCompanyId(Integer companyId);
  27 +
  28 + /**
  29 + * 根据用户ID查找任务列表
  30 + */
  31 + List<ArticleGenerationTask> findByUserId(Integer userId);
  32 +
  33 + /**
  34 + * 根据状态查找任务列表
  35 + */
  36 + List<ArticleGenerationTask> findByStatus(ArticleGenerationTask.TaskStatus status);
  37 +
  38 + /**
  39 + * 根据公司ID和状态查找任务列表
  40 + */
  41 + List<ArticleGenerationTask> findByCompanyIdAndStatus(Integer companyId, ArticleGenerationTask.TaskStatus status);
  42 +
  43 + /**
  44 + * 根据用户ID和状态查找任务列表
  45 + */
  46 + List<ArticleGenerationTask> findByUserIdAndStatus(Integer userId, ArticleGenerationTask.TaskStatus status);
  47 +
  48 + /**
  49 + * 查找所有任务
  50 + */
  51 + List<ArticleGenerationTask> findAll();
  52 +
  53 + /**
  54 + * 删除任务
  55 + */
  56 + void deleteById(Integer id);
  57 +}
  1 +package com.aigeo.article.service;
  2 +
  3 +import com.aigeo.article.entity.GeneratedArticle;
  4 +
  5 +import java.util.List;
  6 +import java.util.Optional;
  7 +
  8 +/**
  9 + * 生成的文章服务接口
  10 + */
  11 +public interface GeneratedArticleService {
  12 +
  13 + /**
  14 + * 保存文章
  15 + */
  16 + GeneratedArticle save(GeneratedArticle article);
  17 +
  18 + /**
  19 + * 根据ID查找文章
  20 + */
  21 + Optional<GeneratedArticle> findById(Integer id);
  22 +
  23 + /**
  24 + * 根据任务ID查找文章列表
  25 + */
  26 + List<GeneratedArticle> findByTaskId(Integer taskId);
  27 +
  28 + /**
  29 + * 根据公司ID查找文章列表
  30 + */
  31 + List<GeneratedArticle> findByCompanyId(Integer companyId);
  32 +
  33 + /**
  34 + * 根据任务ID和版本查找文章
  35 + */
  36 + GeneratedArticle findByTaskIdAndVersion(Integer taskId, Integer version);
  37 +
  38 + /**
  39 + * 根据任务ID查找选定的文章
  40 + */
  41 + GeneratedArticle findByTaskIdAndIsSelectedTrue(Integer taskId);
  42 +
  43 + /**
  44 + * 根据状态查找文章列表
  45 + */
  46 + List<GeneratedArticle> findByStatus(GeneratedArticle.ArticleStatus status);
  47 +
  48 + /**
  49 + * 根据公司ID和状态查找文章列表
  50 + */
  51 + List<GeneratedArticle> findByCompanyIdAndStatus(Integer companyId, GeneratedArticle.ArticleStatus status);
  52 +
  53 + /**
  54 + * 查找所有文章
  55 + */
  56 + List<GeneratedArticle> findAll();
  57 +
  58 + /**
  59 + * 删除文章
  60 + */
  61 + void deleteById(Integer id);
  62 +}
  1 +package com.aigeo.article.service.impl;
  2 +
  3 +import com.aigeo.article.entity.ArticleGenerationTask;
  4 +import com.aigeo.article.repository.ArticleGenerationTaskRepository;
  5 +import com.aigeo.article.service.ArticleGenerationTaskService;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * 文章生成任务服务实现类
  14 + */
  15 +@Service
  16 +public class ArticleGenerationTaskServiceImpl implements ArticleGenerationTaskService {
  17 +
  18 + @Autowired
  19 + private ArticleGenerationTaskRepository articleGenerationTaskRepository;
  20 +
  21 + @Override
  22 + public ArticleGenerationTask save(ArticleGenerationTask task) {
  23 + return articleGenerationTaskRepository.save(task);
  24 + }
  25 +
  26 + @Override
  27 + public Optional<ArticleGenerationTask> findById(Integer id) {
  28 + return articleGenerationTaskRepository.findById(id);
  29 + }
  30 +
  31 + @Override
  32 + public List<ArticleGenerationTask> findByCompanyId(Integer companyId) {
  33 + return articleGenerationTaskRepository.findByCompanyId(companyId);
  34 + }
  35 +
  36 + @Override
  37 + public List<ArticleGenerationTask> findByUserId(Integer userId) {
  38 + return articleGenerationTaskRepository.findByUserId(userId);
  39 + }
  40 +
  41 + @Override
  42 + public List<ArticleGenerationTask> findByStatus(ArticleGenerationTask.TaskStatus status) {
  43 + return articleGenerationTaskRepository.findByStatus(status);
  44 + }
  45 +
  46 + @Override
  47 + public List<ArticleGenerationTask> findByCompanyIdAndStatus(Integer companyId, ArticleGenerationTask.TaskStatus status) {
  48 + return articleGenerationTaskRepository.findByCompanyIdAndStatus(companyId, status);
  49 + }
  50 +
  51 + @Override
  52 + public List<ArticleGenerationTask> findByUserIdAndStatus(Integer userId, ArticleGenerationTask.TaskStatus status) {
  53 + return articleGenerationTaskRepository.findByUserIdAndStatus(userId, status);
  54 + }
  55 +
  56 + @Override
  57 + public List<ArticleGenerationTask> findAll() {
  58 + return articleGenerationTaskRepository.findAll();
  59 + }
  60 +
  61 + @Override
  62 + public void deleteById(Integer id) {
  63 + articleGenerationTaskRepository.deleteById(id);
  64 + }
  65 +}
  1 +package com.aigeo.article.service.impl;
  2 +
  3 +import com.aigeo.article.entity.GeneratedArticle;
  4 +import com.aigeo.article.repository.GeneratedArticleRepository;
  5 +import com.aigeo.article.service.GeneratedArticleService;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * 生成的文章服务实现类
  14 + */
  15 +@Service
  16 +public class GeneratedArticleServiceImpl implements GeneratedArticleService {
  17 +
  18 + @Autowired
  19 + private GeneratedArticleRepository generatedArticleRepository;
  20 +
  21 + @Override
  22 + public GeneratedArticle save(GeneratedArticle article) {
  23 + return generatedArticleRepository.save(article);
  24 + }
  25 +
  26 + @Override
  27 + public Optional<GeneratedArticle> findById(Integer id) {
  28 + return generatedArticleRepository.findById(id);
  29 + }
  30 +
  31 + @Override
  32 + public List<GeneratedArticle> findByTaskId(Integer taskId) {
  33 + return generatedArticleRepository.findByTaskId(taskId);
  34 + }
  35 +
  36 + @Override
  37 + public List<GeneratedArticle> findByCompanyId(Integer companyId) {
  38 + return generatedArticleRepository.findByCompanyId(companyId);
  39 + }
  40 +
  41 + @Override
  42 + public GeneratedArticle findByTaskIdAndVersion(Integer taskId, Integer version) {
  43 + return generatedArticleRepository.findByTaskIdAndVersion(taskId, version);
  44 + }
  45 +
  46 + @Override
  47 + public GeneratedArticle findByTaskIdAndIsSelectedTrue(Integer taskId) {
  48 + return generatedArticleRepository.findByTaskIdAndIsSelectedTrue(taskId);
  49 + }
  50 +
  51 + @Override
  52 + public List<GeneratedArticle> findByStatus(GeneratedArticle.ArticleStatus status) {
  53 + return generatedArticleRepository.findByStatus(status);
  54 + }
  55 +
  56 + @Override
  57 + public List<GeneratedArticle> findByCompanyIdAndStatus(Integer companyId, GeneratedArticle.ArticleStatus status) {
  58 + return generatedArticleRepository.findByCompanyIdAndStatus(companyId, status);
  59 + }
  60 +
  61 + @Override
  62 + public List<GeneratedArticle> findAll() {
  63 + return generatedArticleRepository.findAll();
  64 + }
  65 +
  66 + @Override
  67 + public void deleteById(Integer id) {
  68 + generatedArticleRepository.deleteById(id);
  69 + }
  70 +}
  1 +/**
  2 + * 文章模块业务逻辑包
  3 + */
  4 +package com.aigeo.article.service;
  1 +package com.aigeo.auth.dto;
  2 +
  3 +import com.aigeo.company.entity.User;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * 用户注册响应DTO
  9 + */
  10 +@Data
  11 +@Schema(description = "用户注册响应数据")
  12 +public class RegisterResponse {
  13 +
  14 + @Schema(description = "访问令牌")
  15 + private String token;
  16 +
  17 + @Schema(description = "过期时间(毫秒)")
  18 + private Long expiresIn;
  19 +
  20 + @Schema(description = "用户信息")
  21 + private User user;
  22 +}
  1 +package com.aigeo.auth.service;
  2 +
  3 +import com.aigeo.company.entity.User;
  4 +import com.aigeo.company.service.UserService;
  5 +import org.springframework.beans.factory.annotation.Autowired;
  6 +import org.springframework.security.core.authority.SimpleGrantedAuthority;
  7 +import org.springframework.security.core.userdetails.UserDetails;
  8 +import org.springframework.security.core.userdetails.UserDetailsService;
  9 +import org.springframework.security.core.userdetails.UsernameNotFoundException;
  10 +import org.springframework.stereotype.Service;
  11 +
  12 +import java.util.Collections;
  13 +import java.util.Optional;
  14 +
  15 +@Service
  16 +public class CustomUserDetailsService implements UserDetailsService {
  17 +
  18 + @Autowired
  19 + private UserService userService;
  20 +
  21 + @Override
  22 + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  23 + Optional<User> userOptional = userService.findByUsername(username);
  24 + if (!userOptional.isPresent()) {
  25 + userOptional = userService.findByEmail(username);
  26 + }
  27 +
  28 + if (!userOptional.isPresent()) {
  29 + throw new UsernameNotFoundException("User not found with username or email: " + username);
  30 + }
  31 +
  32 + User user = userOptional.get();
  33 + return new org.springframework.security.core.userdetails.User(
  34 + user.getUsername(),
  35 + user.getPasswordHash(),
  36 + user.getIsActive(),
  37 + true,
  38 + true,
  39 + true,
  40 + Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()))
  41 + );
  42 + }
  43 +}
  1 +package com.aigeo.auth.service.impl;
  2 +
  3 +import com.aigeo.auth.dto.LoginRequest;
  4 +import com.aigeo.auth.dto.LoginResponse;
  5 +import com.aigeo.auth.dto.RegisterRequest;
  6 +import com.aigeo.auth.dto.RegisterResponse;
  7 +import com.aigeo.auth.service.AuthService;
  8 +import com.aigeo.company.entity.Company;
  9 +import com.aigeo.company.entity.User;
  10 +import com.aigeo.company.service.CompanyService;
  11 +import com.aigeo.company.service.UserService;
  12 +import com.aigeo.common.enums.CompanyStatus;
  13 +import com.aigeo.common.enums.UserRole;
  14 +import com.aigeo.common.exception.BusinessException;
  15 +import com.aigeo.util.JwtUtil;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.beans.factory.annotation.Autowired;
  18 +import org.springframework.security.crypto.password.PasswordEncoder;
  19 +import org.springframework.stereotype.Service;
  20 +
  21 +import java.time.LocalDateTime;
  22 +import java.util.Optional;
  23 +
  24 +/**
  25 + * 认证服务实现类
  26 + */
  27 +@Slf4j
  28 +@Service
  29 +public class AuthServiceImpl implements AuthService {
  30 +
  31 + private final UserService userService;
  32 + private final CompanyService companyService;
  33 + private final PasswordEncoder passwordEncoder;
  34 + private final JwtUtil jwtUtil;
  35 +
  36 + public AuthServiceImpl(UserService userService, CompanyService companyService,
  37 + PasswordEncoder passwordEncoder, JwtUtil jwtUtil) {
  38 + this.userService = userService;
  39 + this.companyService = companyService;
  40 + this.passwordEncoder = passwordEncoder;
  41 + this.jwtUtil = jwtUtil;
  42 + }
  43 +
  44 + @Override
  45 + public LoginResponse login(LoginRequest loginRequest) {
  46 + try {
  47 + // 查找用户
  48 + Optional<User> userOptional = userService.findByUsername(loginRequest.getUsername());
  49 + if (!userOptional.isPresent()) {
  50 + // 尝试通过邮箱查找
  51 + userOptional = userService.findByEmail(loginRequest.getUsername());
  52 + }
  53 +
  54 + if (!userOptional.isPresent()) {
  55 + throw new BusinessException(400, "用户名或密码错误");
  56 + }
  57 +
  58 + User user = userOptional.get();
  59 +
  60 + // 验证密码
  61 + if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPasswordHash())) {
  62 + throw new BusinessException(400, "用户名或密码错误");
  63 + }
  64 +
  65 + // 生成JWT token
  66 + String token = jwtUtil.generateToken(user);
  67 +
  68 + // 构建响应
  69 + LoginResponse response = new LoginResponse();
  70 + response.setToken(token);
  71 + response.setExpiresIn(jwtUtil.getExpirationTimeSeconds());
  72 + response.setUser(user);
  73 +
  74 + return response;
  75 + } catch (Exception e) {
  76 + log.error("用户登录失败: {}", loginRequest.getUsername(), e);
  77 + throw new BusinessException(500, "登录失败");
  78 + }
  79 + }
  80 +
  81 + @Override
  82 + public RegisterResponse register(RegisterRequest registerRequest) {
  83 + try {
  84 + // 检查用户名是否已存在
  85 + if (userService.findByUsername(registerRequest.getUsername()).isPresent()) {
  86 + throw new BusinessException(400, "用户名已存在");
  87 + }
  88 +
  89 + // 检查邮箱是否已存在
  90 + if (userService.findByEmail(registerRequest.getEmail()).isPresent()) {
  91 + throw new BusinessException(400, "邮箱已被注册");
  92 + }
  93 +
  94 + // 验证密码和确认密码是否一致
  95 + if (!registerRequest.getPassword().equals(registerRequest.getConfirmPassword())) {
  96 + throw new BusinessException(400, "密码和确认密码不一致");
  97 + }
  98 +
  99 + // 确定公司ID
  100 + Integer companyId = registerRequest.getCompanyId();
  101 + if (companyId == null) {
  102 + // 如果没有提供公司ID,则需要提供公司名称来创建新公司
  103 + if (registerRequest.getCompanyName() == null || registerRequest.getCompanyName().isEmpty()) {
  104 + throw new BusinessException(400, "必须提供公司ID或公司名称");
  105 + }
  106 +
  107 + // 创建新公司
  108 + Company company = new Company();
  109 + company.setName(registerRequest.getCompanyName());
  110 + company.setBillingEmail(registerRequest.getEmail());
  111 + company.setStatus(CompanyStatus.TRIAL);
  112 + company.setTrialExpiryDate(LocalDateTime.now().plusDays(30)); // 30天试用期
  113 +
  114 + Company savedCompany = companyService.save(company);
  115 + companyId = savedCompany.getId();
  116 + log.info("为新用户 {} 创建了新公司 {}, 公司ID: {}",
  117 + registerRequest.getUsername(), registerRequest.getCompanyName(), companyId);
  118 + }
  119 +
  120 + // 创建新用户
  121 + User user = new User();
  122 + user.setUsername(registerRequest.getUsername());
  123 + user.setEmail(registerRequest.getEmail());
  124 + user.setPasswordHash(passwordEncoder.encode(registerRequest.getPassword()));
  125 + user.setFullName(registerRequest.getFullName());
  126 + user.setPhone(registerRequest.getPhone());
  127 + user.setCompanyId(companyId);
  128 + user.setRole(UserRole.EDITOR); // 使用现有的枚举值
  129 + user.setAvatarUrl(registerRequest.getAvatarUrl());
  130 + user.setIsActive(true);
  131 +
  132 + // 保存用户
  133 + User savedUser = userService.save(user);
  134 +
  135 + // 生成JWT token
  136 + String token = jwtUtil.generateToken(savedUser);
  137 +
  138 + // 构建响应
  139 + RegisterResponse response = new RegisterResponse();
  140 + response.setToken(token);
  141 + response.setExpiresIn(jwtUtil.getExpirationTimeSeconds());
  142 + response.setUser(savedUser);
  143 +
  144 + return response;
  145 + } catch (BusinessException e) {
  146 + throw e;
  147 + } catch (Exception e) {
  148 + log.error("用户注册失败: {}", registerRequest.getUsername(), e);
  149 + throw new BusinessException(500, "注册失败");
  150 + }
  151 + }
  152 +}
  1 +package com.aigeo.common;
  2 +
  3 +import com.aigeo.common.enums.ResultCode;
  4 +import lombok.Data;
  5 +
  6 +import java.io.Serializable;
  7 +
  8 +/**
  9 + * 统一响应结果封装类
  10 + */
  11 +@Data
  12 +public class Result<T> implements Serializable {
  13 +
  14 + private static final long serialVersionUID = 1L;
  15 +
  16 + /**
  17 + * 状态码
  18 + */
  19 + private int code;
  20 +
  21 + /**
  22 + * 消息
  23 + */
  24 + private String message;
  25 +
  26 + /**
  27 + * 数据
  28 + */
  29 + private T data;
  30 +
  31 + public Result() {
  32 + }
  33 +
  34 + public Result(int code, String message, T data) {
  35 + this.code = code;
  36 + this.message = message;
  37 + this.data = data;
  38 + }
  39 +
  40 + /**
  41 + * 成功
  42 + */
  43 + public static <T> Result<T> success() {
  44 + return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null);
  45 + }
  46 +
  47 + /**
  48 + * 成功
  49 + */
  50 + public static <T> Result<T> success(T data) {
  51 + return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
  52 + }
  53 +
  54 + /**
  55 + * 成功
  56 + */
  57 + public static <T> Result<T> success(String message, T data) {
  58 + return new Result<>(ResultCode.SUCCESS.getCode(), message, data);
  59 + }
  60 +
  61 + /**
  62 + * 失败
  63 + */
  64 + public static <T> Result<T> error() {
  65 + return new Result<>(ResultCode.INTERNAL_SERVER_ERROR.getCode(), ResultCode.INTERNAL_SERVER_ERROR.getMessage(), null);
  66 + }
  67 +
  68 + /**
  69 + * 失败
  70 + */
  71 + public static <T> Result<T> error(String message) {
  72 + return new Result<>(ResultCode.INTERNAL_SERVER_ERROR.getCode(), message, null);
  73 + }
  74 +
  75 + /**
  76 + * 失败
  77 + */
  78 + public static <T> Result<T> error(ResultCode resultCode) {
  79 + return new Result<>(resultCode.getCode(), resultCode.getMessage(), null);
  80 + }
  81 +
  82 + /**
  83 + * 失败
  84 + */
  85 + public static <T> Result<T> error(int code, String message) {
  86 + return new Result<>(code, message, null);
  87 + }
  88 +
  89 + /**
  90 + * 失败
  91 + */
  92 + public static <T> Result<T> error(ResultCode resultCode, String message) {
  93 + return new Result<>(resultCode.getCode(), message, null);
  94 + }
  95 +
  96 + /**
  97 + * 失败
  98 + */
  99 + public static <T> Result<T> error(int code, String message, T data) {
  100 + return new Result<>(code, message, data);
  101 + }
  102 +}
  1 +package com.aigeo.common;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * 分页结果封装类
  10 + */
  11 +@Data
  12 +@Schema(description = "分页结果封装类")
  13 +public class ResultPage<T> {
  14 +
  15 + @Schema(description = "当前页码")
  16 + private int pageNumber;
  17 +
  18 + @Schema(description = "每页记录数")
  19 + private int pageSize;
  20 +
  21 + @Schema(description = "总记录数")
  22 + private long total;
  23 +
  24 + @Schema(description = "总页数")
  25 + private int totalPages;
  26 +
  27 + @Schema(description = "当前页数据")
  28 + private List<T> items;
  29 +
  30 + public ResultPage() {
  31 + }
  32 +
  33 + public ResultPage(int pageNumber, int pageSize, long total, List<T> items) {
  34 + this.pageNumber = pageNumber;
  35 + this.pageSize = pageSize;
  36 + this.total = total;
  37 + this.items = items;
  38 + this.totalPages = (int) Math.ceil((double) total / pageSize);
  39 + }
  40 +
  41 + /**
  42 + * 创建分页结果
  43 + */
  44 + public static <T> ResultPage<T> of(int pageNumber, int pageSize, long total, List<T> items) {
  45 + return new ResultPage<>(pageNumber, pageSize, total, items);
  46 + }
  47 +}
  1 +package com.aigeo.common.enums;
  2 +
  3 +/**
  4 + * 响应结果码枚举
  5 + */
  6 +public enum ResultCode {
  7 + SUCCESS(200, "操作成功"),
  8 + BAD_REQUEST(400, "请求参数错误"),
  9 + UNAUTHORIZED(401, "未授权"),
  10 + FORBIDDEN(403, "禁止访问"),
  11 + NOT_FOUND(404, "资源不存在"),
  12 + INTERNAL_SERVER_ERROR(500, "服务器内部错误");
  13 +
  14 + private final int code;
  15 + private final String message;
  16 +
  17 + ResultCode(int code, String message) {
  18 + this.code = code;
  19 + this.message = message;
  20 + }
  21 +
  22 + public int getCode() {
  23 + return code;
  24 + }
  25 +
  26 + public String getMessage() {
  27 + return message;
  28 + }
  29 +
  30 + public static ResultCode fromCode(Integer code) {
  31 + if (code == null) {
  32 + return INTERNAL_SERVER_ERROR;
  33 + }
  34 +
  35 + for (ResultCode resultCode : ResultCode.values()) {
  36 + if (resultCode.getCode() == code) {
  37 + return resultCode;
  38 + }
  39 + }
  40 + return INTERNAL_SERVER_ERROR;
  41 + }
  42 +}
  1 +/**
  2 + * 公司模块控制器包
  3 + */
  4 +package com.aigeo.company.controller;
  1 +package com.aigeo.company.dto;
  2 +
  3 +import com.aigeo.common.enums.CompanyStatus;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +/**
  10 + * 公司DTO
  11 + */
  12 +@Data
  13 +@Schema(description = "公司数据传输对象")
  14 +public class CompanyDTO {
  15 +
  16 + @Schema(description = "公司ID")
  17 + private Integer id;
  18 +
  19 + @Schema(description = "公司名称")
  20 + private String name;
  21 +
  22 + @Schema(description = "公司域名")
  23 + private String domain;
  24 +
  25 + @Schema(description = "公司状态")
  26 + private CompanyStatus status;
  27 +
  28 + @Schema(description = "试用到期日")
  29 + private LocalDateTime trialExpiryDate;
  30 +
  31 + @Schema(description = "企业默认设置(JSON)")
  32 + private String defaultSettings;
  33 +
  34 + @Schema(description = "账单邮箱")
  35 + private String billingEmail;
  36 +
  37 + @Schema(description = "联系电话")
  38 + private String contactPhone;
  39 +
  40 + @Schema(description = "公司地址")
  41 + private String address;
  42 +
  43 + @Schema(description = "公司Logo URL")
  44 + private String logoUrl;
  45 +
  46 + @Schema(description = "创建时间")
  47 + private LocalDateTime createdAt;
  48 +
  49 + @Schema(description = "最后更新时间")
  50 + private LocalDateTime updatedAt;
  51 +}
  1 +package com.aigeo.company.dto;
  2 +
  3 +import com.aigeo.common.enums.UserRole;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +/**
  10 + * 用户DTO
  11 + */
  12 +@Data
  13 +@Schema(description = "用户数据传输对象")
  14 +public class UserDTO {
  15 +
  16 + @Schema(description = "用户ID")
  17 + private Integer id;
  18 +
  19 + @Schema(description = "所属公司ID")
  20 + private Integer companyId;
  21 +
  22 + @Schema(description = "用户名")
  23 + private String username;
  24 +
  25 + @Schema(description = "用户邮箱")
  26 + private String email;
  27 +
  28 + @Schema(description = "用户全名")
  29 + private String fullName;
  30 +
  31 + @Schema(description = "用户头像URL")
  32 + private String avatarUrl;
  33 +
  34 + @Schema(description = "手机号")
  35 + private String phone;
  36 +
  37 + @Schema(description = "用户角色")
  38 + private UserRole role;
  39 +
  40 + @Schema(description = "是否启用")
  41 + private Boolean isActive;
  42 +
  43 + @Schema(description = "最近登录时间")
  44 + private LocalDateTime lastLogin;
  45 +
  46 + @Schema(description = "上次修改密码时间")
  47 + private LocalDateTime lastPasswordChange;
  48 +
  49 + @Schema(description = "登录失败次数")
  50 + private Integer failedLoginAttempts;
  51 +
  52 + @Schema(description = "锁定截止时间")
  53 + private LocalDateTime lockedUntil;
  54 +
  55 + @Schema(description = "用户时区")
  56 + private String timezone;
  57 +
  58 + @Schema(description = "用户个性化设置")
  59 + private String preferences;
  60 +
  61 + @Schema(description = "创建时间")
  62 + private LocalDateTime createdAt;
  63 +
  64 + @Schema(description = "最后更新时间")
  65 + private LocalDateTime updatedAt;
  66 +}
  1 +/**
  2 + * 公司模块实体包
  3 + */
  4 +package com.aigeo.company.entity;
  1 +/**
  2 + * 公司模块数据访问包
  3 + */
  4 +package com.aigeo.company.repository;
  1 +package com.aigeo.company.service.impl;
  2 +
  3 +import com.aigeo.common.enums.CompanyStatus;
  4 +import com.aigeo.company.entity.Company;
  5 +import com.aigeo.company.repository.CompanyRepository;
  6 +import com.aigeo.company.service.CompanyService;
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.stereotype.Service;
  9 +
  10 +import java.util.List;
  11 +import java.util.Optional;
  12 +
  13 +/**
  14 + * 公司服务实现类
  15 + */
  16 +@Service
  17 +public class CompanyServiceImpl implements CompanyService {
  18 +
  19 + @Autowired
  20 + private CompanyRepository companyRepository;
  21 +
  22 + @Override
  23 + public Company save(Company company) {
  24 + return companyRepository.save(company);
  25 + }
  26 +
  27 + @Override
  28 + public Optional<Company> findById(Integer id) {
  29 + return companyRepository.findById(id);
  30 + }
  31 +
  32 + @Override
  33 + public List<Company> findByStatus(CompanyStatus status) {
  34 + return companyRepository.findByStatus(status);
  35 + }
  36 +
  37 + @Override
  38 + public List<Company> findByName(String name) {
  39 + return companyRepository.findByName(name);
  40 + }
  41 +
  42 + @Override
  43 + public List<Company> findByDomain(String domain) {
  44 + return companyRepository.findByDomain(domain);
  45 + }
  46 +
  47 + @Override
  48 + public List<Company> findAll() {
  49 + return companyRepository.findAll();
  50 + }
  51 +
  52 + @Override
  53 + public void deleteById(Integer id) {
  54 + companyRepository.deleteById(id);
  55 + }
  56 +}
  1 +package com.aigeo.company.service.impl;
  2 +
  3 +import com.aigeo.common.enums.UserRole;
  4 +import com.aigeo.company.entity.User;
  5 +import com.aigeo.company.repository.UserRepository;
  6 +import com.aigeo.company.service.UserService;
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.stereotype.Service;
  9 +
  10 +import java.util.List;
  11 +import java.util.Optional;
  12 +
  13 +/**
  14 + * 用户服务实现类
  15 + */
  16 +@Service
  17 +public class UserServiceImpl implements UserService {
  18 +
  19 + @Autowired
  20 + private UserRepository userRepository;
  21 +
  22 + @Override
  23 + public User save(User user) {
  24 + return userRepository.save(user);
  25 + }
  26 +
  27 + @Override
  28 + public Optional<User> findById(Integer id) {
  29 + return userRepository.findById(id);
  30 + }
  31 +
  32 + @Override
  33 + public Optional<User> findByUsername(String username) {
  34 + return userRepository.findByUsername(username);
  35 + }
  36 +
  37 + @Override
  38 + public Optional<User> findByEmail(String email) {
  39 + return userRepository.findByEmail(email);
  40 + }
  41 +
  42 + @Override
  43 + public List<User> findByCompanyId(Integer companyId) {
  44 + return userRepository.findByCompanyId(companyId);
  45 + }
  46 +
  47 + @Override
  48 + public List<User> findActiveByCompanyId(Integer companyId) {
  49 + return userRepository.findByCompanyIdAndIsActiveTrue(companyId);
  50 + }
  51 +
  52 + @Override
  53 + public List<User> findByRole(UserRole role) {
  54 + return userRepository.findByRole(role);
  55 + }
  56 +
  57 + @Override
  58 + public List<User> findByCompanyIdAndRole(Integer companyId, UserRole role) {
  59 + return userRepository.findByCompanyIdAndRole(companyId, role);
  60 + }
  61 +
  62 + @Override
  63 + public List<User> findAll() {
  64 + return userRepository.findAll();
  65 + }
  66 +
  67 + @Override
  68 + public void deleteById(Integer id) {
  69 + userRepository.deleteById(id);
  70 + }
  71 +}
  1 +/**
  2 + * 公司模块业务逻辑包
  3 + */
  4 +package com.aigeo.company.service;
  1 +package com.aigeo.config;
  2 +
  3 +import com.aigeo.auth.service.CustomUserDetailsService;
  4 +import com.aigeo.util.JwtUtil;
  5 +import jakarta.servlet.FilterChain;
  6 +import jakarta.servlet.ServletException;
  7 +import jakarta.servlet.http.HttpServletRequest;
  8 +import jakarta.servlet.http.HttpServletResponse;
  9 +import org.springframework.beans.factory.annotation.Autowired;
  10 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  11 +import org.springframework.security.core.context.SecurityContextHolder;
  12 +import org.springframework.security.core.userdetails.UserDetails;
  13 +import org.springframework.security.core.userdetails.UserDetailsService;
  14 +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
  15 +import org.springframework.stereotype.Component;
  16 +import org.springframework.web.filter.OncePerRequestFilter;
  17 +
  18 +import java.io.IOException;
  19 +
  20 +@Component
  21 +public class JwtAuthenticationFilter extends OncePerRequestFilter {
  22 +
  23 + @Autowired
  24 + private JwtUtil jwtUtil;
  25 +
  26 + @Autowired
  27 + private CustomUserDetailsService userDetailsService;
  28 +
  29 + @Override
  30 + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
  31 + throws ServletException, IOException {
  32 +
  33 + final String requestTokenHeader = request.getHeader("Authorization");
  34 +
  35 + String username = null;
  36 + String jwtToken = null;
  37 +
  38 + if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
  39 + jwtToken = requestTokenHeader.substring(7);
  40 + try {
  41 + username = jwtUtil.getUsernameFromToken(jwtToken);
  42 + } catch (Exception e) {
  43 + logger.error("Unable to get JWT Token", e);
  44 + }
  45 + }
  46 +
  47 + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
  48 + UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
  49 +
  50 + // 创建一个临时User对象用于验证token
  51 + com.aigeo.company.entity.User tempUser = new com.aigeo.company.entity.User();
  52 + tempUser.setUsername(username);
  53 +
  54 + if (jwtUtil.validateToken(jwtToken, tempUser)) {
  55 + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
  56 + userDetails, null, userDetails.getAuthorities());
  57 + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  58 + SecurityContextHolder.getContext().setAuthentication(authToken);
  59 + }
  60 + }
  61 + chain.doFilter(request, response);
  62 + }
  63 +}
  1 +package com.aigeo.config;
  2 +
  3 +import io.swagger.v3.oas.models.Components;
  4 +import io.swagger.v3.oas.models.OpenAPI;
  5 +import io.swagger.v3.oas.models.info.Contact;
  6 +import io.swagger.v3.oas.models.info.Info;
  7 +import io.swagger.v3.oas.models.info.License;
  8 +import io.swagger.v3.oas.models.security.SecurityRequirement;
  9 +import io.swagger.v3.oas.models.security.SecurityScheme;
  10 +import org.springframework.context.annotation.Bean;
  11 +import org.springframework.context.annotation.Configuration;
  12 +
  13 +@Configuration
  14 +public class Knife4jConfig {
  15 +
  16 + @Bean
  17 + public OpenAPI customOpenAPI() {
  18 + return new OpenAPI()
  19 + .info(new Info()
  20 + .title("AIGEO API Documentation")
  21 + .version("1.0.0")
  22 + .description("AI Content Generation Platform API Documentation")
  23 + .contact(new Contact()
  24 + .name("AIGEO Team")
  25 + .url("https://www.aigeo.com")
  26 + .email("contact@aigeo.com"))
  27 + .license(new License()
  28 + .name("Apache 2.0")
  29 + .url("http://www.apache.org/licenses/LICENSE-2.0.html")))
  30 + .components(new Components()
  31 + .addSecuritySchemes("bearerAuth", new SecurityScheme()
  32 + .type(SecurityScheme.Type.HTTP)
  33 + .scheme("bearer")
  34 + .bearerFormat("JWT")))
  35 + .addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
  36 + }
  37 +}
  1 +package com.aigeo.config;
  2 +
  3 +import org.springframework.context.annotation.Configuration;
  4 +import org.springframework.scheduling.annotation.EnableScheduling;
  5 +
  6 +@Configuration
  7 +@EnableScheduling
  8 +public class QuartzConfig {
  9 +
  10 +}
  1 +package com.aigeo.config;
  2 +
  3 +import org.springframework.context.annotation.Bean;
  4 +import org.springframework.context.annotation.Configuration;
  5 +import org.springframework.data.redis.connection.RedisConnectionFactory;
  6 +import org.springframework.data.redis.core.RedisTemplate;
  7 +import org.springframework.data.redis.serializer.StringRedisSerializer;
  8 +
  9 +@Configuration
  10 +public class RedisConfig {
  11 +
  12 + /*
  13 + @Bean
  14 + public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
  15 + RedisTemplate<String, Object> template = new RedisTemplate<>();
  16 + template.setConnectionFactory(connectionFactory);
  17 + template.setKeySerializer(new StringRedisSerializer());
  18 + template.setValueSerializer(new StringRedisSerializer());
  19 + return template;
  20 + }
  21 + */
  22 +}
  1 +package com.aigeo.config;
  2 +
  3 +import com.aigeo.auth.service.CustomUserDetailsService;
  4 +import com.aigeo.config.JwtAuthenticationFilter;
  5 +import org.springframework.beans.factory.annotation.Autowired;
  6 +import org.springframework.context.annotation.Bean;
  7 +import org.springframework.context.annotation.Configuration;
  8 +import org.springframework.security.authentication.AuthenticationManager;
  9 +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
  10 +import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  11 +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  12 +import org.springframework.security.config.http.SessionCreationPolicy;
  13 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  14 +import org.springframework.security.crypto.password.PasswordEncoder;
  15 +import org.springframework.security.web.SecurityFilterChain;
  16 +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  17 +
  18 +@Configuration
  19 +@EnableWebSecurity
  20 +public class SecurityConfig {
  21 +
  22 + @Autowired
  23 + private CustomUserDetailsService userDetailsService;
  24 +
  25 + @Autowired
  26 + private JwtAuthenticationFilter jwtAuthenticationFilter;
  27 +
  28 + @Bean
  29 + public PasswordEncoder passwordEncoder() {
  30 + return new BCryptPasswordEncoder();
  31 + }
  32 +
  33 + @Bean
  34 + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
  35 + return config.getAuthenticationManager();
  36 + }
  37 +
  38 + @Bean
  39 + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  40 + http.csrf(csrf -> csrf.disable())
  41 + .authorizeHttpRequests(authz -> authz
  42 + .requestMatchers("/auth/**").permitAll()
  43 + .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html",
  44 + "/doc.html", "/webjars/**", "/swagger-resources/**").permitAll()
  45 + .anyRequest().authenticated()
  46 + )
  47 + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
  48 +
  49 + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
  50 +
  51 + return http.build();
  52 + }
  53 +}
  1 +package com.aigeo.config;
  2 +
  3 +import org.springframework.context.annotation.Configuration;
  4 +import org.springframework.web.servlet.config.annotation.CorsRegistry;
  5 +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  6 +
  7 +@Configuration
  8 +public class WebConfig implements WebMvcConfigurer {
  9 +
  10 + @Override
  11 + public void addCorsMappings(CorsRegistry registry) {
  12 + registry.addMapping("/api/**")
  13 + .allowedOrigins("*")
  14 + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
  15 + .allowedHeaders("*")
  16 + .maxAge(3600);
  17 + }
  18 +}
  1 +/**
  2 + * 关键词模块控制器包
  3 + */
  4 +package com.aigeo.keyword.controller;
  1 +package com.aigeo.keyword.dto;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * 关键词DTO
  10 + */
  11 +@Data
  12 +@Schema(description = "关键词数据传输对象")
  13 +public class KeywordDTO {
  14 +
  15 + @Schema(description = "关键词ID")
  16 + private Integer id;
  17 +
  18 + @Schema(description = "公司ID")
  19 + private Integer companyId;
  20 +
  21 + @Schema(description = "关键词文本")
  22 + private String keyword;
  23 +
  24 + @Schema(description = "搜索量估算")
  25 + private Integer searchVolume;
  26 +
  27 + @Schema(description = "估计每次点击成本")
  28 + private Double cpc;
  29 +
  30 + @Schema(description = "关键词难度评分")
  31 + private Byte difficulty;
  32 +
  33 + @Schema(description = "来源类型")
  34 + private String source;
  35 +
  36 + @Schema(description = "关键词状态")
  37 + private String status;
  38 +
  39 + @Schema(description = "关键词标签")
  40 + private String tags;
  41 +
  42 + @Schema(description = "关键词被文章使用次数")
  43 + private Integer usageCount;
  44 +
  45 + @Schema(description = "创建时间")
  46 + private LocalDateTime createdAt;
  47 +
  48 + @Schema(description = "更新时间")
  49 + private LocalDateTime updatedAt;
  50 +}
  1 +package com.aigeo.keyword.dto;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * 话题DTO
  10 + */
  11 +@Data
  12 +@Schema(description = "话题数据传输对象")
  13 +public class TopicDTO {
  14 +
  15 + @Schema(description = "话题ID")
  16 + private Integer id;
  17 +
  18 + @Schema(description = "公司ID")
  19 + private Integer companyId;
  20 +
  21 + @Schema(description = "来源任务ID")
  22 + private Integer sourceTaskId;
  23 +
  24 + @Schema(description = "话题标题")
  25 + private String title;
  26 +
  27 + @Schema(description = "话题描述")
  28 + private String description;
  29 +
  30 + @Schema(description = "原始来源链接")
  31 + private String sourceUrl;
  32 +
  33 + @Schema(description = "话题状态")
  34 + private String status;
  35 +
  36 + @Schema(description = "创建时间")
  37 + private LocalDateTime createdAt;
  38 +
  39 + @Schema(description = "更新时间")
  40 + private LocalDateTime updatedAt;
  41 +}
  1 +/**
  2 + * 关键词模块数据传输对象包
  3 + */
  4 +package com.aigeo.keyword.dto;
  1 +/**
  2 + * 关键词模块实体包
  3 + */
  4 +package com.aigeo.keyword.entity;
  1 +/**
  2 + * 关键词模块数据访问包
  3 + */
  4 +package com.aigeo.keyword.repository;
  1 +package com.aigeo.keyword.service.impl;
  2 +
  3 +import com.aigeo.keyword.entity.Keyword;
  4 +import com.aigeo.keyword.repository.KeywordRepository;
  5 +import com.aigeo.keyword.service.KeywordService;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * 关键词服务实现类
  14 + */
  15 +@Service
  16 +public class KeywordServiceImpl implements KeywordService {
  17 +
  18 + @Autowired
  19 + private KeywordRepository keywordRepository;
  20 +
  21 + @Override
  22 + public Keyword save(Keyword keyword) {
  23 + return keywordRepository.save(keyword);
  24 + }
  25 +
  26 + @Override
  27 + public Optional<Keyword> findById(Integer id) {
  28 + return keywordRepository.findById(id);
  29 + }
  30 +
  31 + @Override
  32 + public List<Keyword> findByCompanyId(Integer companyId) {
  33 + return keywordRepository.findByCompanyId(companyId);
  34 + }
  35 +
  36 + @Override
  37 + public List<Keyword> findByKeyword(String keyword) {
  38 + return keywordRepository.findByKeyword(keyword);
  39 + }
  40 +
  41 + @Override
  42 + public List<Keyword> findByCompanyIdAndKeyword(Integer companyId, String keyword) {
  43 + return keywordRepository.findByCompanyIdAndKeyword(companyId, keyword);
  44 + }
  45 +
  46 + @Override
  47 + public List<Keyword> findByStatus(String status) {
  48 + return keywordRepository.findByStatus(status);
  49 + }
  50 +
  51 + @Override
  52 + public List<Keyword> findByCompanyIdAndStatus(Integer companyId, String status) {
  53 + return keywordRepository.findByCompanyIdAndStatus(companyId, status);
  54 + }
  55 +
  56 + @Override
  57 + public List<Keyword> findByTagsContaining(String tag) {
  58 + return keywordRepository.findByTagsContaining(tag);
  59 + }
  60 +
  61 + @Override
  62 + public List<Keyword> findAll() {
  63 + return keywordRepository.findAll();
  64 + }
  65 +
  66 + @Override
  67 + public void deleteById(Integer id) {
  68 + keywordRepository.deleteById(id);
  69 + }
  70 +}
  1 +package com.aigeo.keyword.service.impl;
  2 +
  3 +import com.aigeo.keyword.entity.Topic;
  4 +import com.aigeo.keyword.repository.TopicRepository;
  5 +import com.aigeo.keyword.service.TopicService;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * 话题服务实现类
  14 + */
  15 +@Service
  16 +public class TopicServiceImpl implements TopicService {
  17 +
  18 + @Autowired
  19 + private TopicRepository topicRepository;
  20 +
  21 + @Override
  22 + public Topic save(Topic topic) {
  23 + return topicRepository.save(topic);
  24 + }
  25 +
  26 + @Override
  27 + public Optional<Topic> findById(Integer id) {
  28 + return topicRepository.findById(id);
  29 + }
  30 +
  31 + @Override
  32 + public List<Topic> findByCompanyId(Integer companyId) {
  33 + return topicRepository.findByCompanyId(companyId);
  34 + }
  35 +
  36 + @Override
  37 + public List<Topic> findBySourceTaskId(Integer sourceTaskId) {
  38 + return topicRepository.findBySourceTaskId(sourceTaskId);
  39 + }
  40 +
  41 + @Override
  42 + public List<Topic> findByTitle(String title) {
  43 + return topicRepository.findByTitle(title);
  44 + }
  45 +
  46 + @Override
  47 + public List<Topic> findByCompanyIdAndTitle(Integer companyId, String title) {
  48 + return topicRepository.findByCompanyIdAndTitle(companyId, title);
  49 + }
  50 +
  51 + @Override
  52 + public List<Topic> findByStatus(String status) {
  53 + return topicRepository.findByStatus(status);
  54 + }
  55 +
  56 + @Override
  57 + public List<Topic> findByCompanyIdAndStatus(Integer companyId, String status) {
  58 + return topicRepository.findByCompanyIdAndStatus(companyId, status);
  59 + }
  60 +
  61 + @Override
  62 + public List<Topic> findAll() {
  63 + return topicRepository.findAll();
  64 + }
  65 +
  66 + @Override
  67 + public void deleteById(Integer id) {
  68 + topicRepository.deleteById(id);
  69 + }
  70 +}
  1 +/**
  2 + * 关键词模块业务逻辑包
  3 + */
  4 +package com.aigeo.keyword.service;
  1 +package com.aigeo.landingpage.controller;
  2 +
  3 +import com.aigeo.landingpage.entity.LandingPageProject;
  4 +import com.aigeo.landingpage.service.LandingPageProjectService;
  5 +import com.aigeo.common.Result;
  6 +import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.springframework.beans.factory.annotation.Autowired;
  10 +import org.springframework.web.bind.annotation.*;
  11 +
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * 落地页项目控制器
  16 + */
  17 +@Slf4j
  18 +@RestController
  19 +@RequestMapping("/api/landing-page-projects")
  20 +@Tag(name = "落地页项目管理", description = "落地页项目相关接口")
  21 +public class LandingPageProjectController {
  22 +
  23 + @Autowired
  24 + private LandingPageProjectService landingPageProjectService;
  25 +
  26 + @PostMapping
  27 + @Operation(summary = "创建落地页项目", description = "创建新的落地页项目")
  28 + public Result<LandingPageProject> createProject(@RequestBody LandingPageProject project) {
  29 + try {
  30 + LandingPageProject savedProject = landingPageProjectService.save(project);
  31 + return Result.success("项目创建成功", savedProject);
  32 + } catch (Exception e) {
  33 + log.error("创建落地页项目失败", e);
  34 + return Result.error("项目创建失败");
  35 + }
  36 + }
  37 +
  38 + @GetMapping("/{id}")
  39 + @Operation(summary = "获取落地页项目详情", description = "根据ID获取落地页项目详情")
  40 + public Result<LandingPageProject> getProjectById(@PathVariable Integer id) {
  41 + try {
  42 + return landingPageProjectService.findById(id)
  43 + .map(project -> Result.success("查询成功", project))
  44 + .orElse(Result.error("项目不存在"));
  45 + } catch (Exception e) {
  46 + log.error("获取落地页项目详情失败, id: {}", id, e);
  47 + return Result.error("查询失败");
  48 + }
  49 + }
  50 +
  51 + @GetMapping
  52 + @Operation(summary = "获取落地页项目列表", description = "获取所有落地页项目列表")
  53 + public Result<List<LandingPageProject>> getAllProjects() {
  54 + try {
  55 + List<LandingPageProject> projects = landingPageProjectService.findAll();
  56 + return Result.success("查询成功", projects);
  57 + } catch (Exception e) {
  58 + log.error("获取落地页项目列表失败", e);
  59 + return Result.error("查询失败");
  60 + }
  61 + }
  62 +
  63 + @GetMapping("/company/{companyId}")
  64 + @Operation(summary = "根据公司ID获取落地页项目列表", description = "根据公司ID获取落地页项目列表")
  65 + public Result<List<LandingPageProject>> getProjectsByCompanyId(@PathVariable Integer companyId) {
  66 + try {
  67 + List<LandingPageProject> projects = landingPageProjectService.findByCompanyId(companyId);
  68 + return Result.success("查询成功", projects);
  69 + } catch (Exception e) {
  70 + log.error("根据公司ID获取落地页项目列表失败, companyId: {}", companyId, e);
  71 + return Result.error("查询失败");
  72 + }
  73 + }
  74 +
  75 + @GetMapping("/user/{userId}")
  76 + @Operation(summary = "根据用户ID获取落地页项目列表", description = "根据用户ID获取落地页项目列表")
  77 + public Result<List<LandingPageProject>> getProjectsByUserId(@PathVariable Integer userId) {
  78 + try {
  79 + List<LandingPageProject> projects = landingPageProjectService.findByUserId(userId);
  80 + return Result.success("查询成功", projects);
  81 + } catch (Exception e) {
  82 + log.error("根据用户ID获取落地页项目列表失败, userId: {}", userId, e);
  83 + return Result.error("查询失败");
  84 + }
  85 + }
  86 +
  87 + @GetMapping("/status/{status}")
  88 + @Operation(summary = "根据状态获取落地页项目列表", description = "根据状态获取落地页项目列表")
  89 + public Result<List<LandingPageProject>> getProjectsByStatus(@PathVariable String status) {
  90 + try {
  91 + LandingPageProject.ProjectStatus projectStatus = LandingPageProject.ProjectStatus.fromCode(status);
  92 + List<LandingPageProject> projects = landingPageProjectService.findByStatus(projectStatus);
  93 + return Result.success("查询成功", projects);
  94 + } catch (Exception e) {
  95 + log.error("根据状态获取落地页项目列表失败, status: {}", status, e);
  96 + return Result.error("查询失败");
  97 + }
  98 + }
  99 +
  100 + @PutMapping("/{id}")
  101 + @Operation(summary = "更新落地页项目", description = "更新落地页项目信息")
  102 + public Result<LandingPageProject> updateProject(@PathVariable Integer id, @RequestBody LandingPageProject project) {
  103 + try {
  104 + if (!landingPageProjectService.findById(id).isPresent()) {
  105 + return Result.error("项目不存在");
  106 + }
  107 + project.setId(id);
  108 + LandingPageProject updatedProject = landingPageProjectService.save(project);
  109 + return Result.success("项目更新成功", updatedProject);
  110 + } catch (Exception e) {
  111 + log.error("更新落地页项目失败, id: {}", id, e);
  112 + return Result.error("项目更新失败");
  113 + }
  114 + }
  115 +
  116 + @DeleteMapping("/{id}")
  117 + @Operation(summary = "删除落地页项目", description = "删除指定ID的落地页项目")
  118 + public Result<String> deleteProject(@PathVariable Integer id) {
  119 + try {
  120 + if (!landingPageProjectService.findById(id).isPresent()) {
  121 + return Result.error("项目不存在");
  122 + }
  123 + landingPageProjectService.deleteById(id);
  124 + return Result.success("项目删除成功");
  125 + } catch (Exception e) {
  126 + log.error("删除落地页项目失败, id: {}", id, e);
  127 + return Result.error("项目删除失败");
  128 + }
  129 + }
  130 +}
  1 +/**
  2 + * 落地页模块控制器包
  3 + */
  4 +package com.aigeo.landingpage.controller;
  1 +package com.aigeo.landingpage.dto;
  2 +
  3 +import com.aigeo.landingpage.entity.LandingPageProject;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import org.springframework.beans.BeanUtils;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +/**
  11 + * 落地页项目DTO
  12 + */
  13 +@Data
  14 +@Schema(description = "落地页项目DTO")
  15 +public class LandingPageProjectDTO {
  16 +
  17 + @Schema(description = "项目ID")
  18 + private Integer id;
  19 +
  20 + @Schema(description = "公司ID")
  21 + private Integer companyId;
  22 +
  23 + @Schema(description = "项目名称")
  24 + private String name;
  25 +
  26 + @Schema(description = "域名")
  27 + private String domain;
  28 +
  29 + @Schema(description = "状态")
  30 + private String status;
  31 +
  32 + @Schema(description = "创建时间")
  33 + private LocalDateTime createdAt;
  34 +
  35 + @Schema(description = "更新时间")
  36 + private LocalDateTime updatedAt;
  37 +
  38 + /**
  39 + * 将DTO转换为实体类
  40 + */
  41 + public LandingPageProject toEntity() {
  42 + LandingPageProject entity = new LandingPageProject();
  43 + BeanUtils.copyProperties(this, entity);
  44 + return entity;
  45 + }
  46 +
  47 + /**
  48 + * 将实体类转换为DTO
  49 + */
  50 + public static LandingPageProjectDTO fromEntity(LandingPageProject entity) {
  51 + LandingPageProjectDTO dto = new LandingPageProjectDTO();
  52 + BeanUtils.copyProperties(entity, dto);
  53 + return dto;
  54 + }
  55 +}
  1 +/**
  2 + * 落地页模块实体包
  3 + */
  4 +package com.aigeo.landingpage.entity;
  1 +/**
  2 + * 落地页模块数据访问包
  3 + */
  4 +package com.aigeo.landingpage.repository;
  1 +package com.aigeo.landingpage.service.impl;
  2 +
  3 +import com.aigeo.landingpage.entity.LandingPageProject;
  4 +import com.aigeo.landingpage.repository.LandingPageProjectRepository;
  5 +import com.aigeo.landingpage.service.LandingPageProjectService;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * 落地页项目服务实现类
  14 + */
  15 +@Service
  16 +public class LandingPageProjectServiceImpl implements LandingPageProjectService {
  17 +
  18 + @Autowired
  19 + private LandingPageProjectRepository landingPageProjectRepository;
  20 +
  21 + @Override
  22 + public LandingPageProject save(LandingPageProject project) {
  23 + return landingPageProjectRepository.save(project);
  24 + }
  25 +
  26 + @Override
  27 + public Optional<LandingPageProject> findById(Integer id) {
  28 + return landingPageProjectRepository.findById(id);
  29 + }
  30 +
  31 + @Override
  32 + public List<LandingPageProject> findByCompanyId(Integer companyId) {
  33 + return landingPageProjectRepository.findByCompanyId(companyId);
  34 + }
  35 +
  36 + @Override
  37 + public List<LandingPageProject> findByUserId(Integer userId) {
  38 + return landingPageProjectRepository.findByUserId(userId);
  39 + }
  40 +
  41 + @Override
  42 + public List<LandingPageProject> findByStatus(LandingPageProject.ProjectStatus status) {
  43 + return landingPageProjectRepository.findByStatus(status);
  44 + }
  45 +
  46 + @Override
  47 + public List<LandingPageProject> findByCompanyIdAndStatus(Integer companyId, LandingPageProject.ProjectStatus status) {
  48 + return landingPageProjectRepository.findByCompanyIdAndStatus(companyId, status);
  49 + }
  50 +
  51 + @Override
  52 + public List<LandingPageProject> findByUserIdAndStatus(Integer userId, LandingPageProject.ProjectStatus status) {
  53 + return landingPageProjectRepository.findByUserIdAndStatus(userId, status);
  54 + }
  55 +
  56 + @Override
  57 + public List<LandingPageProject> findAll() {
  58 + return landingPageProjectRepository.findAll();
  59 + }
  60 +
  61 + @Override
  62 + public void deleteById(Integer id) {
  63 + landingPageProjectRepository.deleteById(id);
  64 + }
  65 +}
  1 +/**
  2 + * 落地页模块业务逻辑包
  3 + */
  4 +package com.aigeo.landingpage.service;
  1 +/**
  2 + * 平台模块控制器包
  3 + */
  4 +package com.aigeo.platform.controller;
  1 +package com.aigeo.platform.dto;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * 发布平台DTO
  10 + */
  11 +@Data
  12 +@Schema(description = "发布平台数据传输对象")
  13 +public class PublishingPlatformDTO {
  14 +
  15 + @Schema(description = "平台ID")
  16 + private Integer id;
  17 +
  18 + @Schema(description = "平台类型ID")
  19 + private Integer typeId;
  20 +
  21 + @Schema(description = "平台名称")
  22 + private String name;
  23 +
  24 + @Schema(description = "平台代码")
  25 + private String code;
  26 +
  27 + @Schema(description = "平台描述")
  28 + private String description;
  29 +
  30 + @Schema(description = "平台图标")
  31 + private String icon;
  32 +
  33 + @Schema(description = "认证类型")
  34 + private String authType;
  35 +
  36 + @Schema(description = "API配置模板")
  37 + private String apiConfigTemplate;
  38 +
  39 + @Schema(description = "字符限制")
  40 + private Integer characterLimit;
  41 +
  42 + @Schema(description = "是否启用")
  43 + private Boolean isActive;
  44 +
  45 + @Schema(description = "创建时间")
  46 + private LocalDateTime createdAt;
  47 +
  48 + @Schema(description = "更新时间")
  49 + private LocalDateTime updatedAt;
  50 +}
  1 +/**
  2 + * 平台模块数据传输对象包
  3 + */
  4 +package com.aigeo.platform.dto;
  1 +/**
  2 + * 平台模块实体包
  3 + */
  4 +package com.aigeo.platform.entity;
  1 +/**
  2 + * 平台模块数据访问包
  3 + */
  4 +package com.aigeo.platform.repository;
  1 +package com.aigeo.platform.service.impl;
  2 +
  3 +import com.aigeo.platform.entity.PublishingPlatform;
  4 +import com.aigeo.platform.repository.PublishingPlatformRepository;
  5 +import com.aigeo.platform.service.PublishingPlatformService;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * 发布平台服务实现类
  14 + */
  15 +@Service
  16 +public class PublishingPlatformServiceImpl implements PublishingPlatformService {
  17 +
  18 + @Autowired
  19 + private PublishingPlatformRepository publishingPlatformRepository;
  20 +
  21 + @Override
  22 + public PublishingPlatform save(PublishingPlatform platform) {
  23 + return publishingPlatformRepository.save(platform);
  24 + }
  25 +
  26 + @Override
  27 + public Optional<PublishingPlatform> findById(Integer id) {
  28 + return publishingPlatformRepository.findById(id);
  29 + }
  30 +
  31 + @Override
  32 + public List<PublishingPlatform> findByTypeId(Integer typeId) {
  33 + return publishingPlatformRepository.findByTypeId(typeId);
  34 + }
  35 +
  36 + @Override
  37 + public List<PublishingPlatform> findActivePlatforms() {
  38 + return publishingPlatformRepository.findByIsActiveTrue();
  39 + }
  40 +
  41 + @Override
  42 + public List<PublishingPlatform> findActiveByTypeId(Integer typeId) {
  43 + return publishingPlatformRepository.findByTypeIdAndIsActiveTrue(typeId);
  44 + }
  45 +
  46 + @Override
  47 + public PublishingPlatform findByCode(String code) {
  48 + return publishingPlatformRepository.findByCode(code);
  49 + }
  50 +
  51 + @Override
  52 + public List<PublishingPlatform> findAll() {
  53 + return publishingPlatformRepository.findAll();
  54 + }
  55 +
  56 + @Override
  57 + public void deleteById(Integer id) {
  58 + publishingPlatformRepository.deleteById(id);
  59 + }
  60 +}
  1 +/**
  2 + * 平台模块业务逻辑包
  3 + */
  4 +package com.aigeo.platform.service;
  1 +package com.aigeo.repository;
  2 +
  3 +import com.aigeo.article.entity.ArticleGenerationTask;
  4 +import org.springframework.data.jpa.repository.JpaRepository;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.List;
  8 +
  9 +@Repository
  10 +public interface ArticleGenerationTaskRepository extends JpaRepository<ArticleGenerationTask, Integer> {
  11 + List<ArticleGenerationTask> findByCompanyId(Integer companyId);
  12 + List<ArticleGenerationTask> findByCompanyIdAndStatus(Integer companyId, String status);
  13 +}
  1 +package com.aigeo.repository;
  2 +
  3 +import com.aigeo.company.entity.Company;
  4 +import org.springframework.data.jpa.repository.JpaRepository;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.Optional;
  8 +
  9 +@Repository
  10 +public interface CompanyRepository extends JpaRepository<Company, Integer> {
  11 + Optional<Company> findByName(String name);
  12 + Optional<Company> findByDomain(String domain);
  13 +}
  1 +package com.aigeo.repository;
  2 +
  3 +import com.aigeo.ai.entity.DifyApiConfig;
  4 +import org.springframework.data.jpa.repository.JpaRepository;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.List;
  8 +import java.util.Optional;
  9 +
  10 +@Repository
  11 +public interface DifyApiConfigRepository extends JpaRepository<DifyApiConfig, Integer> {
  12 + List<DifyApiConfig> findByCompanyId(Integer companyId);
  13 + List<DifyApiConfig> findByCompanyIdAndIsActiveTrue(Integer companyId);
  14 + Optional<DifyApiConfig> findByCompanyIdAndIsActiveTrueOrderByCreatedAtDesc(Integer companyId);
  15 +}
  1 +package com.aigeo.repository;
  2 +
  3 +import com.aigeo.ai.entity.Feature;
  4 +import org.springframework.data.jpa.repository.JpaRepository;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.List;
  8 +
  9 +@Repository
  10 +public interface FeatureRepository extends JpaRepository<Feature, Integer> {
  11 + List<Feature> findByIsActiveTrue();
  12 +}
  1 +package com.aigeo.repository;
  2 +
  3 +import com.aigeo.article.entity.GeneratedArticle;
  4 +import org.springframework.data.jpa.repository.JpaRepository;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.List;
  8 +
  9 +@Repository
  10 +public interface GeneratedArticleRepository extends JpaRepository<GeneratedArticle, Integer> {
  11 + List<GeneratedArticle> findByTaskId(Integer taskId);
  12 + List<GeneratedArticle> findByCompanyId(Integer companyId);
  13 + List<GeneratedArticle> findByCompanyIdAndStatus(Integer companyId, String status);
  14 +}
  1 +package com.aigeo.repository;
  2 +
  3 +import com.aigeo.landingpage.entity.LandingPageProject;
  4 +import org.springframework.data.jpa.repository.JpaRepository;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.List;
  8 +
  9 +@Repository
  10 +public interface LandingPageProjectRepository extends JpaRepository<LandingPageProject, Integer> {
  11 + List<LandingPageProject> findByCompanyId(Integer companyId);
  12 + List<LandingPageProject> findByCompanyIdAndStatus(Integer companyId, String status);
  13 +}
  1 +package com.aigeo.repository;
  2 +
  3 +import com.aigeo.company.entity.User;
  4 +import org.springframework.data.jpa.repository.JpaRepository;
  5 +import org.springframework.stereotype.Repository;
  6 +
  7 +import java.util.Optional;
  8 +
  9 +@Repository
  10 +public interface UserRepository extends JpaRepository<User, Integer> {
  11 + Optional<User> findByUsername(String username);
  12 + Optional<User> findByEmail(String email);
  13 +}
  1 +package com.aigeo.util;
  2 +
  3 +import com.aigeo.company.entity.User;
  4 +import io.jsonwebtoken.Claims;
  5 +import io.jsonwebtoken.Jwts;
  6 +import io.jsonwebtoken.io.Decoders;
  7 +import io.jsonwebtoken.security.Keys;
  8 +import org.springframework.beans.factory.annotation.Value;
  9 +import org.springframework.stereotype.Component;
  10 +
  11 +import javax.crypto.SecretKey;
  12 +import java.util.Date;
  13 +import java.util.HashMap;
  14 +import java.util.Map;
  15 +import java.util.function.Function;
  16 +
  17 +/**
  18 + * JWT工具类
  19 + */
  20 +@Component
  21 +public class JwtUtil {
  22 +
  23 + @Value("${jwt.secret:mySecretKeyForAigeoApplicationWhichIsLongEnough}")
  24 + private String secret;
  25 +
  26 + @Value("${jwt.expiration:86400}")
  27 + private Long expiration;
  28 +
  29 + /**
  30 + * 获取签名密钥
  31 + */
  32 + private SecretKey getSigningKey() {
  33 + byte[] keyBytes = Decoders.BASE64.decode(secret);
  34 + return Keys.hmacShaKeyFor(keyBytes);
  35 + }
  36 +
  37 + /**
  38 + * 从token中获取用户名
  39 + */
  40 + public String getUsernameFromToken(String token) {
  41 + Claims claims = getClaimsFromToken(token);
  42 + return claims.getSubject();
  43 + }
  44 +
  45 + /**
  46 + * 从token中提取用户名(别名方法)
  47 + */
  48 + public String extractUsername(String token) {
  49 + return getUsernameFromToken(token);
  50 + }
  51 +
  52 + /**
  53 + * 从token中获取Claims
  54 + */
  55 + private Claims getClaimsFromToken(String token) {
  56 + return Jwts.parser()
  57 + .verifyWith(getSigningKey())
  58 + .build()
  59 + .parseSignedClaims(token)
  60 + .getPayload();
  61 + }
  62 +
  63 + /**
  64 + * 检查token是否过期
  65 + */
  66 + private boolean isTokenExpired(String token) {
  67 + Date expiredDate = getClaimsFromToken(token).getExpiration();
  68 + return expiredDate.before(new Date());
  69 + }
  70 +
  71 + public Date extractExpiration(String token) {
  72 + return getClaimsFromToken(token).getExpiration();
  73 + }
  74 +
  75 + public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
  76 + final Claims claims = getClaimsFromToken(token);
  77 + return claimsResolver.apply(claims);
  78 + }
  79 +
  80 + public String generateToken(User user) {
  81 + Map<String, Object> claims = new HashMap<>();
  82 + return createToken(claims, user.getUsername());
  83 + }
  84 +
  85 + private String createToken(Map<String, Object> claims, String subject) {
  86 + return Jwts.builder()
  87 + .claims(claims)
  88 + .subject(subject)
  89 + .issuedAt(new Date(System.currentTimeMillis()))
  90 + .expiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
  91 + .signWith(getSigningKey())
  92 + .compact();
  93 + }
  94 +
  95 + public Boolean validateToken(String token, User user) {
  96 + if (user == null) {
  97 + return false;
  98 + }
  99 +
  100 + final String username = getUsernameFromToken(token);
  101 + return (username.equals(user.getUsername()) && !isTokenExpired(token));
  102 + }
  103 +
  104 + /**
  105 + * 获取过期时间(秒)
  106 + */
  107 + public Long getExpirationTimeSeconds() {
  108 + return expiration;
  109 + }
  110 +
  111 + /**
  112 + * 获取过期时间(毫秒)
  113 + */
  114 + public Long getExpirationTime() {
  115 + return expiration * 1000;
  116 + }
  117 +}
  1 +/**
  2 + * 网站模块控制器包
  3 + */
  4 +package com.aigeo.website.controller;
  1 +package com.aigeo.website.dto;
  2 +
  3 +import com.aigeo.website.entity.WebsiteProject;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +/**
  10 + * 网站项目DTO
  11 + */
  12 +@Data
  13 +@Schema(description = "网站项目数据传输对象")
  14 +public class WebsiteProjectDTO {
  15 +
  16 + @Schema(description = "项目ID")
  17 + private Integer id;
  18 +
  19 + @Schema(description = "公司ID")
  20 + private Integer companyId;
  21 +
  22 + @Schema(description = "用户ID")
  23 + private Integer userId;
  24 +
  25 + @Schema(description = "项目名称")
  26 + private String projectName;
  27 +
  28 + @Schema(description = "网站名称")
  29 + private String siteName;
  30 +
  31 + @Schema(description = "项目状态")
  32 + private WebsiteProject.ProjectStatus status;
  33 +
  34 + @Schema(description = "创建时间")
  35 + private LocalDateTime createdAt;
  36 +
  37 + @Schema(description = "最后更新时间")
  38 + private LocalDateTime updatedAt;
  39 +}
  1 +/**
  2 + * 网站模块数据传输对象包
  3 + */
  4 +package com.aigeo.website.dto;
  1 +/**
  2 + * 网站模块实体包
  3 + */
  4 +package com.aigeo.website.entity;
  1 +/**
  2 + * 网站模块数据访问包
  3 + */
  4 +package com.aigeo.website.repository;
  1 +package com.aigeo.website.service.impl;
  2 +
  3 +import com.aigeo.website.entity.WebsiteProject;
  4 +import com.aigeo.website.repository.WebsiteProjectRepository;
  5 +import com.aigeo.website.service.WebsiteProjectService;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.List;
  10 +import java.util.Optional;
  11 +
  12 +/**
  13 + * 网站项目服务实现类
  14 + */
  15 +@Service
  16 +public class WebsiteProjectServiceImpl implements WebsiteProjectService {
  17 +
  18 + @Autowired
  19 + private WebsiteProjectRepository websiteProjectRepository;
  20 +
  21 + @Override
  22 + public WebsiteProject save(WebsiteProject project) {
  23 + return websiteProjectRepository.save(project);
  24 + }
  25 +
  26 + @Override
  27 + public Optional<WebsiteProject> findById(Integer id) {
  28 + return websiteProjectRepository.findById(id);
  29 + }
  30 +
  31 + @Override
  32 + public List<WebsiteProject> findByCompanyId(Integer companyId) {
  33 + return websiteProjectRepository.findByCompanyId(companyId);
  34 + }
  35 +
  36 + @Override
  37 + public List<WebsiteProject> findByUserId(Integer userId) {
  38 + return websiteProjectRepository.findByUserId(userId);
  39 + }
  40 +
  41 + @Override
  42 + public List<WebsiteProject> findByStatus(WebsiteProject.ProjectStatus status) {
  43 + return websiteProjectRepository.findByStatus(status);
  44 + }
  45 +
  46 + @Override
  47 + public List<WebsiteProject> findByCompanyIdAndStatus(Integer companyId, WebsiteProject.ProjectStatus status) {
  48 + return websiteProjectRepository.findByCompanyIdAndStatus(companyId, status);
  49 + }
  50 +
  51 + @Override
  52 + public List<WebsiteProject> findByUserIdAndStatus(Integer userId, WebsiteProject.ProjectStatus status) {
  53 + return websiteProjectRepository.findByUserIdAndStatus(userId, status);
  54 + }
  55 +
  56 + @Override
  57 + public List<WebsiteProject> findAll() {
  58 + return websiteProjectRepository.findAll();
  59 + }
  60 +
  61 + @Override
  62 + public void deleteById(Integer id) {
  63 + websiteProjectRepository.deleteById(id);
  64 + }
  65 +}
  1 +/**
  2 + * 网站模块业务逻辑包
  3 + */
  4 +package com.aigeo.website.service;
  1 +server:
  2 + port: 8080
  3 + servlet:
  4 + context-path: /api
  5 + encoding:
  6 + charset: UTF-8
  7 + enabled: true
  8 + force: true
  9 +
  10 +spring:
  11 + profiles:
  12 + active: dev
  13 + application:
  14 + name: aigeo
  15 + main:
  16 + allow-bean-definition-overriding: true
  17 +
  18 + # 数据库配置
  19 + datasource:
  20 + driver-class-name: com.mysql.cj.jdbc.Driver
  21 + url: jdbc:mysql://115.175.226.205:33062/ai_3?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
  22 + username: root
  23 + password: sz1234567890
  24 + type: com.zaxxer.hikari.HikariDataSource
  25 + hikari:
  26 + minimum-idle: 5
  27 + maximum-pool-size: 20
  28 + auto-commit: true
  29 + idle-timeout: 30000
  30 + pool-name: HikariCP
  31 + max-lifetime: 1800000
  32 + connection-timeout: 30000
  33 + connection-test-query: SELECT 1
  34 +
  35 + # JPA配置
  36 + jpa:
  37 + hibernate:
  38 + ddl-auto: none
  39 + show-sql: false
  40 + properties:
  41 + hibernate:
  42 + dialect: org.hibernate.dialect.MySQL8Dialect
  43 + format_sql: true
  44 + use_sql_comments: false
  45 + open-in-view: false
  46 +
  47 + # Redis配置
  48 + data:
  49 + redis:
  50 + host: 115.175.226.205
  51 + port: 6379
  52 + database: 0
  53 + password: sz123321
  54 + timeout: 3000
  55 + jedis:
  56 + pool:
  57 + max-active: 20
  58 + max-wait: -1
  59 + max-idle: 10
  60 + min-idle: 0
  61 +
  62 + # JSON配置
  63 + jackson:
  64 + time-zone: GMT+8
  65 + date-format: yyyy-MM-dd HH:mm:ss
  66 + default-property-inclusion: NON_NULL
  67 + serialization:
  68 + write-dates-as-timestamps: false
  69 + write-date-timestamps-as-nanoseconds: false
  70 + deserialization:
  71 + read-date-timestamps-as-nanoseconds: false
  72 +
  73 + # 文件上传配置
  74 + servlet:
  75 + multipart:
  76 + max-file-size: 100MB
  77 + max-request-size: 100MB
  78 + enabled: true
  79 +
  80 + # 定时任务配置
  81 + quartz:
  82 + job-store-type: jdbc
  83 + jdbc:
  84 + initialize-schema: never
  85 + properties:
  86 + org:
  87 + quartz:
  88 + scheduler:
  89 + instanceName: AigeoScheduler
  90 + instanceId: AUTO
  91 + jobStore:
  92 + class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
  93 + driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
  94 + tablePrefix: QRTZ_
  95 + isClustered: false
  96 + useProperties: false
  97 + threadPool:
  98 + class: org.quartz.simpl.SimpleThreadPool
  99 + threadCount: 10
  100 + threadPriority: 5
  101 + threadsInheritContextClassLoaderOfInitializingThread: true
  102 +
  103 + # Security配置
  104 + security:
  105 + user:
  106 + name: admin
  107 + password: admin123
  108 + sql:
  109 + init:
  110 + data-locations:
  111 + mode:
  112 + schema-locations:
  113 +
  114 +# MyBatis Plus配置
  115 +mybatis-plus:
  116 + configuration:
  117 + map-underscore-to-camel-case: true
  118 + cache-enabled: false
  119 + call-setters-on-nulls: true
  120 + jdbc-type-for-null: 'null'
  121 +
  122 +# SpringDoc配置
  123 +springdoc:
  124 + swagger-ui:
  125 + path: /swagger-ui.html
  126 + tags-sorter: alpha
  127 + operations-sorter: alpha
  128 + api-docs:
  129 + path: /v3/api-docs
  130 + group-configs:
  131 + - group: 'default'
  132 + paths-to-match: '/**'
  133 + packages-to-scan: com.aigeo
  134 +
  135 +# Knife4j配置
  136 +knife4j:
  137 + enable: true
  138 + production: false
  139 + basic:
  140 + enable: false
  141 + setting:
  142 + language: zh_cn
  1 +-- 初始化数据脚本
  2 +
  3 +-- 插入默认公司
  4 +INSERT INTO ai_companies (name, status, created_at, updated_at)
  5 +VALUES ('Default Company', 'active', NOW(), NOW());
  6 +
  7 +-- 插入默认用户 (密码为 "password" 的BCrypt哈希)
  8 +INSERT INTO ai_users (company_id, username, email, password_hash, role, is_active, created_at, updated_at)
  9 +VALUES (1, 'admin', 'admin@aigeo.com', '$2a$10$wQ8vI6jJ2x6Dit4G3E0jVOvH9JqKz3Zs5r1D4r6H7a8B9c0D1e2F3g4', 'admin', 1, NOW(), NOW());
  10 +
  11 +-- 插入AI功能模块
  12 +INSERT INTO ai_features (feature_key, name, description, category, is_premium, sort_order, is_active, created_at)
  13 +VALUES
  14 +('ai_article', 'AI文章生成', '基于关键词和主题自动生成高质量文章', 'content', 0, 1, 1, NOW()),
  15 +('ai_landing_page', 'AI落地页生成', '根据业务需求自动生成营销落地页', 'marketing', 0, 2, 1, NOW()),
  16 +('ai_website', 'AI网站生成', '一键生成企业官网或电商网站', 'website', 1, 3, 1, NOW());
  17 +
  18 +-- 插入文章类型
  19 +INSERT INTO ai_article_types (name, description, is_active, created_at, updated_at)
  20 +VALUES
  21 +('产品介绍', '产品介绍类文章', 1, NOW(), NOW()),
  22 +('新闻稿', '企业新闻稿', 1, NOW(), NOW()),
  23 +('博客文章', '技术博客或行业分析文章', 1, NOW(), NOW());
  24 +
  25 +-- 插入落地页模板
  26 +INSERT INTO ai_landing_page_templates (name, code, description, is_active, created_at, updated_at)
  27 +VALUES
  28 +('单栏布局', 'single-column', '简洁的单栏布局模板', 1, NOW(), NOW()),
  29 +('两栏布局', 'two-column', '经典的两栏布局模板', 1, NOW(), NOW()),
  30 +('产品展示', 'product-showcase', '专注于产品展示的模板', 1, NOW(), NOW());
  1 +-- 数据库模式初始化脚本
  2 +
  3 +-- ====================================================================================================
  4 +-- 1) 核心:公司、用户、权限、订阅
  5 +-- ====================================================================================================
  6 +
  7 +-- 创建公司表(多租户根)
  8 +CREATE TABLE IF NOT EXISTS `ai_companies` (
  9 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '公司主键ID',
  10 + `name` VARCHAR(255) NOT NULL COMMENT '公司名称',
  11 + `domain` VARCHAR(100) DEFAULT NULL COMMENT '公司域名,用于多租户访问',
  12 + `status` ENUM('active','suspended','trial') DEFAULT 'trial' COMMENT '公司状态',
  13 + `trial_expiry_date` DATE DEFAULT NULL COMMENT '试用到期日',
  14 + `default_settings` JSON DEFAULT NULL COMMENT '企业默认设置(JSON)',
  15 + `billing_email` VARCHAR(100) DEFAULT NULL COMMENT '账单邮箱',
  16 + `contact_phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
  17 + `address` TEXT DEFAULT NULL COMMENT '公司地址',
  18 + `logo_url` VARCHAR(255) DEFAULT NULL COMMENT '公司Logo URL',
  19 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  20 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  21 + PRIMARY KEY (`id`),
  22 + UNIQUE KEY `uk_companies_domain` (`domain`)
  23 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='公司表(多租户根)';
  24 +
  25 +-- 创建用户表
  26 +CREATE TABLE IF NOT EXISTS `ai_users` (
  27 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '用户主键ID',
  28 + `company_id` INT NOT NULL COMMENT '关联公司ID',
  29 + `username` VARCHAR(50) NOT NULL COMMENT '用户名',
  30 + `email` VARCHAR(100) NOT NULL COMMENT '邮箱',
  31 + `password_hash` VARCHAR(255) NOT NULL COMMENT '密码哈希',
  32 + `full_name` VARCHAR(100) DEFAULT NULL COMMENT '用户全名',
  33 + `phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
  34 + `role` ENUM('admin','editor','viewer') DEFAULT 'editor' COMMENT '用户角色',
  35 + `avatar_url` VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
  36 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  37 + `last_login_at` TIMESTAMP NULL DEFAULT NULL COMMENT '最后登录时间',
  38 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  39 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  40 + PRIMARY KEY (`id`),
  41 + UNIQUE KEY `uk_users_username` (`username`),
  42 + UNIQUE KEY `uk_users_email` (`email`),
  43 + KEY `idx_users_company_id` (`company_id`)
  44 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
  45 +
  46 +-- 创建AI功能表
  47 +CREATE TABLE IF NOT EXISTS `ai_features` (
  48 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '功能主键ID',
  49 + `feature_key` VARCHAR(50) NOT NULL COMMENT '功能标识符',
  50 + `name` VARCHAR(100) NOT NULL COMMENT '功能名称',
  51 + `description` TEXT DEFAULT NULL COMMENT '功能描述',
  52 + `category` VARCHAR(50) DEFAULT NULL COMMENT '功能分类',
  53 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  54 + `config_schema` JSON DEFAULT NULL COMMENT '配置Schema(JSON)',
  55 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  56 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  57 + PRIMARY KEY (`id`),
  58 + UNIQUE KEY `uk_features_key` (`feature_key`)
  59 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI功能表';
  60 +
  61 +-- 创建AI配置表
  62 +CREATE TABLE IF NOT EXISTS `ai_dify_configs` (
  63 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '配置主键ID',
  64 + `company_id` INT NOT NULL COMMENT '关联公司ID',
  65 + `name` VARCHAR(100) NOT NULL COMMENT '配置名称',
  66 + `provider` ENUM('dify','openai','azure','custom') NOT NULL COMMENT 'API提供商',
  67 + `api_key` VARCHAR(255) NOT NULL COMMENT 'API密钥',
  68 + `api_endpoint` VARCHAR(255) DEFAULT NULL COMMENT 'API端点',
  69 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  70 + `model_settings` JSON DEFAULT NULL COMMENT '模型设置(JSON)',
  71 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  72 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  73 + PRIMARY KEY (`id`),
  74 + KEY `idx_dify_configs_company_id` (`company_id`)
  75 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI配置表';
  76 +
  77 +-- 创建文章生成任务表
  78 +CREATE TABLE IF NOT EXISTS `article_generation_tasks` (
  79 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务主键ID',
  80 + `company_id` INT NOT NULL COMMENT '关联公司ID',
  81 + `user_id` INT NOT NULL COMMENT '关联用户ID',
  82 + `name` VARCHAR(255) NOT NULL COMMENT '任务名称',
  83 + `topic` VARCHAR(255) DEFAULT NULL COMMENT '主题',
  84 + `keywords` TEXT DEFAULT NULL COMMENT '关键词',
  85 + `article_count` INT DEFAULT 1 COMMENT '文章数量',
  86 + `platform_type` VARCHAR(50) DEFAULT NULL COMMENT '平台类型',
  87 + `status` ENUM('pending','processing','completed','failed') DEFAULT 'pending' COMMENT '任务状态',
  88 + `settings` JSON DEFAULT NULL COMMENT '生成设置(JSON)',
  89 + `result_summary` JSON DEFAULT NULL COMMENT '结果摘要(JSON)',
  90 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  91 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  92 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
  93 + PRIMARY KEY (`id`),
  94 + KEY `idx_tasks_company_id` (`company_id`),
  95 + KEY `idx_tasks_user_id` (`user_id`),
  96 + KEY `idx_tasks_status` (`status`)
  97 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章生成任务表';
  98 +
  99 +-- 创建生成文章表
  100 +CREATE TABLE IF NOT EXISTS `generated_articles` (
  101 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章主键ID',
  102 + `task_id` INT NOT NULL COMMENT '关联任务ID',
  103 + `company_id` INT NOT NULL COMMENT '关联公司ID',
  104 + `title` VARCHAR(255) NOT NULL COMMENT '文章标题',
  105 + `content` LONGTEXT NOT NULL COMMENT '文章内容',
  106 + `status` ENUM('draft','selected','published') DEFAULT 'draft' COMMENT '文章状态',
  107 + `version` INT DEFAULT 1 COMMENT '版本号',
  108 + `is_selected` TINYINT(1) DEFAULT 0 COMMENT '是否选中',
  109 + `metadata` JSON DEFAULT NULL COMMENT '元数据(JSON)',
  110 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  111 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  112 + PRIMARY KEY (`id`),
  113 + KEY `idx_articles_task_id` (`task_id`),
  114 + KEY `idx_articles_company_id` (`company_id`),
  115 + KEY `idx_articles_status` (`status`)
  116 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='生成文章表';
  117 +
  118 +-- 创建落地页项目表
  119 +CREATE TABLE IF NOT EXISTS `landing_page_projects` (
  120 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '项目主键ID',
  121 + `company_id` INT NOT NULL COMMENT '关联公司ID',
  122 + `user_id` INT NOT NULL COMMENT '关联用户ID',
  123 + `name` VARCHAR(255) NOT NULL COMMENT '项目名称',
  124 + `description` TEXT DEFAULT NULL COMMENT '项目描述',
  125 + `industry` VARCHAR(100) DEFAULT NULL COMMENT '行业',
  126 + `status` ENUM('draft','generating','completed','published') DEFAULT 'draft' COMMENT '项目状态',
  127 + `settings` JSON DEFAULT NULL COMMENT '项目设置(JSON)',
  128 + `result_summary` JSON DEFAULT NULL COMMENT '结果摘要(JSON)',
  129 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  130 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  131 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
  132 + PRIMARY KEY (`id`),
  133 + KEY `idx_landing_projects_company_id` (`company_id`),
  134 + KEY `idx_landing_projects_user_id` (`user_id`),
  135 + KEY `idx_landing_projects_status` (`status`)
  136 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='落地页项目表';
  137 +
  138 +-- 创建网站项目表
  139 +CREATE TABLE IF NOT EXISTS `website_projects` (
  140 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '项目主键ID',
  141 + `company_id` INT NOT NULL COMMENT '关联公司ID',
  142 + `user_id` INT NOT NULL COMMENT '关联用户ID',
  143 + `name` VARCHAR(255) NOT NULL COMMENT '项目名称',
  144 + `description` TEXT DEFAULT NULL COMMENT '项目描述',
  145 + `industry` VARCHAR(100) DEFAULT NULL COMMENT '行业',
  146 + `status` ENUM('draft','generating','completed','published') DEFAULT 'draft' COMMENT '项目状态',
  147 + `settings` JSON DEFAULT NULL COMMENT '项目设置(JSON)',
  148 + `result_summary` JSON DEFAULT NULL COMMENT '结果摘要(JSON)',
  149 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  150 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  151 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
  152 + PRIMARY KEY (`id`),
  153 + KEY `idx_website_projects_company_id` (`company_id`),
  154 + KEY `idx_website_projects_user_id` (`user_id`),
  155 + KEY `idx_website_projects_status` (`status`)
  156 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='网站项目表';
  157 +
  158 +-- 创建发布平台表
  159 +CREATE TABLE IF NOT EXISTS `publishing_platforms` (
  160 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '平台主键ID',
  161 + `company_id` INT NOT NULL COMMENT '关联公司ID',
  162 + `name` VARCHAR(100) NOT NULL COMMENT '平台名称',
  163 + `type` VARCHAR(50) NOT NULL COMMENT '平台类型',
  164 + `config` JSON NOT NULL COMMENT '平台配置(JSON)',
  165 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  166 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  167 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  168 + PRIMARY KEY (`id`),
  169 + KEY `idx_platforms_company_id` (`company_id`)
  170 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='发布平台表';
  171 +
  172 +-- 创建关键词表
  173 +CREATE TABLE IF NOT EXISTS `keywords` (
  174 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '关键词主键ID',
  175 + `company_id` INT NOT NULL COMMENT '关联公司ID',
  176 + `keyword` VARCHAR(255) NOT NULL COMMENT '关键词',
  177 + `search_volume` INT DEFAULT NULL COMMENT '搜索量',
  178 + `competition` ENUM('low','medium','high') DEFAULT NULL COMMENT '竞争度',
  179 + `trend_data` JSON DEFAULT NULL COMMENT '趋势数据(JSON)',
  180 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  181 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  182 + PRIMARY KEY (`id`),
  183 + UNIQUE KEY `uk_keywords_company_keyword` (`company_id`, `keyword`),
  184 + KEY `idx_keywords_company_id` (`company_id`)
  185 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='关键词表';
  186 +
  187 +-- 创建主题表
  188 +CREATE TABLE IF NOT EXISTS `topics` (
  189 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '主题主键ID',
  190 + `company_id` INT NOT NULL COMMENT '关联公司ID',
  191 + `name` VARCHAR(255) NOT NULL COMMENT '主题名称',
  192 + `description` TEXT DEFAULT NULL COMMENT '主题描述',
  193 + `related_keywords` JSON DEFAULT NULL COMMENT '相关关键词(JSON)',
  194 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  195 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  196 + PRIMARY KEY (`id`),
  197 + KEY `idx_topics_company_id` (`company_id`)
  198 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='主题表';
  199 +
  200 +-- ====================================================================================================
  201 +-- 2) 订阅与计费系统
  202 +-- ====================================================================================================
  203 +
  204 +-- 订阅计划定义表
  205 +-- 定义可用的订阅计划及其基本属性。
  206 +CREATE TABLE `ai_subscription_plans` (
  207 + `id` INT NOT NULL AUTO_INCREMENT,
  208 + `plan_key` VARCHAR(50) NOT NULL COMMENT '计划标识符(如 free, basic, premium)',
  209 + `name` VARCHAR(100) NOT NULL COMMENT '计划显示名称',
  210 + `description` TEXT DEFAULT NULL COMMENT '计划描述',
  211 + `price_monthly` DECIMAL(10,2) DEFAULT 0.00 COMMENT '月费价格',
  212 + `price_yearly` DECIMAL(10,2) DEFAULT 0.00 COMMENT '年费价格',
  213 + `max_users` INT DEFAULT 1 COMMENT '最大用户数',
  214 + `max_storage_mb` INT DEFAULT 100 COMMENT '最大存储空间(MB)',
  215 + `max_api_calls_per_day` INT DEFAULT 1000 COMMENT '每日API调用限制',
  216 + `features` JSON DEFAULT NULL COMMENT '包含的功能列表(JSON)',
  217 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  218 + `sort_order` INT DEFAULT 0 COMMENT '排序权重',
  219 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  220 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  221 + PRIMARY KEY (`id`),
  222 + UNIQUE KEY `uk_plans_plan_key` (`plan_key`),
  223 + KEY `idx_plans_active` (`is_active`)
  224 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订阅计划定义表';
  225 +
  226 +-- 公司订阅记录表
  227 +-- 记录每个公司的订阅历史和当前状态。
  228 +CREATE TABLE `ai_company_subscriptions` (
  229 + `id` INT NOT NULL AUTO_INCREMENT,
  230 + `company_id` INT NOT NULL COMMENT '公司ID',
  231 + `plan_id` INT NOT NULL COMMENT '订阅计划ID',
  232 + `plan_key` VARCHAR(50) NOT NULL COMMENT '冗余字段:计划标识符',
  233 + `subscription_type` ENUM('monthly','yearly') DEFAULT 'monthly' COMMENT '订阅类型',
  234 + `status` ENUM('active','cancelled','expired','suspended') DEFAULT 'active' COMMENT '订阅状态',
  235 + `start_date` DATE NOT NULL COMMENT '订阅开始日期',
  236 + `end_date` DATE DEFAULT NULL COMMENT '订阅结束日期',
  237 + `next_billing_date` DATE DEFAULT NULL COMMENT '下次计费日期',
  238 + `trial_start_date` DATE DEFAULT NULL COMMENT '试用开始日期',
  239 + `trial_end_date` DATE DEFAULT NULL COMMENT '试用结束日期',
  240 + `amount` DECIMAL(10,2) DEFAULT 0.00 COMMENT '订阅金额',
  241 + `payment_method` VARCHAR(50) DEFAULT NULL COMMENT '支付方式',
  242 + `payment_status` ENUM('pending','paid','failed','refunded') DEFAULT 'pending' COMMENT '支付状态',
  243 + `auto_renew` TINYINT(1) DEFAULT 1 COMMENT '是否自动续费',
  244 + `cancel_reason` TEXT DEFAULT NULL COMMENT '取消原因',
  245 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  246 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  247 + PRIMARY KEY (`id`),
  248 + KEY `idx_subscriptions_company` (`company_id`),
  249 + KEY `idx_subscriptions_status` (`status`),
  250 + KEY `idx_subscriptions_next_billing` (`next_billing_date`),
  251 + CONSTRAINT `fk_subscription_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE,
  252 + CONSTRAINT `fk_subscription_plan` FOREIGN KEY (`plan_id`) REFERENCES `ai_subscription_plans` (`id`)
  253 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司订阅记录表';
  254 +
  255 +-- 订阅支付记录表
  256 +-- 记录具体的支付交易。
  257 +CREATE TABLE `ai_subscription_payments` (
  258 + `id` INT NOT NULL AUTO_INCREMENT,
  259 + `subscription_id` INT NOT NULL COMMENT '订阅ID',
  260 + `company_id` INT NOT NULL COMMENT '公司ID',
  261 + `amount` DECIMAL(10,2) NOT NULL COMMENT '支付金额',
  262 + `currency` VARCHAR(3) DEFAULT 'CNY' COMMENT '货币类型',
  263 + `payment_method` VARCHAR(50) DEFAULT NULL COMMENT '支付方式(如 alipay, wechat, stripe)',
  264 + `transaction_id` VARCHAR(255) DEFAULT NULL COMMENT '交易ID',
  265 + `payment_status` ENUM('pending','success','failed','refunded') DEFAULT 'pending' COMMENT '支付状态',
  266 + `payment_date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '支付时间',
  267 + `period_start` DATE DEFAULT NULL COMMENT '计费周期开始日期',
  268 + `period_end` DATE DEFAULT NULL COMMENT '计费周期结束日期',
  269 + `invoice_url` VARCHAR(500) DEFAULT NULL COMMENT '发票URL',
  270 + `failure_reason` TEXT DEFAULT NULL COMMENT '失败原因',
  271 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  272 + PRIMARY KEY (`id`),
  273 + KEY `idx_payments_company` (`company_id`),
  274 + KEY `idx_payments_subscription` (`subscription_id`),
  275 + KEY `idx_payments_status` (`payment_status`),
  276 + CONSTRAINT `fk_payment_subscription` FOREIGN KEY (`subscription_id`) REFERENCES `ai_company_subscriptions` (`id`),
  277 + CONSTRAINT `fk_payment_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`)
  278 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订阅支付记录表';
  279 +
  280 +-- ====================================================================================================
  281 +-- 3) AI功能模块与权限控制
  282 +-- ====================================================================================================
  283 +
  284 +-- AI功能模块定义表
  285 +-- 定义系统提供的所有AI功能模块。
  286 +CREATE TABLE `ai_features` (
  287 + `id` INT NOT NULL AUTO_INCREMENT,
  288 + `feature_key` VARCHAR(50) NOT NULL COMMENT '功能标识符 (如 ai_copywriting, ai_landing_page)',
  289 + `name` VARCHAR(100) NOT NULL COMMENT '功能名称',
  290 + `description` TEXT DEFAULT NULL COMMENT '功能描述',
  291 + `category` VARCHAR(50) DEFAULT NULL COMMENT '功能分类(如 content, marketing, website)',
  292 + `is_premium` TINYINT(1) DEFAULT 0 COMMENT '是否为高级功能',
  293 + `sort_order` INT DEFAULT 0 COMMENT '排序权重',
  294 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  295 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  296 + PRIMARY KEY (`id`),
  297 + UNIQUE KEY `uk_features_key` (`feature_key`),
  298 + KEY `idx_features_category` (`category`),
  299 + KEY `idx_features_active` (`is_active`)
  300 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI功能模块定义表';
  301 +
  302 +-- 订阅计划功能权限表
  303 +-- 定义每个订阅计划对各AI功能的访问权限和使用限制。
  304 +CREATE TABLE `ai_plan_features` (
  305 + `id` INT NOT NULL AUTO_INCREMENT,
  306 + `plan_id` INT NOT NULL COMMENT '订阅计划ID',
  307 + `feature_id` INT NOT NULL COMMENT '功能ID',
  308 + `is_allowed` TINYINT(1) DEFAULT 1 COMMENT '是否允许使用',
  309 + `usage_limit` INT DEFAULT NULL COMMENT '使用限制(如每月次数,NULL为无限制)',
  310 + `limit_period` ENUM('daily','monthly','yearly','total') DEFAULT 'monthly' COMMENT '限制周期',
  311 + `custom_config` JSON DEFAULT NULL COMMENT '自定义配置(JSON)',
  312 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  313 + PRIMARY KEY (`id`),
  314 + UNIQUE KEY `uk_plan_feature` (`plan_id`, `feature_id`),
  315 + CONSTRAINT `fk_planfeature_plan` FOREIGN KEY (`plan_id`) REFERENCES `ai_subscription_plans` (`id`) ON DELETE CASCADE,
  316 + CONSTRAINT `fk_planfeature_feature` FOREIGN KEY (`feature_id`) REFERENCES `ai_features` (`id`) ON DELETE CASCADE
  317 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订阅计划功能权限表';
  318 +
  319 +-- 功能使用记录表
  320 +-- 记录用户对公司功能的具体使用情况,用于计费和分析。
  321 +CREATE TABLE `ai_feature_usage` (
  322 + `id` BIGINT NOT NULL AUTO_INCREMENT,
  323 + `company_id` INT NOT NULL COMMENT '公司ID',
  324 + `user_id` INT DEFAULT NULL COMMENT '用户ID(可选)',
  325 + `feature_id` INT NOT NULL COMMENT '功能ID',
  326 + `usage_type` VARCHAR(50) DEFAULT NULL COMMENT '使用类型(如 generate, export, analyze)',
  327 + `usage_count` INT DEFAULT 1 COMMENT '使用次数',
  328 + `related_resource_id` VARCHAR(100) DEFAULT NULL COMMENT '相关资源ID(如文章ID、页面ID)',
  329 + `ip_address` VARCHAR(45) DEFAULT NULL COMMENT '客户端IP',
  330 + `user_agent` TEXT DEFAULT NULL COMMENT '用户代理',
  331 + `metadata` JSON DEFAULT NULL COMMENT '额外元数据',
  332 + `used_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '使用时间',
  333 + PRIMARY KEY (`id`),
  334 + KEY `idx_usage_company_feature` (`company_id`, `feature_id`),
  335 + KEY `idx_usage_feature_date` (`feature_id`, `used_at`),
  336 + KEY `idx_usage_user` (`user_id`),
  337 + CONSTRAINT `fk_usage_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE,
  338 + CONSTRAINT `fk_usage_feature` FOREIGN KEY (`feature_id`) REFERENCES `ai_features` (`id`)
  339 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='功能使用记录表';
  340 +
  341 +-- 功能使用统计表(用于快速查询)
  342 +-- 按日统计功能使用量,提高查询效率。
  343 +CREATE TABLE `ai_feature_usage_stats` (
  344 + `id` INT NOT NULL AUTO_INCREMENT,
  345 + `company_id` INT NOT NULL COMMENT '公司ID',
  346 + `feature_id` INT NOT NULL COMMENT '功能ID',
  347 + `stat_date` DATE NOT NULL COMMENT '统计日期',
  348 + `usage_count` INT DEFAULT 0 COMMENT '当日使用次数',
  349 + `last_used_at` TIMESTAMP DEFAULT NULL COMMENT '最后使用时间',
  350 + PRIMARY KEY (`id`),
  351 + UNIQUE KEY `uk_stats_company_feature_date` (`company_id`, `feature_id`, `stat_date`),
  352 + KEY `idx_stats_company` (`company_id`),
  353 + KEY `idx_stats_feature` (`feature_id`),
  354 + CONSTRAINT `fk_stats_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE,
  355 + CONSTRAINT `fk_stats_feature` FOREIGN KEY (`feature_id`) REFERENCES `ai_features` (`id`) ON DELETE CASCADE
  356 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='功能使用统计表';
  357 +
  358 +-- ====================================================================================================
  359 +-- 4) AI内容生成核心:模型、Prompt、文件
  360 +-- ====================================================================================================
  361 +
  362 +-- AI服务配置表
  363 +-- 存储连接到不同AI服务(如Dify, OpenAI)的配置信息。
  364 +CREATE TABLE `ai_dify_api_configs` (
  365 + `id` INT NOT NULL AUTO_INCREMENT COMMENT 'AI配置主键ID',
  366 + `company_id` INT DEFAULT NULL COMMENT '公司ID(NULL 表示共享/通用)',
  367 + `provider` ENUM('dify','openai','anthropic','google','azure_openai','other') DEFAULT 'dify' COMMENT 'AI 提供方',
  368 + `name` VARCHAR(100) NOT NULL COMMENT '配置名称(便于识别)',
  369 + `base_url` VARCHAR(255) DEFAULT NULL COMMENT 'API 基础地址(可选)',
  370 + `api_key` VARCHAR(255) DEFAULT NULL COMMENT 'API Key/Token',
  371 + `model_name` VARCHAR(100) DEFAULT NULL COMMENT '模型名称',
  372 + `temperature` DECIMAL(3,2) DEFAULT 0.70 COMMENT '默认温度值',
  373 + `top_p` DECIMAL(3,2) DEFAULT 1.00 COMMENT 'TopP 值',
  374 + `max_tokens` INT DEFAULT 2048 COMMENT '最大生成 token 数',
  375 + `request_headers` JSON DEFAULT NULL COMMENT '额外请求头(JSON)',
  376 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  377 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  378 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  379 + PRIMARY KEY (`id`),
  380 + KEY `idx_dify_company` (`company_id`)
  381 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI 服务配置(Dify/OpenAI 等)';
  382 +
  383 +-- Prompt 模板表
  384 +-- 存储可复用的Prompt模板。
  385 +CREATE TABLE `ai_prompt_templates` (
  386 + `id` INT NOT NULL AUTO_INCREMENT COMMENT 'Prompt 模板主键ID',
  387 + `company_id` INT DEFAULT NULL COMMENT '公司ID(NULL 表示系统模板)',
  388 + `name` VARCHAR(100) NOT NULL COMMENT '模板名称',
  389 + `description` VARCHAR(255) DEFAULT NULL COMMENT '模板描述',
  390 + `language` VARCHAR(20) DEFAULT 'zh' COMMENT '默认语言编码(en/zh 等)',
  391 + `content` LONGTEXT NOT NULL COMMENT '模板内容(可含变量占位符)',
  392 + `variables` JSON DEFAULT NULL COMMENT '变量说明(JSON)',
  393 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  394 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  395 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  396 + PRIMARY KEY (`id`),
  397 + KEY `idx_prompt_company` (`company_id`)
  398 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='Prompt 模板表';
  399 +
  400 +-- 上传文件表
  401 +-- 管理用户上传的文件,如知识库、图片、视频等。
  402 +CREATE TABLE `ai_uploaded_files` (
  403 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '上传文件主键ID',
  404 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  405 + `user_id` INT NOT NULL COMMENT '上传者用户ID(外键)',
  406 + `file_name` VARCHAR(255) NOT NULL COMMENT '原始文件名',
  407 + `file_path` VARCHAR(500) NOT NULL COMMENT '服务器存储路径或外部URL',
  408 + `file_type` ENUM('knowledge','image','video','document','other') NOT NULL COMMENT '文件类型',
  409 + `file_size` BIGINT DEFAULT 0 COMMENT '文件大小(字节)',
  410 + `mime_type` VARCHAR(100) DEFAULT NULL COMMENT 'MIME 类型',
  411 + `checksum` VARCHAR(64) DEFAULT NULL COMMENT '校验和(可选)',
  412 + `version` INT DEFAULT 1 COMMENT '版本号(用于版本控制)',
  413 + `status` ENUM('active','archived','deleted') DEFAULT 'active' COMMENT '文件状态',
  414 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
  415 + PRIMARY KEY (`id`),
  416 + KEY `idx_files_company` (`company_id`),
  417 + KEY `idx_files_user` (`user_id`),
  418 + CONSTRAINT `fk_file_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  419 + CONSTRAINT `fk_file_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`)
  420 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='上传文件表(知识库/媒体等)';
  421 +
  422 +-- ====================================================================================================
  423 +-- 5) AI内容生成:文章
  424 +-- ====================================================================================================
  425 +
  426 +-- 文章类型表
  427 +-- 分类文章,如产品介绍、新闻稿等。
  428 +CREATE TABLE `ai_article_types` (
  429 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章类型主键ID',
  430 + `name` VARCHAR(50) NOT NULL COMMENT '文章类型名称(如产品介绍)',
  431 + `description` VARCHAR(255) DEFAULT NULL COMMENT '类型说明',
  432 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  433 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  434 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  435 + PRIMARY KEY (`id`),
  436 + UNIQUE KEY `uk_article_types_name` (`name`)
  437 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章类型表';
  438 +
  439 +-- 文章生成配置表
  440 +-- 存储文章生成的参数和偏好设置。
  441 +CREATE TABLE `ai_article_generation_configs` (
  442 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章生成配置主键ID',
  443 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  444 + `name` VARCHAR(100) NOT NULL COMMENT '配置名称',
  445 + `platform_id` INT DEFAULT NULL COMMENT '适用发布平台ID(可选)',
  446 + `article_type_id` INT DEFAULT NULL COMMENT '文章类型ID(外键)',
  447 + `writing_language` VARCHAR(20) DEFAULT 'en' COMMENT '写作语言(en/zh)',
  448 + `remove_ai_tone` TINYINT(1) DEFAULT 1 COMMENT '是否去除AI味道',
  449 + `ai_taste_level` ENUM('colloquial','junior_high','senior_high','professional') DEFAULT 'junior_high' COMMENT 'AI风格等级',
  450 + `article_length_min` INT DEFAULT 800 COMMENT '最小长度(字符)',
  451 + `article_length_max` INT DEFAULT 1500 COMMENT '最大长度(字符)',
  452 + `auto_seo_optimization` TINYINT(1) DEFAULT 1 COMMENT '自动SEO优化',
  453 + `keyword_density_min` DECIMAL(5,2) DEFAULT 1.00 COMMENT '关键词密度下限(%)',
  454 + `keyword_density_max` DECIMAL(5,2) DEFAULT 2.00 COMMENT '关键词密度上限(%)',
  455 + `auto_geo_optimization` TINYINT(1) DEFAULT 1 COMMENT '是否自动GEO优化',
  456 + `auto_structured_data` TINYINT(1) DEFAULT 1 COMMENT '是否自动生成结构化数据',
  457 + `multi_version_count` INT DEFAULT 3 COMMENT '生成版本数量',
  458 + `extra_options` JSON DEFAULT NULL COMMENT '额外选项(JSON)',
  459 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  460 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  461 + PRIMARY KEY (`id`),
  462 + KEY `idx_config_company` (`company_id`),
  463 + KEY `idx_config_platform` (`platform_id`),
  464 + KEY `idx_config_article_type` (`article_type_id`),
  465 + CONSTRAINT `fk_config_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  466 + CONSTRAINT `fk_config_platform` FOREIGN KEY (`platform_id`) REFERENCES `ai_publishing_platforms` (`id`),
  467 + CONSTRAINT `fk_config_article_type` FOREIGN KEY (`article_type_id`) REFERENCES `ai_article_types` (`id`)
  468 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI 文章生成配置';
  469 +
  470 +-- 文章生成任务表
  471 +-- 记录每次文章生成的请求和状态。
  472 +CREATE TABLE `ai_article_generation_tasks` (
  473 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章生成任务主键ID',
  474 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  475 + `user_id` INT NOT NULL COMMENT '发起用户ID(外键)',
  476 + `config_id` INT DEFAULT NULL COMMENT '使用的生成配置ID(外键)',
  477 + `article_theme` VARCHAR(255) DEFAULT NULL COMMENT '文章主题/标题(输入)',
  478 + `topic_ids` TEXT DEFAULT NULL COMMENT '所选话题ID列表(逗号分隔)',
  479 + `reference_urls` TEXT DEFAULT NULL COMMENT '参考URL列表(逗号分隔)',
  480 + `reference_content` LONGTEXT DEFAULT NULL COMMENT '高度参考链接抓取到的内容摘要',
  481 + `status` ENUM('pending','processing','completed','failed') DEFAULT 'pending' COMMENT '任务状态',
  482 + `progress` TINYINT DEFAULT 0 COMMENT '进度(0-100)',
  483 + `error_message` TEXT DEFAULT NULL COMMENT '错误信息',
  484 + `dify_api_config_id` INT DEFAULT NULL COMMENT '调用的AI配置ID(外键)',
  485 + `prompt_template_id` INT DEFAULT NULL COMMENT '使用的 Prompt 模板 ID(外键)',
  486 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  487 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
  488 + PRIMARY KEY (`id`),
  489 + KEY `idx_task_company_status` (`company_id`,`status`),
  490 + KEY `idx_task_user` (`user_id`),
  491 + KEY `idx_task_config` (`config_id`),
  492 + CONSTRAINT `fk_task_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  493 + CONSTRAINT `fk_task_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`),
  494 + CONSTRAINT `fk_task_config` FOREIGN KEY (`config_id`) REFERENCES `ai_article_generation_configs` (`id`),
  495 + CONSTRAINT `fk_task_ai_config` FOREIGN KEY (`dify_api_config_id`) REFERENCES `ai_dify_api_configs` (`id`),
  496 + CONSTRAINT `fk_task_prompt_template` FOREIGN KEY (`prompt_template_id`) REFERENCES `ai_prompt_templates` (`id`)
  497 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章生成任务';
  498 +
  499 +-- 生成的文章表(多版本支持)
  500 +-- 存储AI生成的最终文章内容。
  501 +CREATE TABLE `ai_generated_articles` (
  502 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '生成文章主键ID',
  503 + `task_id` INT DEFAULT NULL COMMENT '来源生成任务ID(外键,可空)',
  504 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  505 + `version` INT DEFAULT 1 COMMENT '文章版本号(用于多版本)',
  506 + `title` VARCHAR(255) DEFAULT NULL COMMENT '文章标题',
  507 + `content` LONGTEXT DEFAULT NULL COMMENT '文章纯文本内容(不含HTML)',
  508 + `html_content` LONGTEXT DEFAULT NULL COMMENT '文章HTML格式内容(含排版)',
  509 + `faq_section` JSON DEFAULT NULL COMMENT 'FAQ 部分(JSON)',
  510 + `structured_data` JSON DEFAULT NULL COMMENT '结构化数据 JSON-LD(全文)',
  511 + `word_count` INT DEFAULT 0 COMMENT '文章字数统计',
  512 + `keyword_density` JSON DEFAULT NULL COMMENT '关键词密度分析结果(JSON)',
  513 + `is_selected` TINYINT(1) DEFAULT 0 COMMENT '是否被选为最终版本(1是)',
  514 + `status` ENUM('draft','approved','archived','deleted') DEFAULT 'draft' COMMENT '文章状态',
  515 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  516 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  517 + PRIMARY KEY (`id`),
  518 + KEY `idx_articles_company_status` (`company_id`,`status`),
  519 + KEY `idx_articles_task` (`task_id`),
  520 + CONSTRAINT `fk_article_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`),
  521 + CONSTRAINT `fk_article_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`)
  522 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='生成的文章表(多版本支持)';
  523 +
  524 +-- 文章FAQ列表
  525 +-- 存储文章的FAQ部分。
  526 +CREATE TABLE `ai_article_faqs` (
  527 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章 FAQ 主键ID',
  528 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  529 + `question` VARCHAR(255) NOT NULL COMMENT 'FAQ 问题',
  530 + `answer` TEXT NOT NULL COMMENT 'FAQ 回答',
  531 + PRIMARY KEY (`id`),
  532 + KEY `idx_article_faqs_article` (`article_id`),
  533 + CONSTRAINT `fk_faq_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE
  534 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章 FAQ 列表';
  535 +
  536 +-- 文章结构化数据
  537 +-- 存储文章的结构化数据(JSON-LD)。
  538 +CREATE TABLE `ai_article_structured_data` (
  539 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '结构化数据主键ID',
  540 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  541 + `json_ld` JSON NOT NULL COMMENT 'JSON-LD 结构化数据内容',
  542 + PRIMARY KEY (`id`),
  543 + KEY `idx_structured_article` (`article_id`),
  544 + CONSTRAINT `fk_structured_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE
  545 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章结构化数据(单独表便于管理/更新)';
  546 +
  547 +-- 文章媒体资源
  548 +-- 存储文章关联的图片、视频等媒体。
  549 +CREATE TABLE `ai_article_media` (
  550 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章媒体资源主键ID',
  551 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  552 + `media_type` ENUM('image','video','audio') NOT NULL COMMENT '媒体类型',
  553 + `url` VARCHAR(500) NOT NULL COMMENT '媒体链接或存储路径',
  554 + `prompt` TEXT DEFAULT NULL COMMENT '生成提示语(AI 配图时记录)',
  555 + `source_type` ENUM('ai_generated','user_provided','external') DEFAULT 'external' COMMENT '资源来源类型',
  556 + `alt_text` VARCHAR(255) DEFAULT NULL COMMENT '图片替代文本(SEO)',
  557 + `caption` VARCHAR(255) DEFAULT NULL COMMENT '图片说明/标题',
  558 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  559 + PRIMARY KEY (`id`),
  560 + KEY `idx_media_article` (`article_id`),
  561 + CONSTRAINT `fk_media_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE
  562 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章媒体资源(多张图片/多视频)';
  563 +
  564 +-- 文章多语言翻译表
  565 +-- 存储文章的不同语言版本。
  566 +CREATE TABLE `ai_article_translations` (
  567 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章翻译主键ID',
  568 + `article_id` INT NOT NULL COMMENT '原文章ID(外键)',
  569 + `language` VARCHAR(20) NOT NULL COMMENT '翻译目标语言(如 en/zh)',
  570 + `title` VARCHAR(255) DEFAULT NULL COMMENT '译文标题',
  571 + `content` LONGTEXT DEFAULT NULL COMMENT '译文纯文本内容',
  572 + `html_content` LONGTEXT DEFAULT NULL COMMENT '译文 HTML 内容',
  573 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  574 + PRIMARY KEY (`id`),
  575 + UNIQUE KEY `uk_article_language` (`article_id`,`language`),
  576 + KEY `idx_translations_article` (`article_id`),
  577 + CONSTRAINT `fk_translation_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE
  578 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章多语言翻译表';
  579 +
  580 +-- ====================================================================================================
  581 +-- 6) AI内容生成:落地页
  582 +-- ====================================================================================================
  583 +
  584 +-- 落地页模板表
  585 +-- 存储可用的落地页布局模板。
  586 +CREATE TABLE `ai_landing_page_templates` (
  587 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '落地页模板主键ID',
  588 + `name` VARCHAR(100) NOT NULL COMMENT '模板名称(如:单栏布局)',
  589 + `code` VARCHAR(50) NOT NULL COMMENT '模板代码(如:single-column)',
  590 + `description` VARCHAR(255) DEFAULT NULL COMMENT '模板描述',
  591 + `preview_image_url` VARCHAR(255) DEFAULT NULL COMMENT '预览图URL',
  592 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  593 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  594 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  595 + PRIMARY KEY (`id`),
  596 + UNIQUE KEY `uk_lp_templates_code` (`code`)
  597 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='落地页布局与设计模板';
  598 +
  599 +-- 落地页项目表
  600 +-- 代表一个完整的落地页创建流程。
  601 +CREATE TABLE `ai_landing_page_projects` (
  602 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '落地页项目主键ID',
  603 + `company_id` INT NOT NULL COMMENT '所属公司ID(外键)',
  604 + `user_id` INT NOT NULL COMMENT '创建者用户ID(外键)',
  605 + `name` VARCHAR(255) NOT NULL COMMENT '落地页项目名称(用于内部识别)',
  606 + `status` ENUM('draft','configuring','generated','published','archived') DEFAULT 'draft' COMMENT '项目状态',
  607 + `last_step_completed` INT DEFAULT 0 COMMENT '最后完成的步骤号',
  608 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  609 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  610 + PRIMARY KEY (`id`),
  611 + KEY `idx_lp_projects_company` (`company_id`),
  612 + KEY `idx_lp_projects_user` (`user_id`),
  613 + CONSTRAINT `fk_lp_project_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  614 + CONSTRAINT `fk_lp_project_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`)
  615 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI落地页构建项目表';
  616 +
  617 +-- 落地页各步骤配置数据
  618 +-- 存储落地页构建过程中用户输入的所有配置信息。
  619 +CREATE TABLE `ai_landing_page_step_configs` (
  620 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '配置主键ID',
  621 + `project_id` INT NOT NULL COMMENT '落地页项目ID(外键)',
  622 + -- Step 1: 目标用户
  623 + `target_audience_desc` TEXT COMMENT '目标用户描述',
  624 + `user_pain_points` TEXT COMMENT '用户痛点(可JSON或换行分隔)',
  625 + `user_expectations` TEXT COMMENT '用户期望结果',
  626 + `age_groups` VARCHAR(255) DEFAULT NULL COMMENT '年龄段(逗号分隔)',
  627 + `gender_preference` ENUM('male','female','balanced') DEFAULT 'balanced' COMMENT '性别倾向',
  628 + `behavior_characteristics` VARCHAR(255) DEFAULT NULL COMMENT '用户行为特征(逗号分隔)',
  629 + `decision_making_styles` VARCHAR(255) DEFAULT NULL COMMENT '用户决策方式(逗号分隔)',
  630 + -- Step 2: 落地页目标
  631 + `industry_primary` VARCHAR(100) DEFAULT NULL COMMENT '一级行业',
  632 + `industry_secondary` VARCHAR(100) DEFAULT NULL COMMENT '二级行业',
  633 + `industry_tertiary` VARCHAR(100) DEFAULT NULL COMMENT '子分类',
  634 + `marketing_goal` VARCHAR(50) DEFAULT NULL COMMENT '营销目标(lead-collection, product-sales等)',
  635 + -- Step 3: 风格与配色
  636 + `design_style` VARCHAR(50) DEFAULT NULL COMMENT '设计风格(modern, professional等)',
  637 + `primary_color` VARCHAR(20) DEFAULT NULL COMMENT '主色调',
  638 + `accent_color` VARCHAR(20) DEFAULT NULL COMMENT '辅助色调',
  639 + `template_id` INT DEFAULT NULL COMMENT '布局模板ID(外键)',
  640 + -- Step 4: 核心卖点
  641 + `unique_value_proposition` TEXT COMMENT '独特价值主张',
  642 + `core_advantages` JSON DEFAULT NULL COMMENT '核心优势列表(JSON数组)',
  643 + `primary_keyword` VARCHAR(255) DEFAULT NULL COMMENT '主要关键词',
  644 + `secondary_keywords` JSON DEFAULT NULL COMMENT '次要关键词列表(JSON数组)',
  645 + -- Step 5: 页面内容
  646 + `content_generation_type` ENUM('ai','custom','upload') DEFAULT 'ai' COMMENT '内容生成方式',
  647 + `company_name` VARCHAR(255) DEFAULT NULL COMMENT '公司全称',
  648 + `brand_name` VARCHAR(255) DEFAULT NULL COMMENT '品牌名称',
  649 + `company_description` TEXT COMMENT '公司介绍',
  650 + `video_url` VARCHAR(500) DEFAULT NULL COMMENT '视频链接',
  651 + `logo_file_id` INT DEFAULT NULL COMMENT '公司Logo文件ID(关联ai_uploaded_files)',
  652 + `contact_info` JSON DEFAULT NULL COMMENT '联系信息(地址、电话、邮箱、工作时间)',
  653 + `social_media_links` JSON DEFAULT NULL COMMENT '社交媒体链接(JSON数组)',
  654 + -- Step 6: CTA
  655 + `primary_cta_text` VARCHAR(100) DEFAULT NULL COMMENT '主要CTA按钮文案',
  656 + `secondary_cta_texts` JSON DEFAULT NULL COMMENT '次要CTA按钮文案(JSON数组)',
  657 + -- Step 7: 信任元素
  658 + `testimonials` JSON DEFAULT NULL COMMENT '客户评价/推荐语(JSON数组,含姓名、职位、内容)',
  659 + `social_proofs` JSON DEFAULT NULL COMMENT '社会证明数据(JSON数组,含标签、数值)',
  660 + -- Step 8: 表单字段
  661 + `form_fields` JSON DEFAULT NULL COMMENT '表单字段配置(JSON对象,key为字段名,value为是否启用)',
  662 + -- Step 9: 生成与部署
  663 + `page_title` VARCHAR(255) DEFAULT NULL COMMENT '页面SEO标题',
  664 + `page_description` TEXT COMMENT '页面SEO描述',
  665 + `ga_tracking_code` TEXT COMMENT '谷歌广告跟踪代码',
  666 + `deployment_method` ENUM('ftp','link') DEFAULT 'link' COMMENT '部署方式',
  667 + `deployment_config` JSON DEFAULT NULL COMMENT '部署配置(FTP信息或子域名)',
  668 + `pricing_plan` VARCHAR(50) DEFAULT NULL COMMENT '选择的套餐(basic, pro, enterprise)',
  669 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  670 + PRIMARY KEY (`id`),
  671 + UNIQUE KEY `uk_lp_config_project` (`project_id`),
  672 + CONSTRAINT `fk_lp_config_project` FOREIGN KEY (`project_id`) REFERENCES `ai_landing_page_projects` (`id`) ON DELETE CASCADE,
  673 + CONSTRAINT `fk_lp_config_template` FOREIGN KEY (`template_id`) REFERENCES `ai_landing_page_templates` (`id`)
  674 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='落地页各步骤配置数据';
  675 +
  676 +-- 落地页AI生成任务表
  677 +-- 记录落地页生成的请求和状态。
  678 +CREATE TABLE `ai_landing_page_generation_tasks` (
  679 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务主键ID',
  680 + `project_id` INT NOT NULL COMMENT '落地页项目ID (外键)',
  681 + `user_id` INT NOT NULL COMMENT '发起用户ID (外键)',
  682 + `status` ENUM('pending','processing','completed','failed') DEFAULT 'pending' COMMENT '任务状态',
  683 + `progress` TINYINT DEFAULT 0 COMMENT '进度 (0-100)',
  684 + `dify_api_config_id` INT DEFAULT NULL COMMENT '调用的AI配置ID (外键, 关联 ai_dify_api_configs)',
  685 + `prompt_template_id` INT DEFAULT NULL COMMENT '使用的Prompt模板ID (外键, 关联 ai_prompt_templates)',
  686 + `final_prompt_snapshot` LONGTEXT DEFAULT NULL COMMENT '发送给AI的最终Prompt快照(用于调试和记录)',
  687 + `error_message` TEXT DEFAULT NULL COMMENT '错误信息',
  688 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  689 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
  690 + PRIMARY KEY (`id`),
  691 + KEY `idx_lp_task_project` (`project_id`),
  692 + KEY `idx_lp_task_status` (`status`),
  693 + CONSTRAINT `fk_lp_task_project` FOREIGN KEY (`project_id`) REFERENCES `ai_landing_page_projects` (`id`),
  694 + CONSTRAINT `fk_lp_task_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`),
  695 + CONSTRAINT `fk_lp_task_ai_config` FOREIGN KEY (`dify_api_config_id`) REFERENCES `ai_dify_api_configs` (`id`),
  696 + CONSTRAINT `fk_lp_task_prompt_template` FOREIGN KEY (`prompt_template_id`) REFERENCES `ai_prompt_templates` (`id`)
  697 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='落地页AI生成任务表';
  698 +
  699 +-- 最终生成的落地页(支持多版本)
  700 +-- 存储AI生成的最终落地页HTML代码。
  701 +CREATE TABLE `ai_generated_landing_pages` (
  702 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '生成落地页主键ID',
  703 + `project_id` INT NOT NULL COMMENT '来源项目ID(外键)',
  704 + `version_code` VARCHAR(20) NOT NULL DEFAULT 'A' COMMENT '版本标识(用于A/B测试,如A, B)',
  705 + `html_content` LONGTEXT COMMENT '落地页HTML内容',
  706 + `status` ENUM('draft','final','published') DEFAULT 'draft' COMMENT '版本状态',
  707 + `publish_url` VARCHAR(500) DEFAULT NULL COMMENT '发布后的URL(使用link方式时)',
  708 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间',
  709 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  710 + PRIMARY KEY (`id`),
  711 + UNIQUE KEY `uk_lp_project_version` (`project_id`, `version_code`),
  712 + CONSTRAINT `fk_lp_page_project` FOREIGN KEY (`project_id`) REFERENCES `ai_landing_page_projects` (`id`)
  713 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='最终生成的落地页(支持多版本)';
  714 +
  715 +-- 落地页项目关联的媒体资产
  716 +-- 存储落地页项目中使用的图片、Logo等文件。
  717 +CREATE TABLE `ai_landing_page_assets` (
  718 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '资产主键ID',
  719 + `project_id` INT NOT NULL COMMENT '落地页项目ID(外键)',
  720 + `asset_type` ENUM('product_image','certification_logo','testimonial_avatar') NOT NULL COMMENT '资产类型',
  721 + `uploaded_file_id` INT NOT NULL COMMENT '上传文件ID(外键)',
  722 + `related_data` JSON DEFAULT NULL COMMENT '相关数据(如关联的产品名或评价人)',
  723 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  724 + PRIMARY KEY (`id`),
  725 + KEY `idx_lp_assets_project` (`project_id`),
  726 + CONSTRAINT `fk_lp_asset_project` FOREIGN KEY (`project_id`) REFERENCES `ai_landing_page_projects` (`id`),
  727 + CONSTRAINT `fk_lp_asset_file` FOREIGN KEY (`uploaded_file_id`) REFERENCES `ai_uploaded_files` (`id`)
  728 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='落地页项目关联的媒体资产';
  729 +
  730 +-- ====================================================================================================
  731 +-- 7) AI内容生成:网站 (简化版)
  732 +-- ====================================================================================================
  733 +
  734 +-- AI网站构建项目总表
  735 +-- 代表一个完整的网站创建流程。
  736 +CREATE TABLE `ai_website_projects` (
  737 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '网站项目主键ID',
  738 + `company_id` INT NOT NULL COMMENT '所属公司ID (外键)',
  739 + `user_id` INT NOT NULL COMMENT '创建者用户ID (外键)',
  740 + `project_name` VARCHAR(255) NOT NULL COMMENT '项目名称 (用于内部识别)',
  741 + `site_name` VARCHAR(255) DEFAULT NULL COMMENT '最终生成的网站名称',
  742 + `status` ENUM('draft','configuring','generating','completed','published') DEFAULT 'draft' COMMENT '项目状态',
  743 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  744 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  745 + PRIMARY KEY (`id`),
  746 + KEY `idx_website_projects_company` (`company_id`),
  747 + CONSTRAINT `fk_website_project_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  748 + CONSTRAINT `fk_website_project_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`)
  749 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI网站构建项目总表';
  750 +
  751 +-- AI网站构建器配置(存储站点地图)
  752 +-- 存储网站的全局配置和站点地图结构。
  753 +CREATE TABLE `ai_website_build_configs` (
  754 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '配置主键ID',
  755 + `project_id` INT NOT NULL COMMENT '网站项目ID (外键)',
  756 + `website_type` VARCHAR(50) DEFAULT NULL COMMENT '网站类型 (business, portfolio等)',
  757 + `site_identity` JSON DEFAULT NULL COMMENT '网站身份信息 (名称, 标语, 描述, 关键词)',
  758 + `design_preferences` JSON DEFAULT NULL COMMENT '设计偏好 (风格, 主色调)',
  759 + `sitemap_structure` JSON DEFAULT NULL COMMENT '站点地图结构定义 (JSON, 描述栏目层级和类型)',
  760 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
  761 + PRIMARY KEY (`id`),
  762 + UNIQUE KEY `uk_website_config_project` (`project_id`),
  763 + CONSTRAINT `fk_website_config_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`) ON DELETE CASCADE
  764 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI网站构建器配置(存储站点地图)';
  765 +
  766 +-- 网站栏目结构表 (支持无限层级)
  767 +-- 定义网站的栏目结构。
  768 +CREATE TABLE `ai_website_channels` (
  769 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '网站栏目主键ID',
  770 + `project_id` INT NOT NULL COMMENT '所属网站项目ID (外键)',
  771 + `parent_id` INT DEFAULT NULL COMMENT '父栏目ID (用于实现层级结构)',
  772 + `name` VARCHAR(100) NOT NULL COMMENT '栏目名称 (如: 公司介绍, 新闻中心)',
  773 + `path` VARCHAR(100) NOT NULL COMMENT 'URL路径 (如: /about, /news)',
  774 + `channel_type` ENUM('single_page', 'article_list', 'product_list', 'custom') NOT NULL COMMENT '栏目类型',
  775 + `display_order` INT DEFAULT 0 COMMENT '显示顺序',
  776 + `is_visible_in_nav` TINYINT(1) DEFAULT 1 COMMENT '是否在主导航中可见',
  777 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  778 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  779 + PRIMARY KEY (`id`),
  780 + KEY `idx_channel_project` (`project_id`),
  781 + KEY `idx_channel_parent` (`parent_id`),
  782 + CONSTRAINT `fk_channel_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  783 + CONSTRAINT `fk_channel_parent` FOREIGN KEY (`parent_id`) REFERENCES `ai_website_channels` (`id`)
  784 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='网站栏目结构表 (支持无限层级)';
  785 +
  786 +-- 网站文章内容表
  787 +-- 存储网站栏目下的文章内容。
  788 +CREATE TABLE `ai_website_articles` (
  789 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章主键ID',
  790 + `project_id` INT NOT NULL COMMENT '所属网站项目ID (外键)',
  791 + `channel_id` INT NOT NULL COMMENT '所属文章栏目ID (外键)',
  792 + `title` VARCHAR(255) NOT NULL COMMENT '文章标题',
  793 + `summary` TEXT DEFAULT NULL COMMENT '文章摘要',
  794 + `content` LONGTEXT COMMENT '文章主体内容 (Markdown或HTML)',
  795 + `status` ENUM('draft', 'published') DEFAULT 'draft' COMMENT '发布状态',
  796 + `published_at` TIMESTAMP NULL DEFAULT NULL COMMENT '发布时间',
  797 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  798 + PRIMARY KEY (`id`),
  799 + KEY `idx_article_project_channel` (`project_id`, `channel_id`),
  800 + CONSTRAINT `fk_website_article_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  801 + CONSTRAINT `fk_website_article_channel` FOREIGN KEY (`channel_id`) REFERENCES `ai_website_channels` (`id`)
  802 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='网站文章内容表';
  803 +
  804 +-- 网站产品内容表
  805 +-- 存储网站栏目下的产品内容。
  806 +CREATE TABLE `ai_website_products` (
  807 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '产品主键ID',
  808 + `project_id` INT NOT NULL COMMENT '所属网站项目ID (外键)',
  809 + `channel_id` INT NOT NULL COMMENT '所属产品栏目ID (外键)',
  810 + `name` VARCHAR(255) NOT NULL COMMENT '产品名称',
  811 + `description` LONGTEXT COMMENT '产品详细描述',
  812 + `specifications` JSON DEFAULT NULL COMMENT '产品规格 (JSON格式)',
  813 + `main_image_url` VARCHAR(255) DEFAULT NULL COMMENT '产品主图URL',
  814 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  815 + PRIMARY KEY (`id`),
  816 + KEY `idx_product_project_channel` (`project_id`, `channel_id`),
  817 + CONSTRAINT `fk_website_product_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  818 + CONSTRAINT `fk_website_product_channel` FOREIGN KEY (`channel_id`) REFERENCES `ai_website_channels` (`id`)
  819 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='网站产品内容表';
  820 +
  821 +-- 网站AI生成任务表(支持父子任务)
  822 +-- 记录网站生成的请求和状态。
  823 +CREATE TABLE `ai_website_generation_tasks` (
  824 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务主键ID',
  825 + `project_id` INT NOT NULL COMMENT '网站项目ID (外键)',
  826 + `user_id` INT NOT NULL COMMENT '发起用户ID (外键)',
  827 + `parent_task_id` INT DEFAULT NULL COMMENT '父任务ID (用于父子任务)',
  828 + `task_type` ENUM('full_site', 'generate_shell', 'generate_page_content', 'regenerate_channel') NOT NULL COMMENT '任务类型',
  829 + `target_id` INT DEFAULT NULL COMMENT '任务目标ID (如特定页面或栏目ID)',
  830 + `status` ENUM('pending','processing','completed','failed', 'waiting_for_children') DEFAULT 'pending' COMMENT '任务状态',
  831 + `dify_api_config_id` INT DEFAULT NULL COMMENT '调用的AI配置ID (外键)',
  832 + `config_snapshot` JSON DEFAULT NULL COMMENT '生成时刻的配置快照',
  833 + `error_message` TEXT DEFAULT NULL COMMENT '错误信息',
  834 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  835 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '完成时间',
  836 + PRIMARY KEY (`id`),
  837 + KEY `idx_website_task_project` (`project_id`),
  838 + KEY `idx_website_task_parent` (`parent_task_id`),
  839 + CONSTRAINT `fk_website_task_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  840 + CONSTRAINT `fk_website_task_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`),
  841 + CONSTRAINT `fk_website_task_parent` FOREIGN KEY (`parent_task_id`) REFERENCES `ai_website_generation_tasks` (`id`),
  842 + CONSTRAINT `fk_website_task_ai_config` FOREIGN KEY (`dify_api_config_id`) REFERENCES `ai_dify_api_configs` (`id`)
  843 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='网站AI生成任务表(支持父子任务)';
  844 +
  845 +-- 生成的网站页面(映射到栏目或内容项)
  846 +-- 存储AI生成的最终网站页面HTML代码。
  847 +CREATE TABLE `ai_generated_website_pages` (
  848 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '生成页面主键ID',
  849 + `project_id` INT NOT NULL COMMENT '来源网站项目ID (外键)',
  850 + `task_id` INT NOT NULL COMMENT '来源生成任务ID (外键)',
  851 + `pageable_type` VARCHAR(100) NOT NULL COMMENT '关联类型 (Channel, Article, Product)',
  852 + `pageable_id` INT NOT NULL COMMENT '关联类型ID',
  853 + `file_name` VARCHAR(100) NOT NULL COMMENT '文件名 (如: index.html, news/article-1.html)',
  854 + `html_content` LONGTEXT COMMENT '页面的完整HTML内容',
  855 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间',
  856 + PRIMARY KEY (`id`),
  857 + KEY `idx_pageable` (`pageable_type`, `pageable_id`),
  858 + CONSTRAINT `fk_website_page_project` FOREIGN KEY (`project_id`) REFERENCES `ai_website_projects` (`id`),
  859 + CONSTRAINT `fk_website_page_task` FOREIGN KEY (`task_id`) REFERENCES `ai_website_generation_tasks` (`id`)
  860 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='生成的网站页面(映射到栏目或内容项)';
  861 +
  862 +-- ====================================================================================================
  863 +-- 8) 内容发布系统
  864 +-- ====================================================================================================
  865 +
  866 +-- 目标网站类型表
  867 +-- 对目标平台进行大类划分。
  868 +CREATE TABLE `ai_publishing_platform_types` (
  869 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '平台类型主键ID',
  870 + `name` VARCHAR(50) NOT NULL COMMENT '平台类型名称(如 Blog, Social, Video)',
  871 + `description` VARCHAR(255) DEFAULT NULL COMMENT '类型描述',
  872 + `icon` VARCHAR(100) DEFAULT NULL COMMENT '图标标识',
  873 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  874 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  875 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  876 + PRIMARY KEY (`id`),
  877 + UNIQUE KEY `uk_platform_types_name` (`name`)
  878 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='目标网站类型表';
  879 +
  880 +-- 目标网站表
  881 +-- 定义支持发布的目标平台及其API配置模板。
  882 +CREATE TABLE `ai_publishing_platforms` (
  883 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '目标网站主键ID',
  884 + `type_id` INT NOT NULL COMMENT '目标网站类型ID(外键)',
  885 + `name` VARCHAR(100) NOT NULL COMMENT '目标网站名称(如 WordPress, LinkedIn, 小红书)',
  886 + `code` VARCHAR(50) NOT NULL COMMENT '目标网站代码(唯一)',
  887 + `description` VARCHAR(255) DEFAULT NULL COMMENT '目标网站说明',
  888 + `icon` VARCHAR(100) DEFAULT NULL COMMENT '目标网站图标',
  889 + `auth_type` ENUM('oauth','api_key','basic','custom') NOT NULL COMMENT '认证类型',
  890 + `api_config_template` JSON DEFAULT NULL COMMENT 'API 配置模板(JSON schema)',
  891 + `character_limit` INT DEFAULT NULL COMMENT '目标网站字符限制(如微博)',
  892 + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用',
  893 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  894 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  895 + PRIMARY KEY (`id`),
  896 + UNIQUE KEY `uk_platforms_code` (`code`),
  897 + KEY `idx_platforms_type` (`type_id`),
  898 + CONSTRAINT `fk_platform_type` FOREIGN KEY (`type_id`) REFERENCES `ai_publishing_platform_types` (`id`)
  899 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='目标网站表';
  900 +
  901 +-- 企业在目标网站的配置(API key/Token等)
  902 +-- 存储公司对特定平台的授权信息。
  903 +CREATE TABLE `ai_company_platform_configs` (
  904 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '企业平台配置主键ID',
  905 + `company_id` INT NOT NULL COMMENT '授权公司ID(外键)',
  906 + `platform_id` INT NOT NULL COMMENT '目标网站ID(外键)',
  907 + `config_data` JSON NOT NULL COMMENT '目标网站API配置信息(如 token、client_id)',
  908 + `account_name` VARCHAR(100) DEFAULT NULL COMMENT '企业在该目标网站的账号名/展示名',
  909 + `is_enabled` TINYINT(1) DEFAULT 1 COMMENT '是否启用该配置',
  910 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  911 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  912 + PRIMARY KEY (`id`),
  913 + UNIQUE KEY `uk_company_platform` (`company_id`,`platform_id`),
  914 + KEY `idx_company_platform_company` (`company_id`),
  915 + KEY `idx_company_platform_platform` (`platform_id`),
  916 + CONSTRAINT `fk_company_platform_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`) ON DELETE CASCADE,
  917 + CONSTRAINT `fk_company_platform_platform` FOREIGN KEY (`platform_id`) REFERENCES `ai_publishing_platforms` (`id`)
  918 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='企业在目标网站的配置(API key/Token等)';
  919 +
  920 +-- 定时发布任务表
  921 +-- 管理计划在未来某个时间点执行的发布任务。
  922 +CREATE TABLE `ai_scheduled_publishing_tasks` (
  923 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '定时发布任务主键ID',
  924 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  925 + `publishable_type` VARCHAR(50) DEFAULT 'article' COMMENT '发布内容类型(article, landing_page)',
  926 + `publishable_id` INT NOT NULL COMMENT '发布内容ID',
  927 + `article_id` INT DEFAULT NULL COMMENT '要发布的文章ID(外键) - 兼容旧版',
  928 + `platform_config_id` INT NOT NULL COMMENT '使用的企业平台配置ID(外键)',
  929 + `schedule_time` TIMESTAMP NOT NULL COMMENT '计划发布时间',
  930 + `status` ENUM('scheduled','processing','success','failed','cancelled') DEFAULT 'scheduled' COMMENT '任务状态',
  931 + `retry_count` INT DEFAULT 0 COMMENT '已重试次数',
  932 + `max_retries` INT DEFAULT 3 COMMENT '最大重试次数',
  933 + `last_attempt_at` TIMESTAMP NULL DEFAULT NULL COMMENT '最近一次尝试执行时间',
  934 + `error_message` TEXT DEFAULT NULL COMMENT '错误信息(失败时记录)',
  935 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  936 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  937 + PRIMARY KEY (`id`),
  938 + KEY `idx_schedule_company_time` (`company_id`, `schedule_time`),
  939 + KEY `idx_schedule_status` (`status`),
  940 + CONSTRAINT `fk_schedule_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  941 + CONSTRAINT `fk_schedule_platform_config` FOREIGN KEY (`platform_config_id`) REFERENCES `ai_company_platform_configs` (`id`)
  942 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时发布任务表';
  943 +
  944 +-- 目标网站发布记录表(执行日志)
  945 +-- 记录所有发布操作的执行结果。
  946 +CREATE TABLE `ai_publishing_records` (
  947 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '发布记录主键ID',
  948 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  949 + `publishable_type` VARCHAR(50) DEFAULT 'article' COMMENT '发布内容类型(article, landing_page)',
  950 + `publishable_id` INT NOT NULL COMMENT '发布内容ID',
  951 + `article_id` INT DEFAULT NULL COMMENT '文章ID(外键) - 兼容旧版',
  952 + `platform_config_id` INT NOT NULL COMMENT '目标网站配置ID(外键)',
  953 + `scheduled_task_id` INT DEFAULT NULL COMMENT '来源的定时任务ID(如果是定时发布则关联)',
  954 + `status` ENUM('scheduled','published','failed') DEFAULT 'scheduled' COMMENT '发布记录状态',
  955 + `publish_url` VARCHAR(500) DEFAULT NULL COMMENT '发布后目标网站返回的文章URL',
  956 + `external_post_id` VARCHAR(255) DEFAULT NULL COMMENT '目标网站返回的外部帖文/文章ID',
  957 + `publish_time` TIMESTAMP NULL DEFAULT NULL COMMENT '实际发布时间',
  958 + `error_message` TEXT DEFAULT NULL COMMENT '失败原因(若失败)',
  959 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
  960 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
  961 + PRIMARY KEY (`id`),
  962 + KEY `idx_publishing_company_time` (`company_id`,`publish_time`),
  963 + KEY `idx_publishing_status` (`status`),
  964 + CONSTRAINT `fk_publishing_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  965 + CONSTRAINT `fk_publishing_platform_config` FOREIGN KEY (`platform_config_id`) REFERENCES `ai_company_platform_configs` (`id`),
  966 + CONSTRAINT `fk_publishing_scheduled_task` FOREIGN KEY (`scheduled_task_id`) REFERENCES `ai_scheduled_publishing_tasks` (`id`)
  967 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='目标网站发布记录表(执行日志)';
  968 +
  969 +-- ====================================================================================================
  970 +-- 9) 关键词与话题管理 (SEO/内容规划)
  971 +-- ====================================================================================================
  972 +
  973 +-- 关键词表
  974 +-- 管理用于内容生成和SEO的关键词。
  975 +CREATE TABLE `ai_keywords` (
  976 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '关键词主键ID',
  977 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  978 + `keyword` VARCHAR(255) NOT NULL COMMENT '关键词文本',
  979 + `search_volume` INT DEFAULT 0 COMMENT '搜索量估算',
  980 + `cpc` DECIMAL(10,2) DEFAULT 0.00 COMMENT '估计每次点击成本',
  981 + `difficulty` TINYINT DEFAULT 0 COMMENT '关键词难度评分(0-100)',
  982 + `source` ENUM('baidu','google','manual','expansion','import') DEFAULT 'manual' COMMENT '来源类型',
  983 + `status` ENUM('pending','planned','generated','published') DEFAULT 'pending' COMMENT '关键词状态',
  984 + `tags` VARCHAR(255) DEFAULT NULL COMMENT '关键词标签,逗号分隔',
  985 + `usage_count` INT DEFAULT 0 COMMENT '关键词被文章使用次数',
  986 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  987 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  988 + PRIMARY KEY (`id`),
  989 + UNIQUE KEY `uk_company_keyword` (`company_id`,`keyword`),
  990 + KEY `idx_keywords_company_status` (`company_id`,`status`),
  991 + CONSTRAINT `fk_keyword_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`)
  992 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='关键词表';
  993 +
  994 +-- 关键词扩展表(保存 related_searches)
  995 +-- 存储关键词的扩展词(如相关搜索词)。
  996 +CREATE TABLE `ai_keyword_expansions` (
  997 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '关键词扩展主键ID',
  998 + `keyword_id` INT NOT NULL COMMENT '原始关键词ID(外键)',
  999 + `related_keyword` VARCHAR(255) NOT NULL COMMENT '扩展关键词(来自 related_searches 等)',
  1000 + `source` ENUM('baidu','google','import') DEFAULT 'google' COMMENT '扩展来源',
  1001 + `raw_response` JSON DEFAULT NULL COMMENT 'API 原始返回(便于溯源)',
  1002 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  1003 + PRIMARY KEY (`id`),
  1004 + KEY `idx_keyword_exp_keyword` (`keyword_id`),
  1005 + CONSTRAINT `fk_keyword_expansion_keyword` FOREIGN KEY (`keyword_id`) REFERENCES `ai_keywords` (`id`) ON DELETE CASCADE
  1006 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='关键词扩展表(保存 related_searches)';
  1007 +
  1008 +-- 话题表
  1009 +-- 管理更宽泛的内容主题。
  1010 +CREATE TABLE `ai_topics` (
  1011 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '话题主键ID',
  1012 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  1013 + `source_task_id` INT DEFAULT NULL COMMENT '来源的抓取任务ID(外键,可空)',
  1014 + `title` VARCHAR(255) NOT NULL COMMENT '话题标题',
  1015 + `description` TEXT DEFAULT NULL COMMENT '话题描述或摘要',
  1016 + `source_url` VARCHAR(500) DEFAULT NULL COMMENT '原始来源链接(可选)',
  1017 + `status` ENUM('raw','curated','rejected') DEFAULT 'raw' COMMENT '话题状态',
  1018 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  1019 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  1020 + PRIMARY KEY (`id`),
  1021 + KEY `idx_topics_company_status` (`company_id`,`status`),
  1022 + KEY `idx_topics_source_task` (`source_task_id`),
  1023 + CONSTRAINT `fk_topic_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  1024 + CONSTRAINT `fk_topic_source_task` FOREIGN KEY (`source_task_id`) REFERENCES `ai_search_sources_tasks` (`id`)
  1025 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='话题表';
  1026 +
  1027 +-- 话题与关键词的多对多映射
  1028 +-- 建立话题和关键词之间的关联。
  1029 +CREATE TABLE `ai_topic_keywords` (
  1030 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '话题-关键词关联主键ID',
  1031 + `topic_id` INT NOT NULL COMMENT '话题ID(外键)',
  1032 + `keyword_id` INT NOT NULL COMMENT '关键词ID(外键)',
  1033 + PRIMARY KEY (`id`),
  1034 + UNIQUE KEY `uk_topic_keyword` (`topic_id`,`keyword_id`),
  1035 + KEY `idx_topic_keywords_topic` (`topic_id`),
  1036 + KEY `idx_topic_keywords_keyword` (`keyword_id`),
  1037 + CONSTRAINT `fk_topic_keyword_topic` FOREIGN KEY (`topic_id`) REFERENCES `ai_topics` (`id`) ON DELETE CASCADE,
  1038 + CONSTRAINT `fk_topic_keyword_keyword` FOREIGN KEY (`keyword_id`) REFERENCES `ai_keywords` (`id`) ON DELETE CASCADE
  1039 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='话题与关键词的多对多映射';
  1040 +
  1041 +-- AI数据源配置表
  1042 +-- 配置用于抓取话题的外部数据源(如SERP API)。
  1043 +CREATE TABLE `ai_search_sources_api` (
  1044 + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '数据源配置主键ID',
  1045 + `name` VARCHAR(100) NOT NULL COMMENT '数据来源名称 (如 百度前10、Google also ask)',
  1046 + `source_type` ENUM('serp_api', 'rss', 'custom_api', 'web_scraping', 'database') NOT NULL COMMENT '来源类型',
  1047 + `api_config` JSON NOT NULL COMMENT 'API 配置',
  1048 + `result_parser` JSON NOT NULL COMMENT '结果解析规则',
  1049 + `is_active` BOOLEAN DEFAULT TRUE COMMENT '是否启用',
  1050 + `priority` TINYINT UNSIGNED DEFAULT 10 COMMENT '优先级 (1-100, 数字越小优先级越高)',
  1051 + `rate_limit` JSON COMMENT '限流配置 {"requests": 100, "period": 3600}',
  1052 + `health_check` JSON COMMENT '健康检查配置',
  1053 + `metadata` JSON COMMENT '元数据 (描述、版本等)',
  1054 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  1055 + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  1056 + PRIMARY KEY (`id`),
  1057 + INDEX `idx_source_name` (`name`),
  1058 + INDEX `idx_source_type` (`source_type`),
  1059 + INDEX `idx_source_active` (`is_active`),
  1060 + INDEX `idx_source_priority` (`priority`)
  1061 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI数据源配置表';
  1062 +
  1063 +-- AI数据源抓取任务表
  1064 +-- 记录从外部数据源抓取话题的任务。
  1065 +CREATE TABLE `ai_search_sources_tasks` (
  1066 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '话题抓取任务主键ID',
  1067 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  1068 + `user_id` INT NOT NULL COMMENT '发起任务的用户ID(外键)',
  1069 + `topic_source_id` INT NOT NULL COMMENT '话题来源配置ID(外键)',
  1070 + `keywords` TEXT DEFAULT NULL COMMENT '用于本次抓取的关键词快照(逗号分隔)',
  1071 + `topic_id` INT DEFAULT NULL COMMENT '关联的话题ID(外键,可空)',
  1072 + `keyword_id` INT DEFAULT NULL COMMENT '关联的关键词ID(外键,可空)',
  1073 + `status` ENUM('pending','processing','completed','failed') DEFAULT 'pending' COMMENT '任务状态',
  1074 + `result_count` INT DEFAULT 0 COMMENT '抓取结果数量',
  1075 + `raw_response` JSON DEFAULT NULL COMMENT 'API 原始返回(JSON)',
  1076 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  1077 + `completed_at` TIMESTAMP NULL DEFAULT NULL COMMENT '任务完成时间',
  1078 + PRIMARY KEY (`id`),
  1079 + KEY `idx_topic_fetch_company` (`company_id`),
  1080 + KEY `idx_topic_fetch_source` (`topic_source_id`),
  1081 + KEY `idx_topic_fetch_status` (`status`),
  1082 + CONSTRAINT `fk_search_task_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  1083 + CONSTRAINT `fk_search_task_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`),
  1084 + CONSTRAINT `fk_search_task_source` FOREIGN KEY (`topic_source_id`) REFERENCES `ai_search_sources_api` (`id`)
  1085 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI数据源抓取任务表';
  1086 +
  1087 +-- ====================================================================================================
  1088 +-- 10) 任务关联表 (连接生成任务与资源)
  1089 +-- ====================================================================================================
  1090 +
  1091 +-- 任务参考链接明细(可标注高度参考)
  1092 +-- 存储文章生成任务中使用的参考链接。
  1093 +CREATE TABLE `ai_task_references` (
  1094 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务参考链接主键ID',
  1095 + `task_id` INT NOT NULL COMMENT '对应任务ID(外键)',
  1096 + `url` VARCHAR(500) NOT NULL COMMENT '参考链接URL',
  1097 + `title` VARCHAR(255) DEFAULT NULL COMMENT '页面标题(可选)',
  1098 + `source_engine` VARCHAR(50) DEFAULT NULL COMMENT '来源引擎(google/baidu/zhihu等)',
  1099 + `position` INT DEFAULT NULL COMMENT '在搜索结果中的排名位置',
  1100 + `is_high_reference` TINYINT(1) DEFAULT 0 COMMENT '是否标记为高度参考(1是)',
  1101 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  1102 + PRIMARY KEY (`id`),
  1103 + KEY `idx_task_references_task` (`task_id`),
  1104 + CONSTRAINT `fk_task_reference_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`) ON DELETE CASCADE
  1105 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='任务参考链接明细(可标注高度参考)';
  1106 +
  1107 +-- 生成任务与关键词映射(规划用)
  1108 +-- 将生成任务与计划使用的关键词关联。
  1109 +CREATE TABLE `ai_task_keywords` (
  1110 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务-关键词关联主键ID',
  1111 + `task_id` INT NOT NULL COMMENT '生成任务ID(外键)',
  1112 + `keyword_id` INT NOT NULL COMMENT '关键词ID(外键)',
  1113 + `is_primary` TINYINT(1) DEFAULT 0 COMMENT '是否主关键词',
  1114 + PRIMARY KEY (`id`),
  1115 + UNIQUE KEY `uk_task_keyword` (`task_id`,`keyword_id`),
  1116 + KEY `idx_task_keywords_task` (`task_id`),
  1117 + KEY `idx_task_keywords_keyword` (`keyword_id`),
  1118 + CONSTRAINT `fk_task_keyword_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`) ON DELETE CASCADE,
  1119 + CONSTRAINT `fk_task_keyword_keyword` FOREIGN KEY (`keyword_id`) REFERENCES `ai_keywords` (`id`) ON DELETE CASCADE
  1120 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='生成任务与关键词映射(规划用)';
  1121 +
  1122 +-- 生成任务与话题映射
  1123 +-- 将生成任务与相关话题关联。
  1124 +CREATE TABLE `ai_task_topics` (
  1125 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务-话题关联主键ID',
  1126 + `task_id` INT NOT NULL COMMENT '生成任务ID(外键)',
  1127 + `topic_id` INT NOT NULL COMMENT '话题ID(外键)',
  1128 + PRIMARY KEY (`id`),
  1129 + UNIQUE KEY `uk_task_topic` (`task_id`,`topic_id`),
  1130 + KEY `idx_task_topics_task` (`task_id`),
  1131 + KEY `idx_task_topics_topic` (`topic_id`),
  1132 + CONSTRAINT `fk_task_topic_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`) ON DELETE CASCADE,
  1133 + CONSTRAINT `fk_task_topic_topic` FOREIGN KEY (`topic_id`) REFERENCES `ai_topics` (`id`) ON DELETE CASCADE
  1134 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='生成任务与话题映射';
  1135 +
  1136 +-- 任务知识库来源(公司库或上传的解析内容)
  1137 +-- 指定生成任务使用的知识来源。
  1138 +CREATE TABLE `ai_task_knowledge_sources` (
  1139 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '任务知识来源主键ID',
  1140 + `task_id` INT NOT NULL COMMENT '任务ID(外键)',
  1141 + `knowledge_type` ENUM('company','uploaded') NOT NULL COMMENT '知识来源类型(公司知识库/上传文件)',
  1142 + `knowledge_id` INT DEFAULT NULL COMMENT '当 knowledge_type=uploaded 时,关联 ai_uploaded_files.id',
  1143 + `content` LONGTEXT DEFAULT NULL COMMENT '若上传文件被解析,存解析后的文本内容',
  1144 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  1145 + PRIMARY KEY (`id`),
  1146 + KEY `idx_task_knowledge_task` (`task_id`),
  1147 + KEY `idx_task_knowledge_knowledge` (`knowledge_id`),
  1148 + CONSTRAINT `fk_task_knowledge_task` FOREIGN KEY (`task_id`) REFERENCES `ai_article_generation_tasks` (`id`) ON DELETE CASCADE,
  1149 + CONSTRAINT `fk_task_knowledge_file` FOREIGN KEY (`knowledge_id`) REFERENCES `ai_uploaded_files` (`id`)
  1150 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='任务知识库来源(公司库或上传的解析内容)';
  1151 +
  1152 +-- 文章与关键词关联(实际使用)
  1153 +-- 将最终生成的文章与实际使用的关键词关联。
  1154 +CREATE TABLE `ai_article_keywords` (
  1155 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章关键词关联主键ID',
  1156 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  1157 + `keyword_id` INT NOT NULL COMMENT '关键词ID(外键)',
  1158 + `is_primary` TINYINT(1) DEFAULT 0 COMMENT '是否主关键词(1主/0辅)',
  1159 + PRIMARY KEY (`id`),
  1160 + UNIQUE KEY `uk_article_keyword` (`article_id`,`keyword_id`),
  1161 + KEY `idx_article_keywords_article` (`article_id`),
  1162 + KEY `idx_article_keywords_keyword` (`keyword_id`),
  1163 + CONSTRAINT `fk_article_keyword_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE,
  1164 + CONSTRAINT `fk_article_keyword_keyword` FOREIGN KEY (`keyword_id`) REFERENCES `ai_keywords` (`id`) ON DELETE CASCADE
  1165 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章与关键词关联(实际使用)';
  1166 +
  1167 +-- 文章与话题关联
  1168 +-- 将最终生成的文章与相关话题关联。
  1169 +CREATE TABLE `ai_article_topics` (
  1170 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '文章话题关联主键ID',
  1171 + `article_id` INT NOT NULL COMMENT '文章ID(外键)',
  1172 + `topic_id` INT NOT NULL COMMENT '话题ID(外键)',
  1173 + PRIMARY KEY (`id`),
  1174 + UNIQUE KEY `uk_article_topic` (`article_id`,`topic_id`),
  1175 + KEY `idx_article_topics_article` (`article_id`),
  1176 + KEY `idx_article_topics_topic` (`topic_id`),
  1177 + CONSTRAINT `fk_article_topic_article` FOREIGN KEY (`article_id`) REFERENCES `ai_generated_articles` (`id`) ON DELETE CASCADE,
  1178 + CONSTRAINT `fk_article_topic_topic` FOREIGN KEY (`topic_id`) REFERENCES `ai_topics` (`id`) ON DELETE CASCADE
  1179 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章与话题关联';
  1180 +
  1181 +-- ====================================================================================================
  1182 +-- 11) 统计与活动日志
  1183 +-- ====================================================================================================
  1184 +
  1185 +-- 用户活动日志(审计/分析)
  1186 +-- 记录用户的常规活动,用于行为分析。
  1187 +CREATE TABLE `ai_user_activity_logs` (
  1188 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '用户活动日志主键ID',
  1189 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  1190 + `user_id` INT NOT NULL COMMENT '用户ID(外键)',
  1191 + `activity_type` VARCHAR(50) NOT NULL COMMENT '活动类型(如 login/create_article)',
  1192 + `activity_details` JSON DEFAULT NULL COMMENT '活动详情(JSON)',
  1193 + `ip_address` VARCHAR(45) DEFAULT NULL COMMENT '操作IP',
  1194 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  1195 + PRIMARY KEY (`id`),
  1196 + KEY `idx_activity_company_user` (`company_id`,`user_id`),
  1197 + KEY `idx_activity_type` (`activity_type`),
  1198 + CONSTRAINT `fk_activity_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`),
  1199 + CONSTRAINT `fk_activity_user` FOREIGN KEY (`user_id`) REFERENCES `ai_users` (`id`)
  1200 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户活动日志(审计/分析)';
  1201 +
  1202 +-- 公司使用统计
  1203 +-- 统计公司的资源使用情况,用于计费和容量管理。
  1204 +CREATE TABLE `ai_company_usage_stats` (
  1205 + `id` INT NOT NULL AUTO_INCREMENT COMMENT '公司用量统计主键ID',
  1206 + `company_id` INT NOT NULL COMMENT '公司ID(外键)',
  1207 + `stat_date` DATE NOT NULL COMMENT '统计日期',
  1208 + `article_count` INT DEFAULT 0 COMMENT '当日生成文章数量',
  1209 + `ai_token_usage` BIGINT DEFAULT 0 COMMENT '当日 AI 令牌使用量',
  1210 + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
  1211 + PRIMARY KEY (`id`),
  1212 + UNIQUE KEY `uk_company_date` (`company_id`,`stat_date`),
  1213 + KEY `idx_usage_company` (`company_id`),
  1214 + CONSTRAINT `fk_usage_stats_company` FOREIGN KEY (`company_id`) REFERENCES `ai_companies` (`id`)
  1215 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='公司使用统计';
  1216 +
  1217 +SET FOREIGN_KEY_CHECKS = 1;
  1 +AI落地页生成系统流程详解
  2 +1. 项目创建与初始化
  3 +
  4 +目标: 为用户启动一个新的落地页创建项目,记录其基本信息和当前所处的步骤。
  5 +涉及表:
  6 +ai_landing_page_projects: 存储项目的基本信息(名称、状态、所属公司/用户)。
  7 +ai_landing_page_step_configs: 存储项目在不同步骤中用户输入的详细配置。
  8 +处理逻辑:
  9 +用户在前端界面发起“新建落地页项目”请求。
  10 +后端服务创建一条 ai_landing_page_projects 记录,初始状态设为 draft 或 configuring,last_step_completed 设为 0。
  11 +前端引导用户逐步完成配置步骤。
  12 +2. 步骤式配置 (Step-by-Step Configuration)
  13 +
  14 +目标: 通过引导用户完成一系列配置步骤,收集生成落地页所需的核心信息。
  15 +涉及表:
  16 +ai_landing_page_step_configs: 存储所有步骤的配置数据。
  17 +步骤详解 (基于表结构推断):
  18 +Step 1: 目标用户 (Target Audience)
  19 +配置项: target_audience_desc, user_pain_points, user_expectations, age_groups, gender_preference, behavior_characteristics, decision_making_styles
  20 +目的: 明确落地页是为谁而设计。
  21 +Step 2: 落地页目标 (Page Goal)
  22 +配置项: industry_primary/secondary/tertiary, marketing_goal
  23 +目的: 确定落地页的行业背景和核心营销目的(如收集线索 lead-collection、产品销售 product-sales)。
  24 +Step 3: 风格与配色 (Style & Design)
  25 +配置项: design_style, primary_color, accent_color, template_id (关联 ai_landing_page_templates)
  26 +目的: 选择落地页的视觉风格和布局模板。
  27 +Step 4: 核心卖点 (Core Value Proposition)
  28 +配置项: unique_value_proposition, core_advantages, primary_keyword, secondary_keywords
  29 +目的: 定义落地页要传达的核心信息和优化关键词。
  30 +Step 5: 页面内容 (Content Details)
  31 +配置项: content_generation_type (AI生成 ai / 自定义 custom / 上传 upload), company_name, brand_name, company_description, video_url
  32 +目的: 决定内容来源,并提供基础信息。
  33 +Step 6-N: 其他内容模块 (如CTA、表单、信任元素等)
  34 +(表结构中未完全体现,但实际应用中通常会有)
  35 +Step 9: 生成与部署 (Generation & Deployment)
  36 +配置项: page_title, page_description, ga_tracking_code, deployment_method (ftp/link), deployment_config, pricing_plan
  37 +目的: 设置SEO信息、跟踪代码和部署方式。
  38 +处理逻辑:
  39 +每完成一个步骤,前端将该步骤的数据发送到后端。
  40 +后端服务将数据存入或更新 ai_landing_page_step_configs 表中对应的项目记录。
  41 +更新 ai_landing_page_projects 表中的 last_step_completed 字段。
  42 +当所有步骤完成(或达到可生成状态),用户可以触发生成。
  43 +3. 内容与资产准备
  44 +
  45 +目标: 根据配置,准备AI生成所需的内容提示(Prompt)和媒体资源。
  46 +涉及表:
  47 +ai_landing_page_step_configs: 获取配置信息。
  48 +ai_landing_page_assets: 获取上传的图片、Logo等。
  49 +ai_uploaded_files: 存储实际上传的文件。
  50 +处理逻辑:
  51 +Prompt构建: 根据 ai_landing_page_step_configs 中的信息(如目标用户、卖点、内容类型等)和 ai_prompt_templates(如果使用模板)构建发送给Dify的Prompt。
  52 +资产关联: 如果用户上传了图片等资源,将其存入 ai_uploaded_files,并在 ai_landing_page_assets 中记录与项目的关联关系。
  53 +4. AI生成任务创建与执行
  54 +
  55 +目标: 将准备好的信息提交给AI模型(如Dify)进行落地页内容(主要是HTML)的生成。
  56 +涉及表:
  57 +ai_landing_page_generation_tasks: 记录生成任务。
  58 +处理逻辑:
  59 +用户点击“生成落地页”按钮。
  60 +后端服务创建一条 ai_landing_page_generation_tasks 记录,关联到 ai_landing_page_projects,并记录使用的 dify_api_config_id、prompt_template_id 等。
  61 +调用Dify API: 将构建好的Prompt、配置信息、资产链接等发送给Dify API。
  62 +异步处理: 与文章生成类似,Dify处理需要时间,应采用异步方式处理任务状态更新。
  63 +5. 结果接收与存储
  64 +
  65 +目标: 接收Dify返回的生成结果(HTML代码),并存储。
  66 +涉及表:
  67 +ai_generated_landing_pages: 存储生成的落地页HTML。
  68 +处理逻辑:
  69 +通过Dify回调或主动轮询获取生成结果。
  70 +将返回的HTML内容存入 ai_generated_landing_pages 表,关联到 project_id 和生成的 version_code (支持A/B测试)。
  71 +更新 ai_landing_page_generation_tasks 的状态为 completed。
  72 +更新 ai_landing_page_projects 的状态为 generated。
  73 +6. 预览、编辑与发布
  74 +
  75 +目标: 允许用户查看生成结果,进行微调,并最终发布。
  76 +涉及表:
  77 +ai_generated_landing_pages: 提供预览内容。
  78 +ai_landing_page_projects: 更新项目状态。
  79 +ai_landing_page_step_configs: (如果允许编辑并重新生成)
  80 +处理逻辑:
  81 +预览: 前端加载 ai_generated_landing_pages 中的HTML进行展示。
  82 +编辑: (可选) 提供简单的可视化编辑器,或允许用户修改原始HTML/Markdown,保存为新版本。
  83 +发布:
  84 +根据 ai_landing_page_step_configs 中的 deployment_method 和 deployment_config 进行部署。
  85 +如果是 link 方式,生成一个可访问的链接,并存入 ai_generated_landing_pages.publish_url。
  86 +如果是 ftp 方式,则需要后端或前端(通过JS)将HTML文件上传到指定FTP服务器。
  87 +更新 ai_landing_page_projects 状态为 published。
  88 +更新 ai_generated_landing_pages 状态为 published。
  89 +落地页生成开发建议
  90 +清晰的步骤引导UI: 前端必须提供清晰、易懂的多步骤表单,引导用户完成配置。
  91 +配置数据持久化: 每一步的输入都应及时保存到 ai_landing_page_step_configs,避免用户丢失进度。
  92 +模板化Prompt: 为不同类型的落地页(如线索收集、产品销售)设计不同的Prompt模板 (ai_prompt_templates),提高生成质量。
  93 +资产管理系统: 完善文件上传、存储 (ai_uploaded_files) 和关联 (ai_landing_page_assets) 逻辑,确保AI能访问到正确的媒体资源链接。
  94 +异步任务队列: 落地页生成任务(调用Dify)应使用异步处理,通过任务ID跟踪状态,并及时更新数据库。
  95 +版本管理: 利用 ai_generated_landing_pages.version_code 实现A/B测试或多版本迭代。
  96 +部署集成: 实现 link 和 ftp 两种部署方式的后端逻辑。link 方式可能需要一个简单的静态文件服务或CDN支持;ftp 方式需要安全地处理FTP凭据。
  97 +错误处理与日志: 详细记录每个步骤和API调用的错误,方便排查问题。
  98 +SEO预设: 在 ai_landing_page_step_configs 的Step 9中预设SEO信息,并在生成HTML时正确嵌入。
  99 +数据校验: 对用户输入的各项配置进行严格校验,确保数据质量和后续流程的顺利进行。
  100 +
  101 +
  102 +AI文章生成系统开发文档
  103 +1. 模块概述
  104 +
  105 +本模块旨在自动化生成高质量文章内容。其核心流程包括:关键词扩展、话题发现、内容生成配置、调用AI模型(如Dify)生成文章、以及对生成内容进行SEO优化和结构化处理。
  106 +
  107 +2. 功能流程详解
  108 +
  109 +2.1 关键词获取与扩展
  110 +
  111 +目标: 基于初始关键词,通过搜索引擎API挖掘相关长尾词,丰富关键词库。
  112 +输入:
  113 +一个或多个初始关键词(逗号分隔,最多3个)。
  114 +企业ID (用于数据隔离和去重)。
  115 +可选:用户上传的关键词文件。
  116 +处理逻辑:
  117 +解析输入: 将逗号分隔的关键词字符串拆分为单个关键词列表。
  118 +API调用:
  119 +对列表中的每个关键词,分别调用 SearchAPI。
  120 +百度API: https://www.searchapi.io/api/v1/search?engine=baidu&q={keyword}&api_key={YOUR_API_KEY}
  121 +Google API (简化版): https://www.searchapi.io/api/v1/search?engine=google_light&q={keyword}&api_key={YOUR_API_KEY}
  122 +Google API (完整版): https://www.searchapi.io/api/v1/search?engine=google&q={keyword}&api_key={YOUR_API_KEY} (根据需求选择)
  123 +注意: 根据实际需求,可能需要设置 gl, hl, lr, domain 等参数以获取特定区域或语言的结果。
  124 +数据解析:
  125 +从API响应中提取 related_searches 字段(百度/Google Light/Google)。
  126 +数据存储:
  127 +将提取到的新关键词存入 ai_keywords 表。
  128 +使用 company_id 进行关联,并确保 keyword 字段在 company_id 范围内唯一 (uk_company_keyword)。如果关键词已存在,则忽略或更新计数(如 usage_count)。
  129 +用户交互:
  130 +界面回显: 将成功获取并存储的新关键词列表返回给前端界面展示。
  131 +文件导入: 提供接口支持用户上传符合固定格式的关键词文件,解析后批量存入 ai_keywords 表。
  132 +关键词加载: 提供接口,按企业ID (company_id) 查询 ai_keywords 表,并根据 usage_count 升序排序返回关键词列表供用户选择。
  133 +输出: 更新后的关键词库,前端展示的关键词列表。
  134 +2.2 话题发现与参考来源
  135 +
  136 +目标: 根据选中的关键词,利用不同数据源挖掘潜在的文章主题和参考链接。
  137 +输入:
  138 +用户选中的一个或多个关键词。
  139 +用户选择的“话题参考来源”配置(来自 ai_search_sources_api 表)。
  140 +处理逻辑:
  141 +配置读取: 根据用户选择,从 ai_search_sources_api 表读取对应来源的配置 (name, source_type, api_config, result_parser)。
  142 +API请求构建: 根据 api_config 中的规则(如 engine, q 的拼接方式),为每个选中的关键词构建具体的 SearchAPI 请求URL。
  143 +示例(根据您提供的配置):
  144 +百度前10: engine=baidu&q={keyword}
  145 +谷歌前10: engine=google_light&q={keyword} (或 engine=google)
  146 +谷歌 also ask: engine=google_light&q={keyword} (解析 related_questions)
  147 +百度大家还在搜: engine=baidu&q={keyword} (解析 people_also_search_for - 注意:百度API响应字段可能不同,请核实)
  148 +知乎前10: engine=baidu&q=site:zhihu.com {keyword}
  149 +维基百科: engine=google_light&q=site:wikipedia.org {keyword} (或 engine=google)
  150 +百家号前10: engine=baidu&q=site:baijiahao.baidu.com {keyword}
  151 +Quora前10: engine=google_light&q=site:quora.com {keyword} (或 engine=google)
  152 +Linkedin前10: engine=google_light&q=site:linkedin.com {keyword} (或 engine=google)
  153 +API调用与数据解析:
  154 +执行构建好的API请求。
  155 +根据 result_parser 中的规则(如提取 organic_results, related_questions, people_also_search_for),解析API响应,提取所需数据(通常是标题、链接)。
  156 +结果整合: 将所有来源返回的话题/链接进行整合,去重(基于链接),并可能根据来源权重或相关性进行排序。
  157 +输出: 一个包含潜在主题和参考链接的列表。
  158 +2.3 内容生成配置
  159 +
  160 +目标: 收集用户对于即将生成文章的具体要求和参数。
  161 +输入:
  162 +文章主题/标题: 用户手动输入或从上一步话题发现中选择。
  163 +参考链接: 用户手动输入或从上一步话题发现中选择并勾选“高度参考”。
  164 +生成参数:
  165 +写作语言 (e.g., 中文, English)
  166 +文章类型 (e.g., 产品介绍) - 关联 ai_article_types 表
  167 +适用平台 (e.g., 官网博客)
  168 +AI模型 (e.g., GPT-4, Claude) - 与Dify配置关联
  169 +文章长度 (e.g., 800-1500字符)
  170 +风格要求 (e.g., 口语化, 去除AI味)
  171 +SEO选项 (e.g., 自动优化关键词布局, 生成FAQ, 生成结构化数据)
  172 +媒体选项 (e.g., AI配图, 自主配图, 视频嵌入)
  173 +权威证明 (e.g., 自动化, 自定义)
  174 +版本数 (e.g., 生成3个不同版本)
  175 +关键词链接: 用户指定主关键词和辅关键词及其对应的内链URL。
  176 +知识库: 用户选择引用企业自有知识库或上传本地文档。
  177 +处理逻辑:
  178 +表单验证: 确保必填项已填写,参数合法。
  179 +任务记录: 将用户提交的所有配置信息、选中的关键词、主题、参考链接等存入 ai_article_generation_tasks 表。
  180 +关联关系: 在 ai_task_keywords 表中记录任务与关键词的关联关系,并标记主关键词。
  181 +知识库处理:
  182 +如果选择“引用知识库”,记录关联的知识库ID。
  183 +如果选择“参考知识库上传”,调用文件上传服务,将文档解析为文本,并将解析后的文本内容或文件ID记录在任务表的 reference_content 或通过 ai_task_knowledge_files 表关联。
  184 +Dify API配置选择:
  185 +根据是否使用自有知识库,从 ai_dify_api_configs 表中选择合适的配置 (dify_api_config_id) 关联到任务。
  186 +输出: 创建成功的任务记录 (ai_article_generation_tasks ID)。
  187 +2.4 调用AI模型生成文章 (Dify)
  188 +
  189 +目标: 将配置好的任务信息发送给Dify API,触发文章生成过程。
  190 +输入:
  191 +ai_article_generation_tasks 表中的任务记录。
  192 +从关联表中获取的关键词、主题、参考链接、生成参数等。
  193 +处理逻辑:
  194 +数据组装: 根据Dify API的要求,将任务信息、关键词、主题、参考内容、生成参数(如Prompt模板、风格、长度)等组装成API请求体。
  195 +API调用: 调用选定的Dify API配置 (ai_dify_api_configs) 对应的接口,发起文章生成请求。
  196 +异步处理: Dify处理通常需要时间,应采用异步方式。可以轮询Dify API状态,或等待Dify回调。
  197 +输出: Dify返回的生成结果(可能包含多个版本)。
  198 +2.5 结果处理与优化
  199 +
  200 +目标: 接收AI生成的原始内容,并根据配置进行后续处理和优化。
  201 +输入:
  202 +Dify返回的原始文章内容(文本和HTML)、FAQ、结构化数据等。
  203 +任务配置中的优化选项。
  204 +处理逻辑:
  205 +版本存储: 将每个生成的版本分别存入 ai_generated_articles 表,使用相同的 task_id 和不同的 version 号。
  206 +关键词关联: 在 ai_article_keywords 表中记录生成文章与实际使用关键词的关联关系。
  207 +SEO优化 (可选):
  208 +分析文章内容和关键词密度,进行微调(此步骤复杂,通常由AI模型在生成时处理,或作为后处理插件)。
  209 +FAQ处理 (可选): 将Dify生成的FAQ部分存入 ai_generated_articles 表的 faq_section 字段(JSON格式)。
  210 +结构化数据 (可选): 将Dify生成的JSON-LD结构化数据存入 ai_generated_articles 表的 structured_data 字段,或存入单独的 ai_article_structured_data 表。
  211 +媒体处理 (可选):
  212 +AI配图: 根据文章内容或用户提示,调用如 Pollinations 等API生成图片,并将图片信息存入 ai_article_media 表。
  213 +自主配图/视频: 记录用户提供的图片/视频URL和描述信息到 ai_article_media 表。
  214 +权威证明 (可选): 根据配置生成 sameAs 等信息(可能需要嵌入到结构化数据中)。
  215 +输出: 存储在数据库中的、经过处理和优化的多个文章版本。
  216 +3. 数据库表关系梳理 (基于提供内容)
  217 +ai_keywords: 存储关键词及其元数据。
  218 +ai_search_sources_api: 存储话题来源的API配置。
  219 +ai_article_generation_tasks: 存储每次生成任务的配置和状态。
  220 +ai_task_keywords: 任务与关键词的规划关联。
  221 +ai_generated_articles: 存储AI生成的最终文章内容(多版本)。
  222 +ai_article_keywords: 文章与实际使用关键词的关联。
  223 +ai_article_topics: 文章与话题的关联(如果需要)。
  224 +ai_article_media: 文章关联的图片、视频等媒体资源。
  225 +ai_task_knowledge_files: 任务使用的上传知识库文件。
  226 +ai_dify_api_configs: Dify API的配置信息。
  227 +ai_prompt_templates: Prompt模板(如果使用)。
  228 +ai_article_types: 文章类型定义。
  229 +ai_article_structured_data: (可选) 文章结构化数据单独存储。
  230 +4. 开发建议
  231 +模块化设计:
  232 +将流程拆分为独立的服务或函数:关键词扩展服务、话题发现服务、任务创建服务、Dify调用服务、结果处理服务。
  233 +异步处理:
  234 +话题发现、Dify调用、结果优化等耗时操作应使用消息队列(如 RabbitMQ, Redis Queue)或异步任务框架(如 Celery)处理,避免阻塞用户界面。
  235 +API调用容错:
  236 +对SearchAPI和Dify API的调用实现重试机制和错误处理(如超时、API限制、返回错误码)。
  237 +配置管理:
  238 +将API Key (vAvKvXELE3fyDCp6ZXhTGd9e)、Dify配置、SearchAPI参数等敏感信息和可变配置存放在环境变量或配置中心,而非硬编码。
  239 +缓存策略:
  240 +对于频繁请求的相同关键词的 related_searches 结果,可以考虑使用缓存(如 Redis)来减少API调用次数和成本。
  241 +前端交互:
  242 +提供清晰的步骤引导用户完成流程。
  243 +实现关键词加载、话题列表展示、任务配置表单、生成进度提示、多版本结果展示等功能。
  244 +日志记录:
  245 +详细记录每个步骤的操作日志和API调用日志,便于调试和问题追踪。
  246 +权限控制:
  247 +确保用户只能访问和操作其所属企业的数据(基于 company_id)。
  248 +数据校验与清洗:
  249 +对从API获取的数据(如关键词、链接、文章内容)进行必要的清洗和校验,防止注入攻击或无效数据。
  250 +SEO优化深度:
  251 +基础的关键词密度检查可以实现,但复杂的SEO优化(如语义分析、可读性评分)建议依赖AI模型能力或集成专业SEO工具。
  252 +
  253 +
  1 +# AIGEO 内容生成平台 API 接口文档
  2 +
  3 +本文档为 Vue3 前端开发工程师提供完整的 API 接口说明,包括接口地址、请求方法、参数说明和返回结果等。
  4 +
  5 +## 1. 认证相关接口
  6 +
  7 +### 1.1 用户登录
  8 +**接口地址**: `POST /api/auth/login`
  9 +**接口说明**: 用户登录认证
  10 +
  11 +**请求参数**:
  12 +```json
  13 +{
  14 + "username": "string", // 用户名或邮箱
  15 + "password": "string" // 密码
  16 +}
  17 +```
  18 +
  19 +**响应结果**:
  20 +```json
  21 +{
  22 + "code": 200,
  23 + "message": "操作成功",
  24 + "data": {
  25 + "token": "string", // 访问令牌
  26 + "refreshToken": "string", // 刷新令牌
  27 + "expiresIn": 0, // 过期时间(秒)
  28 + "user": {
  29 + "id": 0,
  30 + "username": "string",
  31 + "email": "string",
  32 + "fullName": "string",
  33 + "role": "string"
  34 + }
  35 + }
  36 +}
  37 +```
  38 +
  39 +### 1.2 用户注册
  40 +**接口地址**: `POST /api/auth/register`
  41 +**接口说明**: 新用户注册
  42 +
  43 +**请求参数**:
  44 +```json
  45 +{
  46 + "username": "string", // 用户名
  47 + "email": "string", // 邮箱
  48 + "password": "string", // 密码
  49 + "fullName": "string" // 姓名
  50 +}
  51 +```
  52 +
  53 +**响应结果**:
  54 +```json
  55 +{
  56 + "code": 200,
  57 + "message": "操作成功",
  58 + "data": {
  59 + "id": 0,
  60 + "username": "string",
  61 + "email": "string",
  62 + "fullName": "string",
  63 + "role": "string",
  64 + "companyId": 0
  65 + }
  66 +}
  67 +```
  68 +
  69 +### 1.3 刷新令牌
  70 +**接口地址**: `POST /api/auth/refresh`
  71 +**接口说明**: 使用刷新令牌获取新的访问令牌
  72 +
  73 +**请求参数**:
  74 +```json
  75 +{
  76 + "refreshToken": "string"
  77 +}
  78 +```
  79 +
  80 +**响应结果**:
  81 +```json
  82 +{
  83 + "code": 200,
  84 + "message": "操作成功",
  85 + "data": {
  86 + "token": "string",
  87 + "refreshToken": "string",
  88 + "expiresIn": 0
  89 + }
  90 +}
  91 +```
  92 +
  93 +## 2. 用户管理接口
  94 +
  95 +### 2.1 分页查询用户列表
  96 +**接口地址**: `GET /api/users/list`
  97 +**接口说明**: 支持按用户名、邮箱、角色等条件分页查询用户列表
  98 +
  99 +**请求参数**:
  100 +| 参数名 | 类型 | 必填 | 说明 |
  101 +| --- | --- | --- | --- |
  102 +| page | integer | 否 | 页码(默认0) |
  103 +| size | integer | 否 | 每页大小(默认10) |
  104 +| sortBy | string | 否 | 排序字段 |
  105 +| sortDir | string | 否 | 排序方向(asc/desc) |
  106 +| username | string | 否 | 用户名筛选 |
  107 +| email | string | 否 | 邮箱筛选 |
  108 +| role | string | 否 | 角色筛选 |
  109 +
  110 +**响应结果**:
  111 +```json
  112 +{
  113 + "code": 200,
  114 + "message": "操作成功",
  115 + "data": {
  116 + "content": [
  117 + {
  118 + "id": 0,
  119 + "username": "string",
  120 + "email": "string",
  121 + "fullName": "string",
  122 + "role": "string",
  123 + "companyId": 0,
  124 + "isActive": true,
  125 + "createdAt": "2023-01-01T00:00:00",
  126 + "updatedAt": "2023-01-01T00:00:00"
  127 + }
  128 + ],
  129 + "pageable": {
  130 + "pageNumber": 0,
  131 + "pageSize": 10,
  132 + "sort": {
  133 + "sorted": true,
  134 + "unsorted": false,
  135 + "empty": false
  136 + },
  137 + "offset": 0,
  138 + "paged": true,
  139 + "unpaged": false
  140 + },
  141 + "totalElements": 0,
  142 + "totalPages": 0,
  143 + "last": true,
  144 + "size": 10,
  145 + "number": 0,
  146 + "sort": {
  147 + "sorted": true,
  148 + "unsorted": false,
  149 + "empty": false
  150 + },
  151 + "first": true,
  152 + "numberOfElements": 0,
  153 + "empty": false
  154 + }
  155 +}
  156 +```
  157 +
  158 +### 2.2 获取所有用户
  159 +**接口地址**: `GET /api/users`
  160 +**接口说明**: 获取所有用户列表
  161 +
  162 +**响应结果**:
  163 +```json
  164 +{
  165 + "code": 200,
  166 + "message": "操作成功",
  167 + "data": [
  168 + {
  169 + "id": 0,
  170 + "username": "string",
  171 + "email": "string",
  172 + "fullName": "string",
  173 + "role": "string",
  174 + "companyId": 0,
  175 + "isActive": true,
  176 + "createdAt": "2023-01-01T00:00:00",
  177 + "updatedAt": "2023-01-01T00:00:00"
  178 + }
  179 + ]
  180 +}
  181 +```
  182 +
  183 +### 2.3 根据ID获取用户
  184 +**接口地址**: `GET /api/users/{id}`
  185 +**接口说明**: 根据用户ID获取用户详情
  186 +
  187 +**响应结果**:
  188 +```json
  189 +{
  190 + "code": 200,
  191 + "message": "操作成功",
  192 + "data": {
  193 + "id": 0,
  194 + "username": "string",
  195 + "email": "string",
  196 + "fullName": "string",
  197 + "role": "string",
  198 + "companyId": 0,
  199 + "isActive": true,
  200 + "createdAt": "2023-01-01T00:00:00",
  201 + "updatedAt": "2023-01-01T00:00:00"
  202 + }
  203 +}
  204 +```
  205 +
  206 +### 2.4 根据公司ID获取用户列表
  207 +**接口地址**: `GET /api/users/company/{companyId}`
  208 +**接口说明**: 根据公司ID获取用户列表
  209 +
  210 +**响应结果**:
  211 +```json
  212 +{
  213 + "code": 200,
  214 + "message": "操作成功",
  215 + "data": [
  216 + {
  217 + "id": 0,
  218 + "username": "string",
  219 + "email": "string",
  220 + "fullName": "string",
  221 + "role": "string",
  222 + "companyId": 0,
  223 + "isActive": true,
  224 + "createdAt": "2023-01-01T00:00:00",
  225 + "updatedAt": "2023-01-01T00:00:00"
  226 + }
  227 + ]
  228 +}
  229 +```
  230 +
  231 +### 2.5 创建用户
  232 +**接口地址**: `POST /api/users`
  233 +**接口说明**: 创建新用户
  234 +
  235 +**请求参数**:
  236 +```json
  237 +{
  238 + "username": "string",
  239 + "email": "string",
  240 + "fullName": "string",
  241 + "role": "string",
  242 + "companyId": 0,
  243 + "isActive": true
  244 +}
  245 +```
  246 +
  247 +**响应结果**:
  248 +```json
  249 +{
  250 + "code": 200,
  251 + "message": "操作成功",
  252 + "data": {
  253 + "id": 0,
  254 + "username": "string",
  255 + "email": "string",
  256 + "fullName": "string",
  257 + "role": "string",
  258 + "companyId": 0,
  259 + "isActive": true,
  260 + "createdAt": "2023-01-01T00:00:00",
  261 + "updatedAt": "2023-01-01T00:00:00"
  262 + }
  263 +}
  264 +```
  265 +
  266 +### 2.6 更新用户
  267 +**接口地址**: `PUT /api/users/{id}`
  268 +**接口说明**: 根据用户ID更新用户信息
  269 +
  270 +**请求参数**:
  271 +```json
  272 +{
  273 + "username": "string",
  274 + "email": "string",
  275 + "fullName": "string",
  276 + "role": "string",
  277 + "companyId": 0,
  278 + "isActive": true
  279 +}
  280 +```
  281 +
  282 +**响应结果**:
  283 +```json
  284 +{
  285 + "code": 200,
  286 + "message": "操作成功",
  287 + "data": {
  288 + "id": 0,
  289 + "username": "string",
  290 + "email": "string",
  291 + "fullName": "string",
  292 + "role": "string",
  293 + "companyId": 0,
  294 + "isActive": true,
  295 + "createdAt": "2023-01-01T00:00:00",
  296 + "updatedAt": "2023-01-01T00:00:00"
  297 + }
  298 +}
  299 +```
  300 +
  301 +### 2.7 删除用户
  302 +**接口地址**: `DELETE /api/users/{id}`
  303 +**接口说明**: 根据用户ID删除用户
  304 +
  305 +**响应结果**:
  306 +```json
  307 +{
  308 + "code": 200,
  309 + "message": "操作成功",
  310 + "data": null
  311 +}
  312 +```
  313 +
  314 +## 3. 公司管理接口
  315 +
  316 +### 3.1 分页查询公司列表
  317 +**接口地址**: `GET /api/companies/list`
  318 +**接口说明**: 支持按公司名称、状态等条件分页查询公司列表
  319 +
  320 +**请求参数**:
  321 +| 参数名 | 类型 | 必填 | 说明 |
  322 +| --- | --- | --- | --- |
  323 +| page | integer | 否 | 页码(默认0) |
  324 +| size | integer | 否 | 每页大小(默认10) |
  325 +| sortBy | string | 否 | 排序字段 |
  326 +| sortDir | string | 否 | 排序方向(asc/desc) |
  327 +| name | string | 否 | 公司名称筛选 |
  328 +| status | string | 否 | 状态筛选 |
  329 +
  330 +**响应结果**:
  331 +```json
  332 +{
  333 + "code": 200,
  334 + "message": "操作成功",
  335 + "data": {
  336 + "content": [
  337 + {
  338 + "id": 0,
  339 + "name": "string",
  340 + "subdomain": "string",
  341 + "planType": "string",
  342 + "maxUsers": 0,
  343 + "maxArticlesPerMonth": 0,
  344 + "status": "string",
  345 + "trialExpiryDate": "2023-01-01T00:00:00",
  346 + "defaultSettings": {},
  347 + "createdAt": "2023-01-01T00:00:00",
  348 + "updatedAt": "2023-01-01T00:00:00"
  349 + }
  350 + ],
  351 + "pageable": {
  352 + "pageNumber": 0,
  353 + "pageSize": 10,
  354 + "sort": {
  355 + "sorted": true,
  356 + "unsorted": false,
  357 + "empty": false
  358 + },
  359 + "offset": 0,
  360 + "paged": true,
  361 + "unpaged": false
  362 + },
  363 + "totalElements": 0,
  364 + "totalPages": 0,
  365 + "last": true,
  366 + "size": 10,
  367 + "number": 0,
  368 + "sort": {
  369 + "sorted": true,
  370 + "unsorted": false,
  371 + "empty": false
  372 + },
  373 + "first": true,
  374 + "numberOfElements": 0,
  375 + "empty": false
  376 + }
  377 +}
  378 +```
  379 +
  380 +### 3.2 获取所有公司
  381 +**接口地址**: `GET /api/companies`
  382 +**接口说明**: 获取所有公司列表
  383 +
  384 +**响应结果**:
  385 +```json
  386 +{
  387 + "code": 200,
  388 + "message": "操作成功",
  389 + "data": [
  390 + {
  391 + "id": 0,
  392 + "name": "string",
  393 + "subdomain": "string",
  394 + "planType": "string",
  395 + "maxUsers": 0,
  396 + "maxArticlesPerMonth": 0,
  397 + "status": "string",
  398 + "trialExpiryDate": "2023-01-01T00:00:00",
  399 + "defaultSettings": {},
  400 + "createdAt": "2023-01-01T00:00:00",
  401 + "updatedAt": "2023-01-01T00:00:00"
  402 + }
  403 + ]
  404 +}
  405 +```
  406 +
  407 +### 3.3 根据ID获取公司
  408 +**接口地址**: `GET /api/companies/{id}`
  409 +**接口说明**: 根据公司ID获取公司详情
  410 +
  411 +**响应结果**:
  412 +```json
  413 +{
  414 + "code": 200,
  415 + "message": "操作成功",
  416 + "data": {
  417 + "id": 0,
  418 + "name": "string",
  419 + "subdomain": "string",
  420 + "planType": "string",
  421 + "maxUsers": 0,
  422 + "maxArticlesPerMonth": 0,
  423 + "status": "string",
  424 + "trialExpiryDate": "2023-01-01T00:00:00",
  425 + "defaultSettings": {},
  426 + "createdAt": "2023-01-01T00:00:00",
  427 + "updatedAt": "2023-01-01T00:00:00"
  428 + }
  429 +}
  430 +```
  431 +
  432 +### 3.4 创建公司
  433 +**接口地址**: `POST /api/companies`
  434 +**接口说明**: 创建新公司
  435 +
  436 +**请求参数**:
  437 +```json
  438 +{
  439 + "name": "string",
  440 + "subdomain": "string",
  441 + "planType": "string",
  442 + "maxUsers": 0,
  443 + "maxArticlesPerMonth": 0,
  444 + "status": "string",
  445 + "trialExpiryDate": "2023-01-01T00:00:00",
  446 + "defaultSettings": {}
  447 +}
  448 +```
  449 +
  450 +**响应结果**:
  451 +```json
  452 +{
  453 + "code": 200,
  454 + "message": "操作成功",
  455 + "data": {
  456 + "id": 0,
  457 + "name": "string",
  458 + "subdomain": "string",
  459 + "planType": "string",
  460 + "maxUsers": 0,
  461 + "maxArticlesPerMonth": 0,
  462 + "status": "string",
  463 + "trialExpiryDate": "2023-01-01T00:00:00",
  464 + "defaultSettings": {},
  465 + "createdAt": "2023-01-01T00:00:00",
  466 + "updatedAt": "2023-01-01T00:00:00"
  467 + }
  468 +}
  469 +```
  470 +
  471 +### 3.5 更新公司
  472 +**接口地址**: `PUT /api/companies/{id}`
  473 +**接口说明**: 根据公司ID更新公司信息
  474 +
  475 +**请求参数**:
  476 +```json
  477 +{
  478 + "name": "string",
  479 + "subdomain": "string",
  480 + "planType": "string",
  481 + "maxUsers": 0,
  482 + "maxArticlesPerMonth": 0,
  483 + "status": "string",
  484 + "trialExpiryDate": "2023-01-01T00:00:00",
  485 + "defaultSettings": {}
  486 +}
  487 +```
  488 +
  489 +**响应结果**:
  490 +```json
  491 +{
  492 + "code": 200,
  493 + "message": "操作成功",
  494 + "data": {
  495 + "id": 0,
  496 + "name": "string",
  497 + "subdomain": "string",
  498 + "planType": "string",
  499 + "maxUsers": 0,
  500 + "maxArticlesPerMonth": 0,
  501 + "status": "string",
  502 + "trialExpiryDate": "2023-01-01T00:00:00",
  503 + "defaultSettings": {},
  504 + "createdAt": "2023-01-01T00:00:00",
  505 + "updatedAt": "2023-01-01T00:00:00"
  506 + }
  507 +}
  508 +```
  509 +
  510 +### 3.6 删除公司
  511 +**接口地址**: `DELETE /api/companies/{id}`
  512 +**接口说明**: 根据公司ID删除公司
  513 +
  514 +**响应结果**:
  515 +```json
  516 +{
  517 + "code": 200,
  518 + "message": "操作成功",
  519 + "data": null
  520 +}
  521 +```
  522 +
  523 +## 4. AI配置管理接口
  524 +
  525 +### 4.1 获取所有AI配置
  526 +**接口地址**: `GET /api/ai-configs`
  527 +**接口说明**: 获取所有AI配置列表
  528 +
  529 +**响应结果**:
  530 +```json
  531 +{
  532 + "code": 200,
  533 + "message": "操作成功",
  534 + "data": [
  535 + {
  536 + "id": 0,
  537 + "companyId": 0,
  538 + "provider": "string",
  539 + "name": "string",
  540 + "baseUrl": "string",
  541 + "apiKey": "string",
  542 + "modelName": "string",
  543 + "temperature": 0,
  544 + "topP": 0,
  545 + "maxTokens": 0,
  546 + "requestHeaders": {},
  547 + "isActive": true,
  548 + "isShared": true,
  549 + "createdAt": "2023-01-01T00:00:00",
  550 + "updatedAt": "2023-01-01T00:00:00"
  551 + }
  552 + ]
  553 +}
  554 +```
  555 +
  556 +### 4.2 分页查询AI配置
  557 +**接口地址**: `GET /api/ai-configs/list`
  558 +**接口说明**: 分页查询AI配置列表
  559 +
  560 +**请求参数**:
  561 +| 参数名 | 类型 | 必填 | 说明 |
  562 +| --- | --- | --- | --- |
  563 +| page | integer | 否 | 页码(默认0) |
  564 +| size | integer | 否 | 每页大小(默认10) |
  565 +
  566 +**响应结果**:
  567 +```json
  568 +{
  569 + "code": 200,
  570 + "message": "操作成功",
  571 + "data": {
  572 + "content": [
  573 + {
  574 + "id": 0,
  575 + "companyId": 0,
  576 + "provider": "string",
  577 + "name": "string",
  578 + "baseUrl": "string",
  579 + "apiKey": "string",
  580 + "modelName": "string",
  581 + "temperature": 0,
  582 + "topP": 0,
  583 + "maxTokens": 0,
  584 + "requestHeaders": {},
  585 + "isActive": true,
  586 + "isShared": true,
  587 + "createdAt": "2023-01-01T00:00:00",
  588 + "updatedAt": "2023-01-01T00:00:00"
  589 + }
  590 + ],
  591 + "pageable": {
  592 + "pageNumber": 0,
  593 + "pageSize": 10,
  594 + "sort": {
  595 + "sorted": true,
  596 + "unsorted": false,
  597 + "empty": false
  598 + },
  599 + "offset": 0,
  600 + "paged": true,
  601 + "unpaged": false
  602 + },
  603 + "totalElements": 0,
  604 + "totalPages": 0,
  605 + "last": true,
  606 + "size": 10,
  607 + "number": 0,
  608 + "sort": {
  609 + "sorted": true,
  610 + "unsorted": false,
  611 + "empty": false
  612 + },
  613 + "first": true,
  614 + "numberOfElements": 0,
  615 + "empty": false
  616 + }
  617 +}
  618 +```
  619 +
  620 +### 4.3 根据ID获取AI配置
  621 +**接口地址**: `GET /api/ai-configs/{id}`
  622 +**接口说明**: 根据配置ID获取AI配置详情
  623 +
  624 +**响应结果**:
  625 +```json
  626 +{
  627 + "code": 200,
  628 + "message": "操作成功",
  629 + "data": {
  630 + "id": 0,
  631 + "companyId": 0,
  632 + "provider": "string",
  633 + "name": "string",
  634 + "baseUrl": "string",
  635 + "apiKey": "string",
  636 + "modelName": "string",
  637 + "temperature": 0,
  638 + "topP": 0,
  639 + "maxTokens": 0,
  640 + "requestHeaders": {},
  641 + "isActive": true,
  642 + "isShared": true,
  643 + "createdAt": "2023-01-01T00:00:00",
  644 + "updatedAt": "2023-01-01T00:00:00"
  645 + }
  646 +}
  647 +```
  648 +
  649 +### 4.4 根据公司ID获取AI配置
  650 +**接口地址**: `GET /api/ai-configs/company/{companyId}`
  651 +**接口说明**: 根据公司ID获取AI配置列表
  652 +
  653 +**响应结果**:
  654 +```json
  655 +{
  656 + "code": 200,
  657 + "message": "操作成功",
  658 + "data": [
  659 + {
  660 + "id": 0,
  661 + "companyId": 0,
  662 + "provider": "string",
  663 + "name": "string",
  664 + "baseUrl": "string",
  665 + "apiKey": "string",
  666 + "modelName": "string",
  667 + "temperature": 0,
  668 + "topP": 0,
  669 + "maxTokens": 0,
  670 + "requestHeaders": {},
  671 + "isActive": true,
  672 + "isShared": true,
  673 + "createdAt": "2023-01-01T00:00:00",
  674 + "updatedAt": "2023-01-01T00:00:00"
  675 + }
  676 + ]
  677 +}
  678 +```
  679 +
  680 +### 4.5 获取共享的AI配置
  681 +**接口地址**: `GET /api/ai-configs/shared`
  682 +**接口说明**: 获取共享的AI配置列表
  683 +
  684 +**响应结果**:
  685 +```json
  686 +{
  687 + "code": 200,
  688 + "message": "操作成功",
  689 + "data": [
  690 + {
  691 + "id": 0,
  692 + "companyId": 0,
  693 + "provider": "string",
  694 + "name": "string",
  695 + "baseUrl": "string",
  696 + "apiKey": "string",
  697 + "modelName": "string",
  698 + "temperature": 0,
  699 + "topP": 0,
  700 + "maxTokens": 0,
  701 + "requestHeaders": {},
  702 + "isActive": true,
  703 + "isShared": true,
  704 + "createdAt": "2023-01-01T00:00:00",
  705 + "updatedAt": "2023-01-01T00:00:00"
  706 + }
  707 + ]
  708 +}
  709 +```
  710 +
  711 +### 4.6 获取激活的AI配置
  712 +**接口地址**: `GET /api/ai-configs/active`
  713 +**接口说明**: 获取激活的AI配置列表
  714 +
  715 +**响应结果**:
  716 +```json
  717 +{
  718 + "code": 200,
  719 + "message": "操作成功",
  720 + "data": [
  721 + {
  722 + "id": 0,
  723 + "companyId": 0,
  724 + "provider": "string",
  725 + "name": "string",
  726 + "baseUrl": "string",
  727 + "apiKey": "string",
  728 + "modelName": "string",
  729 + "temperature": 0,
  730 + "topP": 0,
  731 + "maxTokens": 0,
  732 + "requestHeaders": {},
  733 + "isActive": true,
  734 + "isShared": true,
  735 + "createdAt": "2023-01-01T00:00:00",
  736 + "updatedAt": "2023-01-01T00:00:00"
  737 + }
  738 + ]
  739 +}
  740 +```
  741 +
  742 +### 4.7 创建AI配置
  743 +**接口地址**: `POST /api/ai-configs`
  744 +**接口说明**: 创建新的AI配置
  745 +
  746 +**请求参数**:
  747 +```json
  748 +{
  749 + "companyId": 0,
  750 + "provider": "string",
  751 + "name": "string",
  752 + "baseUrl": "string",
  753 + "apiKey": "string",
  754 + "modelName": "string",
  755 + "temperature": 0,
  756 + "topP": 0,
  757 + "maxTokens": 0,
  758 + "requestHeaders": {},
  759 + "isActive": true,
  760 + "isShared": true
  761 +}
  762 +```
  763 +
  764 +**响应结果**:
  765 +```json
  766 +{
  767 + "code": 200,
  768 + "message": "操作成功",
  769 + "data": {
  770 + "id": 0,
  771 + "companyId": 0,
  772 + "provider": "string",
  773 + "name": "string",
  774 + "baseUrl": "string",
  775 + "apiKey": "string",
  776 + "modelName": "string",
  777 + "temperature": 0,
  778 + "topP": 0,
  779 + "maxTokens": 0,
  780 + "requestHeaders": {},
  781 + "isActive": true,
  782 + "isShared": true,
  783 + "createdAt": "2023-01-01T00:00:00",
  784 + "updatedAt": "2023-01-01T00:00:00"
  785 + }
  786 +}
  787 +```
  788 +
  789 +### 4.8 更新AI配置
  790 +**接口地址**: `PUT /api/ai-configs/{id}`
  791 +**接口说明**: 根据配置ID更新AI配置
  792 +
  793 +**请求参数**:
  794 +```json
  795 +{
  796 + "companyId": 0,
  797 + "provider": "string",
  798 + "name": "string",
  799 + "baseUrl": "string",
  800 + "apiKey": "string",
  801 + "modelName": "string",
  802 + "temperature": 0,
  803 + "topP": 0,
  804 + "maxTokens": 0,
  805 + "requestHeaders": {},
  806 + "isActive": true,
  807 + "isShared": true
  808 +}
  809 +```
  810 +
  811 +**响应结果**:
  812 +```json
  813 +{
  814 + "code": 200,
  815 + "message": "操作成功",
  816 + "data": {
  817 + "id": 0,
  818 + "companyId": 0,
  819 + "provider": "string",
  820 + "name": "string",
  821 + "baseUrl": "string",
  822 + "apiKey": "string",
  823 + "modelName": "string",
  824 + "temperature": 0,
  825 + "topP": 0,
  826 + "maxTokens": 0,
  827 + "requestHeaders": {},
  828 + "isActive": true,
  829 + "isShared": true,
  830 + "createdAt": "2023-01-01T00:00:00",
  831 + "updatedAt": "2023-01-01T00:00:00"
  832 + }
  833 +}
  834 +```
  835 +
  836 +### 4.9 删除AI配置
  837 +**接口地址**: `DELETE /api/ai-configs/{id}`
  838 +**接口说明**: 根据配置ID删除AI配置
  839 +
  840 +**响应结果**:
  841 +```json
  842 +{
  843 + "code": 200,
  844 + "message": "操作成功",
  845 + "data": null
  846 +}
  847 +```
  848 +
  849 +## 5. 文章生成任务管理接口
  850 +
  851 +### 5.1 分页查询文章生成任务列表
  852 +**接口地址**: `GET /api/article-tasks/list`
  853 +**接口说明**: 支持按任务状态、用户、公司等条件分页查询文章生成任务列表
  854 +
  855 +**请求参数**:
  856 +| 参数名 | 类型 | 必填 | 说明 |
  857 +| --- | --- | --- | --- |
  858 +| page | integer | 否 | 页码(默认0) |
  859 +| size | integer | 否 | 每页大小(默认10) |
  860 +| sortBy | string | 否 | 排序字段 |
  861 +| sortDir | string | 否 | 排序方向(asc/desc) |
  862 +| status | string | 否 | 任务状态筛选 |
  863 +| userId | integer | 否 | 用户ID筛选 |
  864 +| companyId | integer | 否 | 公司ID筛选 |
  865 +
  866 +**响应结果**:
  867 +```json
  868 +{
  869 + "code": 200,
  870 + "message": "操作成功",
  871 + "data": {
  872 + "content": [
  873 + {
  874 + "id": 0,
  875 + "companyId": 0,
  876 + "userId": 0,
  877 + "title": "string",
  878 + "description": "string",
  879 + "keywords": "string",
  880 + "aiTasteLevel": "string",
  881 + "status": "string",
  882 + "referenceUrls": [],
  883 + "generatedContent": "string",
  884 + "errorMessage": "string",
  885 + "createdAt": "2023-01-01T00:00:00",
  886 + "updatedAt": "2023-01-01T00:00:00",
  887 + "scheduledAt": "2023-01-01T00:00:00"
  888 + }
  889 + ],
  890 + "pageable": {
  891 + "pageNumber": 0,
  892 + "pageSize": 10,
  893 + "sort": {
  894 + "sorted": true,
  895 + "unsorted": false,
  896 + "empty": false
  897 + },
  898 + "offset": 0,
  899 + "paged": true,
  900 + "unpaged": false
  901 + },
  902 + "totalElements": 0,
  903 + "totalPages": 0,
  904 + "last": true,
  905 + "size": 10,
  906 + "number": 0,
  907 + "sort": {
  908 + "sorted": true,
  909 + "unsorted": false,
  910 + "empty": false
  911 + },
  912 + "first": true,
  913 + "numberOfElements": 0,
  914 + "empty": false
  915 + }
  916 +}
  917 +```
  918 +
  919 +## 6. 关键词管理接口
  920 +
  921 +### 6.1 分页查询关键词列表
  922 +**接口地址**: `GET /api/keywords/list`
  923 +**接口说明**: 支持按关键词、状态、公司等条件分页查询关键词列表
  924 +
  925 +**请求参数**:
  926 +| 参数名 | 类型 | 必填 | 说明 |
  927 +| --- | --- | --- | --- |
  928 +| page | integer | 否 | 页码(默认0) |
  929 +| size | integer | 否 | 每页大小(默认10) |
  930 +| sortBy | string | 否 | 排序字段 |
  931 +| sortDir | string | 否 | 排序方向(asc/desc) |
  932 +| keyword | string | 否 | 关键词筛选 |
  933 +| status | string | 否 | 状态筛选 |
  934 +| companyId | integer | 否 | 公司ID筛选 |
  935 +
  936 +**响应结果**:
  937 +```json
  938 +{
  939 + "code": 200,
  940 + "message": "操作成功",
  941 + "data": {
  942 + "content": [
  943 + {
  944 + "id": 0,
  945 + "companyId": 0,
  946 + "keyword": "string",
  947 + "searchVolume": 0,
  948 + "competition": "string",
  949 + "status": "string",
  950 + "source": "string",
  951 + "sourceTaskId": 0,
  952 + "createdAt": "2023-01-01T00:00:00",
  953 + "updatedAt": "2023-01-01T00:00:00"
  954 + }
  955 + ],
  956 + "pageable": {
  957 + "pageNumber": 0,
  958 + "pageSize": 10,
  959 + "sort": {
  960 + "sorted": true,
  961 + "unsorted": false,
  962 + "empty": false
  963 + },
  964 + "offset": 0,
  965 + "paged": true,
  966 + "unpaged": false
  967 + },
  968 + "totalElements": 0,
  969 + "totalPages": 0,
  970 + "last": true,
  971 + "size": 10,
  972 + "number": 0,
  973 + "sort": {
  974 + "sorted": true,
  975 + "unsorted": false,
  976 + "empty": false
  977 + },
  978 + "first": true,
  979 + "numberOfElements": 0,
  980 + "empty": false
  981 + }
  982 +}
  983 +```
  984 +
  985 +### 6.2 获取所有关键词
  986 +**接口地址**: `GET /api/keywords`
  987 +**接口说明**: 获取所有关键词列表
  988 +
  989 +**响应结果**:
  990 +```json
  991 +{
  992 + "code": 200,
  993 + "message": "操作成功",
  994 + "data": [
  995 + {
  996 + "id": 0,
  997 + "companyId": 0,
  998 + "keyword": "string",
  999 + "searchVolume": 0,
  1000 + "competition": "string",
  1001 + "status": "string",
  1002 + "source": "string",
  1003 + "sourceTaskId": 0,
  1004 + "createdAt": "2023-01-01T00:00:00",
  1005 + "updatedAt": "2023-01-01T00:00:00"
  1006 + }
  1007 + ]
  1008 +}
  1009 +```
  1010 +
  1011 +### 6.3 根据ID获取关键词
  1012 +**接口地址**: `GET /api/keywords/{id}`
  1013 +**接口说明**: 根据关键词ID获取关键词详情
  1014 +
  1015 +**响应结果**:
  1016 +```json
  1017 +{
  1018 + "code": 200,
  1019 + "message": "操作成功",
  1020 + "data": {
  1021 + "id": 0,
  1022 + "companyId": 0,
  1023 + "keyword": "string",
  1024 + "searchVolume": 0,
  1025 + "competition": "string",
  1026 + "status": "string",
  1027 + "source": "string",
  1028 + "sourceTaskId": 0,
  1029 + "createdAt": "2023-01-01T00:00:00",
  1030 + "updatedAt": "2023-01-01T00:00:00"
  1031 + }
  1032 +}
  1033 +```
  1034 +
  1035 +### 6.4 根据公司ID获取关键词列表
  1036 +**接口地址**: `GET /api/keywords/company/{companyId}`
  1037 +**接口说明**: 根据公司ID获取关键词列表
  1038 +
  1039 +**响应结果**:
  1040 +```json
  1041 +{
  1042 + "code": 200,
  1043 + "message": "操作成功",
  1044 + "data": [
  1045 + {
  1046 + "id": 0,
  1047 + "companyId": 0,
  1048 + "keyword": "string",
  1049 + "searchVolume": 0,
  1050 + "competition": "string",
  1051 + "status": "string",
  1052 + "source": "string",
  1053 + "sourceTaskId": 0,
  1054 + "createdAt": "2023-01-01T00:00:00",
  1055 + "updatedAt": "2023-01-01T00:00:00"
  1056 + }
  1057 + ]
  1058 +}
  1059 +```
  1060 +
  1061 +### 6.5 根据公司ID和状态获取关键词列表
  1062 +**接口地址**: `GET /api/keywords/company/{companyId}/status/{status}`
  1063 +**接口说明**: 根据公司ID和状态获取关键词列表
  1064 +
  1065 +**响应结果**:
  1066 +```json
  1067 +{
  1068 + "code": 200,
  1069 + "message": "操作成功",
  1070 + "data": [
  1071 + {
  1072 + "id": 0,
  1073 + "companyId": 0,
  1074 + "keyword": "string",
  1075 + "searchVolume": 0,
  1076 + "competition": "string",
  1077 + "status": "string",
  1078 + "source": "string",
  1079 + "sourceTaskId": 0,
  1080 + "createdAt": "2023-01-01T00:00:00",
  1081 + "updatedAt": "2023-01-01T00:00:00"
  1082 + }
  1083 + ]
  1084 +}
  1085 +```
  1086 +
  1087 +### 6.6 创建关键词
  1088 +**接口地址**: `POST /api/keywords`
  1089 +**接口说明**: 创建新的关键词
  1090 +
  1091 +**请求参数**:
  1092 +```json
  1093 +{
  1094 + "companyId": 0,
  1095 + "keyword": "string",
  1096 + "searchVolume": 0,
  1097 + "competition": "string",
  1098 + "status": "string",
  1099 + "source": "string",
  1100 + "sourceTaskId": 0
  1101 +}
  1102 +```
  1103 +
  1104 +**响应结果**:
  1105 +```json
  1106 +{
  1107 + "code": 200,
  1108 + "message": "操作成功",
  1109 + "data": {
  1110 + "id": 0,
  1111 + "companyId": 0,
  1112 + "keyword": "string",
  1113 + "searchVolume": 0,
  1114 + "competition": "string",
  1115 + "status": "string",
  1116 + "source": "string",
  1117 + "sourceTaskId": 0,
  1118 + "createdAt": "2023-01-01T00:00:00",
  1119 + "updatedAt": "2023-01-01T00:00:00"
  1120 + }
  1121 +}
  1122 +```
  1123 +
  1124 +### 6.7 更新关键词
  1125 +**接口地址**: `PUT /api/keywords/{id}`
  1126 +**接口说明**: 根据关键词ID更新关键词信息
  1127 +
  1128 +**请求参数**:
  1129 +```json
  1130 +{
  1131 + "companyId": 0,
  1132 + "keyword": "string",
  1133 + "searchVolume": 0,
  1134 + "competition": "string",
  1135 + "status": "string",
  1136 + "source": "string",
  1137 + "sourceTaskId": 0
  1138 +}
  1139 +```
  1140 +
  1141 +**响应结果**:
  1142 +```json
  1143 +{
  1144 + "code": 200,
  1145 + "message": "操作成功",
  1146 + "data": {
  1147 + "id": 0,
  1148 + "companyId": 0,
  1149 + "keyword": "string",
  1150 + "searchVolume": 0,
  1151 + "competition": "string",
  1152 + "status": "string",
  1153 + "source": "string",
  1154 + "sourceTaskId": 0,
  1155 + "createdAt": "2023-01-01T00:00:00",
  1156 + "updatedAt": "2023-01-01T00:00:00"
  1157 + }
  1158 +}
  1159 +```
  1160 +
  1161 +### 6.8 删除关键词
  1162 +**接口地址**: `DELETE /api/keywords/{id}`
  1163 +**接口说明**: 根据关键词ID删除关键词
  1164 +
  1165 +**响应结果**:
  1166 +```json
  1167 +{
  1168 + "code": 200,
  1169 + "message": "操作成功",
  1170 + "data": null
  1171 +}
  1172 +```
  1173 +
  1174 +## 7. 话题管理接口
  1175 +
  1176 +### 7.1 获取所有话题
  1177 +**接口地址**: `GET /api/topics`
  1178 +**接口说明**: 获取所有话题列表
  1179 +
  1180 +**响应结果**:
  1181 +```json
  1182 +{
  1183 + "code": 200,
  1184 + "message": "操作成功",
  1185 + "data": [
  1186 + {
  1187 + "id": 0,
  1188 + "companyId": 0,
  1189 + "title": "string",
  1190 + "description": "string",
  1191 + "sourceUrl": "string",
  1192 + "status": "string",
  1193 + "sourceTaskId": 0,
  1194 + "keywords": [],
  1195 + "createdAt": "2023-01-01T00:00:00",
  1196 + "updatedAt": "2023-01-01T00:00:00"
  1197 + }
  1198 + ]
  1199 +}
  1200 +```
  1201 +
  1202 +### 7.2 根据ID获取话题
  1203 +**接口地址**: `GET /api/topics/{id}`
  1204 +**接口说明**: 根据话题ID获取话题详情
  1205 +
  1206 +**响应结果**:
  1207 +```json
  1208 +{
  1209 + "code": 200,
  1210 + "message": "操作成功",
  1211 + "data": {
  1212 + "id": 0,
  1213 + "companyId": 0,
  1214 + "title": "string",
  1215 + "description": "string",
  1216 + "sourceUrl": "string",
  1217 + "status": "string",
  1218 + "sourceTaskId": 0,
  1219 + "keywords": [],
  1220 + "createdAt": "2023-01-01T00:00:00",
  1221 + "updatedAt": "2023-01-01T00:00:00"
  1222 + }
  1223 +}
  1224 +```
  1225 +
  1226 +### 7.3 根据公司ID获取话题列表
  1227 +**接口地址**: `GET /api/topics/company/{companyId}`
  1228 +**接口说明**: 根据公司ID获取话题列表
  1229 +
  1230 +**响应结果**:
  1231 +```json
  1232 +{
  1233 + "code": 200,
  1234 + "message": "操作成功",
  1235 + "data": [
  1236 + {
  1237 + "id": 0,
  1238 + "companyId": 0,
  1239 + "title": "string",
  1240 + "description": "string",
  1241 + "sourceUrl": "string",
  1242 + "status": "string",
  1243 + "sourceTaskId": 0,
  1244 + "keywords": [],
  1245 + "createdAt": "2023-01-01T00:00:00",
  1246 + "updatedAt": "2023-01-01T00:00:00"
  1247 + }
  1248 + ]
  1249 +}
  1250 +```
  1251 +
  1252 +### 7.4 根据公司ID和状态获取话题列表
  1253 +**接口地址**: `GET /api/topics/company/{companyId}/status/{status}`
  1254 +**接口说明**: 根据公司ID和状态获取话题列表
  1255 +
  1256 +**响应结果**:
  1257 +```json
  1258 +{
  1259 + "code": 200,
  1260 + "message": "操作成功",
  1261 + "data": [
  1262 + {
  1263 + "id": 0,
  1264 + "companyId": 0,
  1265 + "title": "string",
  1266 + "description": "string",
  1267 + "sourceUrl": "string",
  1268 + "status": "string",
  1269 + "sourceTaskId": 0,
  1270 + "keywords": [],
  1271 + "createdAt": "2023-01-01T00:00:00",
  1272 + "updatedAt": "2023-01-01T00:00:00"
  1273 + }
  1274 + ]
  1275 +}
  1276 +```
  1277 +
  1278 +### 7.5 根据来源任务ID获取话题列表
  1279 +**接口地址**: `GET /api/topics/source-task/{sourceTaskId}`
  1280 +**接口说明**: 根据来源任务ID获取话题列表
  1281 +
  1282 +**响应结果**:
  1283 +```json
  1284 +{
  1285 + "code": 200,
  1286 + "message": "操作成功",
  1287 + "data": [
  1288 + {
  1289 + "id": 0,
  1290 + "companyId": 0,
  1291 + "title": "string",
  1292 + "description": "string",
  1293 + "sourceUrl": "string",
  1294 + "status": "string",
  1295 + "sourceTaskId": 0,
  1296 + "keywords": [],
  1297 + "createdAt": "2023-01-01T00:00:00",
  1298 + "updatedAt": "2023-01-01T00:00:00"
  1299 + }
  1300 + ]
  1301 +}
  1302 +```
  1303 +
  1304 +### 7.6 创建话题
  1305 +**接口地址**: `POST /api/topics`
  1306 +**接口说明**: 创建新话题
  1307 +
  1308 +**请求参数**:
  1309 +```json
  1310 +{
  1311 + "companyId": 0,
  1312 + "title": "string",
  1313 + "description": "string",
  1314 + "sourceUrl": "string",
  1315 + "status": "string",
  1316 + "sourceTaskId": 0,
  1317 + "keywords": []
  1318 +}
  1319 +```
  1320 +
  1321 +**响应结果**:
  1322 +```json
  1323 +{
  1324 + "code": 200,
  1325 + "message": "操作成功",
  1326 + "data": {
  1327 + "id": 0,
  1328 + "companyId": 0,
  1329 + "title": "string",
  1330 + "description": "string",
  1331 + "sourceUrl": "string",
  1332 + "status": "string",
  1333 + "sourceTaskId": 0,
  1334 + "keywords": [],
  1335 + "createdAt": "2023-01-01T00:00:00",
  1336 + "updatedAt": "2023-01-01T00:00:00"
  1337 + }
  1338 +}
  1339 +```
  1340 +
  1341 +### 7.7 更新话题
  1342 +**接口地址**: `PUT /api/topics/{id}`
  1343 +**接口说明**: 根据话题ID更新话题信息
  1344 +
  1345 +**请求参数**:
  1346 +```json
  1347 +{
  1348 + "companyId": 0,
  1349 + "title": "string",
  1350 + "description": "string",
  1351 + "sourceUrl": "string",
  1352 + "status": "string",
  1353 + "sourceTaskId": 0,
  1354 + "keywords": []
  1355 +}
  1356 +```
  1357 +
  1358 +**响应结果**:
  1359 +```json
  1360 +{
  1361 + "code": 200,
  1362 + "message": "操作成功",
  1363 + "data": {
  1364 + "id": 0,
  1365 + "companyId": 0,
  1366 + "title": "string",
  1367 + "description": "string",
  1368 + "sourceUrl": "string",
  1369 + "status": "string",
  1370 + "sourceTaskId": 0,
  1371 + "keywords": [],
  1372 + "createdAt": "2023-01-01T00:00:00",
  1373 + "updatedAt": "2023-01-01T00:00:00"
  1374 + }
  1375 +}
  1376 +```
  1377 +
  1378 +### 7.8 删除话题
  1379 +**接口地址**: `DELETE /api/topics/{id}`
  1380 +**接口说明**: 根据话题ID删除话题
  1381 +
  1382 +**响应结果**:
  1383 +```json
  1384 +{
  1385 + "code": 200,
  1386 + "message": "操作成功",
  1387 + "data": null
  1388 +}
  1389 +```
  1390 +
  1391 +## 8. 发布平台管理接口
  1392 +
  1393 +### 8.1 分页查询发布平台列表
  1394 +**接口地址**: `GET /api/platforms/list`
  1395 +**接口说明**: 支持按平台名称、类型、状态等条件分页查询发布平台列表
  1396 +
  1397 +**请求参数**:
  1398 +| 参数名 | 类型 | 必填 | 说明 |
  1399 +| --- | --- | --- | --- |
  1400 +| page | integer | 否 | 页码(默认0) |
  1401 +| size | integer | 否 | 每页大小(默认10) |
  1402 +| sortBy | string | 否 | 排序字段 |
  1403 +| sortDir | string | 否 | 排序方向(asc/desc) |
  1404 +| name | string | 否 | 平台名称筛选 |
  1405 +| type | string | 否 | 平台类型筛选 |
  1406 +| status | string | 否 | 状态筛选 |
  1407 +
  1408 +**响应结果**:
  1409 +```json
  1410 +{
  1411 + "code": 200,
  1412 + "message": "操作成功",
  1413 + "data": {
  1414 + "content": [
  1415 + {
  1416 + "id": 0,
  1417 + "name": "string",
  1418 + "code": "string",
  1419 + "type": "string",
  1420 + "description": "string",
  1421 + "configSchema": {},
  1422 + "isActive": true,
  1423 + "createdAt": "2023-01-01T00:00:00",
  1424 + "updatedAt": "2023-01-01T00:00:00"
  1425 + }
  1426 + ],
  1427 + "pageable": {
  1428 + "pageNumber": 0,
  1429 + "pageSize": 10,
  1430 + "sort": {
  1431 + "sorted": true,
  1432 + "unsorted": false,
  1433 + "empty": false
  1434 + },
  1435 + "offset": 0,
  1436 + "paged": true,
  1437 + "unpaged": false
  1438 + },
  1439 + "totalElements": 0,
  1440 + "totalPages": 0,
  1441 + "last": true,
  1442 + "size": 10,
  1443 + "number": 0,
  1444 + "sort": {
  1445 + "sorted": true,
  1446 + "unsorted": false,
  1447 + "empty": false
  1448 + },
  1449 + "first": true,
  1450 + "numberOfElements": 0,
  1451 + "empty": false
  1452 + }
  1453 +}
  1454 +```
  1455 +
  1456 +### 8.2 获取所有发布平台
  1457 +**接口地址**: `GET /api/platforms`
  1458 +**接口说明**: 获取所有发布平台列表
  1459 +
  1460 +**响应结果**:
  1461 +```json
  1462 +{
  1463 + "code": 200,
  1464 + "message": "操作成功",
  1465 + "data": [
  1466 + {
  1467 + "id": 0,
  1468 + "name": "string",
  1469 + "code": "string",
  1470 + "type": "string",
  1471 + "description": "string",
  1472 + "configSchema": {},
  1473 + "isActive": true,
  1474 + "createdAt": "2023-01-01T00:00:00",
  1475 + "updatedAt": "2023-01-01T00:00:00"
  1476 + }
  1477 + ]
  1478 +}
  1479 +```
  1480 +
  1481 +### 8.3 根据ID获取发布平台
  1482 +**接口地址**: `GET /api/platforms/{id}`
  1483 +**接口说明**: 根据发布平台ID获取平台详情
  1484 +
  1485 +**响应结果**:
  1486 +```json
  1487 +{
  1488 + "code": 200,
  1489 + "message": "操作成功",
  1490 + "data": {
  1491 + "id": 0,
  1492 + "name": "string",
  1493 + "code": "string",
  1494 + "type": "string",
  1495 + "description": "string",
  1496 + "configSchema": {},
  1497 + "isActive": true,
  1498 + "createdAt": "2023-01-01T00:00:00",
  1499 + "updatedAt": "2023-01-01T00:00:00"
  1500 + }
  1501 +}
  1502 +```
  1503 +
  1504 +### 8.4 根据类型ID获取发布平台列表
  1505 +**接口地址**: `GET /api/platforms/type/{typeId}`
  1506 +**接口说明**: 根据类型ID获取发布平台列表
  1507 +
  1508 +**响应结果**:
  1509 +```json
  1510 +{
  1511 + "code": 200,
  1512 + "message": "操作成功",
  1513 + "data": [
  1514 + {
  1515 + "id": 0,
  1516 + "name": "string",
  1517 + "code": "string",
  1518 + "type": "string",
  1519 + "description": "string",
  1520 + "configSchema": {},
  1521 + "isActive": true,
  1522 + "createdAt": "2023-01-01T00:00:00",
  1523 + "updatedAt": "2023-01-01T00:00:00"
  1524 + }
  1525 + ]
  1526 +}
  1527 +```
  1528 +
  1529 +### 8.5 获取激活的发布平台列表
  1530 +**接口地址**: `GET /api/platforms/active`
  1531 +**接口说明**: 获取激活的发布平台列表
  1532 +
  1533 +**响应结果**:
  1534 +```json
  1535 +{
  1536 + "code": 200,
  1537 + "message": "操作成功",
  1538 + "data": [
  1539 + {
  1540 + "id": 0,
  1541 + "name": "string",
  1542 + "code": "string",
  1543 + "type": "string",
  1544 + "description": "string",
  1545 + "configSchema": {},
  1546 + "isActive": true,
  1547 + "createdAt": "2023-01-01T00:00:00",
  1548 + "updatedAt": "2023-01-01T00:00:00"
  1549 + }
  1550 + ]
  1551 +}
  1552 +```
  1553 +
  1554 +### 8.6 根据类型ID获取激活的发布平台列表
  1555 +**接口地址**: `GET /api/platforms/type/{typeId}/active`
  1556 +**接口说明**: 根据类型ID获取激活的发布平台列表
  1557 +
  1558 +**响应结果**:
  1559 +```json
  1560 +{
  1561 + "code": 200,
  1562 + "message": "操作成功",
  1563 + "data": [
  1564 + {
  1565 + "id": 0,
  1566 + "name": "string",
  1567 + "code": "string",
  1568 + "type": "string",
  1569 + "description": "string",
  1570 + "configSchema": {},
  1571 + "isActive": true,
  1572 + "createdAt": "2023-01-01T00:00:00",
  1573 + "updatedAt": "2023-01-01T00:00:00"
  1574 + }
  1575 + ]
  1576 +}
  1577 +```
  1578 +
  1579 +### 8.7 根据编码获取发布平台
  1580 +**接口地址**: `GET /api/platforms/code/{code}`
  1581 +**接口说明**: 根据编码获取发布平台
  1582 +
  1583 +**响应结果**:
  1584 +```json
  1585 +{
  1586 + "code": 200,
  1587 + "message": "操作成功",
  1588 + "data": {
  1589 + "id": 0,
  1590 + "name": "string",
  1591 + "code": "string",
  1592 + "type": "string",
  1593 + "description": "string",
  1594 + "configSchema": {},
  1595 + "isActive": true,
  1596 + "createdAt": "2023-01-01T00:00:00",
  1597 + "updatedAt": "2023-01-01T00:00:00"
  1598 + }
  1599 +}
  1600 +```
  1601 +
  1602 +### 8.8 创建发布平台
  1603 +**接口地址**: `POST /api/platforms`
  1604 +**接口说明**: 创建新的发布平台
  1605 +
  1606 +**请求参数**:
  1607 +```json
  1608 +{
  1609 + "name": "string",
  1610 + "code": "string",
  1611 + "type": "string",
  1612 + "description": "string",
  1613 + "configSchema": {},
  1614 + "isActive": true
  1615 +}
  1616 +```
  1617 +
  1618 +**响应结果**:
  1619 +```json
  1620 +{
  1621 + "code": 200,
  1622 + "message": "操作成功",
  1623 + "data": {
  1624 + "id": 0,
  1625 + "name": "string",
  1626 + "code": "string",
  1627 + "type": "string",
  1628 + "description": "string",
  1629 + "configSchema": {},
  1630 + "isActive": true,
  1631 + "createdAt": "2023-01-01T00:00:00",
  1632 + "updatedAt": "2023-01-01T00:00:00"
  1633 + }
  1634 +}
  1635 +```
  1636 +
  1637 +### 8.9 更新发布平台
  1638 +**接口地址**: `PUT /api/platforms/{id}`
  1639 +**接口说明**: 根据发布平台ID更新平台信息
  1640 +
  1641 +**请求参数**:
  1642 +```json
  1643 +{
  1644 + "name": "string",
  1645 + "code": "string",
  1646 + "type": "string",
  1647 + "description": "string",
  1648 + "configSchema": {},
  1649 + "isActive": true
  1650 +}
  1651 +```
  1652 +
  1653 +**响应结果**:
  1654 +```json
  1655 +{
  1656 + "code": 200,
  1657 + "message": "操作成功",
  1658 + "data": {
  1659 + "id": 0,
  1660 + "name": "string",
  1661 + "code": "string",
  1662 + "type": "string",
  1663 + "description": "string",
  1664 + "configSchema": {},
  1665 + "isActive": true,
  1666 + "createdAt": "2023-01-01T00:00:00",
  1667 + "updatedAt": "2023-01-01T00:00:00"
  1668 + }
  1669 +}
  1670 +```
  1671 +
  1672 +### 8.10 删除发布平台
  1673 +**接口地址**: `DELETE /api/platforms/{id}`
  1674 +**接口说明**: 根据发布平台ID删除平台
  1675 +
  1676 +**响应结果**:
  1677 +```json
  1678 +{
  1679 + "code": 200,
  1680 + "message": "操作成功",
  1681 + "data": null
  1682 +}
  1683 +```
  1684 +
  1685 +## 9. 网站项目管理接口
  1686 +
  1687 +### 9.1 获取所有网站项目
  1688 +**接口地址**: `GET /api/website-projects`
  1689 +**接口说明**: 获取所有网站项目列表
  1690 +
  1691 +**响应结果**:
  1692 +```json
  1693 +{
  1694 + "code": 200,
  1695 + "message": "操作成功",
  1696 + "data": [
  1697 + {
  1698 + "id": 0,
  1699 + "companyId": 0,
  1700 + "userId": 0,
  1701 + "name": "string",
  1702 + "domain": "string",
  1703 + "subdomain": "string",
  1704 + "status": "string",
  1705 + "templateId": 0,
  1706 + "seoSettings": {},
  1707 + "analyticsCode": "string",
  1708 + "customCss": "string",
  1709 + "customJs": "string",
  1710 + "deployedAt": "2023-01-01T00:00:00",
  1711 + "createdAt": "2023-01-01T00:00:00",
  1712 + "updatedAt": "2023-01-01T00:00:00"
  1713 + }
  1714 + ]
  1715 +}
  1716 +```
  1717 +
  1718 +### 9.2 根据ID获取网站项目
  1719 +**接口地址**: `GET /api/website-projects/{id}`
  1720 +**接口说明**: 根据网站项目ID获取项目详情
  1721 +
  1722 +**响应结果**:
  1723 +```json
  1724 +{
  1725 + "code": 200,
  1726 + "message": "操作成功",
  1727 + "data": {
  1728 + "id": 0,
  1729 + "companyId": 0,
  1730 + "userId": 0,
  1731 + "name": "string",
  1732 + "domain": "string",
  1733 + "subdomain": "string",
  1734 + "status": "string",
  1735 + "templateId": 0,
  1736 + "seoSettings": {},
  1737 + "analyticsCode": "string",
  1738 + "customCss": "string",
  1739 + "customJs": "string",
  1740 + "deployedAt": "2023-01-01T00:00:00",
  1741 + "createdAt": "2023-01-01T00:00:00",
  1742 + "updatedAt": "2023-01-01T00:00:00"
  1743 + }
  1744 +}
  1745 +```
  1746 +
  1747 +### 9.3 根据公司ID获取网站项目列表
  1748 +**接口地址**: `GET /api/website-projects/company/{companyId}`
  1749 +**接口说明**: 根据公司ID获取网站项目列表
  1750 +
  1751 +**响应结果**:
  1752 +```json
  1753 +{
  1754 + "code": 200,
  1755 + "message": "操作成功",
  1756 + "data": [
  1757 + {
  1758 + "id": 0,
  1759 + "companyId": 0,
  1760 + "userId": 0,
  1761 + "name": "string",
  1762 + "domain": "string",
  1763 + "subdomain": "string",
  1764 + "status": "string",
  1765 + "templateId": 0,
  1766 + "seoSettings": {},
  1767 + "analyticsCode": "string",
  1768 + "customCss": "string",
  1769 + "customJs": "string",
  1770 + "deployedAt": "2023-01-01T00:00:00",
  1771 + "createdAt": "2023-01-01T00:00:00",
  1772 + "updatedAt": "2023-01-01T00:00:00"
  1773 + }
  1774 + ]
  1775 +}
  1776 +```
  1777 +
  1778 +### 9.4 根据用户ID获取网站项目列表
  1779 +**接口地址**: `GET /api/website-projects/user/{userId}`
  1780 +**接口说明**: 根据用户ID获取网站项目列表
  1781 +
  1782 +**响应结果**:
  1783 +```json
  1784 +{
  1785 + "code": 200,
  1786 + "message": "操作成功",
  1787 + "data": [
  1788 + {
  1789 + "id": 0,
  1790 + "companyId": 0,
  1791 + "userId": 0,
  1792 + "name": "string",
  1793 + "domain": "string",
  1794 + "subdomain": "string",
  1795 + "status": "string",
  1796 + "templateId": 0,
  1797 + "seoSettings": {},
  1798 + "analyticsCode": "string",
  1799 + "customCss": "string",
  1800 + "customJs": "string",
  1801 + "deployedAt": "2023-01-01T00:00:00",
  1802 + "createdAt": "2023-01-01T00:00:00",
  1803 + "updatedAt": "2023-01-01T00:00:00"
  1804 + }
  1805 + ]
  1806 +}
  1807 +```
  1808 +
  1809 +### 9.5 根据状态获取网站项目列表
  1810 +**接口地址**: `GET /api/website-projects/status/{status}`
  1811 +**接口说明**: 根据状态获取网站项目列表
  1812 +
  1813 +**响应结果**:
  1814 +```json
  1815 +{
  1816 + "code": 200,
  1817 + "message": "操作成功",
  1818 + "data": [
  1819 + {
  1820 + "id": 0,
  1821 + "companyId": 0,
  1822 + "userId": 0,
  1823 + "name": "string",
  1824 + "domain": "string",
  1825 + "subdomain": "string",
  1826 + "status": "string",
  1827 + "templateId": 0,
  1828 + "seoSettings": {},
  1829 + "analyticsCode": "string",
  1830 + "customCss": "string",
  1831 + "customJs": "string",
  1832 + "deployedAt": "2023-01-01T00:00:00",
  1833 + "createdAt": "2023-01-01T00:00:00",
  1834 + "updatedAt": "2023-01-01T00:00:00"
  1835 + }
  1836 + ]
  1837 +}
  1838 +```
  1839 +
  1840 +### 9.6 根据公司ID和状态获取网站项目列表
  1841 +**接口地址**: `GET /api/website-projects/company/{companyId}/status/{status}`
  1842 +**接口说明**: 根据公司ID和状态获取网站项目列表
  1843 +
  1844 +**响应结果**:
  1845 +```json
  1846 +{
  1847 + "code": 200,
  1848 + "message": "操作成功",
  1849 + "data": [
  1850 + {
  1851 + "id": 0,
  1852 + "companyId": 0,
  1853 + "userId": 0,
  1854 + "name": "string",
  1855 + "domain": "string",
  1856 + "subdomain": "string",
  1857 + "status": "string",
  1858 + "templateId": 0,
  1859 + "seoSettings": {},
  1860 + "analyticsCode": "string",
  1861 + "customCss": "string",
  1862 + "customJs": "string",
  1863 + "deployedAt": "2023-01-01T00:00:00",
  1864 + "createdAt": "2023-01-01T00:00:00",
  1865 + "updatedAt": "2023-01-01T00:00:00"
  1866 + }
  1867 + ]
  1868 +}
  1869 +```
  1870 +
  1871 +### 9.7 创建网站项目
  1872 +**接口地址**: `POST /api/website-projects`
  1873 +**接口说明**: 创建新的网站项目
  1874 +
  1875 +**请求参数**:
  1876 +```json
  1877 +{
  1878 + "companyId": 0,
  1879 + "userId": 0,
  1880 + "name": "string",
  1881 + "domain": "string",
  1882 + "subdomain": "string",
  1883 + "status": "string",
  1884 + "templateId": 0,
  1885 + "seoSettings": {},
  1886 + "analyticsCode": "string",
  1887 + "customCss": "string",
  1888 + "customJs": "string"
  1889 +}
  1890 +```
  1891 +
  1892 +**响应结果**:
  1893 +```json
  1894 +{
  1895 + "code": 200,
  1896 + "message": "操作成功",
  1897 + "data": {
  1898 + "id": 0,
  1899 + "companyId": 0,
  1900 + "userId": 0,
  1901 + "name": "string",
  1902 + "domain": "string",
  1903 + "subdomain": "string",
  1904 + "status": "string",
  1905 + "templateId": 0,
  1906 + "seoSettings": {},
  1907 + "analyticsCode": "string",
  1908 + "customCss": "string",
  1909 + "customJs": "string",
  1910 + "deployedAt": "2023-01-01T00:00:00",
  1911 + "createdAt": "2023-01-01T00:00:00",
  1912 + "updatedAt": "2023-01-01T00:00:00"
  1913 + }
  1914 +}
  1915 +```
  1916 +
  1917 +### 9.8 更新网站项目
  1918 +**接口地址**: `PUT /api/website-projects/{id}`
  1919 +**接口说明**: 根据网站项目ID更新项目信息
  1920 +
  1921 +**请求参数**:
  1922 +```json
  1923 +{
  1924 + "companyId": 0,
  1925 + "userId": 0,
  1926 + "name": "string",
  1927 + "domain": "string",
  1928 + "subdomain": "string",
  1929 + "status": "string",
  1930 + "templateId": 0,
  1931 + "seoSettings": {},
  1932 + "analyticsCode": "string",
  1933 + "customCss": "string",
  1934 + "customJs": "string"
  1935 +}
  1936 +```
  1937 +
  1938 +**响应结果**:
  1939 +```json
  1940 +{
  1941 + "code": 200,
  1942 + "message": "操作成功",
  1943 + "data": {
  1944 + "id": 0,
  1945 + "companyId": 0,
  1946 + "userId": 0,
  1947 + "name": "string",
  1948 + "domain": "string",
  1949 + "subdomain": "string",
  1950 + "status": "string",
  1951 + "templateId": 0,
  1952 + "seoSettings": {},
  1953 + "analyticsCode": "string",
  1954 + "customCss": "string",
  1955 + "customJs": "string",
  1956 + "deployedAt": "2023-01-01T00:00:00",
  1957 + "createdAt": "2023-01-01T00:00:00",
  1958 + "updatedAt": "2023-01-01T00:00:00"
  1959 + }
  1960 +}
  1961 +```
  1962 +
  1963 +### 9.9 删除网站项目
  1964 +**接口地址**: `DELETE /api/website-projects/{id}`
  1965 +**接口说明**: 根据网站项目ID删除项目
  1966 +
  1967 +**响应结果**:
  1968 +```json
  1969 +{
  1970 + "code": 200,
  1971 + "message": "操作成功",
  1972 + "data": null
  1973 +}
  1974 +```
  1975 +
  1976 +## 10. 落地页模板管理接口
  1977 +
  1978 +### 10.1 获取所有落地页模板
  1979 +**接口地址**: `GET /api/landing-page-templates`
  1980 +**接口说明**: 获取所有落地页模板列表
  1981 +
  1982 +**响应结果**:
  1983 +```json
  1984 +{
  1985 + "code": 200,
  1986 + "message": "操作成功",
  1987 + "data": [
  1988 + {
  1989 + "id": 0,
  1990 + "name": "string",
  1991 + "code": "string",
  1992 + "description": "string",
  1993 + "previewImageUrl": "string",
  1994 + "htmlTemplate": "string",
  1995 + "cssStyles": "string",
  1996 + "jsScripts": "string",
  1997 + "configSchema": {},
  1998 + "isActive": true,
  1999 + "createdAt": "2023-01-01T00:00:00",
  2000 + "updatedAt": "2023-01-01T00:00:00"
  2001 + }
  2002 + ]
  2003 +}
  2004 +```
  2005 +
  2006 +### 10.2 根据ID获取落地页模板
  2007 +**接口地址**: `GET /api/landing-page-templates/{id}`
  2008 +**接口说明**: 根据落地页模板ID获取模板详情
  2009 +
  2010 +**响应结果**:
  2011 +```json
  2012 +{
  2013 + "code": 200,
  2014 + "message": "操作成功",
  2015 + "data": {
  2016 + "id": 0,
  2017 + "name": "string",
  2018 + "code": "string",
  2019 + "description": "string",
  2020 + "previewImageUrl": "string",
  2021 + "htmlTemplate": "string",
  2022 + "cssStyles": "string",
  2023 + "jsScripts": "string",
  2024 + "configSchema": {},
  2025 + "isActive": true,
  2026 + "createdAt": "2023-01-01T00:00:00",
  2027 + "updatedAt": "2023-01-01T00:00:00"
  2028 + }
  2029 +}
  2030 +```
  2031 +
  2032 +### 10.3 获取激活的落地页模板列表
  2033 +**接口地址**: `GET /api/landing-page-templates/active`
  2034 +**接口说明**: 获取激活的落地页模板列表
  2035 +
  2036 +**响应结果**:
  2037 +```json
  2038 +{
  2039 + "code": 200,
  2040 + "message": "操作成功",
  2041 + "data": [
  2042 + {
  2043 + "id": 0,
  2044 + "name": "string",
  2045 + "code": "string",
  2046 + "description": "string",
  2047 + "previewImageUrl": "string",
  2048 + "htmlTemplate": "string",
  2049 + "cssStyles": "string",
  2050 + "jsScripts": "string",
  2051 + "configSchema": {},
  2052 + "isActive": true,
  2053 + "createdAt": "2023-01-01T00:00:00",
  2054 + "updatedAt": "2023-01-01T00:00:00"
  2055 + }
  2056 + ]
  2057 +}
  2058 +```
  2059 +
  2060 +### 10.4 根据编码获取落地页模板
  2061 +**接口地址**: `GET /api/landing-page-templates/code/{code}`
  2062 +**接口说明**: 根据编码获取落地页模板
  2063 +
  2064 +**响应结果**:
  2065 +```json
  2066 +{
  2067 + "code": 200,
  2068 + "message": "操作成功",
  2069 + "data": {
  2070 + "id": 0,
  2071 + "name": "string",
  2072 + "code": "string",
  2073 + "description": "string",
  2074 + "previewImageUrl": "string",
  2075 + "htmlTemplate": "string",
  2076 + "cssStyles": "string",
  2077 + "jsScripts": "string",
  2078 + "configSchema": {},
  2079 + "isActive": true,
  2080 + "createdAt": "2023-01-01T00:00:00",
  2081 + "updatedAt": "2023-01-01T00:00:00"
  2082 + }
  2083 +}
  2084 +```
  2085 +
  2086 +### 10.5 创建落地页模板
  2087 +**接口地址**: `POST /api/landing-page-templates`
  2088 +**接口说明**: 创建新的落地页模板
  2089 +
  2090 +**请求参数**:
  2091 +```json
  2092 +{
  2093 + "name": "string",
  2094 + "code": "string",
  2095 + "description": "string",
  2096 + "previewImageUrl": "string",
  2097 + "htmlTemplate": "string",
  2098 + "cssStyles": "string",
  2099 + "jsScripts": "string",
  2100 + "configSchema": {},
  2101 + "isActive": true
  2102 +}
  2103 +```
  2104 +
  2105 +**响应结果**:
  2106 +```json
  2107 +{
  2108 + "code": 200,
  2109 + "message": "操作成功",
  2110 + "data": {
  2111 + "id": 0,
  2112 + "name": "string",
  2113 + "code": "string",
  2114 + "description": "string",
  2115 + "previewImageUrl": "string",
  2116 + "htmlTemplate": "string",
  2117 + "cssStyles": "string",
  2118 + "jsScripts": "string",
  2119 + "configSchema": {},
  2120 + "isActive": true,
  2121 + "createdAt": "2023-01-01T00:00:00",
  2122 + "updatedAt": "2023-01-01T00:00:00"
  2123 + }
  2124 +}
  2125 +```
  2126 +
  2127 +### 10.6 更新落地页模板
  2128 +**接口地址**: `PUT /api/landing-page-templates/{id}`
  2129 +**接口说明**: 根据落地页模板ID更新模板信息
  2130 +
  2131 +**请求参数**:
  2132 +```json
  2133 +{
  2134 + "name": "string",
  2135 + "code": "string",
  2136 + "description": "string",
  2137 + "previewImageUrl": "string",
  2138 + "htmlTemplate": "string",
  2139 + "cssStyles": "string",
  2140 + "jsScripts": "string",
  2141 + "configSchema": {},
  2142 + "isActive": true
  2143 +}
  2144 +```
  2145 +
  2146 +**响应结果**:
  2147 +```json
  2148 +{
  2149 + "code": 200,
  2150 + "message": "操作成功",
  2151 + "data": {
  2152 + "id": 0,
  2153 + "name": "string",
  2154 + "code": "string",
  2155 + "description": "string",
  2156 + "previewImageUrl": "string",
  2157 + "htmlTemplate": "string",
  2158 + "cssStyles": "string",
  2159 + "jsScripts": "string",
  2160 + "configSchema": {},
  2161 + "isActive": true,
  2162 + "createdAt": "2023-01-01T00:00:00",
  2163 + "updatedAt": "2023-01-01T00:00:00"
  2164 + }
  2165 +}
  2166 +```
  2167 +
  2168 +### 10.7 删除落地页模板
  2169 +**接口地址**: `DELETE /api/landing-page-templates/{id}`
  2170 +**接口说明**: 根据落地页模板ID删除模板
  2171 +
  2172 +**响应结果**:
  2173 +```json
  2174 +{
  2175 + "code": 200,
  2176 + "message": "操作成功",
  2177 + "data": null
  2178 +}
  2179 +```
  2180 +
  2181 +## 通用响应格式
  2182 +
  2183 +所有API接口都使用统一的响应格式:
  2184 +
  2185 +```json
  2186 +{
  2187 + "code": 200, // 状态码
  2188 + "message": "操作成功", // 消息说明
  2189 + "data": {} // 返回数据
  2190 +}
  2191 +```
  2192 +
  2193 +### 常用状态码
  2194 +
  2195 +| 状态码 | 说明 |
  2196 +| --- | --- |
  2197 +| 200 | 操作成功 |
  2198 +| 400 | 请求参数错误 |
  2199 +| 401 | 未授权访问 |
  2200 +| 403 | 禁止访问 |
  2201 +| 404 | 资源未找到 |
  2202 +| 500 | 系统内部错误 |
  2203 +
  2204 +## 认证方式
  2205 +
  2206 +除登录、注册等开放接口外,其他接口都需要在请求头中添加认证信息:
  2207 +
  2208 +```
  2209 +Authorization: Bearer {token}
  2210 +X-Tenant-ID: {tenantId}
  2211 +```
  2212 +
  2213 +其中:
  2214 +- `{token}` 为登录成功后返回的访问令牌
  2215 +- `{tenantId}` 为租户ID,用于多租户数据隔离
  2216 +
  2217 +
  2218 +业务概述
  2219 +Company模块负责管理系统中的公司(租户)信息,是整个多租户架构的核心。每个公司作为一个独立的租户,拥有自己的用户、配置和数据。
  2220 +处理的具体业务
  2221 +公司信息管理:创建、查询、更新、删除公司信息
  2222 +多租户支持:通过子域名识别不同公司
  2223 +套餐管理:支持不同的套餐类型和配额限制
  2224 +状态管理:管理公司的试用、激活、暂停等状态
  2225 +搜索功能:支持按关键词搜索公司
  2226 +数据表字段
  2227 +操作的数据表是 ai_companies,包含以下字段:
  2228 +字段名
  2229 +类型
  2230 +说明
  2231 +id
  2232 +Integer
  2233 +主键,自增ID
  2234 +name
  2235 +String
  2236 +公司名称
  2237 +subdomain
  2238 +String
  2239 +子域名,唯一标识
  2240 +plan_type
  2241 +PlanType枚举
  2242 +套餐类型(FREE/TRIAL/BASIC/PREMIUM/ENTERPRISE)
  2243 +max_users
  2244 +Integer
  2245 +最大用户数
  2246 +max_articles_per_month
  2247 +Integer
  2248 +每月最大文章数
  2249 +status
  2250 +CompanyStatus枚举
  2251 +公司状态(TRIAL/ACTIVE/SUSPENDED)
  2252 +trial_expiry_date
  2253 +LocalDate
  2254 +试用期到期日期
  2255 +default_settings
  2256 +String (JSON)
  2257 +默认配置(JSON格式)
  2258 +created_at
  2259 +LocalDateTime
  2260 +创建时间
  2261 +updated_at
  2262 +LocalDateTime
  2263 +更新时间
  2264 +操作的数据表
  2265 +主表:ai_companies - 存储公司基本信息
  2266 +索引:在subdomain字段上建立了唯一索引uk_companies_subdomain
  2267 +提供的API接口
  2268 +GET /api/companies/list - 分页查询公司列表
  2269 +GET /api/companies - 获取所有公司
  2270 +GET /api/companies/{id} - 根据ID查询公司详情
  2271 +GET /api/companies/subdomain/{subdomain} - 根据子域名查询公司
  2272 +GET /api/companies/active - 查询活跃公司列表
  2273 +POST /api/companies - 创建新公司
  2274 +PUT /api/companies/{id} - 更新公司信息
  2275 +PUT /api/companies/batch-status - 批量更新公司状态
  2276 +DELETE /api/companies/{id} - 删除公司(软删除)
  2277 +GET /api/companies/search - 搜索公司
  2278 +这个模块是整个系统多租户架构的基础,所有其他业务模块都会关联到公司(租户)维度进行数据隔离。