正在显示
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 | +这个模块是整个系统多租户架构的基础,所有其他业务模块都会关联到公司(租户)维度进行数据隔离。 |
-
请 注册 或 登录 后发表评论