正在显示
90 个修改的文件
包含
8158 行增加
和
0 行删除
README.md
0 → 100644
| 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 | +本项目仅供学习和参考使用。 |
logs/aigeo.log
0 → 100644
此 diff 太大无法显示。
src/aigeo_mysql8.sql
0 → 100644
| 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 | +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 | +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 | +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 | +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 | +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 | +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 | +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 | +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 | +} |
src/main/java/com/aigeo/common/Result.java
0 → 100644
| 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 | +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 | +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 | +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.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 | +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 | +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 | +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 | +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 | +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 | +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 | +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 | +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 | +} |
src/main/java/com/aigeo/util/JwtUtil.java
0 → 100644
| 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 | +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 | +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 | +} |
src/main/resources/application.yml
0 → 100644
| 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 |
src/main/resources/data.sql
0 → 100644
| 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()); |
src/main/resources/schema.sql
0 → 100644
| 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; |
src/功能文档.md
0 → 100644
| 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 | + |
vue.md
0 → 100644
| 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 | +这个模块是整个系统多租户架构的基础,所有其他业务模块都会关联到公司(租户)维度进行数据隔离。 |
-
请 注册 或 登录 后发表评论