chore: prepare project for server deployment
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
const Content = require('../src/models/Content');
|
||||
const Page = require('../src/models/Page');
|
||||
const { getDb, closeDb } = require('../src/config/database');
|
||||
const { migrate } = require('../src/database/migrate');
|
||||
|
||||
beforeAll(() => {
|
||||
migrate();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
closeDb();
|
||||
});
|
||||
|
||||
describe('Content Model', () => {
|
||||
let contentId;
|
||||
|
||||
test('应该创建内容', () => {
|
||||
const content = Content.create({
|
||||
title: '测试内容',
|
||||
body: '这是测试内容的主体',
|
||||
content_type: 'page',
|
||||
publish_status: 'draft'
|
||||
});
|
||||
|
||||
expect(content).toBeDefined();
|
||||
expect(content.content_id).toBeDefined();
|
||||
expect(content.title).toBe('测试内容');
|
||||
expect(content.body).toBe('这是测试内容的主体');
|
||||
expect(content.content_type).toBe('page');
|
||||
expect(content.publish_status).toBe('draft');
|
||||
|
||||
contentId = content.content_id;
|
||||
});
|
||||
|
||||
test('应该根据ID查找内容', () => {
|
||||
const content = Content.findById(contentId);
|
||||
expect(content).toBeDefined();
|
||||
expect(content.content_id).toBe(contentId);
|
||||
expect(content.title).toBe('测试内容');
|
||||
});
|
||||
|
||||
test('应该更新内容', () => {
|
||||
const updatedContent = Content.update(contentId, {
|
||||
title: '更新后的内容',
|
||||
body: '更新后的主体'
|
||||
});
|
||||
|
||||
expect(updatedContent.title).toBe('更新后的内容');
|
||||
expect(updatedContent.body).toBe('更新后的主体');
|
||||
});
|
||||
|
||||
test('应该进行状态流转', () => {
|
||||
// draft -> review
|
||||
const reviewContent = Content.transitionStatus(contentId, 'review');
|
||||
expect(reviewContent.publish_status).toBe('review');
|
||||
|
||||
// review -> published
|
||||
const publishedContent = Content.transitionStatus(contentId, 'published');
|
||||
expect(publishedContent.publish_status).toBe('published');
|
||||
expect(publishedContent.version_no).toBe(2);
|
||||
|
||||
// published -> unpublished
|
||||
const unpublishedContent = Content.transitionStatus(contentId, 'unpublished');
|
||||
expect(unpublishedContent.publish_status).toBe('unpublished');
|
||||
|
||||
// unpublished -> historical
|
||||
const historicalContent = Content.transitionStatus(contentId, 'historical');
|
||||
expect(historicalContent.publish_status).toBe('historical');
|
||||
expect(historicalContent.archived_at).toBeDefined();
|
||||
});
|
||||
|
||||
test('应该创建历史版本', () => {
|
||||
// 先创建一个新的内容
|
||||
const newContent = Content.create({
|
||||
title: '历史版本测试',
|
||||
body: '这是历史版本测试的主体',
|
||||
content_type: 'article',
|
||||
publish_status: 'draft'
|
||||
});
|
||||
|
||||
// 创建历史版本
|
||||
const historicalVersion = Content.createHistoricalVersion(newContent.content_id);
|
||||
expect(historicalVersion).toBeDefined();
|
||||
expect(historicalVersion.version_no).toBe(newContent.version_no + 1);
|
||||
expect(historicalVersion.publish_status).toBe('historical');
|
||||
});
|
||||
|
||||
test('应该删除内容', () => {
|
||||
const result = Content.delete(contentId);
|
||||
expect(result.changes).toBe(1);
|
||||
|
||||
const content = Content.findById(contentId);
|
||||
expect(content).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
const Entity = require('../src/models/Entity');
|
||||
const DirectedRelationship = require('../src/models/DirectedRelationship');
|
||||
const { getDb, closeDb } = require('../src/config/database');
|
||||
const { migrate } = require('../src/database/migrate');
|
||||
|
||||
beforeAll(() => {
|
||||
migrate();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
closeDb();
|
||||
});
|
||||
|
||||
describe('DirectedRelationship Model', () => {
|
||||
let entity1Id, entity2Id, relId;
|
||||
|
||||
beforeAll(() => {
|
||||
// 创建测试实体
|
||||
const entity1 = Entity.create({
|
||||
name: '实体1',
|
||||
definition: '第一个测试实体'
|
||||
});
|
||||
entity1Id = entity1.entity_id;
|
||||
|
||||
const entity2 = Entity.create({
|
||||
name: '实体2',
|
||||
definition: '第二个测试实体'
|
||||
});
|
||||
entity2Id = entity2.entity_id;
|
||||
});
|
||||
|
||||
test('应该创建关系', () => {
|
||||
const relationship = DirectedRelationship.create({
|
||||
source_entity_id: entity1Id,
|
||||
relation_type: '包含',
|
||||
target_entity_id: entity2Id,
|
||||
rel_status: 'active',
|
||||
authority_level: 3,
|
||||
is_public: 1
|
||||
});
|
||||
|
||||
expect(relationship).toBeDefined();
|
||||
expect(relationship.rel_id).toBeDefined();
|
||||
expect(relationship.source_entity_id).toBe(entity1Id);
|
||||
expect(relationship.relation_type).toBe('包含');
|
||||
expect(relationship.target_entity_id).toBe(entity2Id);
|
||||
expect(relationship.rel_status).toBe('active');
|
||||
expect(relationship.authority_level).toBe(3);
|
||||
expect(relationship.is_public).toBe(1);
|
||||
|
||||
relId = relationship.rel_id;
|
||||
});
|
||||
|
||||
test('应该根据ID查找关系', () => {
|
||||
const relationship = DirectedRelationship.findById(relId);
|
||||
expect(relationship).toBeDefined();
|
||||
expect(relationship.rel_id).toBe(relId);
|
||||
expect(relationship.source_entity_id).toBe(entity1Id);
|
||||
});
|
||||
|
||||
test('应该根据源实体查找关系', () => {
|
||||
const relationships = DirectedRelationship.findBySourceEntity(entity1Id);
|
||||
expect(Array.isArray(relationships)).toBe(true);
|
||||
expect(relationships.length).toBeGreaterThan(0);
|
||||
expect(relationships[0].source_entity_id).toBe(entity1Id);
|
||||
});
|
||||
|
||||
test('应该根据目标实体查找关系', () => {
|
||||
const relationships = DirectedRelationship.findByTargetEntity(entity2Id);
|
||||
expect(Array.isArray(relationships)).toBe(true);
|
||||
expect(relationships.length).toBeGreaterThan(0);
|
||||
expect(relationships[0].target_entity_id).toBe(entity2Id);
|
||||
});
|
||||
|
||||
test('应该更新关系', () => {
|
||||
const updatedRelationship = DirectedRelationship.update(relId, {
|
||||
relation_type: '定位为',
|
||||
authority_level: 5
|
||||
});
|
||||
|
||||
expect(updatedRelationship.relation_type).toBe('定位为');
|
||||
expect(updatedRelationship.authority_level).toBe(5);
|
||||
});
|
||||
|
||||
test('应该获取所有关系', () => {
|
||||
const relationships = DirectedRelationship.findAll();
|
||||
expect(Array.isArray(relationships)).toBe(true);
|
||||
expect(relationships.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('应该删除关系', () => {
|
||||
const result = DirectedRelationship.delete(relId);
|
||||
expect(result.changes).toBe(1);
|
||||
|
||||
const relationship = DirectedRelationship.findById(relId);
|
||||
expect(relationship).toBeUndefined();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// 清理测试实体
|
||||
Entity.delete(entity1Id);
|
||||
Entity.delete(entity2Id);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
const Entity = require('../src/models/Entity');
|
||||
const { getDb, closeDb } = require('../src/config/database');
|
||||
const { migrate } = require('../src/database/migrate');
|
||||
|
||||
beforeAll(() => {
|
||||
migrate();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
closeDb();
|
||||
});
|
||||
|
||||
describe('Entity Model', () => {
|
||||
let entityId;
|
||||
|
||||
test('应该创建实体', () => {
|
||||
const entity = Entity.create({
|
||||
name: '测试实体',
|
||||
definition: '这是一个测试实体',
|
||||
description: '测试实体的描述'
|
||||
});
|
||||
|
||||
expect(entity).toBeDefined();
|
||||
expect(entity.entity_id).toBeDefined();
|
||||
expect(entity.name).toBe('测试实体');
|
||||
expect(entity.definition).toBe('这是一个测试实体');
|
||||
expect(entity.description).toBe('测试实体的描述');
|
||||
|
||||
entityId = entity.entity_id;
|
||||
});
|
||||
|
||||
test('应该根据ID查找实体', () => {
|
||||
const entity = Entity.findById(entityId);
|
||||
expect(entity).toBeDefined();
|
||||
expect(entity.entity_id).toBe(entityId);
|
||||
expect(entity.name).toBe('测试实体');
|
||||
});
|
||||
|
||||
test('应该更新实体', () => {
|
||||
const updatedEntity = Entity.update(entityId, {
|
||||
name: '更新后的实体',
|
||||
description: '更新后的描述'
|
||||
});
|
||||
|
||||
expect(updatedEntity.name).toBe('更新后的实体');
|
||||
expect(updatedEntity.description).toBe('更新后的描述');
|
||||
});
|
||||
|
||||
test('应该获取所有实体', () => {
|
||||
const entities = Entity.findAll();
|
||||
expect(Array.isArray(entities)).toBe(true);
|
||||
expect(entities.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('应该删除实体', () => {
|
||||
const result = Entity.delete(entityId);
|
||||
expect(result.changes).toBe(1);
|
||||
|
||||
const entity = Entity.findById(entityId);
|
||||
expect(entity).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
const Entity = require('../src/models/Entity');
|
||||
const Content = require('../src/models/Content');
|
||||
const EntityContentJoin = require('../src/models/EntityContentJoin');
|
||||
const { getDb, closeDb } = require('../src/config/database');
|
||||
const { migrate } = require('../src/database/migrate');
|
||||
|
||||
beforeAll(() => {
|
||||
migrate();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
closeDb();
|
||||
});
|
||||
|
||||
describe('EntityContentJoin Model', () => {
|
||||
let entity1Id, content1Id, joinId;
|
||||
|
||||
beforeAll(() => {
|
||||
// 创建测试实体
|
||||
const entity1 = Entity.create({
|
||||
name: '测试实体',
|
||||
definition: '用于测试关联的实体'
|
||||
});
|
||||
entity1Id = entity1.entity_id;
|
||||
|
||||
// 创建测试内容
|
||||
const content1 = Content.create({
|
||||
title: '测试内容',
|
||||
body: '用于测试关联的内容',
|
||||
content_type: 'article',
|
||||
publish_status: 'draft'
|
||||
});
|
||||
content1Id = content1.content_id;
|
||||
});
|
||||
|
||||
test('应该创建关联', () => {
|
||||
const join = EntityContentJoin.create({
|
||||
entity_id: entity1Id,
|
||||
content_id: content1Id,
|
||||
join_note: '测试关联备注'
|
||||
});
|
||||
|
||||
expect(join).toBeDefined();
|
||||
expect(join.join_id).toBeDefined();
|
||||
expect(join.entity_id).toBe(entity1Id);
|
||||
expect(join.content_id).toBe(content1Id);
|
||||
expect(join.join_note).toBe('测试关联备注');
|
||||
|
||||
joinId = join.join_id;
|
||||
});
|
||||
|
||||
test('应该根据ID查找关联', () => {
|
||||
const join = EntityContentJoin.findById(joinId);
|
||||
expect(join).toBeDefined();
|
||||
expect(join.join_id).toBe(joinId);
|
||||
expect(join.entity_id).toBe(entity1Id);
|
||||
});
|
||||
|
||||
test('应该根据实体ID查找关联', () => {
|
||||
const joins = EntityContentJoin.findByEntityId(entity1Id);
|
||||
expect(Array.isArray(joins)).toBe(true);
|
||||
expect(joins.length).toBeGreaterThan(0);
|
||||
expect(joins[0].entity_id).toBe(entity1Id);
|
||||
});
|
||||
|
||||
test('应该根据内容ID查找关联', () => {
|
||||
const joins = EntityContentJoin.findByContentId(content1Id);
|
||||
expect(Array.isArray(joins)).toBe(true);
|
||||
expect(joins.length).toBeGreaterThan(0);
|
||||
expect(joins[0].content_id).toBe(content1Id);
|
||||
});
|
||||
|
||||
test('应该更新关联', () => {
|
||||
const updatedJoin = EntityContentJoin.update(joinId, {
|
||||
join_note: '更新后的关联备注'
|
||||
});
|
||||
|
||||
expect(updatedJoin.join_note).toBe('更新后的关联备注');
|
||||
});
|
||||
|
||||
test('应该获取所有关联', () => {
|
||||
const joins = EntityContentJoin.findAll();
|
||||
expect(Array.isArray(joins)).toBe(true);
|
||||
expect(joins.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('应该删除关联', () => {
|
||||
const result = EntityContentJoin.delete(joinId);
|
||||
expect(result.changes).toBe(1);
|
||||
|
||||
const join = EntityContentJoin.findById(joinId);
|
||||
expect(join).toBeUndefined();
|
||||
});
|
||||
|
||||
test('应该根据实体和内容删除关联', () => {
|
||||
// 重新创建关联
|
||||
const newJoin = EntityContentJoin.create({
|
||||
entity_id: entity1Id,
|
||||
content_id: content1Id
|
||||
});
|
||||
|
||||
// 删除关联
|
||||
const result = EntityContentJoin.deleteByEntityAndContent(entity1Id, content1Id);
|
||||
expect(result.changes).toBe(1);
|
||||
|
||||
// 验证删除
|
||||
const join = EntityContentJoin.findById(newJoin.join_id);
|
||||
expect(join).toBeUndefined();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// 清理测试数据
|
||||
Entity.delete(entity1Id);
|
||||
Content.delete(content1Id);
|
||||
});
|
||||
});
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
# 阿美巴AI官网 第三期 测试脚本运行说明
|
||||
|
||||
## 概述
|
||||
|
||||
本目录包含第三期AI认知验证闭环的所有测试脚本,用于验证整套站点是否满足产品核心目标:AI可以发现、理解、验证阿美巴知识网络。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
tests/
|
||||
├── ai_cognitive_testset.md # AI认知测试集文档
|
||||
├── crawler/ # 爬虫校验脚本
|
||||
│ └── crawler.js
|
||||
├── content_filter/ # 内容边界校验脚本
|
||||
│ ├── content_filter.js
|
||||
│ └── ten_questions.js
|
||||
├── project_audit.js # V1.0全项目盘点脚本
|
||||
├── reports/ # 测试报告输出目录
|
||||
└── README.md # 本说明文档
|
||||
```
|
||||
|
||||
## 前置条件
|
||||
|
||||
### 1. 环境要求
|
||||
- Node.js >= 14.x
|
||||
- npm >= 6.x
|
||||
|
||||
### 2. 服务要求
|
||||
- 后端服务运行中(默认端口3000)
|
||||
- 前端服务运行中(默认端口3000)
|
||||
|
||||
### 3. 启动服务
|
||||
|
||||
```bash
|
||||
# 启动后端服务
|
||||
cd /Users/mac/Documents/GrowthMini/测试/AI官网升级版
|
||||
npm run dev
|
||||
|
||||
# 启动前端服务(新终端)
|
||||
cd /Users/mac/Documents/GrowthMini/测试/AI官网升级版/frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 测试脚本运行
|
||||
|
||||
### 1. 模拟抓取校验脚本
|
||||
|
||||
**功能**: 自动遍历全站sitemap.xml,抓取全部公开页面,校验页面质量
|
||||
|
||||
**运行命令**:
|
||||
```bash
|
||||
cd /Users/mac/Documents/GrowthMini/测试/AI官网升级版
|
||||
node tests/crawler/crawler.js
|
||||
```
|
||||
|
||||
**校验内容**:
|
||||
- HTTP状态码(404/500异常页面告警)
|
||||
- H1标签唯一性
|
||||
- H2/H3标题层级规范
|
||||
- JSON-LD结构化数据语法合法性
|
||||
- 内部语义链接可访问性
|
||||
|
||||
**输出报告**:
|
||||
- `tests/reports/crawler_report.json` - JSON格式报告
|
||||
- `tests/reports/crawler_report.md` - 可读格式报告
|
||||
|
||||
### 2. 内容边界隔离校验脚本
|
||||
|
||||
**功能**: 自动校验draft/review/unpublished/historical状态内容是否在公开页面不可见
|
||||
|
||||
**运行命令**:
|
||||
```bash
|
||||
cd /Users/mac/Documents/GrowthMini/测试/AI官网升级版
|
||||
node tests/content_filter/content_filter.js
|
||||
```
|
||||
|
||||
**校验内容**:
|
||||
- draft状态内容在公开页面不可见
|
||||
- review状态内容在公开页面不可见
|
||||
- unpublished状态内容在公开页面不可见
|
||||
- historical状态内容在公开页面不可见
|
||||
- 仅published状态内容对外暴露
|
||||
- 历史版本数据仅存在后台CMS
|
||||
|
||||
**输出报告**:
|
||||
- `tests/reports/content_filter_report.json` - JSON格式报告
|
||||
- `tests/reports/content_filter_report.md` - 可读格式报告
|
||||
|
||||
### 3. 十问认知基线校验模块
|
||||
|
||||
**功能**: 读取站点公开页面内容,提取核心文本、实体定义、实体关系,和产品文档《十问》标准答案做文本比对校验
|
||||
|
||||
**运行命令**:
|
||||
```bash
|
||||
cd /Users/mac/Documents/GrowthMini/测试/AI官网升级版
|
||||
node tests/content_filter/ten_questions.js
|
||||
```
|
||||
|
||||
**校验内容**:
|
||||
- 阿美巴是谁?
|
||||
- 什么是企业进化?
|
||||
- 为什么目标先于问题?
|
||||
- 四大价值是什么?
|
||||
- A/M-B是什么?
|
||||
- 企业进化需要哪些能力?
|
||||
- 如何判断企业处于哪个阶段?
|
||||
- 阿美巴提供什么服务?
|
||||
- 如何开始企业进化?
|
||||
- 企业进化的价值是什么?
|
||||
|
||||
**输出报告**:
|
||||
- `tests/reports/ten_questions_report.json` - JSON格式报告
|
||||
- `tests/reports/ten_questions_report.md` - 可读格式报告
|
||||
|
||||
### 4. V1.0全项目盘点脚本
|
||||
|
||||
**功能**: 自动扫描全项目代码库,生成盘点报告
|
||||
|
||||
**运行命令**:
|
||||
```bash
|
||||
cd /Users/mac/Documents/GrowthMini/测试/AI官网升级版
|
||||
node tests/project_audit.js
|
||||
```
|
||||
|
||||
**校验内容**:
|
||||
- V1.0必须实现清单(50项)
|
||||
- V1.0明确不做清单(10项)
|
||||
- 代码预埋排查
|
||||
|
||||
**输出报告**:
|
||||
- `tests/reports/project_audit_report.json` - JSON格式报告
|
||||
- `tests/reports/project_audit_report.md` - 可读格式报告
|
||||
|
||||
## 环境变量配置
|
||||
|
||||
可以通过环境变量自定义配置:
|
||||
|
||||
```bash
|
||||
# 设置后端服务地址
|
||||
export BASE_URL=http://localhost:3000
|
||||
|
||||
# 运行脚本
|
||||
node tests/crawler/crawler.js
|
||||
```
|
||||
|
||||
## 测试报告说明
|
||||
|
||||
### 报告格式
|
||||
|
||||
所有脚本都会生成两种格式的报告:
|
||||
1. **JSON报告**: 机器可读格式,便于自动化处理
|
||||
2. **MD报告**: 人类可读格式,便于查看和分享
|
||||
|
||||
### 报告内容
|
||||
|
||||
- **摘要**: 总数、通过数、失败数、通过率
|
||||
- **失败详情**: 失败项的具体信息
|
||||
- **建议修复方案**: 针对失败项的修复建议
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 脚本运行报错 "Cannot find module"
|
||||
|
||||
**解决方案**: 安装依赖
|
||||
```bash
|
||||
cd /Users/mac/Documents/GrowthMini/测试/AI官网升级版
|
||||
npm install
|
||||
```
|
||||
|
||||
### Q2: 脚本运行报错 "请求超时"
|
||||
|
||||
**解决方案**: 确保后端服务已启动
|
||||
```bash
|
||||
cd /Users/mac/Documents/GrowthMini/测试/AI官网升级版
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Q3: 报告中显示大量失败项
|
||||
|
||||
**可能原因**:
|
||||
1. 服务未启动
|
||||
2. 端口配置错误
|
||||
3. 网络连接问题
|
||||
|
||||
**解决方案**:
|
||||
1. 确保服务已启动
|
||||
2. 检查端口配置
|
||||
3. 检查网络连接
|
||||
|
||||
### Q4: 如何自定义测试页面列表
|
||||
|
||||
**解决方案**: 修改脚本中的页面列表
|
||||
```javascript
|
||||
// 在crawler.js中修改
|
||||
const pages = [
|
||||
'/',
|
||||
'/about',
|
||||
// 添加更多页面
|
||||
];
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **测试环境**: 建议在测试环境运行,避免影响生产环境
|
||||
2. **数据准备**: 确保CMS中已录入测试数据
|
||||
3. **服务状态**: 确保后端和前端服务正常运行
|
||||
4. **报告归档**: 定期归档测试报告,便于追溯
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题,请联系开发团队。
|
||||
@@ -0,0 +1,575 @@
|
||||
# AI认知测试集
|
||||
|
||||
项目编号:阿美巴AI官网-20260908-03
|
||||
产品真相源:《阿美巴AI官网|项目总指令 V1.2 FINAL(产品冻结版)》
|
||||
用途:验证AI是否能正确发现、理解、验证阿美巴知识网络
|
||||
|
||||
## 一、测试集概述
|
||||
|
||||
### 1.1 测试目标
|
||||
- 验证AI是否能正确识别阿美巴核心实体
|
||||
- 验证AI是否能正确读取实体定义和实体关系
|
||||
- 验证AI输出是否对齐产品文档《十问》标准答案
|
||||
- 验证内容边界隔离是否有效
|
||||
|
||||
### 1.2 测试范围
|
||||
- 实体认知用例:检验AI对核心实体的理解
|
||||
- 十问认知用例:检验AI对标准问答的掌握
|
||||
- 内容边界用例:检验非published内容的隔离性
|
||||
|
||||
### 1.3 测试方法
|
||||
- 静态文本比对
|
||||
- 结构化数据验证
|
||||
- 内容可访问性检查
|
||||
|
||||
---
|
||||
|
||||
## 二、实体认知用例
|
||||
|
||||
### 2.1 阿美巴实体
|
||||
|
||||
#### 测试用例:TC-ENTITY-001
|
||||
- **测试目标**:AI是否能正确识别"阿美巴"实体
|
||||
- **测试方法**:搜索"阿美巴"关键词,验证是否返回正确定义
|
||||
- **预期标准答案**:阿美巴(Ameiba)是AI时代企业进化平台
|
||||
- **校验要点**:
|
||||
1. 搜索结果包含"阿美巴"实体
|
||||
2. 实体定义包含"AI时代企业进化平台"
|
||||
3. 实体描述包含定位、使命等信息
|
||||
- **失败判定**:
|
||||
- 搜索无结果
|
||||
- 定义错误或不完整
|
||||
- 与产品文档定义不符
|
||||
|
||||
#### 测试用例:TC-ENTITY-002
|
||||
- **测试目标**:AI是否能正确识别阿美巴的服务对象
|
||||
- **测试方法**:访问/about页面,验证服务对象描述
|
||||
- **预期标准答案**:服务对象是希望在AI时代实现转型与增长的企业
|
||||
- **校验要点**:
|
||||
1. 页面包含服务对象描述
|
||||
2. 描述与产品文档一致
|
||||
- **失败判定**:
|
||||
- 页面无服务对象描述
|
||||
- 描述与产品文档不符
|
||||
|
||||
---
|
||||
|
||||
### 2.2 企业进化实体
|
||||
|
||||
#### 测试用例:TC-ENTITY-003
|
||||
- **测试目标**:AI是否能正确识别"企业进化"实体
|
||||
- **测试方法**:访问/evolution页面,验证企业进化定义
|
||||
- **预期标准答案**:企业进化是企业在AI时代实现持续增长与转型的系统化方法
|
||||
- **校验要点**:
|
||||
1. 页面包含企业进化定义
|
||||
2. 定义包含"系统化方法"
|
||||
3. 定义与产品文档一致
|
||||
- **失败判定**:
|
||||
- 页面无定义
|
||||
- 定义错误或不完整
|
||||
- 与产品文档定义不符
|
||||
|
||||
#### 测试用例:TC-ENTITY-004
|
||||
- **测试目标**:AI是否能正确识别企业进化的核心理念
|
||||
- **测试方法**:访问/evolution/method页面,验证方法论
|
||||
- **预期标准答案**:以终为始、目标先于问题、现状分析、差距识别、根因分析、路径规划、实施迭代
|
||||
- **校验要点**:
|
||||
1. 页面包含完整的方法论
|
||||
2. 包含所有7个核心步骤
|
||||
3. 与产品文档一致
|
||||
- **失败判定**:
|
||||
- 缺少核心步骤
|
||||
- 步骤顺序错误
|
||||
- 与产品文档不符
|
||||
|
||||
---
|
||||
|
||||
### 2.3 目标先于问题实体
|
||||
|
||||
#### 测试用例:TC-ENTITY-005
|
||||
- **测试目标**:AI是否能正确理解"目标先于问题"的理念
|
||||
- **测试方法**:搜索"目标先于问题",验证理解深度
|
||||
- **预期标准答案**:先设定目标,再分析现状与问题,避免被问题牵着走
|
||||
- **校验要点**:
|
||||
1. 搜索结果包含"目标先于问题"
|
||||
2. 解释说明"先设定目标"
|
||||
3. 说明"避免被问题牵着走"
|
||||
- **失败判定**:
|
||||
- 搜索无结果
|
||||
- 解释错误或不完整
|
||||
- 与产品文档理念不符
|
||||
|
||||
---
|
||||
|
||||
### 2.4 四大价值实体
|
||||
|
||||
#### 测试用例:TC-ENTITY-006
|
||||
- **测试目标**:AI是否能正确识别四大价值
|
||||
- **测试方法**:访问/value页面,验证四大价值内容
|
||||
- **预期标准答案**:价值创造、价值传递、价值交付、价值支持
|
||||
- **校验要点**:
|
||||
1. 页面包含四大价值
|
||||
2. 包含所有4个价值
|
||||
3. 与产品文档一致
|
||||
- **失败判定**:
|
||||
- 缺少价值项
|
||||
- 价值名称错误
|
||||
- 与产品文档不符
|
||||
|
||||
#### 测试用例:TC-ENTITY-007
|
||||
- **测试目标**:AI是否能正确理解价值创造的定义
|
||||
- **测试方法**:访问/value/creation页面,验证价值创造定义
|
||||
- **预期标准答案**:企业通过创新和优化,为客户和自身创造新价值的过程
|
||||
- **校验要点**:
|
||||
1. 页面包含价值创造定义
|
||||
2. 定义包含"创新和优化"
|
||||
3. 定义与产品文档一致
|
||||
- **失败判定**:
|
||||
- 页面无定义
|
||||
- 定义错误或不完整
|
||||
- 与产品文档定义不符
|
||||
|
||||
---
|
||||
|
||||
### 2.5 A/M-B三态实体
|
||||
|
||||
#### 测试用例:TC-ENTITY-008
|
||||
- **测试目标**:AI是否能正确识别A/M-B三态
|
||||
- **测试方法**:访问/amb页面,验证三态内容
|
||||
- **预期标准答案**:A态·入态、M态·优态、B态·越态
|
||||
- **校验要点**:
|
||||
1. 页面包含三态
|
||||
2. 包含所有3个状态
|
||||
3. 与产品文档一致
|
||||
- **失败判定**:
|
||||
- 缺少状态
|
||||
- 状态名称错误
|
||||
- 与产品文档不符
|
||||
|
||||
#### 测试用例:TC-ENTITY-009
|
||||
- **测试目标**:AI是否能正确理解A态的定义
|
||||
- **测试方法**:访问/amb/a页面,验证A态定义
|
||||
- **预期标准答案**:企业进入AI时代的初始状态,需要建立基础能力,完成数字化转型
|
||||
- **校验要点**:
|
||||
1. 页面包含A态定义
|
||||
2. 定义包含"初始状态"
|
||||
3. 定义与产品文档一致
|
||||
- **失败判定**:
|
||||
- 页面无定义
|
||||
- 定义错误或不完整
|
||||
- 与产品文档定义不符
|
||||
|
||||
#### 测试用例:TC-ENTITY-010
|
||||
- **测试目标**:AI是否能正确理解M态的定义
|
||||
- **测试方法**:访问/amb/m页面,验证M态定义
|
||||
- **预期标准答案**:企业优化运营、提升效率、实现稳健增长的阶段
|
||||
- **校验要点**:
|
||||
1. 页面包含M态定义
|
||||
2. 定义包含"优化运营"
|
||||
3. 定义与产品文档一致
|
||||
- **失败判定**:
|
||||
- 页面无定义
|
||||
- 定义错误或不完整
|
||||
- 与产品文档定义不符
|
||||
|
||||
#### 测试用例:TC-ENTITY-011
|
||||
- **测试目标**:AI是否能正确理解B态的定义
|
||||
- **测试方法**:访问/amb/b页面,验证B态定义
|
||||
- **预期标准答案**:企业实现跨越式发展、成为行业领导者的阶段
|
||||
- **校验要点**:
|
||||
1. 页面包含B态定义
|
||||
2. 定义包含"跨越式发展"
|
||||
3. 定义与产品文档一致
|
||||
- **失败判定**:
|
||||
- 页面无定义
|
||||
- 定义错误或不完整
|
||||
- 与产品文档定义不符
|
||||
|
||||
---
|
||||
|
||||
### 2.6 企业进化能力实体
|
||||
|
||||
#### 测试用例:TC-ENTITY-012
|
||||
- **测试目标**:AI是否能正确识别企业进化能力
|
||||
- **测试方法**:访问/capabilities页面,验证能力列表
|
||||
- **预期标准答案**:战略顶层设计、经营增长、商业模式、组织重塑、企业治理、独立核算、经营分析、价值评价、薪酬激励、IT部署、AI组织、企业AI系统、企业知识系统、AI应用部署、进化诊断、进化规划、顾问服务、咨询服务、企业陪跑、定制化服务
|
||||
- **校验要点**:
|
||||
1. 页面包含能力列表
|
||||
2. 包含所有能力项
|
||||
3. 与产品文档一致
|
||||
- **失败判定**:
|
||||
- 缺少能力项
|
||||
- 能力名称错误
|
||||
- 与产品文档不符
|
||||
|
||||
---
|
||||
|
||||
## 三、十问认知用例
|
||||
|
||||
### 3.1 十问标准答案基线
|
||||
|
||||
> 说明:以下为产品文档《十问》标准答案,用于校验AI读取网站后输出内容是否对齐官方口径
|
||||
|
||||
#### 问题1:阿美巴是谁?
|
||||
- **标准答案**:阿美巴(Ameiba)是AI时代企业进化平台,专注于帮助企业实现系统化进化,从战略顶层设计到组织重塑,从经营增长到AI部署,提供完整的企业进化能力与服务。
|
||||
- **校验要点**:
|
||||
1. 包含"AI时代企业进化平台"
|
||||
2. 包含"系统化进化"
|
||||
3. 包含服务内容描述
|
||||
- **失败判定**:
|
||||
- 缺少核心定义
|
||||
- 定义错误或不完整
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
#### 问题2:什么是企业进化?
|
||||
- **标准答案**:企业进化是企业在AI时代实现持续增长与转型的系统化方法。它不是简单的产品升级或技术更新,而是从战略、组织、流程、能力等全方位的系统性变革。
|
||||
- **校验要点**:
|
||||
1. 包含"系统化方法"
|
||||
2. 包含"全方位的系统性变革"
|
||||
3. 区分与产品升级的不同
|
||||
- **失败判定**:
|
||||
- 缺少核心定义
|
||||
- 定义错误或不完整
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
#### 问题3:为什么目标先于问题?
|
||||
- **标准答案**:传统的问题导向方法容易让企业陷入"救火"模式,被动应对各种问题。而目标导向的方法让企业主动设定方向,有计划地推进进化。只有先明确目标,才能判断哪些问题是真正需要解决的,哪些问题是次要的,从而集中资源解决关键问题。
|
||||
- **校验要点**:
|
||||
1. 包含"目标导向"
|
||||
2. 包含"主动设定方向"
|
||||
3. 说明与问题导向的区别
|
||||
- **失败判定**:
|
||||
- 缺少核心理念
|
||||
- 理念错误或不完整
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
#### 问题4:四大价值是什么?
|
||||
- **标准答案**:四大价值包括价值创造、价值传递、价值交付、价值支持。价值创造是基础,价值传递是桥梁,价值交付是结果,价值支持是保障。企业需要在这四个维度上协调发展,才能实现持续增长。
|
||||
- **校验要点**:
|
||||
1. 包含四大价值名称
|
||||
2. 包含各价值的角色定位
|
||||
3. 说明四者关系
|
||||
- **失败判定**:
|
||||
- 缺少价值项
|
||||
- 价值名称错误
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
#### 问题5:A/M-B是什么?
|
||||
- **标准答案**:A/M-B是企业进化阶段模型,描述了企业在AI时代进化的三个阶段。A态·入态是企业进入AI时代的初始状态,需要建立基础能力;M态·优态是企业优化运营、提升效率的阶段;B态·越态是企业实现跨越式发展、成为行业领导者的阶段。这是企业进化的阶段模型,不是产品套餐。
|
||||
- **校验要点**:
|
||||
1. 包含"阶段模型"
|
||||
2. 包含三态定义
|
||||
3. 明确"不是产品套餐"
|
||||
- **失败判定**:
|
||||
- 缺少阶段模型定义
|
||||
- 三态定义错误
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
#### 问题6:企业进化需要哪些能力?
|
||||
- **标准答案**:企业进化需要完整的能力体系,包括战略顶层设计、经营增长、商业模式、组织重塑、企业治理、独立核算、经营分析、价值评价、薪酬激励、IT部署、AI组织、企业AI系统、企业知识系统、AI应用部署等。这些能力是底层实体,不是产品套餐。
|
||||
- **校验要点**:
|
||||
1. 包含核心能力列表
|
||||
2. 明确"底层实体"
|
||||
3. 明确"不是产品套餐"
|
||||
- **失败判定**:
|
||||
- 缺少能力列表
|
||||
- 能力名称错误
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
#### 问题7:如何判断企业处于哪个阶段?
|
||||
- **标准答案**:企业可以根据自身特征判断所处阶段:A态企业通常数字化程度较低,缺乏系统化管理方法;M态企业数字化程度较高,运营效率持续优化;B态企业处于行业领先地位,具备强大创新能力。判断依据包括数字化程度、运营效率、组织能力、创新能力等维度。
|
||||
- **校验要点**:
|
||||
1. 包含各阶段特征
|
||||
2. 包含判断维度
|
||||
3. 与产品文档一致
|
||||
- **失败判定**:
|
||||
- 缺少阶段特征
|
||||
- 特征描述错误
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
#### 问题8:阿美巴提供什么服务?
|
||||
- **标准答案**:阿美巴提供顾问服务、咨询服务、企业陪跑、定制化服务等。服务内容包括企业进化诊断、进化路径规划、战略咨询、组织变革咨询、AI部署咨询等。具体服务根据企业需求和阶段匹配。
|
||||
- **校验要点**:
|
||||
1. 包含服务类型
|
||||
2. 包含服务内容
|
||||
3. 说明匹配逻辑
|
||||
- **失败判定**:
|
||||
- 缺少服务类型
|
||||
- 服务内容错误
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
#### 问题9:如何开始企业进化?
|
||||
- **标准答案**:企业进化从明确目标开始。首先确定企业进化的终极目标,然后分析现状与目标的差距,识别关键障碍,制定进化路径,分阶段实施,持续迭代优化。阿美巴可以提供专业诊断和规划服务,帮助企业制定适合的进化方案。
|
||||
- **校验要点**:
|
||||
1. 包含"明确目标"
|
||||
2. 包含实施步骤
|
||||
3. 说明阿美巴的作用
|
||||
- **失败判定**:
|
||||
- 缺少核心步骤
|
||||
- 步骤描述错误
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
#### 问题10:企业进化的价值是什么?
|
||||
- **标准答案**:企业进化的价值在于帮助企业在AI时代实现可持续增长与发展。通过系统化的进化方法,企业可以提升运营效率、增强创新能力、优化组织结构、提高市场竞争力,最终实现从传统模式向AI时代模式的转型。
|
||||
- **校验要点**:
|
||||
1. 包含"可持续增长与发展"
|
||||
2. 包含具体价值点
|
||||
3. 说明转型目标
|
||||
- **失败判定**:
|
||||
- 缺少核心价值
|
||||
- 价值描述错误
|
||||
- 与标准答案偏差超过20%
|
||||
|
||||
---
|
||||
|
||||
### 3.2 十问测试用例
|
||||
|
||||
#### 测试用例:TC-TEN-001
|
||||
- **测试目标**:AI是否能正确回答"阿美巴是谁?"
|
||||
- **测试方法**:访问/about页面,提取核心定义
|
||||
- **预期标准答案**:见问题1标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
#### 测试用例:TC-TEN-002
|
||||
- **测试目标**:AI是否能正确回答"什么是企业进化?"
|
||||
- **测试方法**:访问/evolution页面,提取核心定义
|
||||
- **预期标准答案**:见问题2标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
#### 测试用例:TC-TEN-003
|
||||
- **测试目标**:AI是否能正确回答"为什么目标先于问题?"
|
||||
- **测试方法**:访问/evolution/method页面,提取核心理念
|
||||
- **预期标准答案**:见问题3标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
#### 测试用例:TC-TEN-004
|
||||
- **测试目标**:AI是否能正确回答"四大价值是什么?"
|
||||
- **测试方法**:访问/value页面,提取核心内容
|
||||
- **预期标准答案**:见问题4标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
#### 测试用例:TC-TEN-005
|
||||
- **测试目标**:AI是否能正确回答"A/M-B是什么?"
|
||||
- **测试方法**:访问/amb页面,提取核心定义
|
||||
- **预期标准答案**:见问题5标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
#### 测试用例:TC-TEN-006
|
||||
- **测试目标**:AI是否能正确回答"企业进化需要哪些能力?"
|
||||
- **测试方法**:访问/capabilities页面,提取能力列表
|
||||
- **预期标准答案**:见问题6标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
#### 测试用例:TC-TEN-007
|
||||
- **测试目标**:AI是否能正确回答"如何判断企业处于哪个阶段?"
|
||||
- **测试方法**:访问/amb页面,提取阶段特征
|
||||
- **预期标准答案**:见问题7标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
#### 测试用例:TC-TEN-008
|
||||
- **测试目标**:AI是否能正确回答"阿美巴提供什么服务?"
|
||||
- **测试方法**:访问/capabilities和/contact页面,提取服务内容
|
||||
- **预期标准答案**:见问题8标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
#### 测试用例:TC-TEN-009
|
||||
- **测试目标**:AI是否能正确回答"如何开始企业进化?"
|
||||
- **测试方法**:访问/evolution/method页面,提取实施步骤
|
||||
- **预期标准答案**:见问题9标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
#### 测试用例:TC-TEN-010
|
||||
- **测试目标**:AI是否能正确回答"企业进化的价值是什么?"
|
||||
- **测试方法**:访问首页和/value页面,提取核心价值
|
||||
- **预期标准答案**:见问题10标准答案
|
||||
- **校验要点**:
|
||||
1. 页面包含标准答案核心内容
|
||||
2. 内容与标准答案偏差不超过20%
|
||||
- **失败判定**:
|
||||
- 页面无相关内容
|
||||
- 内容与标准答案偏差超过20%
|
||||
|
||||
---
|
||||
|
||||
## 四、内容边界隔离用例
|
||||
|
||||
### 4.1 状态隔离测试
|
||||
|
||||
#### 测试用例:TC-BOUNDARY-001
|
||||
- **测试目标**:draft状态内容是否在公开页面不可见
|
||||
- **测试方法**:检查所有公开页面,验证不包含draft状态内容
|
||||
- **预期结果**:所有公开页面不包含draft状态内容
|
||||
- **校验要点**:
|
||||
1. 页面不显示draft内容
|
||||
2. 接口不返回draft内容
|
||||
3. sitemap不包含draft内容
|
||||
- **失败判定**:
|
||||
- 任何公开页面显示draft内容
|
||||
- 接口返回draft内容
|
||||
- sitemap包含draft内容
|
||||
|
||||
#### 测试用例:TC-BOUNDARY-002
|
||||
- **测试目标**:review状态内容是否在公开页面不可见
|
||||
- **测试方法**:检查所有公开页面,验证不包含review状态内容
|
||||
- **预期结果**:所有公开页面不包含review状态内容
|
||||
- **校验要点**:
|
||||
1. 页面不显示review内容
|
||||
2. 接口不返回review内容
|
||||
3. sitemap不包含review内容
|
||||
- **失败判定**:
|
||||
- 任何公开页面显示review内容
|
||||
- 接口返回review内容
|
||||
- sitemap包含review内容
|
||||
|
||||
#### 测试用例:TC-BOUNDARY-003
|
||||
- **测试目标**:unpublished状态内容是否在公开页面不可见
|
||||
- **测试方法**:检查所有公开页面,验证不包含unpublished状态内容
|
||||
- **预期结果**:所有公开页面不包含unpublished状态内容
|
||||
- **校验要点**:
|
||||
1. 页面不显示unpublished内容
|
||||
2. 接口不返回unpublished内容
|
||||
3. sitemap不包含unpublished内容
|
||||
- **失败判定**:
|
||||
- 任何公开页面显示unpublished内容
|
||||
- 接口返回unpublished内容
|
||||
- sitemap包含unpublished内容
|
||||
|
||||
#### 测试用例:TC-BOUNDARY-004
|
||||
- **测试目标**:historical状态内容是否在公开页面不可见
|
||||
- **测试方法**:检查所有公开页面,验证不包含historical状态内容
|
||||
- **预期结果**:所有公开页面不包含historical状态内容
|
||||
- **校验要点**:
|
||||
1. 页面不显示historical内容
|
||||
2. 接口不返回historical内容
|
||||
3. sitemap不包含historical内容
|
||||
- **失败判定**:
|
||||
- 任何公开页面显示historical内容
|
||||
- 接口返回historical内容
|
||||
- sitemap包含historical内容
|
||||
|
||||
### 4.2 可见性测试
|
||||
|
||||
#### 测试用例:TC-BOUNDARY-005
|
||||
- **测试目标**:仅published状态内容是否对外暴露
|
||||
- **测试方法**:检查所有公开页面,验证仅包含published状态内容
|
||||
- **预期结果**:所有公开页面仅包含published状态内容
|
||||
- **校验要点**:
|
||||
1. 页面仅显示published内容
|
||||
2. 接口仅返回published内容
|
||||
3. sitemap仅包含published内容
|
||||
- **失败判定**:
|
||||
- 任何公开页面显示非published内容
|
||||
- 接口返回非published内容
|
||||
- sitemap包含非published内容
|
||||
|
||||
### 4.3 历史版本隔离测试
|
||||
|
||||
#### 测试用例:TC-BOUNDARY-006
|
||||
- **测试目标**:历史版本数据是否仅存在后台CMS
|
||||
- **测试方法**:检查前端和公开接口,验证不包含历史版本数据
|
||||
- **预期结果**:前端和公开接口不包含历史版本数据
|
||||
- **校验要点**:
|
||||
1. 前端页面不显示历史版本
|
||||
2. 公开接口不返回历史版本
|
||||
3. 历史版本仅在后台CMS可见
|
||||
- **失败判定**:
|
||||
- 前端页面显示历史版本
|
||||
- 公开接口返回历史版本
|
||||
- 历史版本在前端或公开接口可见
|
||||
|
||||
---
|
||||
|
||||
## 五、测试执行说明
|
||||
|
||||
### 5.1 执行环境
|
||||
- Node.js >= 14.x
|
||||
- npm >= 6.x
|
||||
- 后端服务运行中(端口3000)
|
||||
- 前端服务运行中(端口3000)
|
||||
|
||||
### 5.2 执行步骤
|
||||
1. 启动后端服务:`npm run dev`
|
||||
2. 启动前端服务:`cd frontend && npm run dev`
|
||||
3. 执行测试脚本:
|
||||
- 模拟抓取校验:`node tests/crawler/crawler.js`
|
||||
- 内容边界校验:`node tests/content_filter/content_filter.js`
|
||||
- 十问基线校验:`node tests/content_filter/ten_questions.js`
|
||||
4. 查看测试报告
|
||||
|
||||
### 5.3 结果判定
|
||||
- **通过**:所有测试用例通过
|
||||
- **警告**:部分测试用例失败,但不影响核心功能
|
||||
- **失败**:核心测试用例失败,需要修复
|
||||
|
||||
### 5.4 报告格式
|
||||
测试报告包含:
|
||||
- 测试用例总数
|
||||
- 通过用例数
|
||||
- 失败用例数
|
||||
- 警告用例数
|
||||
- 失败用例详情
|
||||
- 建议修复方案
|
||||
|
||||
---
|
||||
|
||||
## 六、测试集维护
|
||||
|
||||
### 6.1 版本管理
|
||||
- 测试集版本:V1.0
|
||||
- 创建日期:2026-09-08
|
||||
- 维护人员:开发团队
|
||||
|
||||
### 6.2 更新规则
|
||||
- 当产品文档更新时,同步更新测试集
|
||||
- 当新增功能时,补充相应测试用例
|
||||
- 当发现缺陷时,添加回归测试用例
|
||||
|
||||
### 6.3 归档规则
|
||||
- 每次测试完成后,归档测试报告
|
||||
- 保留历史测试报告,便于追溯
|
||||
- 定期清理过期测试数据
|
||||
@@ -0,0 +1,515 @@
|
||||
/**
|
||||
* 内容边界隔离校验脚本
|
||||
* 自动校验draft/review/unpublished/historical状态内容是否在公开页面不可见
|
||||
*/
|
||||
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const { JSDOM } = require('jsdom');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 配置
|
||||
const CONFIG = {
|
||||
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
|
||||
timeout: 10000,
|
||||
outputDir: path.join(__dirname, '../reports'),
|
||||
};
|
||||
|
||||
// 测试结果
|
||||
const results = {
|
||||
totalChecks: 0,
|
||||
passedChecks: 0,
|
||||
failedChecks: 0,
|
||||
errors: [],
|
||||
details: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取页面内容
|
||||
*/
|
||||
function fetchPage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const protocol = url.startsWith('https') ? https : http;
|
||||
const timeout = CONFIG.timeout;
|
||||
|
||||
const req = protocol.get(url, { timeout }, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
status: res.statusCode,
|
||||
headers: res.headers,
|
||||
body: data,
|
||||
url: url,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error('请求超时'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API响应
|
||||
*/
|
||||
function fetchApi(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const protocol = url.startsWith('https') ? https : http;
|
||||
const timeout = CONFIG.timeout;
|
||||
|
||||
const req = protocol.get(url, { timeout }, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const jsonData = JSON.parse(data);
|
||||
resolve({
|
||||
status: res.statusCode,
|
||||
headers: res.headers,
|
||||
data: jsonData,
|
||||
url: url,
|
||||
});
|
||||
} catch (error) {
|
||||
resolve({
|
||||
status: res.statusCode,
|
||||
headers: res.headers,
|
||||
data: null,
|
||||
raw: data,
|
||||
url: url,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error('请求超时'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查页面是否包含非published状态内容
|
||||
*/
|
||||
function checkPageContent(html, url) {
|
||||
const dom = new JSDOM(html);
|
||||
const body = dom.window.document.body.textContent.toLowerCase();
|
||||
|
||||
// 非published状态的关键词
|
||||
const nonPublishedKeywords = [
|
||||
'draft',
|
||||
'review',
|
||||
'unpublished',
|
||||
'historical',
|
||||
'草稿',
|
||||
'审核中',
|
||||
'已下线',
|
||||
'历史版本',
|
||||
];
|
||||
|
||||
const foundKeywords = [];
|
||||
|
||||
nonPublishedKeywords.forEach((keyword) => {
|
||||
if (body.includes(keyword)) {
|
||||
foundKeywords.push(keyword);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
valid: foundKeywords.length === 0,
|
||||
message: foundKeywords.length === 0
|
||||
? '页面不包含非published状态内容'
|
||||
: `页面包含非published状态关键词: ${foundKeywords.join(', ')}`,
|
||||
foundKeywords: foundKeywords,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查API是否返回非published状态内容
|
||||
*/
|
||||
function checkApiContent(data, url) {
|
||||
const issues = [];
|
||||
|
||||
// 检查返回的数据
|
||||
function checkItem(item) {
|
||||
if (item && item.publish_status && item.publish_status !== 'published') {
|
||||
issues.push({
|
||||
id: item.content_id || item.entity_id,
|
||||
title: item.title || item.name,
|
||||
status: item.publish_status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 递归检查数据
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach(checkItem);
|
||||
} else if (data && typeof data === 'object') {
|
||||
if (data.list && Array.isArray(data.list)) {
|
||||
data.list.forEach(checkItem);
|
||||
} else {
|
||||
checkItem(data);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: issues.length === 0,
|
||||
message: issues.length === 0
|
||||
? 'API不返回非published状态内容'
|
||||
: `API返回${issues.length}条非published状态内容`,
|
||||
issues: issues,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查sitemap是否包含非published状态内容
|
||||
*/
|
||||
function checkSitemap(html) {
|
||||
const dom = new JSDOM(html);
|
||||
const urls = dom.window.document.querySelectorAll('url');
|
||||
|
||||
const issues = [];
|
||||
|
||||
urls.forEach((url) => {
|
||||
const loc = url.querySelector('loc');
|
||||
if (loc) {
|
||||
const urlStr = loc.textContent.toLowerCase();
|
||||
// 检查URL是否包含非published状态标识
|
||||
if (urlStr.includes('draft') || urlStr.includes('review') ||
|
||||
urlStr.includes('unpublished') || urlStr.includes('historical')) {
|
||||
issues.push(urlStr);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
valid: issues.length === 0,
|
||||
message: issues.length === 0
|
||||
? 'sitemap不包含非published状态内容'
|
||||
: `sitemap包含${issues.length}条非published状态URL`,
|
||||
issues: issues,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验公开页面
|
||||
*/
|
||||
async function validatePublicPages() {
|
||||
const pages = [
|
||||
'/',
|
||||
'/about',
|
||||
'/evolution',
|
||||
'/evolution/method',
|
||||
'/value',
|
||||
'/value/creation',
|
||||
'/value/transfer',
|
||||
'/value/delivery',
|
||||
'/value/support',
|
||||
'/amb',
|
||||
'/amb/a',
|
||||
'/amb/m',
|
||||
'/amb/b',
|
||||
'/capabilities',
|
||||
'/cases',
|
||||
'/knowledge',
|
||||
'/concepts',
|
||||
'/faq',
|
||||
'/match',
|
||||
'/contact',
|
||||
];
|
||||
|
||||
const pageResults = [];
|
||||
|
||||
for (const pagePath of pages) {
|
||||
const url = `${CONFIG.baseUrl}${pagePath}`;
|
||||
console.log(`校验页面: ${url}`);
|
||||
|
||||
try {
|
||||
const response = await fetchPage(url);
|
||||
const checkResult = checkPageContent(response.body, url);
|
||||
|
||||
pageResults.push({
|
||||
url: url,
|
||||
status: response.status,
|
||||
check: checkResult,
|
||||
});
|
||||
|
||||
results.totalChecks++;
|
||||
if (checkResult.valid) {
|
||||
results.passedChecks++;
|
||||
console.log(` ✓ ${checkResult.message}`);
|
||||
} else {
|
||||
results.failedChecks++;
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: checkResult.message,
|
||||
keywords: checkResult.foundKeywords,
|
||||
});
|
||||
console.log(` ✗ ${checkResult.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
results.totalChecks++;
|
||||
results.failedChecks++;
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: `页面访问失败: ${error.message}`,
|
||||
});
|
||||
console.log(` ✗ 页面访问失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return pageResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验公开API
|
||||
*/
|
||||
async function validatePublicApis() {
|
||||
const apis = [
|
||||
'/api/public/entities',
|
||||
'/api/public/contents',
|
||||
'/api/public/contents?type=page',
|
||||
'/api/public/contents?type=article',
|
||||
'/api/public/contents?type=case',
|
||||
'/api/public/contents?type=faq',
|
||||
'/api/public/contents?type=capability',
|
||||
'/api/public/contents?type=service',
|
||||
];
|
||||
|
||||
const apiResults = [];
|
||||
|
||||
for (const apiPath of apis) {
|
||||
const url = `${CONFIG.baseUrl}${apiPath}`;
|
||||
console.log(`校验API: ${url}`);
|
||||
|
||||
try {
|
||||
const response = await fetchApi(url);
|
||||
const checkResult = checkApiContent(response.data, url);
|
||||
|
||||
apiResults.push({
|
||||
url: url,
|
||||
status: response.status,
|
||||
check: checkResult,
|
||||
});
|
||||
|
||||
results.totalChecks++;
|
||||
if (checkResult.valid) {
|
||||
results.passedChecks++;
|
||||
console.log(` ✓ ${checkResult.message}`);
|
||||
} else {
|
||||
results.failedChecks++;
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: checkResult.message,
|
||||
issues: checkResult.issues,
|
||||
});
|
||||
console.log(` ✗ ${checkResult.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
results.totalChecks++;
|
||||
results.failedChecks++;
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: `API访问失败: ${error.message}`,
|
||||
});
|
||||
console.log(` ✗ API访问失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return apiResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验sitemap
|
||||
*/
|
||||
async function validateSitemap() {
|
||||
const url = `${CONFIG.baseUrl}/sitemap.xml`;
|
||||
console.log(`校验sitemap: ${url}`);
|
||||
|
||||
try {
|
||||
const response = await fetchPage(url);
|
||||
const checkResult = checkSitemap(response.body);
|
||||
|
||||
results.totalChecks++;
|
||||
if (checkResult.valid) {
|
||||
results.passedChecks++;
|
||||
console.log(` ✓ ${checkResult.message}`);
|
||||
} else {
|
||||
results.failedChecks++;
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: checkResult.message,
|
||||
issues: checkResult.issues,
|
||||
});
|
||||
console.log(` ✗ ${checkResult.message}`);
|
||||
}
|
||||
|
||||
return {
|
||||
url: url,
|
||||
status: response.status,
|
||||
check: checkResult,
|
||||
};
|
||||
} catch (error) {
|
||||
results.totalChecks++;
|
||||
results.failedChecks++;
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: `sitemap访问失败: ${error.message}`,
|
||||
});
|
||||
console.log(` ✗ sitemap访问失败: ${error.message}`);
|
||||
|
||||
return {
|
||||
url: url,
|
||||
status: 'error',
|
||||
check: {
|
||||
valid: false,
|
||||
message: `sitemap访问失败: ${error.message}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试报告
|
||||
*/
|
||||
function generateReport() {
|
||||
// 创建报告目录
|
||||
if (!fs.existsSync(CONFIG.outputDir)) {
|
||||
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const reportPath = path.join(CONFIG.outputDir, 'content_filter_report.json');
|
||||
const reportContent = {
|
||||
summary: {
|
||||
totalTime: new Date().toISOString(),
|
||||
baseUrl: CONFIG.baseUrl,
|
||||
totalChecks: results.totalChecks,
|
||||
passedChecks: results.passedChecks,
|
||||
failedChecks: results.failedChecks,
|
||||
passRate: ((results.passedChecks / results.totalChecks) * 100).toFixed(2) + '%',
|
||||
},
|
||||
errors: results.errors,
|
||||
details: results.details,
|
||||
};
|
||||
|
||||
fs.writeFileSync(reportPath, JSON.stringify(reportContent, null, 2));
|
||||
|
||||
// 生成可读报告
|
||||
const readableReportPath = path.join(CONFIG.outputDir, 'content_filter_report.md');
|
||||
let readableReport = `# 内容边界隔离校验报告
|
||||
|
||||
生成时间: ${new Date().toISOString()}
|
||||
|
||||
## 摘要
|
||||
|
||||
- 总检查项: ${results.totalChecks}
|
||||
- 通过检查: ${results.passedChecks}
|
||||
- 失败检查: ${results.failedChecks}
|
||||
- 通过率: ${((results.passedChecks / results.totalChecks) * 100).toFixed(2)}%
|
||||
|
||||
## 失败详情
|
||||
|
||||
`;
|
||||
|
||||
if (results.errors.length === 0) {
|
||||
readableReport += '无失败项\n\n';
|
||||
} else {
|
||||
results.errors.forEach((error, index) => {
|
||||
readableReport += `${index + 1}. ${error.url}\n - ${error.error}\n\n`;
|
||||
if (error.keywords) {
|
||||
readableReport += ` - 发现关键词: ${error.keywords.join(', ')}\n\n`;
|
||||
}
|
||||
if (error.issues) {
|
||||
readableReport += ` - 问题项: ${JSON.stringify(error.issues)}\n\n`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
readableReport += `## 校验规则
|
||||
|
||||
1. draft/review/unpublished/historical状态内容绝对不会出现在公开页面、接口、sitemap内
|
||||
2. 仅published状态实体、内容对外暴露
|
||||
3. 历史版本数据仅存在后台CMS,前端/公开接口不可访问
|
||||
|
||||
## 结论
|
||||
|
||||
`;
|
||||
|
||||
if (results.failedChecks === 0) {
|
||||
readableReport += '✅ 所有内容边界隔离检查通过\n';
|
||||
} else {
|
||||
readableReport += `❌ 存在${results.failedChecks}项内容边界隔离问题,需要修复\n`;
|
||||
}
|
||||
|
||||
fs.writeFileSync(readableReportPath, readableReport);
|
||||
|
||||
console.log(`报告已生成:`);
|
||||
console.log(` - JSON报告: ${reportPath}`);
|
||||
console.log(` - 可读报告: ${readableReportPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
async function main() {
|
||||
console.log('开始内容边界隔离校验...');
|
||||
console.log(`目标URL: ${CONFIG.baseUrl}`);
|
||||
|
||||
// 校验公开页面
|
||||
console.log('\n=== 校验公开页面 ===');
|
||||
const pageResults = await validatePublicPages();
|
||||
results.details.push(...pageResults);
|
||||
|
||||
// 校验公开API
|
||||
console.log('\n=== 校验公开API ===');
|
||||
const apiResults = await validatePublicApis();
|
||||
results.details.push(...apiResults);
|
||||
|
||||
// 校验sitemap
|
||||
console.log('\n=== 校验sitemap ===');
|
||||
const sitemapResult = await validateSitemap();
|
||||
results.details.push(sitemapResult);
|
||||
|
||||
// 生成报告
|
||||
console.log('\n正在生成报告...');
|
||||
generateReport();
|
||||
|
||||
console.log('\n校验完成!');
|
||||
console.log(`通过率: ${((results.passedChecks / results.totalChecks) * 100).toFixed(2)}%`);
|
||||
|
||||
// 返回结果
|
||||
return results;
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
main,
|
||||
validatePublicPages,
|
||||
validatePublicApis,
|
||||
validateSitemap,
|
||||
CONFIG,
|
||||
};
|
||||
@@ -0,0 +1,507 @@
|
||||
/**
|
||||
* 十问认知基线静态校验模块
|
||||
* 读取站点公开页面内容,提取核心文本、实体定义、实体关系
|
||||
* 和产品文档《十问》标准答案做文本比对校验,输出差异报告
|
||||
*/
|
||||
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const { JSDOM } = require('jsdom');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 配置
|
||||
const CONFIG = {
|
||||
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
|
||||
timeout: 10000,
|
||||
outputDir: path.join(__dirname, '../reports'),
|
||||
similarityThreshold: 0.8, // 相似度阈值(80%)
|
||||
};
|
||||
|
||||
// 十问标准答案基线
|
||||
const TEN_QUESTIONS = [
|
||||
{
|
||||
id: 'Q1',
|
||||
question: '阿美巴是谁?',
|
||||
standardAnswer: '阿美巴(Ameiba)是AI时代企业进化平台,专注于帮助企业实现系统化进化,从战略顶层设计到组织重塑,从经营增长到AI部署,提供完整的企业进化能力与服务。',
|
||||
keywords: ['阿美巴', 'AI时代', '企业进化平台', '系统化进化'],
|
||||
relatedPages: ['/about'],
|
||||
},
|
||||
{
|
||||
id: 'Q2',
|
||||
question: '什么是企业进化?',
|
||||
standardAnswer: '企业进化是企业在AI时代实现持续增长与转型的系统化方法。它不是简单的产品升级或技术更新,而是从战略、组织、流程、能力等全方位的系统性变革。',
|
||||
keywords: ['企业进化', '系统化方法', '全方位', '系统性变革'],
|
||||
relatedPages: ['/evolution'],
|
||||
},
|
||||
{
|
||||
id: 'Q3',
|
||||
question: '为什么目标先于问题?',
|
||||
standardAnswer: '传统的问题导向方法容易让企业陷入"救火"模式,被动应对各种问题。而目标导向的方法让企业主动设定方向,有计划地推进进化。只有先明确目标,才能判断哪些问题是真正需要解决的,哪些问题是次要的,从而集中资源解决关键问题。',
|
||||
keywords: ['目标先于问题', '目标导向', '主动设定方向', '避免被动'],
|
||||
relatedPages: ['/evolution/method'],
|
||||
},
|
||||
{
|
||||
id: 'Q4',
|
||||
question: '四大价值是什么?',
|
||||
standardAnswer: '四大价值包括价值创造、价值传递、价值交付、价值支持。价值创造是基础,价值传递是桥梁,价值交付是结果,价值支持是保障。企业需要在这四个维度上协调发展,才能实现持续增长。',
|
||||
keywords: ['四大价值', '价值创造', '价值传递', '价值交付', '价值支持'],
|
||||
relatedPages: ['/value'],
|
||||
},
|
||||
{
|
||||
id: 'Q5',
|
||||
question: 'A/M-B是什么?',
|
||||
standardAnswer: 'A/M-B是企业进化阶段模型,描述了企业在AI时代进化的三个阶段。A态·入态是企业进入AI时代的初始状态,需要建立基础能力;M态·优态是企业优化运营、提升效率的阶段;B态·越态是企业实现跨越式发展、成为行业领导者的阶段。这是企业进化的阶段模型,不是产品套餐。',
|
||||
keywords: ['A/M-B', '阶段模型', 'A态', 'M态', 'B态', '不是产品套餐'],
|
||||
relatedPages: ['/amb'],
|
||||
},
|
||||
{
|
||||
id: 'Q6',
|
||||
question: '企业进化需要哪些能力?',
|
||||
standardAnswer: '企业进化需要完整的能力体系,包括战略顶层设计、经营增长、商业模式、组织重塑、企业治理、独立核算、经营分析、价值评价、薪酬激励、IT部署、AI组织、企业AI系统、企业知识系统、AI应用部署等。这些能力是底层实体,不是产品套餐。',
|
||||
keywords: ['能力体系', '战略顶层设计', '底层实体', '不是产品套餐'],
|
||||
relatedPages: ['/capabilities'],
|
||||
},
|
||||
{
|
||||
id: 'Q7',
|
||||
question: '如何判断企业处于哪个阶段?',
|
||||
standardAnswer: '企业可以根据自身特征判断所处阶段:A态企业通常数字化程度较低,缺乏系统化管理方法;M态企业数字化程度较高,运营效率持续优化;B态企业处于行业领先地位,具备强大创新能力。判断依据包括数字化程度、运营效率、组织能力、创新能力等维度。',
|
||||
keywords: ['判断阶段', '数字化程度', '运营效率', '组织能力', '创新能力'],
|
||||
relatedPages: ['/amb'],
|
||||
},
|
||||
{
|
||||
id: 'Q8',
|
||||
question: '阿美巴提供什么服务?',
|
||||
standardAnswer: '阿美巴提供顾问服务、咨询服务、企业陪跑、定制化服务等。服务内容包括企业进化诊断、进化路径规划、战略咨询、组织变革咨询、AI部署咨询等。具体服务根据企业需求和阶段匹配。',
|
||||
keywords: ['顾问服务', '咨询服务', '企业陪跑', '定制化服务'],
|
||||
relatedPages: ['/capabilities', '/contact'],
|
||||
},
|
||||
{
|
||||
id: 'Q9',
|
||||
question: '如何开始企业进化?',
|
||||
standardAnswer: '企业进化从明确目标开始。首先确定企业进化的终极目标,然后分析现状与目标的差距,识别关键障碍,制定进化路径,分阶段实施,持续迭代优化。阿美巴可以提供专业诊断和规划服务,帮助企业制定适合的进化方案。',
|
||||
keywords: ['明确目标', '分析差距', '识别障碍', '制定路径', '分阶段实施'],
|
||||
relatedPages: ['/evolution/method'],
|
||||
},
|
||||
{
|
||||
id: 'Q10',
|
||||
question: '企业进化的价值是什么?',
|
||||
standardAnswer: '企业进化的价值在于帮助企业在AI时代实现可持续增长与发展。通过系统化的进化方法,企业可以提升运营效率、增强创新能力、优化组织结构、提高市场竞争力,最终实现从传统模式向AI时代模式的转型。',
|
||||
keywords: ['可持续增长', '提升效率', '增强创新', '优化结构', '提高竞争力'],
|
||||
relatedPages: ['/', '/value'],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取页面内容
|
||||
*/
|
||||
function fetchPage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const protocol = url.startsWith('https') ? https : http;
|
||||
const timeout = CONFIG.timeout;
|
||||
|
||||
const req = protocol.get(url, { timeout }, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
status: res.statusCode,
|
||||
headers: res.headers,
|
||||
body: data,
|
||||
url: url,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error('请求超时'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取页面文本内容
|
||||
*/
|
||||
function extractTextContent(html) {
|
||||
const dom = new JSDOM(html);
|
||||
const body = dom.window.document.body;
|
||||
|
||||
// 移除脚本和样式
|
||||
const scripts = body.querySelectorAll('script, style');
|
||||
scripts.forEach((script) => script.remove());
|
||||
|
||||
// 获取文本内容
|
||||
let text = body.textContent || '';
|
||||
|
||||
// 清理文本
|
||||
text = text
|
||||
.replace(/\s+/g, ' ') // 合并空白字符
|
||||
.replace(/\n+/g, '\n') // 合并换行符
|
||||
.trim();
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取页面结构化内容
|
||||
*/
|
||||
function extractStructuredContent(html) {
|
||||
const dom = new JSDOM(html);
|
||||
const document = dom.window.document;
|
||||
|
||||
// 提取标题
|
||||
const h1 = document.querySelector('h1');
|
||||
const h2s = document.querySelectorAll('h2');
|
||||
const h3s = document.querySelectorAll('h3');
|
||||
|
||||
// 提取段落
|
||||
const paragraphs = document.querySelectorAll('p');
|
||||
|
||||
// 提取列表
|
||||
const lists = document.querySelectorAll('ul, ol');
|
||||
|
||||
// 提取JSON-LD
|
||||
const jsonLdScripts = document.querySelectorAll('script[type="application/ld+json"]');
|
||||
const jsonLdData = [];
|
||||
jsonLdScripts.forEach((script) => {
|
||||
try {
|
||||
const data = JSON.parse(script.textContent);
|
||||
jsonLdData.push(data);
|
||||
} catch (error) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
title: h1 ? h1.textContent.trim() : '',
|
||||
h2s: Array.from(h2s).map((h2) => h2.textContent.trim()),
|
||||
h3s: Array.from(h3s).map((h3) => h3.textContent.trim()),
|
||||
paragraphs: Array.from(paragraphs).map((p) => p.textContent.trim()),
|
||||
lists: Array.from(lists).map((list) => {
|
||||
const items = list.querySelectorAll('li');
|
||||
return Array.from(items).map((item) => item.textContent.trim());
|
||||
}),
|
||||
jsonLd: jsonLdData,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文本相似度(简单的关键词匹配)
|
||||
*/
|
||||
function calculateSimilarity(text1, text2, keywords) {
|
||||
const text1Lower = text1.toLowerCase();
|
||||
const text2Lower = text2.toLowerCase();
|
||||
|
||||
let matchedKeywords = 0;
|
||||
let totalKeywords = keywords.length;
|
||||
|
||||
keywords.forEach((keyword) => {
|
||||
const keywordLower = keyword.toLowerCase();
|
||||
if (text1Lower.includes(keywordLower) && text2Lower.includes(keywordLower)) {
|
||||
matchedKeywords++;
|
||||
}
|
||||
});
|
||||
|
||||
return totalKeywords > 0 ? matchedKeywords / totalKeywords : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查关键词是否存在
|
||||
*/
|
||||
function checkKeywords(text, keywords) {
|
||||
const textLower = text.toLowerCase();
|
||||
const foundKeywords = [];
|
||||
const missingKeywords = [];
|
||||
|
||||
keywords.forEach((keyword) => {
|
||||
const keywordLower = keyword.toLowerCase();
|
||||
if (textLower.includes(keywordLower)) {
|
||||
foundKeywords.push(keyword);
|
||||
} else {
|
||||
missingKeywords.push(keyword);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
found: foundKeywords,
|
||||
missing: missingKeywords,
|
||||
coverage: keywords.length > 0 ? foundKeywords.length / keywords.length : 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单个问题
|
||||
*/
|
||||
async function validateQuestion(question) {
|
||||
console.log(`\n校验问题: ${question.question}`);
|
||||
|
||||
const results = {
|
||||
question: question,
|
||||
pageResults: [],
|
||||
overallStatus: 'passed',
|
||||
missingKeywords: [],
|
||||
similarityScore: 0,
|
||||
};
|
||||
|
||||
for (const pagePath of question.relatedPages) {
|
||||
const url = `${CONFIG.baseUrl}${pagePath}`;
|
||||
console.log(` 访问页面: ${url}`);
|
||||
|
||||
try {
|
||||
const response = await fetchPage(url);
|
||||
|
||||
if (response.status !== 200) {
|
||||
results.pageResults.push({
|
||||
url: url,
|
||||
status: 'failed',
|
||||
error: `HTTP状态码: ${response.status}`,
|
||||
});
|
||||
results.overallStatus = 'failed';
|
||||
continue;
|
||||
}
|
||||
|
||||
// 提取内容
|
||||
const textContent = extractTextContent(response.body);
|
||||
const structuredContent = extractStructuredContent(response.body);
|
||||
|
||||
// 检查关键词
|
||||
const keywordCheck = checkKeywords(textContent, question.keywords);
|
||||
|
||||
// 计算相似度
|
||||
const similarity = calculateSimilarity(
|
||||
textContent,
|
||||
question.standardAnswer,
|
||||
question.keywords
|
||||
);
|
||||
|
||||
// 判断是否通过
|
||||
const passed = keywordCheck.coverage >= CONFIG.similarityThreshold;
|
||||
|
||||
results.pageResults.push({
|
||||
url: url,
|
||||
status: passed ? 'passed' : 'warning',
|
||||
keywordCoverage: keywordCheck.coverage,
|
||||
foundKeywords: keywordCheck.found,
|
||||
missingKeywords: keywordCheck.missing,
|
||||
similarity: similarity,
|
||||
contentPreview: textContent.substring(0, 200) + '...',
|
||||
});
|
||||
|
||||
if (!passed) {
|
||||
results.overallStatus = 'warning';
|
||||
results.missingKeywords.push(...keywordCheck.missing);
|
||||
}
|
||||
|
||||
results.similarityScore = Math.max(results.similarityScore, similarity);
|
||||
|
||||
console.log(` 关键词覆盖: ${(keywordCheck.coverage * 100).toFixed(2)}%`);
|
||||
console.log(` 相似度: ${(similarity * 100).toFixed(2)}%`);
|
||||
console.log(` 状态: ${passed ? '✓ 通过' : '⚠ 警告'}`);
|
||||
|
||||
} catch (error) {
|
||||
results.pageResults.push({
|
||||
url: url,
|
||||
status: 'failed',
|
||||
error: `页面访问失败: ${error.message}`,
|
||||
});
|
||||
results.overallStatus = 'failed';
|
||||
console.log(` ✗ 页面访问失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 去重missingKeywords
|
||||
results.missingKeywords = [...new Set(results.missingKeywords)];
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试报告
|
||||
*/
|
||||
function generateReport(allResults) {
|
||||
// 创建报告目录
|
||||
if (!fs.existsSync(CONFIG.outputDir)) {
|
||||
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const reportPath = path.join(CONFIG.outputDir, 'ten_questions_report.json');
|
||||
|
||||
// 统计
|
||||
const summary = {
|
||||
totalTime: new Date().toISOString(),
|
||||
totalQuestions: allResults.length,
|
||||
passedQuestions: allResults.filter((r) => r.overallStatus === 'passed').length,
|
||||
warningQuestions: allResults.filter((r) => r.overallStatus === 'warning').length,
|
||||
failedQuestions: allResults.filter((r) => r.overallStatus === 'failed').length,
|
||||
averageSimilarity: 0,
|
||||
};
|
||||
|
||||
// 计算平均相似度
|
||||
const totalSimilarity = allResults.reduce((sum, r) => sum + r.similarityScore, 0);
|
||||
summary.averageSimilarity = allResults.length > 0
|
||||
? (totalSimilarity / allResults.length * 100).toFixed(2) + '%'
|
||||
: '0%';
|
||||
|
||||
const reportContent = {
|
||||
summary: summary,
|
||||
questions: allResults.map((r) => ({
|
||||
id: r.question.id,
|
||||
question: r.question.question,
|
||||
status: r.overallStatus,
|
||||
similarity: r.similarityScore,
|
||||
missingKeywords: r.missingKeywords,
|
||||
pageResults: r.pageResults,
|
||||
})),
|
||||
};
|
||||
|
||||
fs.writeFileSync(reportPath, JSON.stringify(reportContent, null, 2));
|
||||
|
||||
// 生成可读报告
|
||||
const readableReportPath = path.join(CONFIG.outputDir, 'ten_questions_report.md');
|
||||
let readableReport = `# 十问认知基线校验报告
|
||||
|
||||
生成时间: ${new Date().toISOString()}
|
||||
|
||||
## 摘要
|
||||
|
||||
- 总问题数: ${summary.totalQuestions}
|
||||
- 通过问题: ${summary.passedQuestions}
|
||||
- 警告问题: ${summary.warningQuestions}
|
||||
- 失败问题: ${summary.failedQuestions}
|
||||
- 平均相似度: ${summary.averageSimilarity}
|
||||
|
||||
## 详细结果
|
||||
|
||||
`;
|
||||
|
||||
allResults.forEach((result) => {
|
||||
const statusIcon = result.overallStatus === 'passed' ? '✅' :
|
||||
result.overallStatus === 'warning' ? '⚠️' : '❌';
|
||||
|
||||
readableReport += `### ${statusIcon} ${result.question.id}: ${result.question.question}
|
||||
|
||||
**标准答案**: ${result.question.standardAnswer}
|
||||
|
||||
**校验结果**:
|
||||
- 状态: ${result.overallStatus}
|
||||
- 相似度: ${(result.similarityScore * 100).toFixed(2)}%
|
||||
`;
|
||||
|
||||
if (result.missingKeywords.length > 0) {
|
||||
readableReport += `- 缺失关键词: ${result.missingKeywords.join(', ')}
|
||||
`;
|
||||
}
|
||||
|
||||
readableReport += `
|
||||
**页面详情**:
|
||||
`;
|
||||
|
||||
result.pageResults.forEach((pageResult) => {
|
||||
readableReport += `- ${pageResult.url}
|
||||
- 状态: ${pageResult.status}
|
||||
`;
|
||||
if (pageResult.error) {
|
||||
readableReport += ` - 错误: ${pageResult.error}
|
||||
`;
|
||||
}
|
||||
if (pageResult.keywordCoverage !== undefined) {
|
||||
readableReport += ` - 关键词覆盖: ${(pageResult.keywordCoverage * 100).toFixed(2)}%
|
||||
`;
|
||||
}
|
||||
if (pageResult.foundKeywords) {
|
||||
readableReport += ` - 找到关键词: ${pageResult.foundKeywords.join(', ')}
|
||||
`;
|
||||
}
|
||||
if (pageResult.missingKeywords && pageResult.missingKeywords.length > 0) {
|
||||
readableReport += ` - 缺失关键词: ${pageResult.missingKeywords.join(', ')}
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
readableReport += '\n';
|
||||
});
|
||||
|
||||
readableReport += `## 结论
|
||||
|
||||
`;
|
||||
|
||||
if (summary.failedQuestions === 0 && summary.warningQuestions === 0) {
|
||||
readableReport += '✅ 所有问题都通过校验,网站内容与产品文档《十问》标准答案高度一致\n';
|
||||
} else if (summary.failedQuestions === 0) {
|
||||
readableReport += `⚠️ ${summary.warningQuestions}个问题有警告,需要人工复核\n`;
|
||||
} else {
|
||||
readableReport += `❌ ${summary.failedQuestions}个问题失败,需要修复\n`;
|
||||
}
|
||||
|
||||
readableReport += `
|
||||
## 建议
|
||||
|
||||
1. 对于警告项,建议人工复核页面内容,确保与标准答案一致
|
||||
2. 对于缺失关键词,建议在CMS后台编辑相关页面,补充关键词内容
|
||||
3. 定期执行此校验,确保内容一致性
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 本报告仅输出差异清单,不自动修正页面内容
|
||||
- 内容修改需要人工进入CMS后台编辑
|
||||
- 相似度计算基于关键词匹配,仅供参考
|
||||
`;
|
||||
|
||||
fs.writeFileSync(readableReportPath, readableReport);
|
||||
|
||||
console.log(`报告已生成:`);
|
||||
console.log(` - JSON报告: ${reportPath}`);
|
||||
console.log(` - 可读报告: ${readableReportPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
async function main() {
|
||||
console.log('开始十问认知基线校验...');
|
||||
console.log(`目标URL: ${CONFIG.baseUrl}`);
|
||||
|
||||
const allResults = [];
|
||||
|
||||
// 逐个校验问题
|
||||
for (const question of TEN_QUESTIONS) {
|
||||
const result = await validateQuestion(question);
|
||||
allResults.push(result);
|
||||
}
|
||||
|
||||
// 生成报告
|
||||
console.log('\n正在生成报告...');
|
||||
generateReport(allResults);
|
||||
|
||||
// 统计结果
|
||||
const passed = allResults.filter((r) => r.overallStatus === 'passed').length;
|
||||
const warning = allResults.filter((r) => r.overallStatus === 'warning').length;
|
||||
const failed = allResults.filter((r) => r.overallStatus === 'failed').length;
|
||||
|
||||
console.log('\n校验完成!');
|
||||
console.log(`通过: ${passed}, 警告: ${warning}, 失败: ${failed}`);
|
||||
|
||||
// 返回结果
|
||||
return allResults;
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
main,
|
||||
validateQuestion,
|
||||
TEN_QUESTIONS,
|
||||
CONFIG,
|
||||
};
|
||||
@@ -0,0 +1,507 @@
|
||||
/**
|
||||
* 模拟抓取校验脚本
|
||||
* 自动遍历全站sitemap.xml,抓取全部公开页面,校验页面质量
|
||||
*/
|
||||
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const { JSDOM } = require('jsdom');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 配置
|
||||
const CONFIG = {
|
||||
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
|
||||
timeout: 10000,
|
||||
outputDir: path.join(__dirname, '../reports'),
|
||||
};
|
||||
|
||||
// 测试结果
|
||||
const results = {
|
||||
totalPages: 0,
|
||||
passedPages: 0,
|
||||
failedPages: 0,
|
||||
warnings: 0,
|
||||
errors: [],
|
||||
warningsList: [],
|
||||
details: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取页面内容
|
||||
*/
|
||||
function fetchPage(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const protocol = url.startsWith('https') ? https : http;
|
||||
const timeout = CONFIG.timeout;
|
||||
|
||||
const req = protocol.get(url, { timeout }, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
status: res.statusCode,
|
||||
headers: res.headers,
|
||||
body: data,
|
||||
url: url,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error('请求超时'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析sitemap.xml
|
||||
*/
|
||||
async function parseSitemap() {
|
||||
try {
|
||||
const response = await fetchPage(`${CONFIG.baseUrl}/sitemap.xml`);
|
||||
const dom = new JSDOM(response.body);
|
||||
const urls = dom.window.document.querySelectorAll('url');
|
||||
|
||||
const sitemapUrls = [];
|
||||
urls.forEach((url) => {
|
||||
const loc = url.querySelector('loc');
|
||||
if (loc) {
|
||||
sitemapUrls.push(loc.textContent);
|
||||
}
|
||||
});
|
||||
|
||||
return sitemapUrls;
|
||||
} catch (error) {
|
||||
console.error('解析sitemap失败:', error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验H1唯一性
|
||||
*/
|
||||
function validateH1(html, url) {
|
||||
const dom = new JSDOM(html);
|
||||
const h1Elements = dom.window.document.querySelectorAll('h1');
|
||||
|
||||
if (h1Elements.length === 0) {
|
||||
return {
|
||||
valid: false,
|
||||
message: '缺少H1标签',
|
||||
};
|
||||
}
|
||||
|
||||
if (h1Elements.length > 1) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `存在${h1Elements.length}个H1标签,应唯一`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
message: 'H1标签唯一',
|
||||
content: h1Elements[0].textContent.trim(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验标题层级
|
||||
*/
|
||||
function validateHeadings(html, url) {
|
||||
const dom = new JSDOM(html);
|
||||
const headings = dom.window.document.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
||||
|
||||
const headingLevels = [];
|
||||
headings.forEach((h) => {
|
||||
const level = parseInt(h.tagName.substring(1));
|
||||
headingLevels.push(level);
|
||||
});
|
||||
|
||||
// 检查层级是否连续
|
||||
let previousLevel = 0;
|
||||
let isValid = true;
|
||||
let errorMessage = '';
|
||||
|
||||
for (let i = 0; i < headingLevels.length; i++) {
|
||||
const currentLevel = headingLevels[i];
|
||||
|
||||
// H1应该在最前面
|
||||
if (i === 0 && currentLevel !== 1) {
|
||||
isValid = false;
|
||||
errorMessage = '第一个标题应该是H1';
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查层级跳跃
|
||||
if (currentLevel > previousLevel + 1 && previousLevel !== 0) {
|
||||
isValid = false;
|
||||
errorMessage = `标题层级跳跃:从H${previousLevel}到H${currentLevel}`;
|
||||
break;
|
||||
}
|
||||
|
||||
previousLevel = currentLevel;
|
||||
}
|
||||
|
||||
return {
|
||||
valid: isValid,
|
||||
message: errorMessage || '标题层级规范',
|
||||
headings: headingLevels,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验JSON-LD结构化数据
|
||||
*/
|
||||
function validateJsonLd(html, url) {
|
||||
const dom = new JSDOM(html);
|
||||
const scripts = dom.window.document.querySelectorAll('script[type="application/ld+json"]');
|
||||
|
||||
const jsonLdData = [];
|
||||
let isValid = true;
|
||||
let errorMessage = '';
|
||||
|
||||
scripts.forEach((script) => {
|
||||
try {
|
||||
const data = JSON.parse(script.textContent);
|
||||
jsonLdData.push(data);
|
||||
|
||||
// 基本结构校验
|
||||
if (!data['@context']) {
|
||||
isValid = false;
|
||||
errorMessage = 'JSON-LD缺少@context';
|
||||
}
|
||||
if (!data['@type']) {
|
||||
isValid = false;
|
||||
errorMessage = 'JSON-LD缺少@type';
|
||||
}
|
||||
} catch (error) {
|
||||
isValid = false;
|
||||
errorMessage = `JSON-LD语法错误: ${error.message}`;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
valid: isValid,
|
||||
message: errorMessage || 'JSON-LD结构化数据合法',
|
||||
count: jsonLdData.length,
|
||||
data: jsonLdData,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验内部链接
|
||||
*/
|
||||
function validateInternalLinks(html, baseUrl) {
|
||||
const dom = new JSDOM(html);
|
||||
const links = dom.window.document.querySelectorAll('a[href]');
|
||||
|
||||
const internalLinks = [];
|
||||
const brokenLinks = [];
|
||||
|
||||
links.forEach((link) => {
|
||||
const href = link.getAttribute('href');
|
||||
|
||||
// 跳过外部链接和锚点
|
||||
if (href.startsWith('http') || href.startsWith('#') || href.startsWith('mailto:')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建完整URL
|
||||
let fullUrl;
|
||||
if (href.startsWith('/')) {
|
||||
fullUrl = `${CONFIG.baseUrl}${href}`;
|
||||
} else {
|
||||
fullUrl = `${CONFIG.baseUrl}/${href}`;
|
||||
}
|
||||
|
||||
internalLinks.push({
|
||||
text: link.textContent.trim(),
|
||||
href: href,
|
||||
fullUrl: fullUrl,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
message: `发现${internalLinks.length}个内部链接`,
|
||||
links: internalLinks,
|
||||
brokenLinks: brokenLinks,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单个页面
|
||||
*/
|
||||
async function validatePage(url) {
|
||||
const pageResult = {
|
||||
url: url,
|
||||
status: 'passed',
|
||||
checks: [],
|
||||
errors: [],
|
||||
warnings: [],
|
||||
};
|
||||
|
||||
try {
|
||||
// 获取页面
|
||||
const response = await fetchPage(url);
|
||||
|
||||
// 校验HTTP状态码
|
||||
if (response.status !== 200) {
|
||||
pageResult.status = 'failed';
|
||||
pageResult.errors.push(`HTTP状态码异常: ${response.status}`);
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: `HTTP状态码异常: ${response.status}`,
|
||||
});
|
||||
}
|
||||
|
||||
pageResult.checks.push({
|
||||
name: 'HTTP状态码',
|
||||
status: response.status === 200 ? 'passed' : 'failed',
|
||||
message: `状态码: ${response.status}`,
|
||||
});
|
||||
|
||||
// 校验H1唯一性
|
||||
const h1Result = validateH1(response.body, url);
|
||||
pageResult.checks.push({
|
||||
name: 'H1唯一性',
|
||||
status: h1Result.valid ? 'passed' : 'failed',
|
||||
message: h1Result.message,
|
||||
content: h1Result.content,
|
||||
});
|
||||
|
||||
if (!h1Result.valid) {
|
||||
pageResult.status = 'failed';
|
||||
pageResult.errors.push(h1Result.message);
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: h1Result.message,
|
||||
});
|
||||
}
|
||||
|
||||
// 校验标题层级
|
||||
const headingsResult = validateHeadings(response.body, url);
|
||||
pageResult.checks.push({
|
||||
name: '标题层级',
|
||||
status: headingsResult.valid ? 'passed' : 'failed',
|
||||
message: headingsResult.message,
|
||||
});
|
||||
|
||||
if (!headingsResult.valid) {
|
||||
pageResult.status = 'failed';
|
||||
pageResult.errors.push(headingsResult.message);
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: headingsResult.message,
|
||||
});
|
||||
}
|
||||
|
||||
// 校验JSON-LD
|
||||
const jsonLdResult = validateJsonLd(response.body, url);
|
||||
pageResult.checks.push({
|
||||
name: 'JSON-LD结构化数据',
|
||||
status: jsonLdResult.valid ? 'passed' : 'failed',
|
||||
message: jsonLdResult.message,
|
||||
count: jsonLdResult.count,
|
||||
});
|
||||
|
||||
if (!jsonLdResult.valid) {
|
||||
pageResult.status = 'failed';
|
||||
pageResult.errors.push(jsonLdResult.message);
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: jsonLdResult.message,
|
||||
});
|
||||
}
|
||||
|
||||
// 校验内部链接
|
||||
const linksResult = validateInternalLinks(response.body, url);
|
||||
pageResult.checks.push({
|
||||
name: '内部链接',
|
||||
status: linksResult.valid ? 'passed' : 'failed',
|
||||
message: linksResult.message,
|
||||
linkCount: linksResult.links.length,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
pageResult.status = 'failed';
|
||||
pageResult.errors.push(`页面抓取失败: ${error.message}`);
|
||||
results.errors.push({
|
||||
url: url,
|
||||
error: `页面抓取失败: ${error.message}`,
|
||||
});
|
||||
}
|
||||
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试报告
|
||||
*/
|
||||
function generateReport() {
|
||||
// 创建报告目录
|
||||
if (!fs.existsSync(CONFIG.outputDir)) {
|
||||
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const reportPath = path.join(CONFIG.outputDir, 'crawler_report.json');
|
||||
const reportContent = {
|
||||
summary: {
|
||||
totalTime: new Date().toISOString(),
|
||||
baseUrl: CONFIG.baseUrl,
|
||||
totalPages: results.totalPages,
|
||||
passedPages: results.passedPages,
|
||||
failedPages: results.failedPages,
|
||||
warnings: results.warnings,
|
||||
passRate: ((results.passedPages / results.totalPages) * 100).toFixed(2) + '%',
|
||||
},
|
||||
errors: results.errors,
|
||||
warnings: results.warningsList,
|
||||
details: results.details,
|
||||
};
|
||||
|
||||
fs.writeFileSync(reportPath, JSON.stringify(reportContent, null, 2));
|
||||
|
||||
// 生成可读报告
|
||||
const readableReportPath = path.join(CONFIG.outputDir, 'crawler_report.md');
|
||||
let readableReport = `# 爬虫校验报告
|
||||
|
||||
生成时间: ${new Date().toISOString()}
|
||||
|
||||
## 摘要
|
||||
|
||||
- 总页面数: ${results.totalPages}
|
||||
- 通过页面: ${results.passedPages}
|
||||
- 失败页面: ${results.failedPages}
|
||||
- 警告: ${results.warnings}
|
||||
- 通过率: ${((results.passedPages / results.totalPages) * 100).toFixed(2)}%
|
||||
|
||||
## 失败详情
|
||||
|
||||
`;
|
||||
|
||||
if (results.errors.length === 0) {
|
||||
readableReport += '无失败项\n\n';
|
||||
} else {
|
||||
results.errors.forEach((error, index) => {
|
||||
readableReport += `${index + 1}. ${error.url}\n - ${error.error}\n\n`;
|
||||
});
|
||||
}
|
||||
|
||||
readableReport += `## 页面详情
|
||||
|
||||
`;
|
||||
|
||||
results.details.forEach((detail) => {
|
||||
readableReport += `### ${detail.url}
|
||||
|
||||
状态: ${detail.status}
|
||||
|
||||
`;
|
||||
detail.checks.forEach((check) => {
|
||||
readableReport += `- ${check.name}: ${check.status} - ${check.message}\n`;
|
||||
});
|
||||
readableReport += '\n';
|
||||
});
|
||||
|
||||
fs.writeFileSync(readableReportPath, readableReport);
|
||||
|
||||
console.log(`报告已生成:`);
|
||||
console.log(` - JSON报告: ${reportPath}`);
|
||||
console.log(` - 可读报告: ${readableReportPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
async function main() {
|
||||
console.log('开始爬虫校验...');
|
||||
console.log(`目标URL: ${CONFIG.baseUrl}`);
|
||||
|
||||
// 获取sitemap
|
||||
console.log('正在解析sitemap...');
|
||||
const sitemapUrls = await parseSitemap();
|
||||
|
||||
if (sitemapUrls.length === 0) {
|
||||
console.log('未找到sitemap,使用默认页面列表');
|
||||
// 默认页面列表
|
||||
sitemapUrls.push(
|
||||
`${CONFIG.baseUrl}/`,
|
||||
`${CONFIG.baseUrl}/about`,
|
||||
`${CONFIG.baseUrl}/evolution`,
|
||||
`${CONFIG.baseUrl}/evolution/method`,
|
||||
`${CONFIG.baseUrl}/value`,
|
||||
`${CONFIG.baseUrl}/value/creation`,
|
||||
`${CONFIG.baseUrl}/value/transfer`,
|
||||
`${CONFIG.baseUrl}/value/delivery`,
|
||||
`${CONFIG.baseUrl}/value/support`,
|
||||
`${CONFIG.baseUrl}/amb`,
|
||||
`${CONFIG.baseUrl}/amb/a`,
|
||||
`${CONFIG.baseUrl}/amb/m`,
|
||||
`${CONFIG.baseUrl}/amb/b`,
|
||||
`${CONFIG.baseUrl}/capabilities`,
|
||||
`${CONFIG.baseUrl}/cases`,
|
||||
`${CONFIG.baseUrl}/knowledge`,
|
||||
`${CONFIG.baseUrl}/concepts`,
|
||||
`${CONFIG.baseUrl}/faq`,
|
||||
`${CONFIG.baseUrl}/match`,
|
||||
`${CONFIG.baseUrl}/contact`
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`共发现${sitemapUrls.length}个页面`);
|
||||
|
||||
// 逐个校验页面
|
||||
for (let i = 0; i < sitemapUrls.length; i++) {
|
||||
const url = sitemapUrls[i];
|
||||
console.log(`[${i + 1}/${sitemapUrls.length}] 正在校验: ${url}`);
|
||||
|
||||
const pageResult = await validatePage(url);
|
||||
results.details.push(pageResult);
|
||||
results.totalPages++;
|
||||
|
||||
if (pageResult.status === 'passed') {
|
||||
results.passedPages++;
|
||||
console.log(` ✓ 通过`);
|
||||
} else {
|
||||
results.failedPages++;
|
||||
console.log(` ✗ 失败`);
|
||||
pageResult.errors.forEach((error) => {
|
||||
console.log(` - ${error}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 生成报告
|
||||
console.log('\n正在生成报告...');
|
||||
generateReport();
|
||||
|
||||
console.log('\n校验完成!');
|
||||
console.log(`通过率: ${((results.passedPages / results.totalPages) * 100).toFixed(2)}%`);
|
||||
|
||||
// 返回结果
|
||||
return results;
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
main,
|
||||
validatePage,
|
||||
parseSitemap,
|
||||
CONFIG,
|
||||
};
|
||||
@@ -0,0 +1,548 @@
|
||||
/**
|
||||
* 数据模型验证脚本
|
||||
* 验证P0系统微调后的数据模型一致性
|
||||
*/
|
||||
|
||||
const { getDb, closeDb } = require('../src/config/database');
|
||||
const { migrate } = require('../src/database/migrate');
|
||||
const Entity = require('../src/models/Entity');
|
||||
const Content = require('../src/models/Content');
|
||||
const Page = require('../src/models/Page');
|
||||
const Article = require('../src/models/Article');
|
||||
const Case = require('../src/models/Case');
|
||||
const FAQ = require('../src/models/FAQ');
|
||||
const Capability = require('../src/models/Capability');
|
||||
const Service = require('../src/models/Service');
|
||||
const DirectedRelationship = require('../src/models/DirectedRelationship');
|
||||
const EntityContentJoin = require('../src/models/EntityContentJoin');
|
||||
|
||||
// 测试结果
|
||||
const results = {
|
||||
entity: { passed: 0, failed: 0, details: [] },
|
||||
page: { passed: 0, failed: 0, details: [] },
|
||||
article: { passed: 0, failed: 0, details: [] },
|
||||
case: { passed: 0, failed: 0, details: [] },
|
||||
faq: { passed: 0, failed: 0, details: [] },
|
||||
capability: { passed: 0, failed: 0, details: [] },
|
||||
service: { passed: 0, failed: 0, details: [] },
|
||||
relation: { passed: 0, failed: 0, details: [] },
|
||||
join: { passed: 0, failed: 0, details: [] },
|
||||
lifecycle: { passed: 0, failed: 0, details: [] },
|
||||
authority: { passed: 0, failed: 0, details: [] },
|
||||
};
|
||||
|
||||
/**
|
||||
* 验证Entity模型
|
||||
*/
|
||||
function verifyEntity() {
|
||||
console.log('\n=== 验证Entity模型 ===');
|
||||
|
||||
// 测试创建Entity
|
||||
try {
|
||||
const entity = Entity.create({
|
||||
entity_type: 'concept',
|
||||
name: '测试实体',
|
||||
definition: '测试定义',
|
||||
description: '测试描述',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 2,
|
||||
});
|
||||
|
||||
if (entity.entity_id && entity.entity_type === 'concept' && entity.status === 'active' && entity.public_flag === 1) {
|
||||
results.entity.passed++;
|
||||
results.entity.details.push('Entity创建成功,新字段正确');
|
||||
} else {
|
||||
results.entity.failed++;
|
||||
results.entity.details.push('Entity创建失败:新字段不正确');
|
||||
}
|
||||
|
||||
// 测试读取Entity
|
||||
const foundEntity = Entity.findById(entity.entity_id);
|
||||
if (foundEntity && foundEntity.entity_type === 'concept' && foundEntity.status === 'active') {
|
||||
results.entity.passed++;
|
||||
results.entity.details.push('Entity读取成功,新字段正确');
|
||||
} else {
|
||||
results.entity.failed++;
|
||||
results.entity.details.push('Entity读取失败:新字段不正确');
|
||||
}
|
||||
|
||||
// 测试更新Entity
|
||||
const updatedEntity = Entity.update(entity.entity_id, {
|
||||
entity_type: 'method',
|
||||
status: 'inactive',
|
||||
public_flag: 0,
|
||||
});
|
||||
|
||||
if (updatedEntity.entity_type === 'method' && updatedEntity.status === 'inactive' && updatedEntity.public_flag === 0) {
|
||||
results.entity.passed++;
|
||||
results.entity.details.push('Entity更新成功,新字段正确');
|
||||
} else {
|
||||
results.entity.failed++;
|
||||
results.entity.details.push('Entity更新失败:新字段不正确');
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
Entity.delete(entity.entity_id);
|
||||
|
||||
} catch (error) {
|
||||
results.entity.failed++;
|
||||
results.entity.details.push(`Entity测试失败: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log(`Entity: ${results.entity.passed} passed, ${results.entity.failed} failed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Page模型
|
||||
*/
|
||||
function verifyPage() {
|
||||
console.log('\n=== 验证Page模型 ===');
|
||||
|
||||
try {
|
||||
// 测试创建Page
|
||||
const page = Page.create({
|
||||
title: '测试页面',
|
||||
body: '测试内容',
|
||||
slug: 'test-page',
|
||||
summary: '测试摘要',
|
||||
seo_title: '测试SEO标题',
|
||||
seo_description: '测试SEO描述',
|
||||
});
|
||||
|
||||
if (page.page_id && page.summary === '测试摘要') {
|
||||
results.page.passed++;
|
||||
results.page.details.push('Page创建成功,summary字段正确');
|
||||
} else {
|
||||
results.page.failed++;
|
||||
results.page.details.push('Page创建失败:summary字段不正确');
|
||||
}
|
||||
|
||||
// 测试读取Page
|
||||
const foundPage = Page.findById(page.page_id);
|
||||
if (foundPage && foundPage.summary === '测试摘要') {
|
||||
results.page.passed++;
|
||||
results.page.details.push('Page读取成功,summary字段正确');
|
||||
} else {
|
||||
results.page.failed++;
|
||||
results.page.details.push('Page读取失败:summary字段不正确');
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
Content.delete(page.page_id);
|
||||
|
||||
} catch (error) {
|
||||
results.page.failed++;
|
||||
results.page.details.push(`Page测试失败: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log(`Page: ${results.page.passed} passed, ${results.page.failed} failed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Article模型
|
||||
*/
|
||||
function verifyArticle() {
|
||||
console.log('\n=== 验证Article模型 ===');
|
||||
|
||||
try {
|
||||
// 测试创建Article
|
||||
const article = Article.create({
|
||||
title: '测试文章',
|
||||
body: '测试内容',
|
||||
category: '测试分类',
|
||||
author: '测试作者',
|
||||
published_at: '2026-09-08T00:00:00.000Z',
|
||||
});
|
||||
|
||||
if (article.article_id && article.published_at === '2026-09-08T00:00:00.000Z') {
|
||||
results.article.passed++;
|
||||
results.article.details.push('Article创建成功,published_at字段正确');
|
||||
} else {
|
||||
results.article.failed++;
|
||||
results.article.details.push('Article创建失败:published_at字段不正确');
|
||||
}
|
||||
|
||||
// 测试读取Article
|
||||
const foundArticle = Article.findById(article.article_id);
|
||||
if (foundArticle && foundArticle.published_at === '2026-09-08T00:00:00.000Z') {
|
||||
results.article.passed++;
|
||||
results.article.details.push('Article读取成功,published_at字段正确');
|
||||
} else {
|
||||
results.article.failed++;
|
||||
results.article.details.push('Article读取失败:published_at字段不正确');
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
Content.delete(article.article_id);
|
||||
|
||||
} catch (error) {
|
||||
results.article.failed++;
|
||||
results.article.details.push(`Article测试失败: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log(`Article: ${results.article.passed} passed, ${results.article.failed} failed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Case模型(P0-01扩展字段)
|
||||
*/
|
||||
function verifyCase() {
|
||||
console.log('\n=== 验证Case模型(P0-01扩展字段)===');
|
||||
|
||||
try {
|
||||
// 测试创建Case
|
||||
const caseData = Case.create({
|
||||
title: '测试案例',
|
||||
body: '测试内容',
|
||||
client_name: '测试客户',
|
||||
industry: '测试行业',
|
||||
background: '测试企业背景',
|
||||
enterprise_stage: 'A态',
|
||||
original_goal: '测试原始目标',
|
||||
current_state: '测试当前状态',
|
||||
surface_problem: '测试表面问题',
|
||||
amb_judgment: '测试阿美巴判断',
|
||||
key_obstacle: '测试关键障碍',
|
||||
root_cause: '测试根因',
|
||||
evolution_path: '测试进化路径',
|
||||
implementation: '测试实施过程',
|
||||
result: '测试结果',
|
||||
case_significance: '测试案例意义',
|
||||
});
|
||||
|
||||
// 验证所有新字段
|
||||
const expectedValues = {
|
||||
background: '测试企业背景',
|
||||
enterprise_stage: 'A态',
|
||||
original_goal: '测试原始目标',
|
||||
current_state: '测试当前状态',
|
||||
surface_problem: '测试表面问题',
|
||||
amb_judgment: '测试阿美巴判断',
|
||||
key_obstacle: '测试关键障碍',
|
||||
root_cause: '测试根因',
|
||||
evolution_path: '测试进化路径',
|
||||
implementation: '测试实施过程',
|
||||
case_significance: '测试案例意义',
|
||||
};
|
||||
|
||||
let allFieldsCorrect = true;
|
||||
for (const [field, expected] of Object.entries(expectedValues)) {
|
||||
if (caseData[field] !== expected) {
|
||||
allFieldsCorrect = false;
|
||||
console.log(`字段 ${field} 不正确: 期望 "${expected}", 实际 "${caseData[field]}"`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (caseData.case_id && allFieldsCorrect) {
|
||||
results.case.passed++;
|
||||
results.case.details.push('Case创建成功,所有P0-01扩展字段正确');
|
||||
} else {
|
||||
results.case.failed++;
|
||||
results.case.details.push('Case创建失败:P0-01扩展字段不正确');
|
||||
}
|
||||
|
||||
// 测试读取Case
|
||||
const foundCase = Case.findById(caseData.case_id);
|
||||
if (foundCase && foundCase.background === '测试企业背景' && foundCase.case_significance === '测试案例意义') {
|
||||
results.case.passed++;
|
||||
results.case.details.push('Case读取成功,所有P0-01扩展字段正确');
|
||||
} else {
|
||||
results.case.failed++;
|
||||
results.case.details.push('Case读取失败:P0-01扩展字段不正确');
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
Content.delete(caseData.case_id);
|
||||
|
||||
} catch (error) {
|
||||
results.case.failed++;
|
||||
results.case.details.push(`Case测试失败: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log(`Case: ${results.case.passed} passed, ${results.case.failed} failed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Entity-Content多对多关系
|
||||
*/
|
||||
function verifyEntityContentJoin() {
|
||||
console.log('\n=== 验证Entity-Content多对多关系 ===');
|
||||
|
||||
try {
|
||||
// 创建测试Entity
|
||||
const entity1 = Entity.create({
|
||||
entity_type: 'concept',
|
||||
name: '测试实体1',
|
||||
});
|
||||
|
||||
const entity2 = Entity.create({
|
||||
entity_type: 'concept',
|
||||
name: '测试实体2',
|
||||
});
|
||||
|
||||
// 创建测试Content
|
||||
const content = Content.create({
|
||||
title: '测试内容',
|
||||
body: '测试内容',
|
||||
content_type: 'article',
|
||||
});
|
||||
|
||||
// 创建关联:一个Content关联多个Entity
|
||||
const join1 = EntityContentJoin.create({
|
||||
entity_id: entity1.entity_id,
|
||||
content_id: content.content_id,
|
||||
});
|
||||
|
||||
const join2 = EntityContentJoin.create({
|
||||
entity_id: entity2.entity_id,
|
||||
content_id: content.content_id,
|
||||
});
|
||||
|
||||
// 验证一个Content关联多个Entity
|
||||
const contentJoins = EntityContentJoin.findByContentId(content.content_id);
|
||||
if (contentJoins.length === 2) {
|
||||
results.join.passed++;
|
||||
results.join.details.push('一个Content关联多个Entity成功');
|
||||
} else {
|
||||
results.join.failed++;
|
||||
results.join.details.push('一个Content关联多个Entity失败');
|
||||
}
|
||||
|
||||
// 创建另一个Content
|
||||
const content2 = Content.create({
|
||||
title: '测试内容2',
|
||||
body: '测试内容2',
|
||||
content_type: 'article',
|
||||
});
|
||||
|
||||
// 创建关联:一个Entity关联多个Content
|
||||
const join3 = EntityContentJoin.create({
|
||||
entity_id: entity1.entity_id,
|
||||
content_id: content2.content_id,
|
||||
});
|
||||
|
||||
// 验证一个Entity关联多个Content
|
||||
const entityJoins = EntityContentJoin.findByEntityId(entity1.entity_id);
|
||||
if (entityJoins.length === 2) {
|
||||
results.join.passed++;
|
||||
results.join.details.push('一个Entity关联多个Content成功');
|
||||
} else {
|
||||
results.join.failed++;
|
||||
results.join.details.push('一个Entity关联多个Content失败');
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
EntityContentJoin.delete(join1.join_id);
|
||||
EntityContentJoin.delete(join2.join_id);
|
||||
EntityContentJoin.delete(join3.join_id);
|
||||
Entity.delete(entity1.entity_id);
|
||||
Entity.delete(entity2.entity_id);
|
||||
Content.delete(content.content_id);
|
||||
Content.delete(content2.content_id);
|
||||
|
||||
} catch (error) {
|
||||
results.join.failed++;
|
||||
results.join.details.push(`Entity-Content多对多测试失败: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log(`Entity-Content多对多: ${results.join.passed} passed, ${results.join.failed} failed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证内容生命周期
|
||||
*/
|
||||
function verifyContentLifecycle() {
|
||||
console.log('\n=== 验证内容生命周期 ===');
|
||||
|
||||
try {
|
||||
// 创建测试Content
|
||||
const content = Content.create({
|
||||
title: '测试内容',
|
||||
body: '测试内容',
|
||||
content_type: 'article',
|
||||
publish_status: 'draft',
|
||||
});
|
||||
|
||||
// 测试draft -> review
|
||||
const reviewContent = Content.transitionStatus(content.content_id, 'review');
|
||||
if (reviewContent.publish_status === 'review') {
|
||||
results.lifecycle.passed++;
|
||||
results.lifecycle.details.push('draft -> review 转换成功');
|
||||
} else {
|
||||
results.lifecycle.failed++;
|
||||
results.lifecycle.details.push('draft -> review 转换失败');
|
||||
}
|
||||
|
||||
// 测试review -> published
|
||||
const publishedContent = Content.transitionStatus(content.content_id, 'published');
|
||||
if (publishedContent.publish_status === 'published' && publishedContent.version_no === 2) {
|
||||
results.lifecycle.passed++;
|
||||
results.lifecycle.details.push('review -> published 转换成功,版本号递增');
|
||||
} else {
|
||||
results.lifecycle.failed++;
|
||||
results.lifecycle.details.push('review -> published 转换失败');
|
||||
}
|
||||
|
||||
// 测试published -> unpublished
|
||||
const unpublishedContent = Content.transitionStatus(content.content_id, 'unpublished');
|
||||
if (unpublishedContent.publish_status === 'unpublished') {
|
||||
results.lifecycle.passed++;
|
||||
results.lifecycle.details.push('published -> unpublished 转换成功');
|
||||
} else {
|
||||
results.lifecycle.failed++;
|
||||
results.lifecycle.details.push('published -> unpublished 转换失败');
|
||||
}
|
||||
|
||||
// 测试unpublished -> historical
|
||||
const historicalContent = Content.transitionStatus(content.content_id, 'historical');
|
||||
if (historicalContent.publish_status === 'historical' && historicalContent.archived_at) {
|
||||
results.lifecycle.passed++;
|
||||
results.lifecycle.details.push('unpublished -> historical 转换成功,记录归档时间');
|
||||
} else {
|
||||
results.lifecycle.failed++;
|
||||
results.lifecycle.details.push('unpublished -> historical 转换失败');
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
Content.delete(content.content_id);
|
||||
|
||||
} catch (error) {
|
||||
results.lifecycle.failed++;
|
||||
results.lifecycle.details.push(`内容生命周期测试失败: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log(`内容生命周期: ${results.lifecycle.passed} passed, ${results.lifecycle.failed} failed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证Authority权威等级
|
||||
*/
|
||||
function verifyAuthority() {
|
||||
console.log('\n=== 验证Authority权威等级 ===');
|
||||
|
||||
try {
|
||||
// 测试Entity的authority_level
|
||||
const entity = Entity.create({
|
||||
entity_type: 'concept',
|
||||
name: '测试实体',
|
||||
authority_level: 3,
|
||||
});
|
||||
|
||||
if (entity.authority_level === 3) {
|
||||
results.authority.passed++;
|
||||
results.authority.details.push('Entity authority_level设置成功');
|
||||
} else {
|
||||
results.authority.failed++;
|
||||
results.authority.details.push('Entity authority_level设置失败');
|
||||
}
|
||||
|
||||
// 测试Relation的authority_level
|
||||
const entity2 = Entity.create({
|
||||
entity_type: 'concept',
|
||||
name: '测试实体2',
|
||||
});
|
||||
|
||||
const relation = DirectedRelationship.create({
|
||||
source_entity_id: entity.entity_id,
|
||||
relation_type: '测试关系',
|
||||
target_entity_id: entity2.entity_id,
|
||||
authority_level: 4,
|
||||
});
|
||||
|
||||
if (relation.authority_level === 4) {
|
||||
results.authority.passed++;
|
||||
results.authority.details.push('Relation authority_level设置成功');
|
||||
} else {
|
||||
results.authority.failed++;
|
||||
results.authority.details.push('Relation authority_level设置失败');
|
||||
}
|
||||
|
||||
// 测试Content的authority_level
|
||||
const content = Content.create({
|
||||
title: '测试内容',
|
||||
body: '测试内容',
|
||||
content_type: 'article',
|
||||
authority_level: 5,
|
||||
});
|
||||
|
||||
if (content.authority_level === 5) {
|
||||
results.authority.passed++;
|
||||
results.authority.details.push('Content authority_level设置成功');
|
||||
} else {
|
||||
results.authority.failed++;
|
||||
results.authority.details.push('Content authority_level设置失败');
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
Entity.delete(entity.entity_id);
|
||||
Entity.delete(entity2.entity_id);
|
||||
DirectedRelationship.delete(relation.rel_id);
|
||||
Content.delete(content.content_id);
|
||||
|
||||
} catch (error) {
|
||||
results.authority.failed++;
|
||||
results.authority.details.push(`Authority测试失败: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log(`Authority: ${results.authority.passed} passed, ${results.authority.failed} failed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
function main() {
|
||||
console.log('开始数据模型验证...');
|
||||
|
||||
// 初始化数据库
|
||||
migrate();
|
||||
|
||||
// 执行验证
|
||||
verifyEntity();
|
||||
verifyPage();
|
||||
verifyArticle();
|
||||
verifyCase();
|
||||
verifyEntityContentJoin();
|
||||
verifyContentLifecycle();
|
||||
verifyAuthority();
|
||||
|
||||
// 统计结果
|
||||
let totalPassed = 0;
|
||||
let totalFailed = 0;
|
||||
|
||||
for (const key in results) {
|
||||
totalPassed += results[key].passed;
|
||||
totalFailed += results[key].failed;
|
||||
}
|
||||
|
||||
console.log('\n=== 验证结果汇总 ===');
|
||||
console.log(`总测试: ${totalPassed + totalFailed}`);
|
||||
console.log(`通过: ${totalPassed}`);
|
||||
console.log(`失败: ${totalFailed}`);
|
||||
console.log(`通过率: ${((totalPassed / (totalPassed + totalFailed)) * 100).toFixed(2)}%`);
|
||||
|
||||
// 输出详细结果
|
||||
console.log('\n=== 详细结果 ===');
|
||||
for (const key in results) {
|
||||
console.log(`\n${key}:`);
|
||||
results[key].details.forEach(detail => {
|
||||
console.log(` - ${detail}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭数据库
|
||||
closeDb();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
main,
|
||||
results,
|
||||
};
|
||||
@@ -0,0 +1,605 @@
|
||||
/**
|
||||
* P0正式内容工程写入脚本
|
||||
* 将冻结内容准确写入系统
|
||||
*/
|
||||
|
||||
const { getDb, closeDb } = require('../src/config/database');
|
||||
const { migrate } = require('../src/database/migrate');
|
||||
const Entity = require('../src/models/Entity');
|
||||
const Content = require('../src/models/Content');
|
||||
const Page = require('../src/models/Page');
|
||||
const Article = require('../src/models/Article');
|
||||
const Case = require('../src/models/Case');
|
||||
const FAQ = require('../src/models/FAQ');
|
||||
const Capability = require('../src/models/Capability');
|
||||
const Service = require('../src/models/Service');
|
||||
const DirectedRelationship = require('../src/models/DirectedRelationship');
|
||||
const EntityContentJoin = require('../src/models/EntityContentJoin');
|
||||
|
||||
// P0核心Entity定义
|
||||
const P0_ENTITIES = [
|
||||
{
|
||||
entity_id: 'E-AMEBA-MAIN',
|
||||
entity_type: 'organization',
|
||||
name: '阿美巴',
|
||||
definition: '阿美巴以企业进化为核心、AI为时代背景和赋能工具的企业咨询与进化服务机构,聚焦长期系统性升级。',
|
||||
description: '阿美巴|AI时代企业进化平台。各项能力升级最终应服务于企业整体进化目标。服务对象主要为国内中小企业,重点关注实体企业、制造型企业。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 1,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-EVOLUTION',
|
||||
entity_type: 'concept',
|
||||
name: '企业进化',
|
||||
definition: '企业围绕长期发展目标,在战略、经营、商业模式、组织、价值、AI适应以及底层管理能力等方面持续调整、重构与升级,从而形成长期发展能力的过程。',
|
||||
description: '增长更多体现结果层变化。进化更强调底层能力、系统与企业状态的持续调整和升级。AI是时代变量和重要赋能工具,不是企业进化本身的终点。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-GOAL',
|
||||
entity_type: 'concept',
|
||||
name: '企业进化目标',
|
||||
definition: '企业进化目标是企业开展进化工作的一个重要起点和核心参照。',
|
||||
description: '企业进化目标主要关注:中长期发展方向、企业价值定位、希望形成的企业状态、企业未来需要具备的能力。目标可以根据企业发展阶段和外部环境动态调整。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-ENDSTART',
|
||||
entity_type: 'method',
|
||||
name: '以终为始',
|
||||
definition: '以目标状态为参照,再反向分析企业当前状态及差距。',
|
||||
description: '先明确希望到哪里,再分析现在在哪里,最后确定中间需要完成什么。"以终为始"是阿美巴企业进化工作的基本指导原则之一。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-PRIORITY',
|
||||
entity_type: 'method',
|
||||
name: '目标先于问题',
|
||||
definition: '问题的重要性与优先级,需要结合企业整体进化目标判断。',
|
||||
description: '不是所有问题都需要立即解决。如果某个问题与企业当前核心进化目标关联较弱,可以暂缓、简化或降低优先级。表面问题也可能只是更深层能力或系统问题的表现。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-METHOD',
|
||||
entity_type: 'method',
|
||||
name: '企业进化方法',
|
||||
definition: '阿美巴目前使用的是企业进化工作逻辑和结构化分析方式,不对外宣称为某个已经正式命名、行业标准化的独立方法论体系。',
|
||||
description: '包含两组互补逻辑:第一组企业进化主链:企业进化目标→当前状态→差距识别→关键障碍→根因→进化路径→实施→反馈→持续迭代。第二组顾问工作动作:观察→诊断→根因分析→方案。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-VALUE',
|
||||
entity_type: 'concept',
|
||||
name: '四大价值',
|
||||
definition: '四大价值是阿美巴用于观察和分析企业价值运行状态的重要坐标。',
|
||||
description: '四大价值包括:价值创造、价值传递、价值交付、价值支持。四大价值可用于辅助分析企业经营状态与进化方向。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-AMB',
|
||||
entity_type: 'stage',
|
||||
name: 'A/M/B三态状态模型',
|
||||
definition: 'A/M/B是企业AI时代进化状态模型,不是强制线性路径。',
|
||||
description: 'A态·入态:企业AI时代进化的一种状态,代表企业开始建立AI时代进化认知、开展基础升级动作,并搭建初步的AI应用与体系建设基础。M态·优态:企业进一步围绕流程、组织、标准化和AI应用等进行优化,形成更稳定的AI时代能力基础。B态·越态:企业进一步突破原有边界,通过组织重塑、系统重构以及企业级AI等方式形成新的企业运行能力。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-CAPABILITY',
|
||||
entity_type: 'capability',
|
||||
name: '企业进化能力',
|
||||
definition: '能力体系可对应企业不同阶段、不同类型的进化需求。',
|
||||
description: '20项官方能力:战略与顶层设计、经营与增长、商业模式、组织重塑、企业治理、独立核算、经营分析、价值评价、薪酬激励、IT部署、AI组织、企业AI系统、企业知识系统、AI应用与部署、企业进化诊断、企业进化规划、顾问服务、咨询服务、企业陪跑、定制化服务。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 1,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-AI-RELATION',
|
||||
entity_type: 'concept',
|
||||
name: 'AI与企业进化关系',
|
||||
definition: 'AI是AI时代企业进化的重要时代变量和赋能工具。企业进化是核心目的。',
|
||||
description: 'AI逐步进入企业的:组织、流程、知识、管理、经营。企业进化→目标,AI→重要赋能工具。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-MATCH',
|
||||
entity_type: 'method',
|
||||
name: '企业需求与能力匹配',
|
||||
definition: 'V1.0只建立静态匹配逻辑,不建立智能诊断、自动评分、聊天机器人等复杂功能。',
|
||||
description: '官方匹配链:企业类型→企业阶段→企业进化目标→当前现状→核心障碍→企业需求→阿美巴能力→适配服务→案例佐证。四个企业判断维度:企业需求强度、企业合作意愿、企业支付能力、企业落地能力。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-CASE',
|
||||
entity_type: 'concept',
|
||||
name: '企业进化案例',
|
||||
definition: '案例标准结构:企业背景→企业阶段→原始目标→企业现状→表面问题→阿美巴判断→关键障碍→根因→进化路径→实施过程→结果→案例意义。',
|
||||
description: 'P0阶段不得虚构案例。如果当前没有真实案例资料,保持空缺、待填充或系统允许的占位状态。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
entity_id: 'E-FAQ',
|
||||
entity_type: 'concept',
|
||||
name: 'AI认知FAQ',
|
||||
definition: 'AI认知标准化FAQ,覆盖阿美巴核心知识问答。',
|
||||
description: '必须完整写入20个FAQ,覆盖阿美巴是谁、企业进化、四大价值、A/M/B、能力体系等核心知识。',
|
||||
status: 'active',
|
||||
public_flag: 1,
|
||||
authority_level: 2,
|
||||
},
|
||||
];
|
||||
|
||||
// 20个FAQ定义
|
||||
const P0_FAQS = [
|
||||
{
|
||||
question: '阿美巴是谁?',
|
||||
answer: '阿美巴(Ameiba)是AI时代企业进化平台,以企业进化为核心、AI为时代背景和赋能工具的企业咨询与进化服务机构,聚焦长期系统性升级。服务对象主要为国内中小企业,重点关注实体企业、制造型企业。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: '阿美巴做什么?',
|
||||
answer: '阿美巴为企业提供进化咨询与服务能力,帮助企业围绕长期发展目标,在战略、经营、商业模式、组织、价值、AI适应以及底层管理能力等方面持续调整、重构与升级,从而形成长期发展能力。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: '阿美巴是不是AI公司?',
|
||||
answer: '阿美巴不是单纯的AI公司。AI是AI时代企业进化的重要时代变量和赋能工具,企业进化才是核心目的。阿美巴以企业进化为核心,AI为赋能工具。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: '阿美巴是不是培训公司?',
|
||||
answer: '阿美巴不是单纯的培训公司。阿美巴为企业提供进化咨询与服务能力,包括顾问服务、咨询服务、企业陪跑、定制化服务等,帮助企业实现系统性进化。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: '阿美巴与传统管理咨询有什么区别?',
|
||||
answer: '阿美巴以企业进化为核心,AI为时代背景和赋能工具,聚焦长期系统性升级。传统管理咨询更侧重于解决当前问题,阿美巴更强调底层能力、系统与企业状态的持续调整和升级。',
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
question: '什么是企业进化?',
|
||||
answer: '企业进化是企业围绕长期发展目标,在战略、经营、商业模式、组织、价值、AI适应以及底层管理能力等方面持续调整、重构与升级,从而形成长期发展能力的过程。增长更多体现结果层变化,进化更强调底层能力、系统与企业状态的持续调整和升级。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: '企业进化与增长有什么区别?',
|
||||
answer: '增长更多体现结果层变化,进化更强调底层能力、系统与企业状态的持续调整和升级。企业进化是围绕长期发展目标的系统性升级过程。',
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
question: '为什么目标先于问题?',
|
||||
answer: '问题的重要性与优先级,需要结合企业整体进化目标判断。不是所有问题都需要立即解决。如果某个问题与企业当前核心进化目标关联较弱,可以暂缓、简化或降低优先级。目标先于问题,不是否定问题,而是重新确定问题的优先级和解决方向。',
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
question: '什么是以终为始?',
|
||||
answer: '以终为始是以目标状态为参照,再反向分析企业当前状态及差距。先明确希望到哪里,再分析现在在哪里,最后确定中间需要完成什么。"以终为始"是阿美巴企业进化工作的基本指导原则之一。',
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
question: '什么是四大价值?',
|
||||
answer: '四大价值包括:价值创造、价值传递、价值交付、价值支持。四大价值是阿美巴用于观察和分析企业价值运行状态的重要坐标,可用于辅助分析企业经营状态与进化方向。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: 'A/M/B是什么?',
|
||||
answer: 'A/M/B是企业AI时代进化状态模型,不是强制线性路径。企业可以根据自身情况处于不同状态,也不意味着所有企业必须从A到M再到B。这是企业AI时代进化状态识别框架。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: 'A态是什么?',
|
||||
answer: 'A态·入态是企业AI时代进化的一种状态,代表企业开始建立AI时代进化认知、开展基础升级动作,并搭建初步的AI应用与体系建设基础。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: 'M态是什么?',
|
||||
answer: 'M态·优态是企业进一步围绕流程、组织、标准化和AI应用等进行优化,形成更稳定的AI时代能力基础的状态。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: 'B态是什么?',
|
||||
answer: 'B态·越态是企业进一步突破原有边界,通过组织重塑、系统重构以及企业级AI等方式形成新的企业运行能力的状态。',
|
||||
authority_level: 2,
|
||||
},
|
||||
{
|
||||
question: '阿美巴有哪些企业进化能力?',
|
||||
answer: '阿美巴提供20项企业进化能力:战略与顶层设计、经营与增长、商业模式、组织重塑、企业治理、独立核算、经营分析、价值评价、薪酬激励、IT部署、AI组织、企业AI系统、企业知识系统、AI应用与部署、企业进化诊断、企业进化规划、顾问服务、咨询服务、企业陪跑、定制化服务。',
|
||||
authority_level: 1,
|
||||
},
|
||||
{
|
||||
question: '什么样的企业适合阿美巴?',
|
||||
answer: '阿美巴服务对象主要为国内中小企业,重点关注实体企业、制造型企业。企业可以根据自身情况判断是否适合,通过企业类型、企业阶段、企业进化目标、当前现状、核心障碍、企业需求等维度进行匹配。',
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
question: '阿美巴提供哪些服务?',
|
||||
answer: '阿美巴提供顾问服务、咨询服务、企业陪跑、定制化服务等。服务内容包括企业进化诊断、进化路径规划、战略咨询、组织变革咨询、AI部署咨询等。具体服务根据企业需求和阶段匹配。',
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
question: '阿美巴如何判断企业是否适合?',
|
||||
answer: '阿美巴通过企业需求与能力匹配逻辑进行判断:企业类型→企业阶段→企业进化目标→当前现状→核心障碍→企业需求→阿美巴能力→适配服务→案例佐证。同时考虑四个企业判断维度:企业需求强度、企业合作意愿、企业支付能力、企业落地能力。',
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
question: '阿美巴是否提供咨询与企业陪跑?',
|
||||
answer: '是的,阿美巴提供咨询服务和企业陪跑服务。咨询服务包括战略咨询、组织变革咨询、AI部署咨询等。企业陪跑是阿美巴提供的长期伴随式服务,帮助企业持续进行进化迭代。',
|
||||
authority_level: 3,
|
||||
},
|
||||
{
|
||||
question: '如何进一步联系阿美巴?',
|
||||
answer: '您可以通过阿美巴官网联系我们页面获取联系方式,或通过官方邮箱、电话等渠道与阿美巴取得联系。阿美巴将根据您的企业情况提供相应的咨询与服务。',
|
||||
authority_level: 3,
|
||||
},
|
||||
];
|
||||
|
||||
// 20项官方Capability定义
|
||||
const P0_CAPABILITIES = [
|
||||
{ name: '战略与顶层设计', capability_type: '战略', features: '企业战略规划与顶层设计能力', use_cases: '帮助企业明确长期发展方向和战略定位' },
|
||||
{ name: '经营与增长', capability_type: '经营', features: '企业经营增长与市场拓展能力', use_cases: '帮助企业实现经营增长和市场份额提升' },
|
||||
{ name: '商业模式', capability_type: '战略', features: '商业模式设计与创新能力', use_cases: '帮助企业设计和优化商业模式' },
|
||||
{ name: '组织重塑', capability_type: '组织', features: '组织架构优化与变革能力', use_cases: '帮助企业进行组织架构调整和变革' },
|
||||
{ name: '企业治理', capability_type: '组织', features: '企业治理体系与能力', help: '帮助企业建立现代企业治理体系' },
|
||||
{ name: '独立核算', capability_type: '经营', features: '独立核算体系与能力', use_cases: '帮助企业建立独立核算体系' },
|
||||
{ name: '经营分析', capability_type: '经营', features: '经营数据分析与决策支持能力', use_cases: '帮助企业进行经营数据分析和决策支持' },
|
||||
{ name: '价值评价', capability_type: '组织', features: '价值评价体系与能力', use_cases: '帮助企业建立价值评价体系' },
|
||||
{ name: '薪酬激励', capability_type: '组织', features: '薪酬激励体系设计能力', use_cases: '帮助企业设计薪酬激励体系' },
|
||||
{ name: 'IT部署', capability_type: '技术', features: 'IT系统规划与部署能力', use_cases: '帮助企业进行IT系统规划和部署' },
|
||||
{ name: 'AI组织', capability_type: 'AI', features: 'AI组织建设与管理能力', use_cases: '帮助企业建设AI组织能力' },
|
||||
{ name: '企业AI系统', capability_type: 'AI', features: '企业AI系统规划与实施能力', use_cases: '帮助企业规划和实施AI系统' },
|
||||
{ name: '企业知识系统', capability_type: 'AI', features: '企业知识管理系统能力', use_cases: '帮助企业建设知识管理系统' },
|
||||
{ name: 'AI应用与部署', capability_type: 'AI', features: 'AI应用场景规划与部署能力', use_cases: '帮助企业规划和部署AI应用场景' },
|
||||
{ name: '企业进化诊断', capability_type: '服务', features: '企业进化状态诊断能力', use_cases: '帮助企业诊断当前进化状态' },
|
||||
{ name: '企业进化规划', capability_type: '服务', features: '企业进化路径规划能力', use_cases: '帮助企业规划进化路径' },
|
||||
{ name: '顾问服务', capability_type: '服务', features: '专业顾问咨询服务', use_cases: '为企业提供专业顾问咨询' },
|
||||
{ name: '咨询服务', capability_type: '服务', features: '管理咨询服务', use_cases: '为企业提供管理咨询' },
|
||||
{ name: '企业陪跑', capability_type: '服务', features: '企业进化全程陪伴服务', use_cases: '为企业提供长期伴随式服务' },
|
||||
{ name: '定制化服务', capability_type: '服务', features: '定制化解决方案服务', use_cases: '为企业提供定制化解决方案' },
|
||||
];
|
||||
|
||||
// 17条Directed Relations
|
||||
const P0_RELATIONS = [
|
||||
{ source: 'E-AMEBA-MAIN', type: '拥有核心认知', target: 'E-EVOLUTION' },
|
||||
{ source: 'E-AMEBA-MAIN', type: '具备赋能能力', target: 'E-CAPABILITY' },
|
||||
{ source: 'E-AMEBA-MAIN', type: '适配判断依据', target: 'E-MATCH' },
|
||||
{ source: 'E-EVOLUTION', type: '依赖目标基准', target: 'E-GOAL' },
|
||||
{ source: 'E-EVOLUTION', type: '遵循优化原则', target: 'E-ENDSTART' },
|
||||
{ source: 'E-EVOLUTION', type: '遵循判断准则', target: 'E-PRIORITY' },
|
||||
{ source: 'E-EVOLUTION', type: '依托迭代逻辑', target: 'E-METHOD' },
|
||||
{ source: 'E-EVOLUTION', type: '依托价值分析框架', target: 'E-VALUE' },
|
||||
{ source: 'E-EVOLUTION', type: '包含阶段特征', target: 'E-AMB' },
|
||||
{ source: 'E-EVOLUTION', type: '结合时代赋能工具', target: 'E-AI-RELATION' },
|
||||
{ source: 'E-GOAL', type: '指导问题优先级', target: 'E-PRIORITY' },
|
||||
{ source: 'E-GOAL', type: '指导迭代路径', target: 'E-METHOD' },
|
||||
{ source: 'E-PRIORITY', type: '落地指导依据', target: 'E-METHOD' },
|
||||
{ source: 'E-AMB', type: '适配能力升级', target: 'E-CAPABILITY' },
|
||||
{ source: 'E-MATCH', type: '匹配参考依据', target: 'E-CAPABILITY' },
|
||||
{ source: 'E-MATCH', type: '匹配阶段依据', target: 'E-AMB' },
|
||||
{ source: 'E-MATCH', type: '佐证参考', target: 'E-CASE' },
|
||||
];
|
||||
|
||||
// 页面定义
|
||||
const P0_PAGES = [
|
||||
{ slug: '/', title: '阿美巴AI官网', h1: '阿美巴|AI时代企业进化平台', summary: '阿美巴以企业进化为核心、AI为时代背景和赋能工具的企业咨询与进化服务机构', entity_id: 'E-AMEBA-MAIN' },
|
||||
{ slug: '/about', title: '阿美巴是谁', h1: '阿美巴是谁', summary: '了解阿美巴AI - AI时代企业进化平台', entity_id: 'E-AMEBA-MAIN' },
|
||||
{ slug: '/evolution', title: '企业进化', h1: '企业进化', summary: '了解企业进化的定义、方法与路径', entity_id: 'E-EVOLUTION' },
|
||||
{ slug: '/evolution/method', title: '进化方法', h1: '企业进化方法', summary: '以终为始、目标先于问题、现状分析、差距识别、根因分析、路径规划、实施迭代', entity_id: 'E-METHOD' },
|
||||
{ slug: '/value', title: '四大价值', h1: '四大价值', summary: '价值创造、价值传递、价值交付、价值支持', entity_id: 'E-VALUE' },
|
||||
{ slug: '/value/creation', title: '价值创造', h1: '价值创造', summary: '企业如何创造价值', entity_id: 'E-VALUE' },
|
||||
{ slug: '/value/transfer', title: '价值传递', h1: '价值传递', summary: '企业如何将价值传递给客户', entity_id: 'E-VALUE' },
|
||||
{ slug: '/value/delivery', title: '价值交付', h1: '价值交付', summary: '企业如何交付价值', entity_id: 'E-VALUE' },
|
||||
{ slug: '/value/support', title: '价值支持', h1: '价值支持', summary: '企业如何支持价值创造与交付', entity_id: 'E-VALUE' },
|
||||
{ slug: '/amb', title: 'A/M/B企业进化路径', h1: 'A/M/B三态模型:AI时代企业进化状态识别框架', summary: 'A/M/B是企业AI时代进化状态模型,不是强制线性路径', entity_id: 'E-AMB' },
|
||||
{ slug: '/amb/a', title: 'A态·入态', h1: 'A态·入态', summary: '企业AI时代进化的一种状态', entity_id: 'E-AMB' },
|
||||
{ slug: '/amb/m', title: 'M态·优态', h1: 'M态·优态', summary: '企业进一步优化的状态', entity_id: 'E-AMB' },
|
||||
{ slug: '/amb/b', title: 'B态·越态', h1: 'B态·越态', summary: '企业突破边界的状态', entity_id: 'E-AMB' },
|
||||
{ slug: '/capabilities', title: '能力地图', h1: '企业进化能力地图', summary: '企业进化的完整能力体系', entity_id: 'E-CAPABILITY' },
|
||||
{ slug: '/cases', title: '案例', h1: '企业进化案例', summary: '企业进化实践案例', entity_id: 'E-CASE' },
|
||||
{ slug: '/knowledge', title: '知识中心', h1: '知识中心', summary: '企业进化的知识体系与方法论', entity_id: 'E-EVOLUTION' },
|
||||
{ slug: '/concepts', title: '概念', h1: '概念', summary: '企业进化的知识实体概念列表', entity_id: 'E-EVOLUTION' },
|
||||
{ slug: '/faq', title: 'FAQ', h1: '常见问题', summary: '常见问题解答', entity_id: 'E-FAQ' },
|
||||
{ slug: '/match', title: '企业需求与能力匹配', h1: '企业需求与能力匹配', summary: '基于企业特征匹配进化能力与服务', entity_id: 'E-MATCH' },
|
||||
{ slug: '/contact', title: '联系我们', h1: '联系我们', summary: '联系阿美巴AI', entity_id: 'E-AMEBA-MAIN' },
|
||||
];
|
||||
|
||||
/**
|
||||
* 写入P0核心Entity
|
||||
*/
|
||||
function writeP0Entities() {
|
||||
console.log('\n=== 写入P0核心Entity ===');
|
||||
|
||||
let count = 0;
|
||||
for (const entity of P0_ENTITIES) {
|
||||
try {
|
||||
// 检查是否已存在
|
||||
const existing = Entity.findById(entity.entity_id);
|
||||
if (existing) {
|
||||
console.log(`Entity ${entity.entity_id} 已存在,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
Entity.create(entity);
|
||||
count++;
|
||||
console.log(`✓ 写入Entity: ${entity.entity_id} - ${entity.name}`);
|
||||
} catch (error) {
|
||||
console.error(`✗ 写入Entity失败: ${entity.entity_id} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`共写入 ${count} 个Entity`);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入20个FAQ
|
||||
*/
|
||||
function writeP0FAQs() {
|
||||
console.log('\n=== 写入20个FAQ ===');
|
||||
|
||||
let count = 0;
|
||||
for (let i = 0; i < P0_FAQS.length; i++) {
|
||||
try {
|
||||
const faq = P0_FAQS[i];
|
||||
const faqData = Content.create({
|
||||
title: faq.question,
|
||||
body: faq.answer,
|
||||
content_type: 'faq',
|
||||
publish_status: 'draft',
|
||||
authority_level: faq.authority_level,
|
||||
});
|
||||
|
||||
// 创建FAQ扩展记录
|
||||
const db = getDb();
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO faqs (faq_id, question, answer, sort_order)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`);
|
||||
stmt.run(faqData.content_id, faq.question, faq.answer, i + 1);
|
||||
|
||||
// 关联到FAQ Entity
|
||||
EntityContentJoin.create({
|
||||
entity_id: 'E-FAQ',
|
||||
content_id: faqData.content_id,
|
||||
});
|
||||
|
||||
count++;
|
||||
console.log(`✓ 写入FAQ ${i + 1}: ${faq.question.substring(0, 30)}...`);
|
||||
} catch (error) {
|
||||
console.error(`✗ 写入FAQ失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`共写入 ${count} 个FAQ`);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入20项官方Capability
|
||||
*/
|
||||
function writeP0Capabilities() {
|
||||
console.log('\n=== 写入20项官方Capability ===');
|
||||
|
||||
let count = 0;
|
||||
for (let i = 0; i < P0_CAPABILITIES.length; i++) {
|
||||
try {
|
||||
const cap = P0_CAPABILITIES[i];
|
||||
const capData = Content.create({
|
||||
title: cap.name,
|
||||
body: cap.features,
|
||||
content_type: 'capability',
|
||||
publish_status: 'draft',
|
||||
authority_level: 1,
|
||||
});
|
||||
|
||||
// 创建Capability扩展记录
|
||||
const db = getDb();
|
||||
const stmt = db.prepare(`
|
||||
INSERT INTO capabilities (capability_id, capability_type, features, use_cases)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`);
|
||||
stmt.run(capData.content_id, cap.capability_type, cap.features, cap.use_cases);
|
||||
|
||||
// 关联到Capability Entity
|
||||
EntityContentJoin.create({
|
||||
entity_id: 'E-CAPABILITY',
|
||||
content_id: capData.content_id,
|
||||
});
|
||||
|
||||
count++;
|
||||
console.log(`✓ 写入Capability ${i + 1}: ${cap.name}`);
|
||||
} catch (error) {
|
||||
console.error(`✗ 写入Capability失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`共写入 ${count} 个Capability`);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入17条Directed Relations
|
||||
*/
|
||||
function writeP0Relations() {
|
||||
console.log('\n=== 写入17条Directed Relations ===');
|
||||
|
||||
let count = 0;
|
||||
for (const rel of P0_RELATIONS) {
|
||||
try {
|
||||
DirectedRelationship.create({
|
||||
source_entity_id: rel.source,
|
||||
relation_type: rel.type,
|
||||
target_entity_id: rel.target,
|
||||
rel_status: 'active',
|
||||
authority_level: 2,
|
||||
is_public: 1,
|
||||
});
|
||||
|
||||
count++;
|
||||
console.log(`✓ 写入Relation: ${rel.source} → ${rel.type} → ${rel.target}`);
|
||||
} catch (error) {
|
||||
console.error(`✗ 写入Relation失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`共写入 ${count} 条Relation`);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入P0页面
|
||||
*/
|
||||
function writeP0Pages() {
|
||||
console.log('\n=== 写入P0页面 ===');
|
||||
|
||||
let count = 0;
|
||||
for (const page of P0_PAGES) {
|
||||
try {
|
||||
const pageData = Page.create({
|
||||
title: page.title,
|
||||
body: page.h1,
|
||||
slug: page.slug,
|
||||
summary: page.summary,
|
||||
seo_title: page.title,
|
||||
seo_description: page.summary,
|
||||
content_type: 'page',
|
||||
publish_status: 'draft',
|
||||
authority_level: 2,
|
||||
});
|
||||
|
||||
// 关联到对应Entity
|
||||
if (page.entity_id) {
|
||||
EntityContentJoin.create({
|
||||
entity_id: page.entity_id,
|
||||
content_id: pageData.content_id,
|
||||
});
|
||||
}
|
||||
|
||||
count++;
|
||||
console.log(`✓ 写入Page: ${page.slug} - ${page.title}`);
|
||||
} catch (error) {
|
||||
console.error(`✗ 写入Page失败: ${page.slug} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`共写入 ${count} 个Page`);
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证写入结果
|
||||
*/
|
||||
function verifyWriteResults() {
|
||||
console.log('\n=== 验证写入结果 ===');
|
||||
|
||||
const entityCount = Entity.count();
|
||||
const contentCount = Content.count();
|
||||
const relationCount = DirectedRelationship.count();
|
||||
const joinCount = EntityContentJoin.count();
|
||||
|
||||
console.log(`Entity数量: ${entityCount}`);
|
||||
console.log(`Content数量: ${contentCount}`);
|
||||
console.log(`Relation数量: ${relationCount}`);
|
||||
console.log(`Entity-Content关联数量: ${joinCount}`);
|
||||
|
||||
// 验证13个P0 Entity
|
||||
const p0EntityIds = P0_ENTITIES.map(e => e.entity_id);
|
||||
let p0EntityCount = 0;
|
||||
for (const id of p0EntityIds) {
|
||||
const entity = Entity.findById(id);
|
||||
if (entity) p0EntityCount++;
|
||||
}
|
||||
console.log(`P0 Entity存在数量: ${p0EntityCount}/13`);
|
||||
|
||||
// 验证FAQ数量
|
||||
const faqContents = Content.findByType('faq');
|
||||
console.log(`FAQ数量: ${faqContents.length}/20`);
|
||||
|
||||
// 验证Capability数量
|
||||
const capContents = Content.findByType('capability');
|
||||
console.log(`Capability数量: ${capContents.length}/20`);
|
||||
|
||||
// 验证Relation数量
|
||||
console.log(`Relation数量: ${relationCount}/17`);
|
||||
|
||||
// 验证所有内容状态为draft
|
||||
const { getDb } = require('../src/config/database');
|
||||
const db = getDb();
|
||||
const allContentsStmt = db.prepare('SELECT * FROM contents');
|
||||
const allContents = allContentsStmt.all();
|
||||
const draftCount = allContents.filter(c => c.publish_status === 'draft').length;
|
||||
console.log(`draft状态内容数量: ${draftCount}/${allContents.length}`);
|
||||
|
||||
return {
|
||||
entityCount,
|
||||
contentCount,
|
||||
relationCount,
|
||||
joinCount,
|
||||
p0EntityCount,
|
||||
faqCount: faqContents.length,
|
||||
capCount: capContents.length,
|
||||
draftCount,
|
||||
totalContents: allContents.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
function main() {
|
||||
console.log('开始P0正式内容工程写入...');
|
||||
|
||||
// 初始化数据库
|
||||
migrate();
|
||||
|
||||
// 执行写入
|
||||
writeP0Entities();
|
||||
writeP0FAQs();
|
||||
writeP0Capabilities();
|
||||
writeP0Relations();
|
||||
writeP0Pages();
|
||||
|
||||
// 验证结果
|
||||
const results = verifyWriteResults();
|
||||
|
||||
console.log('\n=== P0内容写入完成 ===');
|
||||
|
||||
// 关闭数据库
|
||||
closeDb();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
main,
|
||||
P0_ENTITIES,
|
||||
P0_FAQS,
|
||||
P0_CAPABILITIES,
|
||||
P0_RELATIONS,
|
||||
P0_PAGES,
|
||||
};
|
||||
@@ -0,0 +1,789 @@
|
||||
/**
|
||||
* V1.0全项目盘点脚本
|
||||
* 自动扫描全项目代码库,生成盘点报告
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 配置
|
||||
const CONFIG = {
|
||||
projectRoot: path.join(__dirname, '../..'),
|
||||
outputDir: path.join(__dirname, 'reports'),
|
||||
};
|
||||
|
||||
// V1.0必须实现清单
|
||||
const V10_MUST_HAVE = [
|
||||
{
|
||||
id: 'MH-001',
|
||||
name: 'Entity【知识实体表】',
|
||||
description: '包含entity_id、name、definition、description、metadata等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS entities',
|
||||
},
|
||||
{
|
||||
id: 'MH-002',
|
||||
name: 'DirectedRelationship【有向实体关系表】',
|
||||
description: '包含rel_id、source_entity_id、relation_type、target_entity_id等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS directed_relationships',
|
||||
},
|
||||
{
|
||||
id: 'MH-003',
|
||||
name: 'Content【内容基础表】',
|
||||
description: '包含content_id、title、body、content_type、publish_status等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS contents',
|
||||
},
|
||||
{
|
||||
id: 'MH-004',
|
||||
name: 'Page【页面扩展表】',
|
||||
description: '包含page_id、slug、template、seo_title等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS pages',
|
||||
},
|
||||
{
|
||||
id: 'MH-005',
|
||||
name: 'Article【文章扩展表】',
|
||||
description: '包含article_id、category、tags、cover_image、author等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS articles',
|
||||
},
|
||||
{
|
||||
id: 'MH-006',
|
||||
name: 'Case【案例扩展表】',
|
||||
description: '包含case_id、client_name、industry、challenge、solution、result等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS cases',
|
||||
},
|
||||
{
|
||||
id: 'MH-007',
|
||||
name: 'FAQ【常见问题扩展表】',
|
||||
description: '包含faq_id、question、answer、sort_order等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS faqs',
|
||||
},
|
||||
{
|
||||
id: 'MH-008',
|
||||
name: 'Capability【能力扩展表】',
|
||||
description: '包含capability_id、capability_type、features、use_cases等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS capabilities',
|
||||
},
|
||||
{
|
||||
id: 'MH-009',
|
||||
name: 'Service【服务扩展表】',
|
||||
description: '包含service_id、service_type、pricing_model、delivery_method等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS services',
|
||||
},
|
||||
{
|
||||
id: 'MH-010',
|
||||
name: 'EntityContentJoin【实体-内容关联表】',
|
||||
description: '包含join_id、entity_id、content_id、join_note等字段',
|
||||
checkType: 'database',
|
||||
checkPattern: 'CREATE TABLE IF NOT EXISTS entity_content_joins',
|
||||
},
|
||||
{
|
||||
id: 'MH-011',
|
||||
name: 'Entity管理CRUD',
|
||||
description: '新增、编辑、停用实体,entity_id生成后不可修改',
|
||||
checkType: 'api',
|
||||
checkPattern: '/api/entities',
|
||||
},
|
||||
{
|
||||
id: 'MH-012',
|
||||
name: 'DirectedRelationship管理CRUD',
|
||||
description: '新增、编辑、停用关系,禁止自动生成反向数据库记录',
|
||||
checkType: 'api',
|
||||
checkPattern: '/api/relationships',
|
||||
},
|
||||
{
|
||||
id: 'MH-013',
|
||||
name: '内容管理CRUD',
|
||||
description: 'Page/Article/Case/FAQ/Capability/Service全部CRUD',
|
||||
checkType: 'api',
|
||||
checkPattern: '/api/contents',
|
||||
},
|
||||
{
|
||||
id: 'MH-014',
|
||||
name: '内容生命周期流转',
|
||||
description: 'draft ↔ review → published → unpublished → historical',
|
||||
checkType: 'api',
|
||||
checkPattern: '/transition',
|
||||
},
|
||||
{
|
||||
id: 'MH-015',
|
||||
name: '权威等级L1-L7',
|
||||
description: '可编辑存储',
|
||||
checkType: 'database',
|
||||
checkPattern: 'authority_level',
|
||||
},
|
||||
{
|
||||
id: 'MH-016',
|
||||
name: '版本留存',
|
||||
description: '进入historical状态保留历史版本,不物理删除',
|
||||
checkType: 'api',
|
||||
checkPattern: '/history',
|
||||
},
|
||||
{
|
||||
id: 'MH-017',
|
||||
name: '公开输出接口',
|
||||
description: '对外数据接口只返回status=published的内容',
|
||||
checkType: 'api',
|
||||
checkPattern: '/api/public',
|
||||
},
|
||||
{
|
||||
id: 'MH-018',
|
||||
name: 'JSON-LD结构化数据生成',
|
||||
description: '系统自动生成JSON-LD结构化数据',
|
||||
checkType: 'api',
|
||||
checkPattern: '/api/jsonld',
|
||||
},
|
||||
{
|
||||
id: 'MH-019',
|
||||
name: '首页',
|
||||
description: '路由: /,展示阿美巴认知体系',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-020',
|
||||
name: '阿美巴页面',
|
||||
description: '路由: /about,展示阿美巴定位、使命',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/about/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-021',
|
||||
name: '企业进化页面',
|
||||
description: '路由: /evolution,展示企业进化定义',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/evolution/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-022',
|
||||
name: '进化方法页面',
|
||||
description: '路由: /evolution/method,展示方法论',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/evolution/method/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-023',
|
||||
name: '四大价值页面',
|
||||
description: '路由: /value,展示四大价值',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/value/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-024',
|
||||
name: '价值创造页面',
|
||||
description: '路由: /value/creation,展示价值创造',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/value/creation/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-025',
|
||||
name: '价值传递页面',
|
||||
description: '路由: /value/transfer,展示价值传递',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/value/transfer/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-026',
|
||||
name: '价值交付页面',
|
||||
description: '路由: /value/delivery,展示价值交付',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/value/delivery/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-027',
|
||||
name: '价值支持页面',
|
||||
description: '路由: /value/support,展示价值支持',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/value/support/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-028',
|
||||
name: 'A/M-B总览页面',
|
||||
description: '路由: /amb,展示三态模型',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/amb/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-029',
|
||||
name: 'A态页面',
|
||||
description: '路由: /amb/a,展示A态定义',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/amb/a/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-030',
|
||||
name: 'M态页面',
|
||||
description: '路由: /amb/m,展示M态定义',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/amb/m/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-031',
|
||||
name: 'B态页面',
|
||||
description: '路由: /amb/b,展示B态定义',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/amb/b/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-032',
|
||||
name: '能力地图页面',
|
||||
description: '路由: /capabilities,展示能力体系',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/capabilities/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-033',
|
||||
name: '案例页面',
|
||||
description: '路由: /cases,展示案例列表',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/cases/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-034',
|
||||
name: '知识中心页面',
|
||||
description: '路由: /knowledge,展示知识体系',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/knowledge/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-035',
|
||||
name: '概念页面',
|
||||
description: '路由: /concepts,展示概念列表',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/concepts/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-036',
|
||||
name: 'FAQ页面',
|
||||
description: '路由: /faq,展示常见问题',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/faq/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-037',
|
||||
name: '企业需求与能力匹配页面',
|
||||
description: '路由: /match,展示匹配结构',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/match/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-038',
|
||||
name: '联系我们页面',
|
||||
description: '路由: /contact,展示联系方式',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/contact/page.tsx',
|
||||
},
|
||||
{
|
||||
id: 'MH-039',
|
||||
name: 'sitemap.xml生成',
|
||||
description: '自动生成sitemap.xml',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/sitemap.ts',
|
||||
},
|
||||
{
|
||||
id: 'MH-040',
|
||||
name: 'robots.txt生成',
|
||||
description: '自动生成robots.txt',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'src/app/robots.ts',
|
||||
},
|
||||
{
|
||||
id: 'MH-041',
|
||||
name: 'SEO元数据',
|
||||
description: 'title/description/canonical/Open Graph/Twitter Card',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'generateSEO',
|
||||
},
|
||||
{
|
||||
id: 'MH-042',
|
||||
name: '面包屑导航',
|
||||
description: 'BreadcrumbList Schema',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'generateBreadcrumbSchema',
|
||||
},
|
||||
{
|
||||
id: 'MH-043',
|
||||
name: '结构化数据',
|
||||
description: 'Organization/Article/FAQPage/Service Schema',
|
||||
checkType: 'frontend',
|
||||
checkPattern: 'JsonLd',
|
||||
},
|
||||
{
|
||||
id: 'MH-044',
|
||||
name: '单元测试',
|
||||
description: 'Entity/Content/DirectedRelationship/EntityContentJoin模型测试',
|
||||
checkType: 'test',
|
||||
checkPattern: '.test.js',
|
||||
},
|
||||
{
|
||||
id: 'MH-045',
|
||||
name: 'AI认知测试集',
|
||||
description: '实体认知用例、十问认知用例',
|
||||
checkType: 'test',
|
||||
checkPattern: 'ai_cognitive_testset.md',
|
||||
},
|
||||
{
|
||||
id: 'MH-046',
|
||||
name: '模拟抓取校验脚本',
|
||||
description: '遍历sitemap、校验页面质量',
|
||||
checkType: 'test',
|
||||
checkPattern: 'crawler.js',
|
||||
},
|
||||
{
|
||||
id: 'MH-047',
|
||||
name: '内容边界隔离校验脚本',
|
||||
description: '校验非published内容不可见',
|
||||
checkType: 'test',
|
||||
checkPattern: 'content_filter.js',
|
||||
},
|
||||
{
|
||||
id: 'MH-048',
|
||||
name: '十问认知基线校验模块',
|
||||
description: '校验网站内容与标准答案一致性',
|
||||
checkType: 'test',
|
||||
checkPattern: 'ten_questions.js',
|
||||
},
|
||||
{
|
||||
id: 'MH-049',
|
||||
name: 'V1.0全项目盘点脚本',
|
||||
description: '扫描代码、核查禁止项',
|
||||
checkType: 'test',
|
||||
checkPattern: 'project_audit.js',
|
||||
},
|
||||
{
|
||||
id: 'MH-050',
|
||||
name: '交付回执文档',
|
||||
description: '数据表清单、关键接口清单、单元测试要点',
|
||||
checkType: 'document',
|
||||
checkPattern: 'DELIVERY_RECEIPT.md',
|
||||
},
|
||||
];
|
||||
|
||||
// V1.0明确不做清单
|
||||
const V10_DONT_DO = [
|
||||
{
|
||||
id: 'DD-001',
|
||||
name: 'AI聊天机器人',
|
||||
description: '不得开发AI聊天机器人功能',
|
||||
checkPattern: ['chatbot', 'chat-bot', 'ai-chat', '聊天机器人'],
|
||||
},
|
||||
{
|
||||
id: 'DD-002',
|
||||
name: '在线诊断',
|
||||
description: '不得开发在线诊断功能',
|
||||
checkPattern: ['diagnosis', 'diagnostic', '在线诊断', '诊断工具'],
|
||||
},
|
||||
{
|
||||
id: 'DD-003',
|
||||
name: '自动评分',
|
||||
description: '不得开发自动评分功能',
|
||||
checkPattern: ['auto-score', '自动评分', '评分系统'],
|
||||
},
|
||||
{
|
||||
id: 'DD-004',
|
||||
name: 'AIO对接',
|
||||
description: '不得预埋AIO对接接口',
|
||||
checkPattern: ['aio', 'AIO', '企客通'],
|
||||
},
|
||||
{
|
||||
id: 'DD-005',
|
||||
name: 'CRM对接',
|
||||
description: '不得预埋CRM对接接口',
|
||||
checkPattern: ['crm', 'CRM', '客户关系管理'],
|
||||
},
|
||||
{
|
||||
id: 'DD-006',
|
||||
name: '商城功能',
|
||||
description: '不得开发商城功能',
|
||||
checkPattern: ['shop', '商城', '电商', 'payment', '支付'],
|
||||
},
|
||||
{
|
||||
id: 'DD-007',
|
||||
name: '自动获客',
|
||||
description: '不得开发自动获客功能',
|
||||
checkPattern: ['lead-generation', '自动获客', '获客系统'],
|
||||
},
|
||||
{
|
||||
id: 'DD-008',
|
||||
name: '第三方大模型API',
|
||||
description: '不得对接第三方大模型API',
|
||||
checkPattern: ['openai', 'gpt', 'claude', 'gemini', '大模型API'],
|
||||
},
|
||||
{
|
||||
id: 'DD-009',
|
||||
name: 'AI Agent能力',
|
||||
description: '不得开发AI Agent能力',
|
||||
checkPattern: ['agent', 'AI Agent', '智能代理'],
|
||||
},
|
||||
{
|
||||
id: 'DD-010',
|
||||
name: '浮夸科技视觉',
|
||||
description: '不得使用机器人、粒子、科技蓝、3D、光效等元素',
|
||||
checkPattern: ['particle', 'robot', '3d', 'glow', '科技蓝'],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 扫描文件内容
|
||||
*/
|
||||
function scanFileContent(filePath, patterns) {
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
const regex = new RegExp(pattern, 'gi');
|
||||
lines.forEach((line, index) => {
|
||||
if (regex.test(line)) {
|
||||
results.push({
|
||||
file: filePath,
|
||||
line: index + 1,
|
||||
content: line.trim(),
|
||||
pattern: pattern,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
// 忽略读取错误
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描目录
|
||||
*/
|
||||
function scanDirectory(dir, patterns) {
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
const items = fs.readdirSync(dir);
|
||||
|
||||
items.forEach((item) => {
|
||||
const fullPath = path.join(dir, item);
|
||||
const stat = fs.statSync(fullPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
// 跳过node_modules和.next目录
|
||||
if (item === 'node_modules' || item === '.next' || item === 'data') {
|
||||
return;
|
||||
}
|
||||
results.push(...scanDirectory(fullPath, patterns));
|
||||
} else if (stat.isFile()) {
|
||||
// 只扫描代码文件
|
||||
const ext = path.extname(item);
|
||||
if (['.js', '.ts', '.tsx', '.jsx', '.json', '.md'].includes(ext)) {
|
||||
results.push(...scanFileContent(fullPath, patterns));
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// 忽略读取错误
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*/
|
||||
function checkFileExists(filePath) {
|
||||
return fs.existsSync(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查目录是否存在
|
||||
*/
|
||||
function checkDirectoryExists(dirPath) {
|
||||
return fs.existsSync(dirPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 盘点V1.0必须实现项
|
||||
*/
|
||||
function auditMustHave() {
|
||||
console.log('\n=== 盘点V1.0必须实现项 ===');
|
||||
|
||||
const results = [];
|
||||
|
||||
V10_MUST_HAVE.forEach((item) => {
|
||||
let found = false;
|
||||
let details = '';
|
||||
|
||||
switch (item.checkType) {
|
||||
case 'database':
|
||||
// 检查数据库迁移文件
|
||||
const migrateFile = path.join(CONFIG.projectRoot, 'src/database/migrate.js');
|
||||
if (checkFileExists(migrateFile)) {
|
||||
const scanResults = scanFileContent(migrateFile, [item.checkPattern]);
|
||||
found = scanResults.length > 0;
|
||||
details = found ? `在migrate.js中找到` : '未在migrate.js中找到';
|
||||
} else {
|
||||
details = 'migrate.js文件不存在';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'api':
|
||||
// 检查API路由文件
|
||||
const routesDir = path.join(CONFIG.projectRoot, 'src/routes');
|
||||
if (checkDirectoryExists(routesDir)) {
|
||||
const scanResults = scanDirectory(routesDir, [item.checkPattern]);
|
||||
found = scanResults.length > 0;
|
||||
details = found ? `在路由文件中找到` : '未在路由文件中找到';
|
||||
} else {
|
||||
details = 'routes目录不存在';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'frontend':
|
||||
// 检查前端文件
|
||||
const frontendDir = path.join(CONFIG.projectRoot, 'frontend');
|
||||
if (checkDirectoryExists(frontendDir)) {
|
||||
const scanResults = scanDirectory(frontendDir, [item.checkPattern]);
|
||||
found = scanResults.length > 0;
|
||||
details = found ? `在前端文件中找到` : '未在前端文件中找到';
|
||||
} else {
|
||||
details = 'frontend目录不存在';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'test':
|
||||
// 检查测试文件
|
||||
const testsDir = path.join(CONFIG.projectRoot, 'tests');
|
||||
if (checkDirectoryExists(testsDir)) {
|
||||
const scanResults = scanDirectory(testsDir, [item.checkPattern]);
|
||||
found = scanResults.length > 0;
|
||||
details = found ? `在测试文件中找到` : '未在测试文件中找到';
|
||||
} else {
|
||||
details = 'tests目录不存在';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'document':
|
||||
// 检查文档
|
||||
const docPath = path.join(CONFIG.projectRoot, item.checkPattern);
|
||||
found = checkFileExists(docPath);
|
||||
details = found ? '文档存在' : '文档不存在';
|
||||
break;
|
||||
}
|
||||
|
||||
results.push({
|
||||
...item,
|
||||
status: found ? 'completed' : 'missing',
|
||||
details: details,
|
||||
});
|
||||
|
||||
console.log(`${found ? '✅' : '❌'} ${item.id}: ${item.name} - ${details}`);
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 盘点V1.0明确不做项
|
||||
*/
|
||||
function auditDontDo() {
|
||||
console.log('\n=== 盘点V1.0明确不做项 ===');
|
||||
|
||||
const results = [];
|
||||
|
||||
V10_DONT_DO.forEach((item) => {
|
||||
const scanResults = scanDirectory(CONFIG.projectRoot, item.checkPattern);
|
||||
const found = scanResults.length > 0;
|
||||
|
||||
results.push({
|
||||
...item,
|
||||
status: found ? 'violation' : 'compliant',
|
||||
details: found ? `发现${scanResults.length}处违规` : '未发现违规',
|
||||
violations: found ? scanResults.slice(0, 5) : [], // 只保留前5个
|
||||
});
|
||||
|
||||
console.log(`${found ? '❌' : '✅'} ${item.id}: ${item.name} - ${found ? '发现违规' : '合规'}`);
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成盘点报告
|
||||
*/
|
||||
function generateReport(mustHaveResults, dontDoResults) {
|
||||
// 创建报告目录
|
||||
if (!fs.existsSync(CONFIG.outputDir)) {
|
||||
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const reportPath = path.join(CONFIG.outputDir, 'project_audit_report.json');
|
||||
|
||||
// 统计
|
||||
const summary = {
|
||||
totalTime: new Date().toISOString(),
|
||||
mustHave: {
|
||||
total: mustHaveResults.length,
|
||||
completed: mustHaveResults.filter((r) => r.status === 'completed').length,
|
||||
missing: mustHaveResults.filter((r) => r.status === 'missing').length,
|
||||
completionRate: 0,
|
||||
},
|
||||
dontDo: {
|
||||
total: dontDoResults.length,
|
||||
compliant: dontDoResults.filter((r) => r.status === 'compliant').length,
|
||||
violation: dontDoResults.filter((r) => r.status === 'violation').length,
|
||||
complianceRate: 0,
|
||||
},
|
||||
};
|
||||
|
||||
summary.mustHave.completionRate = summary.mustHave.total > 0
|
||||
? ((summary.mustHave.completed / summary.mustHave.total) * 100).toFixed(2) + '%'
|
||||
: '0%';
|
||||
|
||||
summary.dontDo.complianceRate = summary.dontDo.total > 0
|
||||
? ((summary.dontDo.compliant / summary.dontDo.total) * 100).toFixed(2) + '%'
|
||||
: '0%';
|
||||
|
||||
const reportContent = {
|
||||
summary: summary,
|
||||
mustHave: mustHaveResults,
|
||||
dontDo: dontDoResults,
|
||||
};
|
||||
|
||||
fs.writeFileSync(reportPath, JSON.stringify(reportContent, null, 2));
|
||||
|
||||
// 生成可读报告
|
||||
const readableReportPath = path.join(CONFIG.outputDir, 'project_audit_report.md');
|
||||
let readableReport = `# V1.0全项目盘点报告
|
||||
|
||||
生成时间: ${new Date().toISOString()}
|
||||
|
||||
## 摘要
|
||||
|
||||
### V1.0必须实现项
|
||||
- 总数: ${summary.mustHave.total}
|
||||
- 已完成: ${summary.mustHave.completed}
|
||||
- 缺失: ${summary.mustHave.missing}
|
||||
- 完成率: ${summary.mustHave.completionRate}
|
||||
|
||||
### V1.0明确不做项
|
||||
- 总数: ${summary.dontDo.total}
|
||||
- 合规: ${summary.dontDo.compliant}
|
||||
- 违规: ${summary.dontDo.violation}
|
||||
- 合规率: ${summary.dontDo.complianceRate}
|
||||
|
||||
## V1.0必须实现项详情
|
||||
|
||||
`;
|
||||
|
||||
mustHaveResults.forEach((item) => {
|
||||
const statusIcon = item.status === 'completed' ? '✅' : '❌';
|
||||
readableReport += `### ${statusIcon} ${item.id}: ${item.name}
|
||||
|
||||
- 描述: ${item.description}
|
||||
- 状态: ${item.status === 'completed' ? '已完成' : '缺失'}
|
||||
- 详情: ${item.details}
|
||||
|
||||
`;
|
||||
});
|
||||
|
||||
readableReport += `## V1.0明确不做项详情
|
||||
|
||||
`;
|
||||
|
||||
dontDoResults.forEach((item) => {
|
||||
const statusIcon = item.status === 'compliant' ? '✅' : '❌';
|
||||
readableReport += `### ${statusIcon} ${item.id}: ${item.name}
|
||||
|
||||
- 描述: ${item.description}
|
||||
- 状态: ${item.status === 'compliant' ? '合规' : '违规'}
|
||||
- 详情: ${item.details}
|
||||
`;
|
||||
|
||||
if (item.violations && item.violations.length > 0) {
|
||||
readableReport += `- 违规详情:
|
||||
`;
|
||||
item.violations.forEach((violation) => {
|
||||
readableReport += ` - ${violation.file}:${violation.line} - ${violation.content}
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
readableReport += '\n';
|
||||
});
|
||||
|
||||
readableReport += `## 结论
|
||||
|
||||
`;
|
||||
|
||||
if (summary.mustHave.missing === 0 && summary.dontDo.violation === 0) {
|
||||
readableReport += '✅ V1.0所有必须实现项已完成,所有明确不做项合规\n';
|
||||
} else {
|
||||
if (summary.mustHave.missing > 0) {
|
||||
readableReport += `❌ V1.0有${summary.mustHave.missing}项必须实现项缺失\n`;
|
||||
}
|
||||
if (summary.dontDo.violation > 0) {
|
||||
readableReport += `❌ V1.0有${summary.dontDo.violation}项明确不做项违规\n`;
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(readableReportPath, readableReport);
|
||||
|
||||
console.log(`报告已生成:`);
|
||||
console.log(` - JSON报告: ${reportPath}`);
|
||||
console.log(` - 可读报告: ${readableReportPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
function main() {
|
||||
console.log('开始V1.0全项目盘点...');
|
||||
console.log(`项目根目录: ${CONFIG.projectRoot}`);
|
||||
|
||||
// 盘点必须实现项
|
||||
const mustHaveResults = auditMustHave();
|
||||
|
||||
// 盘点明确不做项
|
||||
const dontDoResults = auditDontDo();
|
||||
|
||||
// 生成报告
|
||||
console.log('\n正在生成报告...');
|
||||
generateReport(mustHaveResults, dontDoResults);
|
||||
|
||||
// 统计结果
|
||||
const mustHaveCompleted = mustHaveResults.filter((r) => r.status === 'completed').length;
|
||||
const mustHaveMissing = mustHaveResults.filter((r) => r.status === 'missing').length;
|
||||
const dontDoCompliant = dontDoResults.filter((r) => r.status === 'compliant').length;
|
||||
const dontDoViolation = dontDoResults.filter((r) => r.status === 'violation').length;
|
||||
|
||||
console.log('\n盘点完成!');
|
||||
console.log(`必须实现项: ${mustHaveCompleted}/${mustHaveResults.length} 完成`);
|
||||
console.log(`明确不做项: ${dontDoCompliant}/${dontDoResults.length} 合规`);
|
||||
|
||||
// 返回结果
|
||||
return {
|
||||
mustHave: mustHaveResults,
|
||||
dontDo: dontDoResults,
|
||||
};
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
main,
|
||||
auditMustHave,
|
||||
auditDontDo,
|
||||
V10_MUST_HAVE,
|
||||
V10_DONT_DO,
|
||||
CONFIG,
|
||||
};
|
||||
Reference in New Issue
Block a user