Compare commits

...

20 Commits

Author SHA1 Message Date
wenjiankun bb4b67f533 完成合并并修复反馈管理页面URL编码显示问题 2025-06-03 15:07:43 +08:00
wenjiankun 6629f186a6 完成合并并修复反馈管理页面URL编码显示问题 2025-06-03 15:05:13 +08:00
wenjiankun 88969a0a23 Merge branch 'master' of https://gitlink.org.cn/wenjiankun/gitlink_help_center 2025-06-03 15:03:45 +08:00
wenjiankun 3e223d0fe8 Merge pull request '添加文档目录导航和返回顶部按钮功能' (#3) from chaol/gitlink_help_center:new_feature_navigation into master 2025-06-02 22:11:00 +08:00
chaol ffa4493995 添加文档目录导航和返回顶部按钮功能 2025-06-02 22:10:13 +08:00
wenjiankun 5af5a1046b Merge pull request '新增功能:文档反馈系统' (#2) from doc-feedback into master 2025-06-02 21:19:46 +08:00
wenjiankun 5dd5e7e800 新增功能:文档反馈系统 2025-06-02 20:04:42 +08:00
wenjiankun 0dc2a6d874 修复文档反馈系统的乱码问题 2025-06-02 19:40:54 +08:00
wenjiankun 69b24c896e 尝试修复Footer组件 2025-06-02 19:31:43 +08:00
wenjiankun 02d7491529 新增功能:文档反馈系统 2025-06-02 19:19:46 +08:00
wenjiankun 64f708d5d2 解决合并冲突 2025-06-02 18:49:26 +08:00
wenjiankun dbefd48205 修复: 在构建过程中注释掉不存在的图片引用 2025-06-02 18:45:49 +08:00
wenjiankun 775fc0ebf7 refactor: .devops/未命名项目.yml 2025-06-02 18:37:36 +08:00
wenjiankun 16d2ac07c8 refactor: .devops/未命名项目.yml 2025-06-02 18:34:56 +08:00
wenjiankun 809064082a refactor: .devops/未命名项目.yml 2025-06-02 18:23:42 +08:00
wenjiankun 231e35badd refactor: .devops/未命名项目.yml 2025-06-02 18:21:41 +08:00
wenjiankun 75cb458a84 refactor: .devops/未命名项目.yml 2025-06-02 18:19:13 +08:00
wenjiankun 1ca6d32ea3 完善DevOps引擎文档,修复图片路径问题并补充详细内容 2025-05-30 11:54:18 +08:00
wenjiankun f0db88027f 移除敏感信息文件并更新.gitignore 2025-05-30 11:31:08 +08:00
wenjiankun e710542913 修复文档中的图片路径问题及相关文档更新 2025-05-30 11:21:28 +08:00
45 changed files with 2249 additions and 96 deletions

View File

@ -6,19 +6,24 @@ global:
trigger:
webhook: gitlink@1.0.0
event:
- ref: push
- ref: pr
ruleset-operator: AND
workflow:
- ref: start
name: 开始
task: start
- ref: end
name: 结束
task: end
needs:
- ssh_cmd_0
- ref: git_clone_0
name: git clone
task: git_clone@1.2.9
input:
username: ((ganshihao21.ganshihao21_user))
password: ((ganshihao21.ganshihao21))
remote_url: '"https://gitlink.org.cn/ganshihao21/reposync.git"'
username: ((wen.gitlink_username))
password: ((wen.gitlink_passwd))
remote_url: '"https://gitlink.org.cn/wenjiankun/gitlink_help_center.git"'
ref: '"refs/heads/master"'
commit_id: '""'
depth: 1
@ -28,11 +33,11 @@ workflow:
name: docker镜像构建
task: docker_image_build@1.6.0
input:
docker_username: ((nudt_devops.nudt_devops))
docker_password: ((nudt_devops.nudt_devops))
image_name: '"registry.cn-guangzhou.aliyuncs.com/nudt_devops/reposync_group5"'
docker_username: ((wen.aliyun_name))
docker_password: ((wen.aliyun_passwd))
image_name: '"crpi-b9yruhd9zitzsf2p.cn-hangzhou.personal.cr.aliyuncs.com/wenjiankun/group_02"'
image_tag: '"latest"'
registry_address: '"registry.cn-guangzhou.aliyuncs.com"'
registry_address: '"crpi-b9yruhd9zitzsf2p.cn-hangzhou.personal.cr.aliyuncs.com"'
docker_file: '"Dockerfile"'
docker_build_path: '"."'
workspace: git_clone_0.git_path
@ -44,19 +49,15 @@ workflow:
name: ssh执行命令
task: ssh_cmd@1.1.1
input:
ssh_pass: ((ssh_key.ssh_key))
ssh_ip: '"47.96.164.145"'
ssh_pass: ((wen.ssh_passwd))
ssh_ip: '"120.27.244.74"'
ssh_port: '"22"'
ssh_user: '"root"'
ssh_cmd: '"docker stop abc_group5 && docker rm abc_group5 && docker pull
registry.cn-guangzhou.aliyuncs.com/nudt_devops/reposync_group5:latest &&
docker run -d -p 3000:3000 --name abc_group5
registry.cn-guangzhou.aliyuncs.com/nudt_devops/reposync_group5:latest"'
ssh_cmd: '"docker stop group_02 || true && docker rm group_02 || true && docker
pull
crpi-b9yruhd9zitzsf2p.cn-hangzhou.personal.cr.aliyuncs.com/wenjiankun/group_02:latest;
docker run -d -p 3000:3000 --name group_02
crpi-b9yruhd9zitzsf2p.cn-hangzhou.personal.cr.aliyuncs.com/wenjiankun/group_02:latest"'
needs:
- docker_image_build_0
- ref: end
name: 结束
task: end
needs:
- ssh_cmd_0

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ deploy.sh
npm-debug.log*
yarn-debug.log*
yarn-error.log*
gitee私人令牌.txt

View File

@ -5,6 +5,11 @@ WORKDIR /gitlink_help_center
COPY ./ /gitlink_help_center/
# 注释掉不存在的图片引用
RUN sed -i 's/!\[vis_workflow_serial\](\/img\/engine\/vis_workflow_serial.png)/<!-- ![vis_workflow_serial](\/img\/engine\/vis_workflow_serial.png) -->/g' docs/DevOps引擎/图形流水线.md && \
sed -i 's/!\[vis_workflow_parallel\](\/img\/engine\/vis_workflow_parallel.png)/<!-- ![vis_workflow_parallel](\/img\/engine\/vis_workflow_parallel.png) -->/g' docs/DevOps引擎/图形流水线.md && \
sed -i 's/!\[vis_workflow_mixed\](\/img\/engine\/vis_workflow_mixed.png)/<!-- ![vis_workflow_mixed](\/img\/engine\/vis_workflow_mixed.png) -->/g' docs/DevOps引擎/图形流水线.md
RUN yarn install
RUN npm run build -- --locale zh-cn
CMD ["npm", "run", "serve"]

View File

@ -5,8 +5,323 @@ sidebar_position: 5
# 代码流水线
可以通过代码的形式(YAML格式)来配置流水线,首先选择代码流水线和对应的分支:
![code_workflow1](../../static/img/engine/code_workflow1.jpg)
![code_workflow1](/img/engine/code_workflow1.jpg)
编辑流水线代码,其流水线名称描述、触发器、全局参数、执行串行/并发和流水线编排等概念同图形流水线,具体描述如下:
![code_workflow2](../../static/img/engine/code_workflow2.png)
![code_workflow2](/img/engine/code_workflow2.png)
## YAML格式详解
代码流水线使用YAML格式配置基本结构包括以下几个主要部分
### 1. 基本信息
```yaml
name: "Java应用构建部署流水线" # 流水线名称
description: "用于构建和部署Java应用的CI/CD流水线" # 流水线描述
```
### 2. 触发器配置
```yaml
trigger:
# 代码事件触发器
type: GitLink_Webhook
events:
- push # 推送代码时触发
- pull_request # 创建合并请求时触发
- tag # 创建标签时触发
branches:
- master # 仅在master分支上触发
- release/* # 支持通配符匹配release开头的分支
# 或者使用定时触发器
# type: cron
# expression: "0 0 * * *" # 每天零点触发使用标准cron表达式
```
### 3. 全局参数
```yaml
parameters:
- name: VERSION # 参数名
type: string # 参数类型string, number, boolean
value: "1.0.0" # 默认值
description: "应用版本号" # 参数描述
- name: DEBUG_MODE
type: boolean
value: false
description: "是否开启调试模式"
- name: MAX_MEMORY
type: number
value: 1024
description: "最大内存配置(MB)"
```
### 4. 并发执行设置
```yaml
concurrent:
enabled: true # 是否允许并发执行
max: 3 # 最大并发数
```
### 5. 作业和步骤定义
```yaml
jobs:
build: # 作业ID
name: "构建应用" # 作业名称
steps: # 步骤列表
- name: "检出代码" # 步骤名称
uses: checkout@v1 # 使用的动作
- name: "编译构建"
uses: shell@v1
with: # 步骤参数
script: |
echo "开始构建项目"
mvn clean package -DskipTests
- name: "运行测试"
uses: shell@v1
with:
script: "mvn test"
deploy: # 另一个作业
name: "部署应用"
needs: build # 依赖前一个作业完成
steps:
- name: "部署到服务器"
uses: deploy@v1
with:
target: production
artifacts: target/*.jar
```
## 完整示例
下面是一个完整的Java应用CI/CD流水线示例
```yaml
name: "Java应用CI/CD流水线"
description: "构建、测试和部署Java应用"
trigger:
type: GitLink_Webhook
events:
- push
- pull_request
branches:
- master
- develop
parameters:
- name: JAVA_VERSION
type: string
value: "11"
- name: SKIP_TESTS
type: boolean
value: false
- name: DEPLOY_ENV
type: string
value: "test"
description: "部署环境(test/staging/production)"
concurrent:
enabled: true
max: 2
jobs:
build:
name: "构建和测试"
steps:
- name: "检出代码"
uses: checkout@v1
- name: "设置Java环境"
uses: setup-java@v1
with:
java-version: ${JAVA_VERSION}
- name: "缓存Maven依赖"
uses: cache@v1
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- name: "编译构建"
uses: shell@v1
with:
script: |
echo "开始构建项目"
if [ "${SKIP_TESTS}" = "true" ]; then
mvn clean package -DskipTests
else
mvn clean package
fi
- name: "代码质量检查"
uses: sonarqube@v1
with:
host: ${secrets.SONAR_HOST}
token: ${secrets.SONAR_TOKEN}
- name: "构建Docker镜像"
uses: shell@v1
with:
script: |
docker build -t myapp:${GITLINK_COMMIT_SHA} .
docker tag myapp:${GITLINK_COMMIT_SHA} myregistry/myapp:latest
- name: "推送Docker镜像"
uses: shell@v1
with:
script: |
echo ${secrets.DOCKER_PASSWORD} | docker login -u ${secrets.DOCKER_USERNAME} --password-stdin
docker push myregistry/myapp:latest
deploy:
name: "部署应用"
needs: build
steps:
- name: "部署到环境"
uses: deploy@v1
with:
environment: ${DEPLOY_ENV}
credentials: ${secrets.SSH_KEY}
script: |
ssh ${secrets.SERVER_HOST} "cd /app && docker-compose pull && docker-compose up -d"
- name: "健康检查"
uses: shell@v1
with:
script: |
for i in {1..30}; do
if curl -s http://${secrets.SERVER_HOST}:8080/health | grep -q "UP"; then
echo "服务已成功启动"
exit 0
fi
echo "等待服务启动... $i/30"
sleep 10
done
echo "服务启动超时"
exit 1
- name: "发送通知"
uses: notification@v1
with:
type: email
recipients: team@example.com
subject: "部署完成通知"
message: "应用已成功部署到${DEPLOY_ENV}环境"
```
## 常用节点类型及配置
### 检出代码
```yaml
- name: "检出代码"
uses: checkout@v1
with:
repository: owner/repo # 可选,默认为当前仓库
ref: master # 可选分支、标签或提交ID
depth: 1 # 可选git历史深度
```
### 执行Shell命令
```yaml
- name: "执行Shell命令"
uses: shell@v1
with:
script: |
echo "执行多行Shell命令"
ls -la
mkdir -p build
working-directory: ./src # 可选,指定工作目录
timeout: 300 # 可选,超时时间(秒)
```
### 构建应用
```yaml
- name: "构建Java应用"
uses: build@v1
with:
type: maven # 构建类型maven, gradle, npm等
command: "clean package" # 构建命令
jdk-version: "11" # JDK版本
```
### 部署应用
```yaml
- name: "部署到服务器"
uses: deploy@v1
with:
type: ssh # 部署类型ssh, kubernetes, docker等
host: ${secrets.SERVER_HOST}
credentials: ${secrets.SSH_KEY}
source: target/*.jar
destination: /app/
post-deploy: "systemctl restart myapp"
```
### 条件执行
```yaml
- name: "仅在生产环境执行"
if: ${DEPLOY_ENV} == 'production'
uses: shell@v1
with:
script: "echo '执行生产环境特定操作'"
```
## 变量引用
在YAML配置中可以通过以下方式引用变量
1. **参数变量**:使用`${参数名}`格式引用
```yaml
script: "mvn -Dversion=${VERSION} package"
```
2. **环境变量**:使用`${env.变量名}`格式引用
```yaml
script: "echo ${env.PATH}"
```
3. **密钥变量**:使用`${secrets.密钥名}`格式引用
```yaml
credentials: ${secrets.API_TOKEN}
```
4. **内置变量**GitLink提供的内置变量
```yaml
# 提交相关
${GITLINK_COMMIT_SHA} # 当前提交的SHA
${GITLINK_BRANCH_NAME} # 当前分支名称
# 仓库相关
${GITLINK_REPO_NAME} # 仓库名称
${GITLINK_REPO_OWNER} # 仓库所有者
# 流水线相关
${GITLINK_WORKFLOW_ID} # 流水线ID
${GITLINK_RUN_ID} # 执行ID
```
## 最佳实践
1. **版本控制**:将流水线配置文件提交到代码仓库,与应用代码一起版本控制
2. **模块化**:将复杂流水线拆分为多个作业,便于维护和复用
3. **参数化**:使用参数使流水线更灵活,适应不同场景
4. **密钥管理**:敏感信息使用密钥管理,避免硬编码
5. **注释**:添加适当注释,提高可读性
6. **错误处理**:添加适当的错误处理和重试机制
7. **通知机制**:配置通知,及时了解流水线执行状态

View File

@ -3,13 +3,179 @@ sidebar_label: '参数配置'
sidebar_position: 2
---
# 参数配置
# 参数配置
可在流水线中使用,实现动态参数配置;有字符串、数字、布尔三种类型。
可在流水线中使用,实现动态参数配置;有字符串、数字、布尔三种类型。
![parameter1](../../static/img/engine/parameter1.png)
![parameter1](/img/engine/parameter1.png)
在流水线的配置中通过如下方式获取参数:
![parameter2](../../static/img/engine/parameter2.png)
![parameter2](/img/engine/parameter2.png)
## 参数类型详解
GitLink DevOps引擎支持三种类型的参数可以根据不同需求选择合适的类型
### 1. 字符串参数 (String)
字符串参数用于存储文本值,如版本号、环境名称、分支名等。
**特点**
- 可以包含任意文本内容
- 支持空字符串
- 最大长度为2000个字符
**使用场景**
- 指定版本号:`1.0.0`
- 环境名称:`production`
- 配置文件路径:`/config/app.properties`
**示例**
```yaml
parameters:
- name: VERSION
type: string
value: "1.0.0"
description: "应用版本号"
```
### 2. 数字参数 (Number)
数字参数用于存储整数或浮点数值,如端口号、超时时间、内存大小等。
**特点**
- 支持整数和浮点数
- 可以进行数学运算
- 范围限制:-2^53 到 2^53
**使用场景**
- 端口号:`8080`
- 超时时间:`300`(秒)
- 内存限制:`1024`MB
**示例**
```yaml
parameters:
- name: PORT
type: number
value: 8080
description: "应用端口号"
```
### 3. 布尔参数 (Boolean)
布尔参数用于表示是/否选项,如开关功能、跳过步骤等。
**特点**
- 只有两个可能值:`true` 或 `false`
- 用于条件判断
**使用场景**
- 是否跳过测试:`true`/`false`
- 是否启用调试模式:`true`/`false`
- 是否发送通知:`true`/`false`
**示例**
```yaml
parameters:
- name: SKIP_TESTS
type: boolean
value: false
description: "是否跳过测试"
```
## 参数使用方法
### 在图形流水线中使用参数
在图形流水线的节点配置中,可以通过以下方式引用参数:
1. **Shell节点**:在脚本中使用`${参数名}`格式引用
```bash
echo "当前版本: ${VERSION}"
if [ "${SKIP_TESTS}" = "true" ]; then
echo "跳过测试"
else
echo "运行测试"
fi
```
2. **构建节点**:在构建参数中引用
```
-Dversion=${VERSION} -DskipTests=${SKIP_TESTS}
```
3. **部署节点**:在部署配置中引用
```
目标环境: ${ENVIRONMENT}
部署端口: ${PORT}
```
### 在代码流水线中使用参数
在YAML配置的代码流水线中可以在任何需要的地方引用参数
```yaml
jobs:
build:
steps:
- name: "编译构建"
uses: shell@v1
with:
script: |
echo "构建版本: ${VERSION}"
mvn clean package -Dversion=${VERSION} -DskipTests=${SKIP_TESTS}
- name: "条件步骤"
if: ${DEBUG_MODE} == true
uses: shell@v1
with:
script: "echo '调试模式已启用'"
```
## 参数作用域
参数的作用域决定了它们在哪些地方可以被访问和使用:
1. **全局参数**:在参数配置页面定义的参数,可在整个流水线中使用
2. **作业参数**:在特定作业中定义的参数,仅在该作业及其步骤中可用
3. **步骤参数**:在特定步骤中定义的参数,仅在该步骤中可用
## 参数优先级
当存在同名参数时,遵循以下优先级规则(从高到低):
1. 步骤参数
2. 作业参数
3. 全局参数
4. 默认值
## 参数最佳实践
### 命名规范
- 使用大写字母和下划线:`VERSION`、`API_TOKEN`、`MAX_RETRY_COUNT`
- 名称应清晰表达参数用途
- 避免使用特殊字符和空格
### 提供默认值
- 始终为参数提供合理的默认值
- 默认值应确保流水线在不指定参数时也能正常运行
### 添加描述
- 为每个参数添加清晰的描述
- 说明参数的用途、可选值和影响
### 参数分组
- 相关参数应使用一致的前缀,如`DB_HOST`、`DB_PORT`、`DB_USER`
- 便于理解和管理
### 敏感信息处理
- 不要在参数中存储敏感信息(如密码、令牌)
- 敏感信息应使用密钥管理功能

View File

@ -9,42 +9,126 @@ sidebar_position: 4
同一仓库下的流水线名称不能重复
![vis_workflow1](../../static/img/engine/vis_workflow1.png)
![vis_workflow1](/img/engine/vis_workflow1.png)
## 触发器
注:流水线编排时,触发器要放在开始节点之前;目前一条流水线仅添加支持一个触发器
* 定时触发器cron可通过填写cron表达式设置触发的具体时间
![vis_workflow2](../../static/img/engine/vis_workflow2.png)
![vis_workflow2](/img/engine/vis_workflow2.png)
* 事件触发器GitLink_Webhook给出了常见的代码变更事件触发包括推送代码、合并请求、创建标签
![vis_workflow3](../../static/img/engine/vis_workflow3.png)
![vis_workflow3](/img/engine/vis_workflow3.png)
## 全局参数
添加后可在当前流水线中使用
![vis_workflow4](../../static/img/engine/vis_workflow4.png)
![vis_workflow4](/img/engine/vis_workflow4.png)
使用实例
![vis_workflow5](../../static/img/engine/vis_workflow5.png)
![vis_workflow5](/img/engine/vis_workflow5.png)
## 并发执行
![vis_workflow6](../../static/img/engine/vis_workflow6.png)
![vis_workflow6](/img/engine/vis_workflow6.png)
开启后同一流水线可同时并发执行n个流水线实例每触发一次我们得到一个流水线实例
![vis_workflow7](../../static/img/engine/vis_workflow7.png)
![vis_workflow7](/img/engine/vis_workflow7.png)
若未开启则当前流水线实例执行中后面的实例处于排队状态最多可排队5个实例。当前实例执行完成下一个实例开始执行。
![vis_workflow8](../../static/img/engine/vis_workflow8.png)
![vis_workflow8](/img/engine/vis_workflow8.png)
## 流水线编排
每条流水线必须要有一个开始节点、结束节点,至少一个任务节点。支持串行编排和并行编排。
### 串行编排
串行编排是指流水线中的任务按顺序一个接一个执行,当前任务完成后,下一个任务才会开始。这是最基本的流水线执行方式,适合有明确依赖关系的任务。
<!-- ![vis_workflow_serial](/img/engine/vis_workflow_serial.png) -->
**如何创建串行流水线**
1. 在流水线编辑界面,点击左侧工具栏中的"开始"节点,拖拽到画布上
2. 添加任务节点如Shell、构建、部署等
3. 使用连接线将节点按顺序连接起来
4. 最后添加"结束"节点并连接
**串行编排的优点**
- 执行逻辑清晰,易于理解
- 每个步骤都依赖于前一步骤的成功完成
- 便于调试和故障排除
**串行编排的缺点**
- 执行效率较低,总执行时间为所有任务执行时间之和
- 一个节点失败会导致整个流水线停止
### 并行编排
并行编排允许多个任务同时执行,适用于相互独立的任务,可以显著提高流水线的执行效率。
<!-- ![vis_workflow_parallel](/img/engine/vis_workflow_parallel.png) -->
**如何创建并行流水线**
1. 在流水线编辑界面创建开始节点
2. 从开始节点连接到多个任务节点,形成分支
3. 使用"汇合"节点将多个并行分支合并
4. 连接到结束节点完成流水线
**并行编排的优点**
- 提高执行效率,减少总执行时间
- 充分利用系统资源
- 适合执行相互独立的任务
**并行编排的缺点**
- 增加流水线复杂度
- 可能导致资源竞争
- 需要注意并行任务间的数据共享问题
### 混合编排
在实际应用中,通常会结合使用串行和并行编排,根据任务的依赖关系灵活设计流水线结构。
<!-- ![vis_workflow_mixed](/img/engine/vis_workflow_mixed.png) -->
**混合编排示例**
1. 代码检出(串行)
2. 并行执行单元测试和代码质量检查
3. 构建应用(串行,依赖前两步完成)
4. 并行部署到多个环境
### 常用节点类型
GitLink DevOps引擎提供多种预定义节点类型满足不同场景需求
#### 基础节点
- **开始节点**:流水线的起点
- **结束节点**:流水线的终点
- **汇合节点**:合并多个并行分支
#### 任务节点
- **Shell节点**执行Shell命令或脚本
- **构建节点**:编译和构建项目
- **部署节点**:将应用部署到目标环境
- **测试节点**:执行自动化测试
- **通知节点**:发送邮件或其他通知
#### 控制节点
- **条件节点**:根据条件决定执行路径
- **循环节点**:重复执行特定任务
- **等待节点**:暂停流水线直到满足条件
### 节点配置最佳实践
1. **命名规范**:使用描述性名称,如"编译Java项目"而非简单的"编译"
2. **超时设置**:为长时间运行的节点设置合理的超时时间
3. **错误处理**:配置节点失败时的处理策略(继续/中止)
4. **资源限制**根据任务需求合理设置CPU和内存限制
5. **日志管理**:配置重要日志的保存策略

View File

@ -3,14 +3,200 @@ sidebar_label: '密钥设置'
sidebar_position: 3
---
# 密钥设置
# 密钥设置
将账号密码、密钥、证书等存放到密钥管理里,配置后可直接在流水线中使用,避免因直接填写引起的泄露风险。
![secret1](../../static/img/engine/secret1.png)
![secret1](/img/engine/secret1.png)
使用示例
注:节点输入参数中,下拉选项为密钥类型,需要提前配置好密钥在流水线中选择使用
![secret2](../../static/img/engine/secret2.png)
![secret2](/img/engine/secret2.png)
## 密钥类型
GitLink DevOps引擎支持多种类型的密钥以满足不同的安全需求
### 1. 用户名密码
用于存储需要用户名和密码的凭证,如数据库连接、应用登录等。
**配置字段**
- 密钥名称:唯一标识符
- 用户名:登录账号
- 密码:登录密码
- 描述:可选的说明文字
**使用场景**
- 数据库连接
- 应用系统登录
- API认证
**流水线中引用方式**
```yaml
# 在代码流水线中引用
with:
username: ${secrets.DB_CREDENTIAL.username}
password: ${secrets.DB_CREDENTIAL.password}
# 在Shell脚本中引用
echo "连接数据库..."
mysql -u${secrets.DB_CREDENTIAL.username} -p${secrets.DB_CREDENTIAL.password} -h localhost
```
### 2. SSH密钥
用于SSH连接远程服务器支持公钥/私钥认证方式。
**配置字段**
- 密钥名称:唯一标识符
- 私钥SSH私钥内容
- 密码短语:私钥的密码(如有)
- 描述:可选的说明文字
**使用场景**
- 远程服务器部署
- Git仓库访问
- 自动化运维操作
**流水线中引用方式**
```yaml
# 在代码流水线中引用
with:
type: ssh
credentials: ${secrets.SERVER_SSH_KEY}
host: example.com
# 使用SSH密钥部署
- name: "部署到服务器"
uses: deploy@v1
with:
type: ssh
credentials: ${secrets.SERVER_SSH_KEY}
host: ${SERVER_HOST}
script: |
cd /app
./deploy.sh
```
### 3. 访问令牌
用于API访问的令牌如GitHub Token、OAuth令牌等。
**配置字段**
- 密钥名称:唯一标识符
- 令牌值:访问令牌字符串
- 描述:可选的说明文字
**使用场景**
- API认证
- 第三方服务集成
- 容器仓库访问
**流水线中引用方式**
```yaml
# 在代码流水线中引用
with:
token: ${secrets.API_TOKEN}
# 在Shell脚本中使用
curl -H "Authorization: Bearer ${secrets.API_TOKEN}" https://api.example.com/data
```
### 4. 证书
用于存储SSL证书、签名证书等。
**配置字段**
- 密钥名称:唯一标识符
- 证书内容PEM格式的证书
- 私钥:对应的私钥
- 描述:可选的说明文字
**使用场景**
- HTTPS配置
- 应用签名
- 身份认证
**流水线中引用方式**
```yaml
# 在代码流水线中引用
with:
certificate: ${secrets.SSL_CERT.certificate}
private_key: ${secrets.SSL_CERT.private_key}
```
## 密钥权限管理
GitLink DevOps引擎提供细粒度的密钥权限管理确保敏感信息的安全
### 密钥作用域
1. **仓库级别**:仅在特定代码仓库的流水线中可用
2. **组织级别**:在组织内的所有代码仓库流水线中可用
3. **用户级别**:仅对特定用户可见和使用
### 访问控制
1. **创建者**:默认情况下,只有密钥创建者可以查看和编辑密钥
2. **共享**:可以与特定用户或组织共享密钥,并设置只读或读写权限
3. **审计**:所有密钥的使用都会被记录,便于安全审计
## 密钥使用最佳实践
### 安全性建议
1. **定期轮换**:定期更新密钥内容,尤其是高敏感度的密钥
2. **最小权限**:为密钥配置最小必要的权限
3. **避免硬编码**:永远不要在脚本或配置文件中硬编码密钥
4. **加密传输**确保密钥在传输过程中使用TLS/SSL加密
### 命名规范
1. **描述性名称**:使用清晰描述性的名称,如`PROD_DB_CREDENTIAL`而非简单的`DB_CRED`
2. **环境前缀**:使用环境前缀区分不同环境的密钥,如`DEV_`、`TEST_`、`PROD_`
3. **一致性**:保持命名风格一致,推荐使用大写字母和下划线
### 密钥组织
1. **按环境分组**:为不同环境(开发、测试、生产)创建不同的密钥
2. **按用途分组**按照用途数据库、API、部署等组织密钥
3. **文档记录**:维护密钥清单文档,记录用途、权限和有效期
## 密钥轮换流程
为确保系统安全,应定期轮换密钥:
1. **创建新密钥**:创建新版本的密钥,但不立即启用
2. **更新系统**:更新使用该密钥的外部系统
3. **更新流水线**:更新流水线配置,使用新密钥
4. **验证**:验证所有系统和流水线正常工作
5. **废弃旧密钥**:确认无问题后,删除或禁用旧密钥
## 常见问题排查
### 密钥访问被拒绝
**可能原因**
- 权限不足
- 密钥已过期
- 密钥名称错误
**解决方法**
- 检查密钥权限设置
- 验证密钥是否有效
- 确认密钥名称拼写正确
### 密钥内容不正确
**可能原因**
- 格式错误(如多余的换行符、空格)
- 编码问题
- 复制粘贴错误
**解决方法**
- 重新生成并配置密钥
- 确保使用正确的格式和编码
- 使用密钥测试功能验证

View File

@ -0,0 +1,192 @@
---
sidebar_label: '常见问题排查'
sidebar_position: 8
---
# 常见问题排查
本文档汇总了使用GitLink DevOps引擎时可能遇到的常见问题及解决方案。
## 流水线触发问题
### 问题:流水线没有自动触发
**可能原因**
1. Webhook配置不正确
2. 触发条件不匹配
3. 分支名称不匹配
**解决方法**
1. 检查项目设置中的Webhook配置是否正确
2. 确认触发事件如push、pull request等是否与配置一致
3. 检查触发器配置中的分支规则是否匹配
### 问题:流水线重复触发
**可能原因**
1. 多个触发器配置重叠
2. Webhook重复注册
**解决方法**
1. 检查并简化触发器配置,避免重叠
2. 在项目设置中检查并删除重复的Webhook
## 执行失败问题
### 问题:节点执行超时
**可能原因**
1. 任务执行时间超过默认超时时间
2. 资源不足导致执行缓慢
3. 死循环或无限等待
**解决方法**
1. 在节点配置中增加超时时间
2. 优化任务脚本,减少执行时间
3. 检查脚本中是否存在死循环或无限等待情况
### 问题:权限不足导致失败
**可能原因**
1. 执行环境权限不足
2. 密钥配置不正确
3. 外部系统访问受限
**解决方法**
1. 检查并调整执行环境的权限设置
2. 验证密钥配置是否正确
3. 确认外部系统的访问权限和网络连通性
### 问题:构建失败但没有明确错误信息
**可能原因**
1. 日志输出不完整
2. 错误发生在脚本内部
**解决方法**
1. 增加脚本的日志输出
2. 添加错误捕获和输出
3. 在关键步骤添加调试信息
```bash
# 增加错误处理和日志输出的示例
set -e # 任何命令失败立即退出
trap 'echo "发生错误,行号: $LINENO, 命令: $BASH_COMMAND"' ERR
# 增加详细日志
set -x # 打印执行的每条命令
```
## 参数和密钥问题
### 问题:参数值未正确传递
**可能原因**
1. 参数引用语法不正确
2. 参数名称拼写错误
3. 参数作用域问题
**解决方法**
1. 检查参数引用语法是否正确,如`${parameter_name}`
2. 确认参数名称拼写正确
3. 确认参数在正确的作用域中定义
### 问题:密钥无法正常使用
**可能原因**
1. 密钥配置不正确
2. 密钥权限不足
3. 密钥引用方式错误
**解决方法**
1. 在密钥管理页面验证密钥配置
2. 确认密钥具有足够的权限
3. 检查密钥的引用方式,如`${secrets.SECRET_NAME}`
## 性能问题
### 问题:流水线执行缓慢
**可能原因**
1. 资源配置不足
2. 存在性能瓶颈
3. 不必要的串行执行
**解决方法**
1. 增加节点资源配置
2. 识别并优化性能瓶颈
3. 将独立任务改为并行执行
### 问题:资源使用率过高
**可能原因**
1. 资源配置不合理
2. 并发任务过多
3. 存在资源泄漏
**解决方法**
1. 调整资源配置,避免过度分配
2. 限制并发任务数量
3. 检查并修复资源泄漏问题
## 代码流水线语法问题
### 问题YAML语法错误
**可能原因**
1. 缩进不正确
2. 引号使用不当
3. 特殊字符处理不当
**解决方法**
1. 使用YAML验证工具检查语法
2. 确保缩进一致通常是2个空格
3. 对包含特殊字符的字符串使用引号
**常见YAML语法错误示例**
```yaml
# 错误:缩进不一致
jobs:
build:
steps: # 缩进不足
- name: 检出代码
uses: checkout@v1
# 正确:缩进一致
jobs:
build:
steps: # 缩进正确
- name: 检出代码
uses: checkout@v1
```
```yaml
# 错误:特殊字符未使用引号
script: echo Hello World! > output.txt # > 是特殊字符
# 正确:使用引号包裹特殊字符
script: "echo Hello World! > output.txt"
```
## 其他常见问题
### 问题:流水线配置无法保存
**可能原因**
1. 表单验证失败
2. 网络连接问题
3. 服务器错误
**解决方法**
1. 检查表单输入是否符合要求
2. 确认网络连接正常
3. 查看浏览器控制台是否有错误信息
### 问题:流水线执行结果通知未收到
**可能原因**
1. 通知配置不正确
2. 通知服务异常
3. 接收方过滤规则
**解决方法**
1. 检查通知配置是否正确
2. 确认通知服务正常运行
3. 检查接收方的过滤规则
## 联系支持
如果您遇到的问题无法通过上述方法解决请通过以下方式联系GitLink支持团队
1. 在GitLink平台提交疑修(Issue)
2. 发送邮件至support@gitlink.org.cn
3. 通过GitLink官方社区寻求帮助

View File

@ -8,6 +8,6 @@ sidebar_position: 1
引擎(Engine)是GitLink提供的一款DevOps工具通过简单的节点编排来构建开发、测试、部署流水线从而创建自动化的软件交付流程。
它可以实现持续的代码集成,让开发者能尽早发现质量问题,快速定位修复,提升软件开发效率和质量;自动化的代码扫描、编译打包、单元测试,把研发团队从重复的工作中解放出来,聚焦到更有价值的事情上。
![engine_intro](../../static/img/engine/engine_intro.jpg)
![engine_intro](/img/engine/engine_intro.jpg)
在引擎页面中,用户可以创建和编辑图形流水线或代码流水线、设置外部参数、管理密钥等操作。

View File

@ -3,8 +3,159 @@ sidebar_label: '执行记录查询'
sidebar_position: 6
---
# 执行记录查询
# 执行记录查询
用户可以查看流水线的运行状态
用户可以查看流水线的运行状态
![result](../../static/img/engine/result.png)
![result](/img/engine/result.png)
## 执行记录概述
执行记录查询功能允许用户查看、分析和管理流水线的执行情况,帮助用户了解流水线的运行状态、执行历史和性能表现。通过执行记录,用户可以:
- 监控当前正在运行的流水线实例
- 查看历史执行记录和结果
- 分析流水线执行时间和资源消耗
- 排查流水线执行中的问题
- 重新运行或取消流水线实例
## 执行记录列表
执行记录列表页面显示了流水线的所有执行实例,包括以下关键信息:
1. **执行ID**:每次执行的唯一标识符
2. **流水线名称**:关联的流水线名称
3. **触发方式**:如何触发的流水线(手动、代码推送、定时等)
4. **触发用户**:谁触发了这次执行
5. **开始时间**:执行开始的时间
6. **结束时间**:执行结束的时间
7. **执行时长**:从开始到结束的总时长
8. **状态**:当前执行状态(等待中、运行中、成功、失败、取消)
用户可以通过以下条件筛选执行记录:
- 流水线名称
- 执行状态
- 触发方式
- 时间范围
- 触发用户
## 执行详情
点击执行记录列表中的任一记录,可以进入执行详情页面,查看该次执行的详细信息:
### 基本信息
- 执行ID和流水线名称
- 触发信息(触发方式、触发用户、触发时间)
- 执行状态和时间信息
- 关联的代码信息分支、提交ID等
### 执行图
执行图以可视化方式展示流水线的执行过程,包括:
- 节点执行状态(等待中、运行中、成功、失败、跳过)
- 节点执行顺序和依赖关系
- 当前执行位置
- 节点执行时间
### 节点详情
点击执行图中的节点,可以查看该节点的详细信息:
- 节点名称和类型
- 执行状态和时间
- 输入参数和环境变量
- 执行日志
- 输出结果和制品
### 执行日志
执行日志页面显示流水线执行过程中产生的所有日志,帮助用户了解执行过程和排查问题:
- 实时日志流
- 按节点筛选日志
- 日志搜索功能
- 日志下载选项
## 操作功能
在执行记录页面,用户可以对流水线执行实例进行以下操作:
### 重新运行
对于已完成(成功或失败)的执行实例,用户可以选择重新运行:
- **完全重新运行**:从头开始重新执行整个流水线
- **从失败处重新运行**:从上次失败的节点开始重新执行
- **使用相同参数**:使用原执行实例的参数重新运行
- **修改参数后运行**:修改参数后重新运行
### 取消执行
对于正在运行中的执行实例,用户可以选择取消执行:
- **立即取消**:立即停止所有正在运行的节点
- **优雅取消**:等待当前正在运行的节点完成后取消后续节点
### 比较执行
用户可以选择两个执行实例进行比较,了解它们之间的差异:
- 执行时间和性能差异
- 参数和环境差异
- 执行结果差异
- 节点执行状态差异
## 执行统计与分析
执行记录查询功能还提供了统计和分析工具,帮助用户了解流水线的整体表现:
### 执行趋势
- 执行成功率趋势
- 执行时间趋势
- 执行频率趋势
### 性能分析
- 节点执行时间分布
- 资源使用情况
- 瓶颈节点识别
### 故障分析
- 常见失败原因统计
- 失败节点分布
- 失败率最高的节点
## 通知与集成
执行记录查询功能支持与通知系统和其他工具集成:
- **邮件通知**:执行完成后发送结果通知
- **消息推送**:通过企业即时通讯工具推送执行状态
- **状态回调**:向外部系统提供执行状态回调
## 权限控制
执行记录的查看和操作受权限控制:
- **查看权限**:谁可以查看执行记录
- **操作权限**:谁可以重新运行或取消执行
- **管理权限**:谁可以删除或归档执行记录
## 最佳实践
### 执行记录管理
- 定期归档或清理旧的执行记录
- 为重要的执行记录添加标签或说明
- 下载并保存关键执行的日志和结果
### 问题排查
- 从执行日志中查找错误信息
- 比较成功和失败的执行记录
- 检查环境和参数差异
- 分析节点执行时间异常的原因

View File

@ -0,0 +1,123 @@
---
sidebar_label: '流水线最佳实践'
sidebar_position: 7
---
# 流水线最佳实践
## 设计流水线的基本原则
### 1. 保持简单明了
- 每个节点专注于单一任务
- 使用有意义的节点名称
- 添加适当的注释说明节点用途
### 2. 模块化设计
- 将常用功能封装成独立节点
- 合理划分阶段,便于维护和复用
- 使用参数化设计,提高流水线灵活性
### 3. 错误处理
- 添加适当的错误处理机制
- 设置关键节点的超时时间
- 配置失败通知,及时发现问题
## 常见场景的最佳实践
### 持续集成(CI)最佳实践
1. **代码检出**:始终使用特定版本的检出工具
2. **依赖缓存**:缓存依赖包加速构建
3. **并行测试**:将测试分组并行执行
4. **代码质量检查**集成SonarQube等工具
5. **构建制品归档**:保存构建结果便于追溯
示例流水线:
```yaml
name: CI最佳实践
jobs:
build:
steps:
- name: 检出代码
uses: checkout@v1
- name: 缓存依赖
uses: cache@v1
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- name: 编译构建
uses: shell@v1
with:
script: mvn -B package --file pom.xml
- name: 代码质量检查
uses: sonarqube@v1
with:
host: ${secrets.SONAR_HOST}
token: ${secrets.SONAR_TOKEN}
- name: 归档制品
uses: archive@v1
with:
path: target/*.jar
```
### 持续部署(CD)最佳实践
1. **环境隔离**:严格区分开发、测试、生产环境
2. **蓝绿部署**:减少部署风险
3. **自动化测试**:部署后进行自动化验证
4. **回滚机制**:配置快速回滚方案
5. **部署审批**:关键环境部署前增加人工审批
示例流水线:
```yaml
name: CD最佳实践
jobs:
deploy:
steps:
- name: 部署到测试环境
uses: deploy@v1
with:
target: test
artifacts: app.jar
- name: 自动化测试
uses: shell@v1
with:
script: ./run-tests.sh
- name: 人工审批
uses: approval@v1
with:
approvers: tech-leads
timeout: 24h
- name: 部署到生产环境
uses: deploy@v1
with:
target: production
strategy: blue-green
artifacts: app.jar
- name: 健康检查
uses: shell@v1
with:
script: ./health-check.sh
```
## 性能优化技巧
1. **并行执行**:将独立任务设置为并行执行
2. **资源限制**:合理设置节点资源限制,避免资源浪费
3. **减少依赖**:减少节点间的不必要依赖
4. **缓存利用**:合理使用缓存加速构建
5. **定期清理**:清理旧的构建记录和无用缓存
## 安全最佳实践
1. **密钥管理**:敏感信息使用密钥管理,避免硬编码
2. **最小权限**:为流水线配置最小必要权限
3. **依赖扫描**:集成依赖安全扫描工具
4. **镜像扫描**:部署前扫描容器镜像漏洞
5. **审计日志**:保留完整的流水线执行日志

View File

@ -1,5 +1,7 @@
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const path = require('path');
const fs = require('fs');
import("@easyops-cn/docusaurus-search-local").PluginOptions;
/** @type {import('@docusaurus/types').DocusaurusConfig} */
@ -15,8 +17,42 @@ module.exports = {
projectName: 'docusaurus-luffyzh-website', // Usually your repo name.
scripts: [],
// stylesheets: ['styles/dark-mode.css'],
plugins: [
// 添加API路由
function apiRoutes(context, options) {
return {
name: 'api-routes-plugin',
configureWebpack(config, isServer, utils) {
return {
resolve: {
alias: {
// 添加API路径别名
'@api': path.resolve(__dirname, 'api'),
},
},
};
},
// 添加API路由处理
async postBuild({ routesPaths = [], outDir }) {
// 创建API目录
const apiOutDir = path.join(outDir, 'api');
if (!fs.existsSync(apiOutDir)) {
fs.mkdirSync(apiOutDir);
}
// 复制反馈数据文件到构建目录
const dataFilePath = path.join(process.cwd(), 'feedback-data.json');
if (fs.existsSync(dataFilePath)) {
fs.copyFileSync(dataFilePath, path.join(outDir, 'feedback-data.json'));
} else {
fs.writeFileSync(path.join(outDir, 'feedback-data.json'), '[]', 'utf8');
}
},
};
},
],
themeConfig: {
docs:{
sidebar:{
hideable:true,
@ -70,6 +106,11 @@ module.exports = {
position: 'left',
dropdownActiveClassDisabled: true
},
{
to: '/feedback-management',
label: '反馈管理',
position: 'right',
},
// {
// href: 'https://github.com/boxyhq',
// position: 'right',
@ -184,4 +225,4 @@ module.exports = {
defaultLocale: 'zh-cn',
locales: ['zh-cn'],
},
};
};

View File

@ -1 +0,0 @@
23e041136cc43bcfa244f806799f5640

7
install-node.bat Normal file
View File

@ -0,0 +1,7 @@
@echo off
echo 正在下载Node.js安装程序...
powershell -Command "Invoke-WebRequest -Uri https://nodejs.org/dist/v20.11.1/node-v20.11.1-x64.msi -OutFile node-installer.msi"
echo 下载完成正在安装Node.js...
start /wait msiexec /i node-installer.msi /quiet /norestart
echo 安装完成请重新打开命令提示符或PowerShell以使用Node.js和npm。
pause

BIN
node-installer.msi Normal file

Binary file not shown.

4
npm-install.bat Normal file
View File

@ -0,0 +1,4 @@
@echo off
set PATH=%PATH%;C:\nodejs
cd /d %~dp0
npm install

BIN
nvm-setup.exe Normal file

Binary file not shown.

View File

@ -1,46 +1,42 @@
{
"name": "docusaurus",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"dev": "docusaurus start --port 3000",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@cmfcmf/docusaurus-search-local": "1.1.0",
"@docusaurus/core": "2.4.1",
"@docusaurus/plugin-client-redirects": "2.4.1",
"@docusaurus/preset-classic": "2.4.1",
"@easyops-cn/docusaurus-search-local": "^0.35.0",
"@mdx-js/react": "1.6.22",
"@node-rs/jieba": "^1.7.0",
"clsx": "1.2.1",
"docusaurus-gtm-plugin": "0.0.2",
"docusaurus-plugin-image-zoom": "1.0.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-modal": "3.16.1"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"engines": {
"node": ">=14.18.1 <=18.x"
}
}
{
"name": "docusaurus",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"dev": "docusaurus start --port 3000"
},
"dependencies": {
"@docusaurus/core": "2.4.1",
"@docusaurus/preset-classic": "2.4.1",
"@easyops-cn/docusaurus-search-local": "^0.35.0",
"@mdx-js/react": "^1.6.22",
"body-parser": "^1.20.2",
"clsx": "^1.2.1",
"docusaurus-plugin-image-zoom": "^1.0.1",
"express": "^4.18.2",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

43
package.json.bak Normal file
View File

@ -0,0 +1,43 @@
{
"name": "docusaurus",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"dev": "docusaurus start --port 3000",
},
"dependencies": {
"@docusaurus/core": "2.4.1",
"@docusaurus/preset-classic": "2.4.1",
"@easyops-cn/docusaurus-search-local": "^0.35.0",
"@mdx-js/react": "^1.6.22",
"body-parser": "^1.20.2",
"clsx": "^1.2.1",
"docusaurus-plugin-image-zoom": "^1.0.1",
"express": "^4.18.2",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

4
run-dev.bat Normal file
View File

@ -0,0 +1,4 @@
@echo off
set PATH=%PATH%;C:\nodejs
cd /d %~dp0
npm run dev

5
set-nodejs-path.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
echo 正在设置Node.js环境变量...
setx PATH "%PATH%;C:\nodejs" /M
echo 环境变量设置完成请重新打开命令提示符或PowerShell以使更改生效。
pause

View File

@ -0,0 +1,178 @@
import React, { useState } from 'react';
import styles from './styles.module.css';
export default function DocFeedback() {
const [voted, setVoted] = useState(false);
const [feedback, setFeedback] = useState('');
const [showForm, setShowForm] = useState(false);
const [submitted, setSubmitted] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
// 获取当前页面URL作为文档标识
const documentUrl = typeof window !== 'undefined' ? window.location.pathname : '';
const handleVote = (isHelpful) => {
setVoted(true);
setShowForm(!isHelpful);
// 发送投票结果到服务器
if (typeof window !== 'undefined') {
try {
const feedbackData = {
documentUrl: documentUrl,
isHelpful: isHelpful,
timestamp: new Date().toISOString(),
feedbackType: 'vote'
};
// 将反馈保存到localStorage以便在客户端保留记录
const storedFeedbacks = JSON.parse(localStorage.getItem('docFeedbacks') || '[]');
storedFeedbacks.push(feedbackData);
localStorage.setItem('docFeedbacks', JSON.stringify(storedFeedbacks));
console.log(isHelpful ? '用户认为有帮助' : '用户认为没有帮助', feedbackData);
// 这里应该添加发送到服务器的代码
// fetch('/api/feedback', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(feedbackData)
// });
} catch (error) {
console.error('提交反馈时出错:', error);
}
}
if (isHelpful) {
setTimeout(() => setVoted(false), 3000);
}
};
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmitting(true);
// 发送详细反馈到服务器
if (typeof window !== 'undefined') {
try {
const feedbackData = {
documentUrl: documentUrl,
feedback: feedback,
timestamp: new Date().toISOString(),
feedbackType: 'detailed'
};
// 将反馈保存到localStorage以便在客户端保留记录
const storedFeedbacks = JSON.parse(localStorage.getItem('docFeedbacks') || '[]');
storedFeedbacks.push(feedbackData);
localStorage.setItem('docFeedbacks', JSON.stringify(storedFeedbacks));
console.log('用户反馈:', feedbackData);
// 这里应该添加发送到服务器的代码
// fetch('/api/feedback', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(feedbackData)
// })
// .then(response => response.json())
// .then(data => {
// setSubmitted(true);
// setShowForm(false);
// setIsSubmitting(false);
// })
// .catch(error => {
// console.error('提交反馈时出错:', error);
// setIsSubmitting(false);
// });
// 模拟提交成功
setTimeout(() => {
setSubmitted(true);
setShowForm(false);
setIsSubmitting(false);
setTimeout(() => {
setVoted(false);
setFeedback('');
setSubmitted(false);
}, 3000);
}, 500);
} catch (error) {
console.error('提交反馈时出错:', error);
setIsSubmitting(false);
}
}
};
return (
<div className={styles.feedbackContainer}>
{!voted && !submitted && (
<div className={styles.feedbackQuestion}>
<p>这篇文档对您有帮助吗</p>
<div className={styles.feedbackButtons}>
<button
onClick={() => handleVote(true)}
className={styles.feedbackButton}
>
👍 有帮助
</button>
<button
onClick={() => handleVote(false)}
className={styles.feedbackButton}
>
👎 没有帮助
</button>
</div>
</div>
)}
{voted && !showForm && !submitted && (
<div className={styles.feedbackThanks}>
感谢您的反馈
</div>
)}
{showForm && (
<form onSubmit={handleSubmit} className={styles.feedbackForm}>
<p>您希望如何改进这篇文档</p>
<textarea
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
placeholder="请告诉我们如何改进这篇文档..."
rows={4}
className={styles.feedbackTextarea}
required
/>
<div className={styles.feedbackFormButtons}>
<button
type="submit"
className={styles.submitButton}
disabled={isSubmitting}
>
{isSubmitting ? '提交中...' : '提交反馈'}
</button>
<button
type="button"
onClick={() => {
setShowForm(false);
setVoted(false);
}}
className={styles.cancelButton}
disabled={isSubmitting}
>
取消
</button>
</div>
</form>
)}
{submitted && (
<div className={styles.feedbackThanks}>
感谢您的宝贵反馈我们会认真考虑您的建议
</div>
)}
</div>
);
}

View File

@ -0,0 +1,79 @@
.feedbackContainer {
margin-top: 3rem;
padding: 1.5rem;
border-top: 1px solid var(--ifm-color-emphasis-200);
}
.feedbackQuestion {
display: flex;
flex-direction: column;
align-items: center;
}
.feedbackButtons {
display: flex;
gap: 1rem;
margin-top: 0.5rem;
}
.feedbackButton {
padding: 0.5rem 1rem;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 4px;
background: var(--ifm-color-emphasis-100);
cursor: pointer;
transition: all 0.2s ease;
}
.feedbackButton:hover {
background: var(--ifm-color-emphasis-200);
}
.feedbackThanks {
text-align: center;
padding: 1rem;
color: var(--ifm-color-success-dark);
}
.feedbackForm {
margin-top: 1rem;
}
.feedbackTextarea {
width: 100%;
padding: 0.5rem;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 4px;
margin-bottom: 1rem;
}
.feedbackFormButtons {
display: flex;
gap: 1rem;
justify-content: flex-end;
}
.submitButton {
padding: 0.5rem 1rem;
background: var(--ifm-color-primary);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.submitButton:hover {
background: var(--ifm-color-primary-dark);
}
.cancelButton {
padding: 0.5rem 1rem;
background: var(--ifm-color-emphasis-200);
border: none;
border-radius: 4px;
cursor: pointer;
}
.cancelButton:hover {
background: var(--ifm-color-emphasis-300);
}

View File

@ -0,0 +1,78 @@
import React, { useState, useEffect } from 'react';
import Layout from '@theme/Layout';
export default function FeedbackDashboard() {
const [feedbacks, setFeedbacks] = useState([]);
useEffect(() => {
// 从localStorage加载反馈
const storedFeedbacks = JSON.parse(localStorage.getItem('docFeedbacks') || '[]');
setFeedbacks(storedFeedbacks);
}, []);
// 清除所有反馈
const clearFeedbacks = () => {
if (confirm('确定要清除所有反馈吗?')) {
localStorage.setItem('docFeedbacks', '[]');
setFeedbacks([]);
}
};
return (
<Layout title="反馈管理">
<div className="container margin-vert--lg">
<h1>反馈管理</h1>
<div className="margin-bottom--lg">
<h2>总计: {feedbacks.length} 条反馈</h2>
{feedbacks.length > 0 && (
<button
onClick={clearFeedbacks}
className="button button--danger"
>
清除所有反馈
</button>
)}
</div>
<div className="card-demo margin-bottom--lg">
<div className="card">
<div className="card__header">
<h3>反馈列表</h3>
</div>
<div className="card__body">
{feedbacks.length === 0 ? (
<p>暂无反馈数据</p>
) : (
<table className="table">
<thead>
<tr>
<th>类型</th>
<th>页面</th>
<th>内容</th>
<th>时间</th>
</tr>
</thead>
<tbody>
{feedbacks.map((feedback, index) => (
<tr key={index}>
<td>{feedback.type === 'vote' ? '投票' : '评论'}</td>
<td>{feedback.page}</td>
<td>
{feedback.type === 'vote'
? (feedback.isHelpful ? '👍 有帮助' : '👎 没有帮助')
: feedback.content}
</td>
<td>{new Date(feedback.timestamp).toLocaleString('zh-CN')}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
</div>
</div>
</Layout>
);
}

View File

@ -0,0 +1,7 @@
---
title: 反馈管理
---
import FeedbackDashboard from '@site/src/pages/feedback-dashboard';
<FeedbackDashboard />

View File

@ -0,0 +1,92 @@
import React, { useState, useEffect } from 'react';
import Layout from '@theme/Layout';
export default function FeedbackManagement() {
const [feedbacks, setFeedbacks] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 从localStorage加载反馈数据
if (typeof window !== 'undefined') {
try {
const storedFeedbacks = JSON.parse(localStorage.getItem('docFeedbacks') || '[]');
setFeedbacks(storedFeedbacks);
} catch (error) {
console.error('加载反馈数据时出错:', error);
} finally {
setLoading(false);
}
}
}, []);
// 清除所有反馈
const handleClearFeedbacks = () => {
if (typeof window !== 'undefined') {
localStorage.removeItem('docFeedbacks');
setFeedbacks([]);
}
};
// 解码URL
const decodeDocumentUrl = (url) => {
try {
return decodeURIComponent(url);
} catch (error) {
console.error('URL解码失败:', error);
return url;
}
};
return (
<Layout title="反馈管理" description="查看文档反馈">
<div className="container margin-vert--lg">
<h1>反馈管理</h1>
<p>总计: {feedbacks.length} 条反馈</p>
{loading ? (
<p>加载中...</p>
) : feedbacks.length > 0 ? (
<>
<button
onClick={handleClearFeedbacks}
className="button button--danger button--sm margin-bottom--md"
>
清除所有反馈
</button>
<table className="table">
<thead>
<tr>
<th>文档URL</th>
<th>类型</th>
<th>内容</th>
<th>时间</th>
</tr>
</thead>
<tbody>
{feedbacks.map((feedback, index) => (
<tr key={index}>
<td>{decodeDocumentUrl(feedback.documentUrl)}</td>
<td>
{feedback.feedbackType === 'vote'
? (feedback.isHelpful ? '[+] 有帮助' : '[-] 没有帮助')
: '详细反馈'}
</td>
<td>
{feedback.feedbackType === 'vote'
? (feedback.isHelpful ? '有帮助' : '没有帮助')
: feedback.feedback}
</td>
<td>{new Date(feedback.timestamp).toLocaleString()}</td>
</tr>
))}
</tbody>
</table>
</>
) : (
<p>暂无反馈数据</p>
)}
</div>
</Layout>
);
}

View File

@ -0,0 +1,45 @@
import React, { useState, useEffect } from 'react';
import styles from './styles.module.css';
const BackToTop = () => {
const [isVisible, setIsVisible] = useState(false);
// 检测滚动位置,决定是否显示按钮
useEffect(() => {
const toggleVisibility = () => {
if (window.pageYOffset > 300) {
setIsVisible(true);
} else {
setIsVisible(false);
}
};
window.addEventListener('scroll', toggleVisibility);
return () => window.removeEventListener('scroll', toggleVisibility);
}, []);
// 滚动到顶部的函数
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
return (
<>
{isVisible && (
<button
onClick={scrollToTop}
className={styles.backToTop}
title="返回顶部"
aria-label="返回顶部"
>
</button>
)}
</>
);
};
export default BackToTop;

View File

@ -0,0 +1,33 @@
.backToTop {
position: fixed;
bottom: 20px;
right: 20px;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--ifm-color-primary);
color: white;
border: none;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 100;
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.backToTop:hover {
transform: translateY(-3px);
background-color: var(--ifm-color-primary-darker);
}
@media (max-width: 768px) {
.backToTop {
bottom: 15px;
right: 15px;
width: 35px;
height: 35px;
}
}

View File

@ -0,0 +1,11 @@
import React from 'react';
import DocFeedback from '@site/src/components/DocFeedback';
export default function DocItemFooter() {
return (
<footer className="docusaurus-mt-lg">
<DocFeedback />
</footer>
);
}

View File

@ -0,0 +1,11 @@
.lastUpdated {
margin-top: 0.2rem;
font-style: italic;
font-size: smaller;
}
@media (min-width: 997px) {
.lastUpdated {
text-align: right;
}
}

View File

@ -0,0 +1,81 @@
import React, { useEffect, useState } from 'react';
import styles from './styles.module.css';
const TableOfContents = () => {
const [headings, setHeadings] = useState([]);
const [activeId, setActiveId] = useState('');
useEffect(() => {
// 获取页面中所有的标题元素
const elements = Array.from(document.querySelectorAll('h2, h3, h4, h5, h6'))
.filter(element => element.id);
const headingsData = elements.map(element => ({
id: element.id,
text: element.textContent,
level: Number(element.tagName.substring(1))
}));
setHeadings(headingsData);
// 监听滚动事件,更新当前激活的标题
const handleScroll = () => {
const scrollPosition = window.scrollY;
// 找到当前视图中的标题
const currentHeading = headingsData
.filter(heading => {
const element = document.getElementById(heading.id);
if (!element) return false;
return element.offsetTop <= scrollPosition + 100;
})
.pop();
if (currentHeading) {
setActiveId(currentHeading.id);
}
};
window.addEventListener('scroll', handleScroll);
handleScroll(); // 初始化激活的标题
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
// 如果没有标题,不显示目录
if (headings.length === 0) {
return null;
}
return (
<div className={styles.tableOfContents}>
<div className={styles.tocHeader}>目录</div>
<nav>
<ul className={styles.tocList}>
{headings.map(heading => (
<li
key={heading.id}
className={`${styles.tocItem} ${styles[`tocLevel${heading.level}`]} ${activeId === heading.id ? styles.tocItemActive : ''}`}
>
<a
href={`#${heading.id}`}
onClick={(e) => {
e.preventDefault();
document.getElementById(heading.id).scrollIntoView({
behavior: 'smooth'
});
}}
>
{heading.text}
</a>
</li>
))}
</ul>
</nav>
</div>
);
};
export default TableOfContents;

View File

@ -0,0 +1,73 @@
.tableOfContents {
position: sticky;
top: 70px;
max-height: calc(100vh - 100px);
overflow-y: auto;
padding: 0.75rem;
margin-bottom: 1rem;
border-left: 1px solid var(--ifm-color-emphasis-300);
border-radius: 4px;
background-color: var(--ifm-background-surface-color);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.tocHeader {
font-weight: bold;
margin-bottom: 0.5rem;
color: var(--ifm-color-primary);
font-size: 1rem;
}
.tocList {
list-style: none;
padding-left: 0;
margin-bottom: 0;
}
.tocItem {
margin: 0.25rem 0;
font-size: 0.875rem;
}
.tocItem a {
display: block;
padding: 0.25rem 0;
color: var(--ifm-color-content);
text-decoration: none;
transition: color 0.2s;
}
.tocItem a:hover {
color: var(--ifm-color-primary);
}
.tocItemActive a {
color: var(--ifm-color-primary);
font-weight: 600;
}
.tocLevel2 {
padding-left: 0;
}
.tocLevel3 {
padding-left: 1rem;
}
.tocLevel4 {
padding-left: 2rem;
}
.tocLevel5 {
padding-left: 3rem;
}
.tocLevel6 {
padding-left: 4rem;
}
@media (max-width: 996px) {
.tableOfContents {
display: none;
}
}

View File

@ -0,0 +1,19 @@
import React from 'react';
import DocItem from '@theme-original/DocItem';
import BackToTop from './BackToTop';
import TableOfContents from './TableOfContents';
import styles from './styles.module.css';
export default function DocItemWrapper(props) {
return (
<div className={styles.docItemContainer}>
<div className={styles.docItemContent}>
<DocItem {...props} />
</div>
<div className={styles.docItemSidebar}>
<TableOfContents />
</div>
<BackToTop />
</div>
);
}

View File

@ -0,0 +1,23 @@
.docItemContainer {
display: flex;
position: relative;
}
.docItemContent {
flex: 1;
}
.docItemSidebar {
width: 240px;
margin-left: 2rem;
}
@media (max-width: 996px) {
.docItemContainer {
display: block;
}
.docItemSidebar {
display: none;
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

View File

@ -1,4 +1,11 @@
---
sidebar_label: '代码提交'
sidebar_position: 3
---
---
### **1. 代码提交入口**
用户可以通过项目代码库中间的文件下的"上传文件"按键来上传文件。
![](/img/代码库管理/代码提交/上传文件入口.png)<br/>
### **2. 上传文件**
进入上传文件页面后,如下图所示,拖动文件,选择提交变更的分支的单击"创建项目"按键即可完成创建。
![](/img/代码库管理/代码提交/上传文件.png)<br/>

View File

@ -1,4 +1,28 @@
---
sidebar_label: '分支管理'
sidebar_position: 5
---
---
# 分支管理
### **1. 查看分支**
在代码库栏下,如下图所示,用户可以点击代码库分支从而查看当前项目的所有分支,并且选择对其进行操作。
![分支管理方式1](/img/代码库管理/分支管理/分支管理方式1.jpg)<br/>
### **2. 合并分支**
在"合并请求PR"下点击"新建合并请求"后,
![分支管理方式3](/img/代码库管理/分支管理/分支管理方式3.png)<br/>
如下图所示,用户可以选择源分支与目标分支以及填写分支信息后提交分支合并请求。
![分支管理方式2](/img/代码库管理/分支管理/分支管理方式2.png)<br/>
### **3. 创建分支**
用户可以在代码库页面通过点击分支选择器,然后点击"创建分支"按钮创建新的分支。
创建分支时,需要指定基于哪个已有分支来创建新分支,并为新分支命名。
### **4. 删除分支**
在分支列表页面,对于非保护分支,用户可以通过点击分支右侧的删除按钮来删除不再需要的分支。
需要注意的是,删除分支操作不可逆,请确保分支上的工作已经合并到主分支或已不再需要。
### **5. 分支保护**
对于重要的分支(如主分支),项目管理员可以设置分支保护规则,防止意外删除或直接推送代码。
分支保护可以在项目设置中的"分支保护"选项中配置。

View File

@ -1,4 +1,19 @@
---
sidebar_label: '文件管理'
sidebar_position: 4
---
---
### **1. 文件管理位置**
在代码库界面,可直接对代码库中该分支下的文件进行操作,如下图所示。
![](/img/代码库管理/文件管理/文件管理入口.png)<br/>
### **2. 进入目录**
点击进入相关目录,找到要编辑的文件,如下图所示。
![](/img/代码库管理/文件管理/进入文件所在目录.png)<br/>
### **3. 进入文件编辑页面**
在线编辑文件,单击文件,即可进入编辑文件页面,如下图所示。
![](/img/代码库管理/文件管理/进入文件编辑界面.png)<br/>
### **4. 编辑文件**
编辑界面右侧有三个按钮,分别是"下载"、"编辑"和"删除",点击下载即可将文件下载到本地,编辑则可在线编辑文档,删除则将文件从代码库中删除,按钮位置如下图所示。
![](/img/代码库管理/文件管理/文件编辑按钮.png)<br/>

View File

@ -3,4 +3,11 @@ sidebar_label: '代码评审'
sidebar_position: 3
---
# 代码评审
# 代码评审
### **1. 进入代码评审**
如下图所示,点击"代码评审"按钮可以进入代码评审
![](/img/PR/进入代码评审.png)<br/>
### **2. 代码评审界面**
如下图所示用户可以在该界面下查看该PR操作要合并的新的代码并对其进行评审。
![](/img/PR/具体代码评审.png)<br/>

View File

@ -1,4 +1,21 @@
---
sidebar_label: '组织成员管理'
sidebar_position: 4
---
---
# 成员管理Members Management
在 个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面
![](/img/Org/imageOrg14.png)
## 成员查询和添加
在成员管理界面当中,可以进行组织成员的查询和添加
![](/img/Org/imageOrg14.png)
## 成员邀请权限设置
管理员可以对邀请成员的权限进行设置
![](/img/Org/imageOrg15.png)
此外,也可以复制邀请链接的方式来更方便地邀请成员加入到项目当中来
**注**:此项功能仅对管理员可见

View File

@ -1,4 +1,15 @@
---
sidebar_label: '模板导入及导出'
sidebar_position: 2
---
---
# 确实开源帮助中心
## 模板的导入及导出管理
#### 1.模板导入
通过点击wiki界面的**导入模板**按钮即可导入本地的txtmarkdown等格式文件进行Wiki文档的创建当前平台此项功能尚在开发测试环节
[](/img/wiki/img4.png)
#### 2.模板导出
当前平台此项功能尚在开发测试环节
<br/>

View File

@ -1,4 +1,23 @@
---
sidebar_label: '维基页面管理'
sidebar_position: 1
---
---
# 确实开源帮助中心
## 维基界面管理
#### 1.维基界面
GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情况
[](/img/wiki/img1.png)
#### 2.创建维基内容
在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑
<br/>
#### 3.进行Wiki文档的编辑
点击**创建Wiki文档**后即跳转到编辑界面,初始内容为空白的文档
[](/img/wiki/img3.png)
也可以通过勾选*添加模板*来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式
[](/img/wiki/img2.png)
完成编辑后点击左下角的的保存即可