# 1. 获取数据平台目前支持的所有项目
```bash
curl -X GET \
  https://schema-manager.hlgdata.com/api/v1/open/project/all \
  -H 'cache-control: no-cache' \
  -H 'postman-token: a2ecaca1-d4fa-aad4-2467-dd80824c7cd4'
```

# 2. 根据项目名请求 schema
```bash
curl -X POST \
  https://schema-manager.hlgdata.com/common/schemas \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'postman-token: 97e4fab9-3aaa-e1cb-180e-50d506483a55' \
  -d '{
	"project": "ai_test"
}'
```

# 3. 请求 gdm 代理接口获取 schema 数据
curl -X GET \
  https://x.gdm.gaoding.com/api/wind/schemas/ai_test/ \
  -H 'cache-control: no-cache' \
  -H 'postman-token: acb494d5-133f-579c-e5f4-4b4d46508145'

# 3. 数据平台 (星图) 目前支持上报的数据的类型
| token | 说明 | 对应的 TS 类型 |
| :- | :- | :- |
| `number` | 数字类型 (包含小数) | `number` |
| `integer` | 整型 | `number` |
| `long` | 长整型 | `number` |
| `boolean` | 布尔值 | `boolean` |
| `string` | 字符串 | `string` |
| `array/string` | 字符串数字 | `string[]` |
| `array/integer` | 整型数字 | `number[]` |

# 4. 埋点数据 API 文件的生成模板
```ts
import { trackerWind } from '@gaoding/gd-tracker';

// (1) 原来的上报方式
// 参考文档：http://doc.fe.gaoding.com/gd-tracker/sdk/web-sdk.html#trackerwind-trackevent
trackerWind.trackEvent('write', {
    event_id: 5,                           // 注意不可缺少 event_id 字段
    template_id: 'xxxxx',                  // 模板id
    template_nature: 'xxxxx',              // 模板属性
    work_id: 'xxxxx'                       // 作品id
});

//           转换
//            ||
//            ||
//            ||
//            ↓↓

// (2) 转换成 API 调用的方式
// trackWrite 的类型定义
export declare namespace Tracker {

    export namespace Write {

        export interface IDetail {
            platform_id?: string;    // 平台
            template_classify?: string;    // 模板分类
            template_group_id?: string;    // 模板组id
            template_id: string;    // 模板id
            template_nature: string;    // 模板属性
            template_scen?: string;    // 模板场景
            template_suit_id?: string;    // 套装id
            work_id: string;    // 作品id
        }

        export interface Api {
            (detail: IDetail, callback?: (result: null | Error) => any): void;
        }
    }
}

// 模板下载
export const trackWrite: Tracker.Write.Api = (option, callback) => {
    const detail = Object.assign({}, option, { event_id: 5 });
    return trackerWind.trackEvent('write', detail, callback);
};
```

# 5. 本地埋点数据校验

## 5.1 本地埋点数据校验设计的说明
本地埋点数据验证的设计采用 client-server 的方式验证，验证的计算环节放在本地服务器中进行:
- 使用方需要在埋点 SDK 初始化的时候多传入两个参数：
  - `isValidate`: 是否对埋点数据进行本地验证；
  - `validateURL`: 对埋点数据进行本地验证的地址。
- 在 CLI 初始化执行的时候会在本地生成 `cli/.cache/gd_web/schema.json` 并把结果缓存在本地。
- 然后启动 validator server 等待埋点 SDK 进行校验的请求。
- 埋点 SDK 上报数据之前发起请求到 validator server 验证数据是否符合 schema 的定义。

