# PingCAP Cloud Hub

## 后端开发（Golang）

### 1. 准备工作

#### 1.1 前置需求

- redis 依赖（用于一些数据的缓存），本地有一个 6749 端口（有别于默认的 6379，为了避免和已有的本地 redis 服务冲突）的 redis 服务即可
  - 可以通过 docker 启动一个 redis 服务，命令如下：
    ```shell
    docker run -d -p 6749:6379 redis
    ```

#### 1.2 本地 MySQL 库表建立

如果只是查询数据，不做数据的增删改，那么直接从本地连接线上的数据库即可，不然为了开发调试，还是连接本地数据库进行开发，本地数据库的库表结构可以参考线上库表，数据可以从线上 dump 部分下来进行调试。

#### 1.3 环境变量

环境变量文件可以划分不同的环境，便于生成不同环境的配置文件，需要配置的环境变量可以在 `script/envs-sample.sample.sh` 中找到，按需复制和填值。

- `script/envs.sh` 是所有环境都一致的环境变量
- `script/envs.dev.sh` 是生成开发环境配置文件的环境变量
- `script/envs.prod.sh` 是生成生产环境配置文件的环境变量

具体的环境变量值请联系 shenjing.zhao@pingcap.com，在环境变量中可以看到数据库的连接信息、飞书应用的 ID 和 Secret 等敏感信息，所以不要将环境变量文件提交到 git 仓库中。

#### 1.4 准备 SA 文件

SA 文件是用于访问 GCP 服务的凭证，PCH 本地开发需要使用 SA 进行 BQ 的访问，需要将 SA 文件放到 `config` 目录下，文件名为 `service_account.json`，这也是敏感文件，是被 gitignore 的。
具体的 SA 文件请联系 shenjing.zhao@pingcap.com。

#### 1.5 安装工具链

```shell
make tools
```

#### 1.6 生成本地配置文件

```shell
make gen TARGETENV=dev
```

可以看到在 `_output/conifg` 目录下生成了最终的配置文件，之后如果本地开发配置发生了改变，就需要再次生成。


### 2. 运行开发环境

#### 2.1 启动服务

- 如果你是 goland 开发，可以在 goland 中配置一个【Go 构建】的运行配置，配置如下：
  - Run kind: Directory
  - Directory: 选择`项目根目录/cmd/pchub-apiserver`
  - Output directory: 选择`项目根目录/_output/tmp`
  - 勾选 `构建后运行`
  - 工作目录：选择`项目根目录`
  - 程序实参（P）：`-c=./_output/config/pchub-apiserver.yaml`

- 如果是命令行启动，可以执行如下命令：
  ```shell
  make go.run.pchub-apiserver
  ```

- **如果你连接的是线上的数据库，请注意连接飞连，线上的数据库有 IP 白名单保护**，另外对线上数据库做增删改操作需要谨慎，可以在本地数据库进行开发调试。
- 服务启动后，会初始读取一遍 `ASK PCH` 飞书群的成员到 redis 中，这在一开始是为了通过飞书群控制灰度发布的名单范围，后续就用来提供给前端接口来区分当前登陆用户在不在这个话题群
- 服务内部没有自动执行的会增删改数据的定时任务，所以启动服务就算是连接线上的数据库也是安全的，不会有数据被误删改的风险

#### 2.2 验证服务

```shell
curl http://localhost:8867/healthz
# {"code":0,"message":"","data":{"status":"ok"}}
```

当前健康检查还不完善，只是证明服务能被访问到

#### 2.3 正常调用各类接口

参见 `internal/apiserver/router.go` 文件查看各个接口路由。


## 前端开发

### 1. 准备工作

#### 1.1 前端环境

