在前端开发场景中,我们经常需要直接与代码仓库交互,比如读取配置文件、提交静态资源变更。本文将基于纯浏览器环境 ,实现一个对标 PHP GitHub 类的 Gitee 操作工具类 ,支持列举目录文件、读取文件内容、API 提交代码三大核心功能,无需后端 (Node.js) 中转,直接对接 Gitee Open API v5。
核心约束与前置准备
1. 纯前端限制说明
- 浏览器同源策略 限制:Gitee API 未完全开放跨域访问,直接调用会触发
CORS错误。 - 解决方案:使用 Gitee 官方跨域代理 或 GitHub Pages 反向代理(本文提供代理配置方案)。
- 令牌安全:前端直接暴露 Gitee 私人令牌存在风险,建议使用临时令牌 或后端签名校验(演示环境可忽略)。
2. 前置准备步骤
-
生成 Gitee 私人令牌 :登录 Gitee → 个人设置 → 私人令牌 → 生成,勾选
repo权限,保存令牌。 -
配置跨域代理 :推荐使用 Cloudflare Workers 搭建免费反向代理,消除 CORS 限制。代理核心代码(Cloudflare Workers):
javascript
运行
addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) }) async function handleRequest(request) { const url = new URL(request.url); const giteeUrl = `https://gitee.com/api/v5${url.pathname}${url.search}`; const modifiedRequest = new Request(giteeUrl, { method: request.method, headers: request.headers, body: request.body }); const response = await fetch(modifiedRequest); const modifiedResponse = new Response(response.body, response); // 允许跨域 modifiedResponse.headers.set('Access-Control-Allow-Origin', '*'); modifiedResponse.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); modifiedResponse.headers.set('Access-Control-Allow-Headers', 'Authorization, Content-Type'); return modifiedResponse; } -
获取代理地址 :部署 Cloudflare Workers 后,得到代理域名(如
https://gitee-proxy.yourname.workers.dev)。
纯前端 Gitee 操作类完整代码
该类完全对标 PHP GitHub 类结构,使用原生 fetch API,无任何第三方依赖,直接在浏览器中运行。
html
预览
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>纯前端 Gitee 操作工具</title>
<style>
.container { width: 800px; margin: 50px auto; }
pre { background: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto; }
button { padding: 8px 15px; margin: 5px; cursor: pointer; }
</style>
</head>
<body>
<div class="container">
<h2>纯前端 Gitee 操作演示</h2>
<div>
<button onclick="testListFiles()">1. 列举根目录文件</button>
<button onclick="testReadFile()">2. 读取 README.md</button>
<button onclick="testPushCode()">3. 提交测试文件</button>
</div>
<pre id="result"></pre>
</div>
<script>
/**
* 纯前端 Gitee 操作类(兼容浏览器,无 Node.js 依赖)
* 支持:列举目录、读取文件、提交代码
* 依赖:跨域代理(Cloudflare Workers)
*/
class 未来之窗_FASG_星蕴隐道_开发_gitee {
/**
* 构造函数初始化配置
* @param {string} token Gitee 私人令牌
* @param {string} proxyUrl 跨域代理地址(必填)
* @param {string} owner 仓库所有者(默认:cyberwin)
* @param {string} repo 仓库名称(默认:fauryalliancerustdesk)
*/
constructor(token, proxyUrl, owner = 'cyberwin', repo = 'fauryalliancerustdesk') {
this.token = token;
this.proxyUrl = proxyUrl; // 跨域代理地址
this.owner = owner;
this.repo = repo;
// 代理后的 API 基础地址
this.apiBaseUrl = `${this.proxyUrl}/repos/${this.owner}/${this.repo}`;
}
/**
* 工具方法:统一发起带跨域的请求
* @param {string} url 请求地址
* @param {string} method 请求方法
* @param {object} data 请求体
* @returns {object} 格式化响应结果
*/
async request(url, method = 'GET', data = null) {
try {
const headers = {
'Authorization': `token ${this.token}`,
'Content-Type': 'application/json; charset=utf-8',
'User-Agent': 'Browser-Gitee-Handler'
};
const options = { method, headers };
if (['POST', 'PUT', 'DELETE'].includes(method) && data) {
options.body = JSON.stringify(data);
}
const response = await fetch(url, options);
const httpCode = response.status;
let responseData = {};
try {
responseData = await response.json();
} catch (e) {
responseData = await response.text();
}
return {
code: httpCode,
data: responseData,
message: httpCode === 200 ? '操作成功' : '请求失败'
};
} catch (e) {
return { code: -1, data: {}, message: `请求异常:${e.message}` };
}
}
/**
* 功能1:列举仓库目录下的文件/子目录
* @param {string} path 目录路径(默认根目录)
* @returns {object} 目录文件列表
*/
async listRepoFiles(path = '') {
const apiUrl = `${this.apiBaseUrl}/contents/${path}`;
const result = await this.request(apiUrl, 'GET');
if (result.code === 200) {
return {
code: 200,
data: result.data.map(item => ({
name: item.name,
path: item.path,
type: item.type // dir/file
})),
message: '目录文件列举成功(纯前端模式)'
};
} else {
return { code: result.code, data: [], message: `列举失败:${JSON.stringify(result.data)}` };
}
}
/**
* 功能2:读取单个文件内容(预览模式+API模式兜底)
* @param {string} filePath 文件路径
* @returns {object} 文件内容及信息
*/
async readSingleFile(filePath) {
if (!filePath) return { code: -2, data: {}, msg: '文件路径不能为空' };
try {
// 1. 预览模式:直接读取原始文件(免令牌,优先使用)
const previewUrl = `${this.proxyUrl}/${this.owner}/${this.repo}/raw/main/${filePath}`;
const previewRes = await fetch(previewUrl);
if (previewRes.status === 200) {
const content = await previewRes.text();
return {
code: 200,
data: { file_path: filePath, content, encoding: 'utf-8' },
msg: '文件读取成功(预览模式)'
};
}
// 2. API 模式兜底:需令牌,Base64 解码
const apiUrl = `${this.apiBaseUrl}/contents/${filePath}`;
const apiRes = await this.request(apiUrl, 'GET');
if (apiRes.code === 200) {
const content = atob(apiRes.data.content);
return {
code: 200,
data: {
file_path: filePath,
content,
encoding: apiRes.data.encoding,
sha: apiRes.data.sha
},
msg: '文件读取成功(API 模式)'
};
} else {
return { code: apiRes.code, data: {}, msg: `读取失败:${JSON.stringify(apiRes.data)}` };
}
} catch (e) {
return { code: -1, data: {}, msg: `读取异常:${e.message}` };
}
}
/**
* 功能3:纯前端提交代码(自动处理 SHA,支持创建/更新文件)
* @param {string} filePath 文件路径
* @param {string} fileContent 文件内容
* @param {string} commitMsg 提交备注
* @param {object} params 可选参数(分支、作者信息)
* @returns {object} 提交结果
*/
async pushCodeCommit(filePath, fileContent, commitMsg, params = {}) {
if (!filePath || !commitMsg) {
return { code: -2, data: {}, msg: '文件路径和提交备注不能为空' };
}
// 默认参数配置
const defaultParams = {
branch: 'main',
author_name: 'Browser-Gitee-Client',
author_email: 'gitee-client@example.com'
};
const finalParams = { ...defaultParams, ...params };
try {
// 步骤1:获取文件 SHA(更新文件必需)
const fileInfo = await this.readSingleFile(filePath);
const fileSha = fileInfo.code === 200 && fileInfo.data.sha ? fileInfo.data.sha : '';
// 步骤2:Base64 编码内容(中文兼容)
const base64Content = btoa(unescape(encodeURIComponent(fileContent)));
// 步骤3:API 提交文件
const apiUrl = `${this.apiBaseUrl}/contents/${filePath}`;
const postData = {
message: commitMsg,
content: base64Content,
sha: fileSha,
branch: finalParams.branch,
author: {
name: finalParams.author_name,
email: finalParams.author_email
}
};
const pushRes = await this.request(apiUrl, 'PUT', postData);
if ([200, 201].includes(pushRes.code)) {
return { code: 200, data: pushRes.data, msg: '代码提交成功(纯前端)' };
} else {
return { code: pushRes.code, data: {}, msg: `提交失败:${JSON.stringify(pushRes.data)}` };
}
} catch (e) {
return { code: -1, data: {}, msg: `提交异常:${e.message}` };
}
}
}
// ==================== 前端演示配置 ====================
// 替换为你的配置
const GITEE_TOKEN = '你的Gitee私人令牌';
const PROXY_URL = '你的Cloudflare Workers代理地址';
// 初始化 Gitee 操作实例
const giteeHandler = new 未来之窗_FASG_星蕴隐道_开发_gitee(GITEE_TOKEN, PROXY_URL);
// 结果输出函数
function showResult(data) {
document.getElementById('result').innerText = JSON.stringify(data, null, 2);
}
// 测试函数1:列举目录文件
async function testListFiles() {
const result = await giteeHandler.listRepoFiles();
showResult(result);
}
// 测试函数2:读取 README.md
async function testReadFile() {
const result = await giteeHandler.readSingleFile('README.md');
showResult(result);
}
// 测试函数3:提交测试文件
async function testPushCode() {
const result = await giteeHandler.pushCodeCommit(
'frontend-test.txt',
'这是纯前端浏览器提交的测试内容!',
'feat: 新增前端测试文件',
{ author_name: '前端开发者', author_email: 'dev@example.com' }
);
showResult(result);
}
</script>
</body>
</html>
核心功能解析
1. 跨域处理核心
- 借助 Cloudflare Workers 搭建反向代理,添加
Access-Control-*响应头,突破浏览器同源限制。 - 类的构造函数强制传入
proxyUrl,确保所有请求走代理通道。
2. 三大功能对标 PHP 版本
listRepoFiles:调用 Gitee Contents API,格式化返回文件 / 目录的名称、路径、类型,与 PHP 类输出结构一致。readSingleFile:优先使用免令牌的原始文件预览地址,失败后自动切换到 API 模式,自动处理 Base64 解码。pushCodeCommit:自动获取文件 SHA(更新文件必需),对中文内容做兼容的 Base64 编码,支持自定义分支和作者信息。
3. 前端演示交互
- 页面提供三个测试按钮,分别对应三大核心功能。
- 结果通过
JSON.stringify格式化输出,便于调试。
安全与优化建议
- 令牌安全 :前端直接存储令牌存在泄露风险,生产环境建议通过后端接口动态获取临时令牌,或使用 OAuth 授权流程。
- 代理优化:可在代理中添加请求频率限制,防止令牌滥用。
- 错误处理:可扩展类的错误处理逻辑,添加重试机制,提升弱网环境下的稳定性。
阿雪技术观
在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者。无论是分享代码、撰写技术博客,还是参与开源项目维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基生命,为科技进步添砖加瓦。
Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Don't just be the one reaping all the benefits; step up and be a contributor too. Whether you're tossing out your code snippets, hammering out some tech blogs, or getting your hands dirty with maintaining and sprucing up open - source projects, every little thing you do might just end up being a massive force that pushes tech forward. And guess what? The Eastern FairyAlliance is this awesome place where we all come together. We're gonna team up and explore the whole silicon - based life thing, and in the process, we'll be fueling the growth of technology