## 5.2 本地埋点数据校验和以往有什么不一样
- 解决 [schema 非生产阶段校验](https://doc.huanleguang.com/pages/viewpage.action?pageId=252576722) 中当前埋点 SDK 实现校验的两个缺陷
  - 以往埋单 SDK 项目列表是通过配置文件写死的。**现在在执行 `gd-tracker-cli` 的时候允许通过命令行传入项目的参数直接生成对应项目的埋点校验规则文件。**
  - 以往埋点校验规则的文件是埋点 SDK 这个 package 在 Jenkins 流水线上面构建时生成的，如果一个项目某个事件的 Schema 发生了变化或者新增了一个项目，埋点 SDK 的 Schema 校验会失效或找不到对应项目，假如要新增一个项目的埋点校验规则文件，需要埋点 SDK 重新发版。**现在的埋点校验规则的文件是通过埋点 SDK 的 CLI 工具生成在本地的，因此不会跟埋点 SDK 的发版绑定，假如一个项目的 Schema 发生了变化，可以通过埋点 SDK 提供的 CLI 工具重新生成新的埋点校验规则文件。**
- 以往埋点 SDK 对数据进行校验，对不符合的埋点数据通过控制台的 console.warn 的来警告开发者。**本地埋点数据验证会以弹幕遮罩层的方式在开发阶段提醒开发者埋点数据不符合 Schema 的校验。**
- **和移动端统一一致的埋点开发流程。**

## 5.3 本地埋点数据校验设计流程
![本地埋点数据校验的设计](./assets/本地埋点数据校验设计.jpg)

## 5.4 埋点 SDK 初始化新增的的两个参数使用说明
```ts
tracker.setup({
  wind: {
    // ...                                                     // 其他参数
    isValidate: window.location.href.includes('//localhost'),  // 是否对埋点数据进行本地验证
    validateURL: 'http://localhost:6427/wind',                 // 对埋点数据进行本地验证的地址
  }
}, false, false);
```

## 5.5 本地埋点数据验证服务
### 5.5.1 cli/index.js
```ts
const Schema = require('./core/schema');
const server = require('./core/validator');

const main = module.exports = async argvs => {
  const project = 'gd_web';
  const schema = new Schema({ project });
  await schema.init();         // schema.init: 会请求星图的接口获取一个项目的所有事件的 schema，并把结果缓存在 `.cache` 文件夹
  schema.generateCode();       // 通过 `.cache` 文件夹的 schema 生成埋点 API 调用的接口文件
  server({                     // 启动本地埋点数据验证的服务
    project,
    port: 6427,
    schema: schema.list
  });
};
```

### 5.5.2 cli/core/validator.ts
```ts
const validator = (app, option) => {
    app.post('/wind', (request, response) => {
        const data = Object.assign({}, request.body);
        const isBatch = Object.prototype.hasOwnProperty.call(data, 'list');
        axios.get(`http://localhost:${option.port}/schemas/${data.project}`).then(res => {
            const schema = Array.isArray(res.data) ? res.data : [];
            const errors = isBatch ? validateBatch(data, schema): validateSingle(data, schema);
            const result = errors.filter(item => item.isError).map(item => ({
                ...item,
                project: data.project,
            }))
            return response.send(JSON.stringify(result));
        });
    });

  return app;
};

export const server = params => {
    const option = Object.assign({}, DEFAULT_OPTION, params);
    const schemas = isArray(option.schemas) ? option.schemas : [];
    const app = express();
    app.use(cors());
    app.use(body.json());
    app.use(body.urlencoded({ extended: false }));

    schemas.forEach((list: any[]) => {
        const arr = Array.isArray(list) ? list : [];
        const first = arr[0];
        if (!first) return;
        app.get(`/schemas/${first.project}`, (request, response) => {
            const result = list.map(item => {
                const { project: _, ...rest } = item;
                return rest;
            });
            response.send(JSON.stringify(result));
        });
        const SCHEMA_MESSAGE = `[gd-tracker-cli] You can get the project schema by: http://localhost:${option.port}/schemas/${first.project}`;
        console.log(color.green(SCHEMA_MESSAGE));
    });

    validator(app, option);

    app.listen(option.port, () => {
        const RUN_MESSAGE = `[gd-tracker-cli] Validator server is run in http://localhost:${option.port}.`;
        const VALIDATE_URL_MESSAGE = `[gd-tracker-cli] The validateURL is http://localhost:${option.port}/wind`;
        console.log(color.green(RUN_MESSAGE));
        console.log(color.green(VALIDATE_URL_MESSAGE));
    });

    return app;
};
```

## 5.6 埋点 SDK 上报数据之前发起请求到 validator server 验证数据是否符合 schema 的定义
```ts
if (isValidate) {
  const headers = { "Content-Type": "application/json" };
  const method = "post";
  const body = JSON.stringify(params);
  const request = new Request(this.validateURL, { headers, method, body });
  fetch(request).then(response => response.json()).then(data => {
    // data: 本地校验结果
    
    // 本地校验结果通过后，再进行上报
    // isFunction(reportData) && reportData();
    
    // 本地校验失败，不进行上报
    // 生成弹幕遮罩层把错误信息的 HTML 代码插入到页面中提示开发者
  }).catch(error => {
    // 本地校验失败，不进行上报
    // 处理错误
  });
}
```

# 6. gd-tracker-cli

## 6.1 在本仓库执行运行 cli
```bash
# (1) 开发命令
yarn dev:cli

# (1). CLI 帮助信息
node ./bin/index.js -h                                # 获取 CLI 的帮助信息
node ./bin/index.js list -h                           # 获取 list 命令的帮助信息
node ./bin/index.js gen -h                            # 获取 gen 命令的帮助信息
node ./bin/index.js validate -h                       # 获取 validate 命令的帮助信息

# (2) 获取所有可支持的项目名
node ./bin/index.js gen -n gd_web

# (3) 指定项目名初始化一个埋点项目
node ./bin/index.js gen -n gd_web                     # n: 指定项目名

# (4) 指定项目名和文件的生成目录初始化一个埋点项目
node ./bin/index.js gen -n gd_web -d ./src/trackers   # n: 指定项目名; d: 指定文件的生成目录 (相对路径)

# (5) 忽略缓存强行从线上拉取 schemas 生成代码
node ./bin/index.js gen -n gd_web -f

# (6) 开启验证服务
node ./bin/index.js validate
node ./bin/index.js validate -p 6427                  # p: 不指定默认是 6427
```

## 6.2 运行本地验证服务 demo
```
yarn dev:cli

node ./bin/index.js gem -n gd_web

node ./bin/index.js validate

cd ./examples/vue2 && yarn dev
```