- Nodejs 环境，推荐使用 [nvm](https://github.com/nvm-sh/nvm) 管理 nodejs 版本，并推荐安装 lts 版本
- 安装 [pnpm](https://pnpm.io/installation)

#### 1.2 进入到前端目录

```shell
cd web
```

后续操作均在该目录下进行

#### 1.3 准备环境变量

环境变量文件可以划分不同的环境，便于构建不同环境的前端资源，需要配置的环境变量可以在 `web/.env-sample` 中找到，按需复制和填值。

- `web/.env` 是所有环境都一致的环境变量
- `web/.env.local` 是开发环境会使用的
- `web/.env.production` 是构建生产需要的

- 可以从中看到是如何基于环境变量的值来切换不同的后端的，这意味着，如果仅需要修复前端问题，我们可以仅启动前端，而在接口方面调用线上的接口，从而不需要启动后端服务。
- 如果你是跟着本步骤进行的话，建议把 `PCHUB_WEB_API_BASE` 切换成 `http://localhost:8867`，把 `PCHUB_WEB_CUBE_BASE` 切换成 `https://pch-cube.pingcap.net/cubejs-api`，这表示：
  - 我们请求**本地**的 PCH 服务，即上面我们已经启动的后端服务
  - 我们请求**线上**的 Cube 服务，因为到这里我们还未启动我们的 cubejs 服务，所以请求线上的


#### 1.4 安装前端依赖

```shell
pnpm install
```

### 2. 运行开发环境

#### 2.1 启动前端服务

注意检查你的环境变量，要访问的接口如果是本地的 endpoint，需要先启动本地的服务，apiserver 和 cubejs-server 是两个不同的服务，可以分开配置，即可以一个访问线上，另一个访问本地启动的服务。

```shell
pnpm run dev
```

#### 2.2 验证前端服务

浏览器打开 `http://localhost:5173` 应该能正常登陆和使用应用

#### 2.3 前端热重载

前端开发是有热重载的，意味着你改前端的代码，前端代码会立即进行该部分的重现构建，从而在浏览器无需刷新就看到修改后的效果，但有时候错误可能会打断这一过程，这时候就需要浏览器刷新下。


## Cubejs 服务开发环境

(cubejs)[https://github.com/cube-js/cube] 是构建数据服务的语义层，意味着我们基于我们的库表构建完 cubejs schema 后，我们可以立即调用 API 结构化得获取我们的数据，而无需开发繁杂的后端接口、取数逻辑。

### 1. 准备

把必要的环境变量放到 `cube/.env` 文件中，同样的，我们可以控制环境变量来控制 cubejs 是从线上还是本地的 db 中读取数据。

### 2. 启动开发环境

cubejs 有良好的 docker 封装，所以我们只需要在 `cube` 目录下运行 `docker compose` 就能运行：

```shell
cd cube
docker compose up -d
```

### 3. 验证开发环境

- 浏览器访问 `http://localhost:4000` 你将看到一个 playground 用来本地调试 cubejs 的 query，而它的 endpoint 为 `http://localhost:4000/cubejs-api/v1`
- 所以这个时候如果我们回过头去修改我们前端环境中的环境变量，把 `PCHUB_WEB_CUBE_BASE` 从 `https://pch-cube.pingcap.net/cubejs-api` 改为 `http://localhost:4000/cubejs-api`，然后再去访问 `http://localhost:5173/department`，访问的就是我们刚才本地启动的 cubejs 服务

---

## 整体部署

整体都部署在 GCP K8S 集群上，而且前后端的服务都是有 CICD 的，所以可以参见 `.github/workflows/cicd.yml` 流程，但是 `cubejs` 服务目前还未进入到 CICD 流程中，而是单独需要本地构建部署的，虽然部署的流程和工具和 CICD 中基本是一致的。

### 1. 敏感数据

目前没有配置中心，所以一些敏感数据需要开发手动部署。

- 如果修改了 apisever 的配置，那么就需要重新部署一下 apiserver 需要的 secret：
  - 在本地通过 `make gen TARGETENV=prod` 生成 apisever 生产环境配置
  - `copy ./secret.yml.sample ./secret.yml` 
  - 运行 `base64 -i ./_output/config/pchub-apiserver.yaml` 编码配置文件
  - 运行 `base64 -i ./_output/config/service_account.json` 编码 sa 文件
  - 将上面两个结果复制到 `./secret.yml` 对应的字段中
  - `kubectl apply -f ./secret.yml` 部署（**在那之前记得提前配置 gcloud 配置 kubectl 能访问线上的 k8s 集群，不赘述**）

- 如果修改了 cubejs 的环境变量
  - `copy ./cube/k8s/secret.yml.sample ./cube/k8s/secret.yml`
  - 运行 `echo -n "相关值" | bash64` 来编码字段值
  - 然后填入到上面 `./secret.yml` 对应的字段中
  - `kubectl apply -f ./cube/k8s/secret.yml` 部署（**在那之前记得提前配置 gcloud 配置 kubectl 能访问线上的 k8s 集群，不赘述**）

- 如果修改了前端相关的环境变量，那么需要复制到 github repo —— settings —— Actions secrets and variables —— Repository secrets 中对应的变量上去，因为在 CICD 流程中会读取这个变量创建 `.env` 文件
  - 把 `web/.env` 和 `web/.env.production` 的所有环境变量配置复制到上述 Github Repository secrets 中名为 `WEB_ENV_FILE` 的变量中
  - 因为前端环境变量是构建时替换（即在浏览器前端代码中是可以找到相关值的），所以其实没有安全性，所以在前端环境变量其实也可以直接提交到 github repo，**项目里只是为了环境变量统一不进 git，做了相关配置**

### 2. 前后端的 CICD 部署流程简单描述

- 拉取代码
- 设置环境变量
- 构建前端资源
- 构建前后端的 docker 镜像
- 推送镜像
- 使用 `kustomize` 工具部署

### 3. cubejs 服务的部署

和上面基本是一样的流程，只是需要手动操作，本地安装下 [kustomize](https://kustomize.io/)

```shell
# 进入目录
cd cube
# 基于官方镜像手动构建镜像
docker build --tag asia-east2-docker.pkg.dev/gcp-digital-infra/cicd-repo/cubejs-model-inside:$(git rev-parse --short=8 HEAD) -f ./devops/cube.dockerfile .
# tag latest
docker tag asia-east2-docker.pkg.dev/gcp-digital-infra/cicd-repo/cubejs-model-inside:$(git rev-parse --short=8 HEAD) asia-east2-docker.pkg.dev/gcp-digital-infra/cicd-repo/cubejs-model-inside:latest
# push
docker push asia-east2-docker.pkg.dev/gcp-digital-infra/cicd-repo/cubejs-model-inside:$(git rev-parse --short=8 HEAD)
docker push asia-east2-docker.pkg.dev/gcp-digital-infra/cicd-repo/cubejs-model-inside:latest
# deploy
cd k8s
kustomize build . | kubectl apply -f -
```

