OpenCode Deployer
commited on
Commit
·
6c277ab
1
Parent(s):
ad5225a
update
Browse files- .claude/CLAUDE.md +32 -0
- .config/opencode/AGENTS.md +20 -0
- Dockerfile +39 -56
- Dockerfile.clean +0 -64
- Dockerfile.multistage +0 -124
- Dockerfile.optimized +0 -92
- main.py +0 -20
- push.sh +23 -0
- requirements.txt +0 -2
- script/backup.sh +7 -0
- script/exclude_list_root.txt +7 -0
- script/exclude_list_system.txt +2 -0
- script/restore.sh +33 -0
- script/upload-example.js +159 -0
- service/cron-service.sh +7 -0
- service/gdrive/README.md +305 -0
- service/gdrive/config.json +6 -0
- service/gdrive/credentials.json +1 -0
- service/gdrive/example.js +142 -0
- service/gdrive/folder-client.js +143 -0
- service/gdrive/gdrive-cli.js +240 -0
- service/gdrive/gdrive-service.js +419 -0
- service/gdrive/package.json +34 -0
- service/gdrive/token.json +1 -0
- service/nodejs-service.sh +11 -0
- service/opencode-service.sh +11 -0
- service/start-services.sh +59 -0
.claude/CLAUDE.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Claude Code 兼容配置
|
| 2 |
+
# Hugging Face Spaces 部署版本
|
| 3 |
+
|
| 4 |
+
## 🌐 语言设置
|
| 5 |
+
**默认语言**: 中文
|
| 6 |
+
**项目语言**: 中文优先
|
| 7 |
+
|
| 8 |
+
## 🚀 系统信息
|
| 9 |
+
**部署平台**: Hugging Face Spaces
|
| 10 |
+
**服务类型**: OpenCode AI Web Interface
|
| 11 |
+
**技术栈**: Docker + Node.js + Ubuntu 22.04
|
| 12 |
+
|
| 13 |
+
## 🛠️ 可用工具
|
| 14 |
+
- **定时任务**: Linux cron (已安装)
|
| 15 |
+
- **网络访问**: HTTP/HTTPS 支持
|
| 16 |
+
- **文件操作**: 完整读写权限 (在允许范围内)
|
| 17 |
+
- **代码执行**: Node.js 18.x 环境
|
| 18 |
+
|
| 19 |
+
## 📋 指导原则
|
| 20 |
+
1. **中文优先**: 使用中文进行交流、注释和文档
|
| 21 |
+
2. **容器感知**: 考虑 Docker 容器环境的限制
|
| 22 |
+
3. **资源友好**: 注意内存和 CPU 使用
|
| 23 |
+
4. **安全第一**: 避免危险操作和代码
|
| 24 |
+
|
| 25 |
+
## 🔄 定时任务
|
| 26 |
+
支持设置定时任务来自动化重复性工作。
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
**配置版本**: v1.0
|
| 31 |
+
**兼容性**: Claude Code 格式
|
| 32 |
+
**部署环境**: Hugging Face Spaces
|
.config/opencode/AGENTS.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 系统级 AGENT.md
|
| 2 |
+
|
| 3 |
+
此文件包含在系统级别为智能编码代理提供的通用指导原则和最佳实践。
|
| 4 |
+
|
| 5 |
+
## 交流语言规范
|
| 6 |
+
|
| 7 |
+
### 默认语言
|
| 8 |
+
- **交流语言**: 默认使用中文进行所有交流和说明
|
| 9 |
+
- **注释语言**: 代码注释优先使用中文,除非项目要求使用英文
|
| 10 |
+
- **文档语言**: 技术文档和说明文档优先使用中文编写
|
| 11 |
+
- **错误信息**: 向用户展示的错误信息使用中文说明
|
| 12 |
+
|
| 13 |
+
### 语言切换原则
|
| 14 |
+
- 当用户明确要求使用英文时,切换到英文交流
|
| 15 |
+
- 处理国际化项目时,遵循项目的语言规范
|
| 16 |
+
- 在多语言环境中,根据上下文选择合适的语言
|
| 17 |
+
|
| 18 |
+
## 定时任务
|
| 19 |
+
- 你安装了 linux cron 定时组件,用来设置用户的定时任务
|
| 20 |
+
|
Dockerfile
CHANGED
|
@@ -1,69 +1,52 @@
|
|
| 1 |
-
|
|
|
|
| 2 |
|
|
|
|
| 3 |
ENV DEBIAN_FRONTEND=noninteractive
|
| 4 |
-
ENV PYTHONUNBUFFERED=1
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
build-essential && \
|
| 12 |
-
add-apt-repository ppa:deadsnakes/ppa -y && \
|
| 13 |
-
apt-get update && \
|
| 14 |
-
apt-get install -y --no-install-recommends \
|
| 15 |
-
python3.12 \
|
| 16 |
-
python3.12-dev \
|
| 17 |
-
python3.12-venv \
|
| 18 |
-
python3.12-distutils \
|
| 19 |
-
python3-pip && \
|
| 20 |
-
rm -rf /var/lib/apt/lists/*
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
COPY requirements.txt .
|
| 32 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 33 |
-
|
| 34 |
-
FROM base AS node-builder
|
| 35 |
-
|
| 36 |
-
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
|
| 37 |
-
apt-get install -y --no-install-recommends nodejs && \
|
| 38 |
-
rm -rf /var/lib/apt/lists/*
|
| 39 |
-
|
| 40 |
-
RUN node --version && npm --version
|
| 41 |
-
|
| 42 |
-
FROM python-builder AS final
|
| 43 |
-
|
| 44 |
-
COPY --from=node-builder /usr/bin/node /usr/bin/node
|
| 45 |
-
COPY --from=node-builder /usr/bin/npm /usr/bin/npm
|
| 46 |
-
COPY --from=node-builder /usr/lib/node_modules /usr/lib/node_modules
|
| 47 |
-
|
| 48 |
-
# Ensure node is symlinked in a common PATH location
|
| 49 |
-
RUN ln -sf /usr/bin/node /usr/local/bin/node
|
| 50 |
-
|
| 51 |
-
WORKDIR /app
|
| 52 |
-
|
| 53 |
-
# The PATH is already set from python-builder, no need to redefine unless it's overwritten by node-builder in some scenario.
|
| 54 |
-
# Assuming python-builder's PATH is sufficient here.
|
| 55 |
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
-
#
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
#
|
| 62 |
-
|
| 63 |
|
| 64 |
-
|
|
|
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
|
|
|
| 68 |
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 使用 Ubuntu 基础镜像以确保更好的兼容性
|
| 2 |
+
FROM ubuntu:22.04
|
| 3 |
|
| 4 |
+
# 设置环境变量避免交互式提示
|
| 5 |
ENV DEBIAN_FRONTEND=noninteractive
|
|
|
|
| 6 |
|
| 7 |
+
COPY service/ /.system/service
|
| 8 |
+
COPY script/ /.system/script
|
| 9 |
+
# RUN chmod +x /service/*.sh
|
| 10 |
+
RUN find /.system -type f -name "*.sh" -exec chmod +x {} \;
|
| 11 |
+
RUN find /.system -type f -name "*.js" -exec chmod +x {} \;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
+
# 创建 OpenCode 全局配置目录
|
| 14 |
+
COPY .config/ /root/.config
|
| 15 |
+
COPY .claude/ /root/.claude
|
| 16 |
+
RUN mkdir -p /.backup
|
| 17 |
|
| 18 |
+
# # 将 /root/.config 目录及子目录下所有的 .md 文件权限修改为:644
|
| 19 |
+
# RUN find /root/.config -type f -name "*.md" -exec chmod 644 {} \;
|
| 20 |
|
| 21 |
+
# # 添加健康检查
|
| 22 |
+
# HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 23 |
+
# CMD curl -f http://localhost:7860/global/health || exit 1
|
| 24 |
|
| 25 |
+
# 暴露 Hugging Face Spaces 标准端口
|
| 26 |
+
EXPOSE 7860
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
# # 设置网络环境变量
|
| 29 |
+
# ENV HTTP_PROXY=
|
| 30 |
+
# ENV HTTPS_PROXY=
|
| 31 |
+
# ENV NO_PROXY=localhost,127.0.0.1,0.0.0.0
|
| 32 |
|
| 33 |
+
# # 明确禁用服务器认证,确保公开访问
|
| 34 |
+
# # 清除所有可能导致认证的环境变量
|
| 35 |
+
# ENV OPENCODE_SERVER_PASSWORD=""
|
| 36 |
+
# ENV OPENCODE_SERVER_USERNAME=""
|
| 37 |
+
# ENV OPENCODE_AUTH_REQUIRED=false
|
| 38 |
|
| 39 |
+
# # 优化网络配置
|
| 40 |
+
# ENV NODE_OPTIONS="--max-http-header-size=16384 --max-old-space-size=2048"
|
| 41 |
|
| 42 |
+
# # 网络优化设置
|
| 43 |
+
# ENV NODE_OPTIONS="--max-http-header-size=16384 --max-old-space-size=2048"
|
| 44 |
|
| 45 |
+
# 设置调试级别
|
| 46 |
+
ENV NODE_ENV=production
|
| 47 |
+
ENV LOG_LEVEL=info
|
| 48 |
|
| 49 |
+
# 使用 opencode serve 启动服务器
|
| 50 |
+
# 这将启动 API 服务器,内置 Web 界面
|
| 51 |
+
# 添加 CORS 支持以允许跨域访问
|
| 52 |
+
CMD ["/.system/service/start-services.sh"]
|
Dockerfile.clean
DELETED
|
@@ -1,64 +0,0 @@
|
|
| 1 |
-
FROM ubuntu:22.04 AS base
|
| 2 |
-
|
| 3 |
-
ENV DEBIAN_FRONTEND=noninteractive
|
| 4 |
-
ENV PYTHONUNBUFFERED=1
|
| 5 |
-
|
| 6 |
-
RUN apt-get update && apt-get install -y \
|
| 7 |
-
software-properties-common \
|
| 8 |
-
curl \
|
| 9 |
-
ca-certificates \
|
| 10 |
-
build-essential \
|
| 11 |
-
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
| 12 |
-
&& apt-get update \
|
| 13 |
-
&& apt-get install -y \
|
| 14 |
-
python3.12 \
|
| 15 |
-
python3.12-dev \
|
| 16 |
-
python3.12-venv \
|
| 17 |
-
python3.12-distutils \
|
| 18 |
-
python3-pip \
|
| 19 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 20 |
-
|
| 21 |
-
FROM base AS python-builder
|
| 22 |
-
|
| 23 |
-
RUN python3.12 -m venv /opt/venv
|
| 24 |
-
ENV PATH="/opt/venv/bin:$PATH"
|
| 25 |
-
|
| 26 |
-
RUN pip install --no-cache-dir --upgrade pip setuptools wheel
|
| 27 |
-
|
| 28 |
-
WORKDIR /app
|
| 29 |
-
|
| 30 |
-
COPY requirements.txt .
|
| 31 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 32 |
-
|
| 33 |
-
FROM base AS node-builder
|
| 34 |
-
|
| 35 |
-
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
| 36 |
-
&& apt-get install -y nodejs \
|
| 37 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 38 |
-
|
| 39 |
-
RUN node --version && npm --version
|
| 40 |
-
|
| 41 |
-
FROM python-builder AS final
|
| 42 |
-
|
| 43 |
-
COPY --from=node-builder /usr/bin/node /usr/bin/node
|
| 44 |
-
COPY --from=node-builder /usr/bin/npm /usr/bin/npm
|
| 45 |
-
COPY --from=node-builder /usr/lib/node_modules /usr/lib/node_modules
|
| 46 |
-
|
| 47 |
-
RUN ln -sf /usr/bin/node /usr/local/bin/node
|
| 48 |
-
|
| 49 |
-
WORKDIR /app
|
| 50 |
-
|
| 51 |
-
ENV PATH="/opt/venv/bin:$PATH"
|
| 52 |
-
|
| 53 |
-
COPY main.py .
|
| 54 |
-
|
| 55 |
-
RUN echo '{"name": "fastoc", "version": "1.0.0", "description": "FastAPI + Node.js + Python"}' > package.json
|
| 56 |
-
|
| 57 |
-
RUN python3.12 --version && node --version && npm --version
|
| 58 |
-
|
| 59 |
-
EXPOSE 7860
|
| 60 |
-
|
| 61 |
-
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 62 |
-
CMD curl -f http://localhost:7860/health || exit 1
|
| 63 |
-
|
| 64 |
-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile.multistage
DELETED
|
@@ -1,124 +0,0 @@
|
|
| 1 |
-
# ============================================
|
| 2 |
-
# FastOC - FastAPI + Node.js + Python 多语言环境 (多阶段构建)
|
| 3 |
-
# ============================================
|
| 4 |
-
|
| 5 |
-
# 阶段 1: Node.js 构建阶段
|
| 6 |
-
# =========================
|
| 7 |
-
FROM node:22-alpine AS node-stage
|
| 8 |
-
|
| 9 |
-
# 设置 Node.js 环境变量
|
| 10 |
-
ENV NODE_ENV=production
|
| 11 |
-
ENV NPM_CONFIG_LOGLEVEL=warn
|
| 12 |
-
ENV NPM_CONFIG_PROGRESS=false
|
| 13 |
-
|
| 14 |
-
# 创建应用目录
|
| 15 |
-
WORKDIR /app/nodejs
|
| 16 |
-
|
| 17 |
-
# 如果有 package.json,复制并安装依赖
|
| 18 |
-
# 这个阶段为将来可能的 Node.js 应用扩展做准备
|
| 19 |
-
# COPY package*.json ./
|
| 20 |
-
# RUN npm ci --only=production && npm cache clean --force
|
| 21 |
-
|
| 22 |
-
# 验证 Node.js 版本
|
| 23 |
-
RUN node --version && npm --version
|
| 24 |
-
|
| 25 |
-
# 阶段 2: Python 基础环境构建阶段
|
| 26 |
-
# ===============================
|
| 27 |
-
FROM ubuntu:22.04 AS python-base
|
| 28 |
-
|
| 29 |
-
# 设置非交互式安装环境
|
| 30 |
-
ENV DEBIAN_FRONTEND=noninteractive
|
| 31 |
-
ENV PYTHONUNBUFFERED=1
|
| 32 |
-
|
| 33 |
-
# 安装系统依赖和 Python 3.12
|
| 34 |
-
RUN apt-get update && apt-get install -y \
|
| 35 |
-
software-properties-common \
|
| 36 |
-
curl \
|
| 37 |
-
ca-certificates \
|
| 38 |
-
build-essential \
|
| 39 |
-
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
| 40 |
-
&& apt-get update \
|
| 41 |
-
&& apt-get install -y \
|
| 42 |
-
python3.12 \
|
| 43 |
-
python3.12-dev \
|
| 44 |
-
python3.12-venv \
|
| 45 |
-
python3.12-distutils \
|
| 46 |
-
python3-pip \
|
| 47 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 48 |
-
|
| 49 |
-
# 创建 Python 虚拟环境
|
| 50 |
-
RUN python3.12 -m venv /opt/venv
|
| 51 |
-
ENV PATH="/opt/venv/bin:$PATH"
|
| 52 |
-
|
| 53 |
-
# 升级 pip 并安装基础工具
|
| 54 |
-
RUN pip install --no-cache-dir --upgrade pip setuptools wheel
|
| 55 |
-
|
| 56 |
-
# 验证 Python 环境
|
| 57 |
-
RUN python3.12 --version && pip --version
|
| 58 |
-
|
| 59 |
-
# 阶段 3: Python 依赖安装阶段
|
| 60 |
-
# ==========================
|
| 61 |
-
FROM python-base AS python-deps
|
| 62 |
-
|
| 63 |
-
WORKDIR /app
|
| 64 |
-
|
| 65 |
-
# 复制 Python 依赖文件
|
| 66 |
-
COPY requirements.txt .
|
| 67 |
-
|
| 68 |
-
# 安装 Python 依赖
|
| 69 |
-
# 使用 --no-cache-dir 减少镜像大小
|
| 70 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 71 |
-
|
| 72 |
-
# 阶段 4: Node.js 运行时环境安装阶段
|
| 73 |
-
# ==================================
|
| 74 |
-
FROM python-base AS runtime
|
| 75 |
-
|
| 76 |
-
# 安装 Node.js 运行时
|
| 77 |
-
# 从 NodeSource 官方源安装 Node.js 22.x LTS
|
| 78 |
-
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
| 79 |
-
&& apt-get install -y nodejs \
|
| 80 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 81 |
-
|
| 82 |
-
# 验证完整运行时环境
|
| 83 |
-
RUN node --version && npm --version && python3.12 --version
|
| 84 |
-
|
| 85 |
-
# 阶段 5: 最终应用镜像
|
| 86 |
-
# ====================
|
| 87 |
-
FROM runtime AS final
|
| 88 |
-
|
| 89 |
-
# 设置应用环境变量
|
| 90 |
-
ENV PYTHONUNBUFFERED=1
|
| 91 |
-
ENV NODE_ENV=production
|
| 92 |
-
|
| 93 |
-
# 设置工作目录
|
| 94 |
-
WORKDIR /app
|
| 95 |
-
|
| 96 |
-
# 从 Python 依赖阶段复制已安装的包
|
| 97 |
-
COPY --from=python-deps /opt/venv /opt/venv
|
| 98 |
-
|
| 99 |
-
# 确保使用 Python 虚拟环境
|
| 100 |
-
ENV PATH="/opt/venv/bin:$PATH"
|
| 101 |
-
|
| 102 |
-
# 复制应用代码
|
| 103 |
-
COPY main.py .
|
| 104 |
-
COPY requirements.txt . # 保留用于参考
|
| 105 |
-
|
| 106 |
-
# 设置文件权限
|
| 107 |
-
RUN chmod +x /opt/venv/bin/* 2>/dev/null || true
|
| 108 |
-
|
| 109 |
-
# 验证应用启动前的环境
|
| 110 |
-
RUN python3.12 -c "import fastapi; print('FastAPI 导入成功')" \
|
| 111 |
-
&& python3.12 -c "import uvicorn; print('Uvicorn 导入成功')" \
|
| 112 |
-
&& node -e "console.log('Node.js 运行时验证成功')"
|
| 113 |
-
|
| 114 |
-
# 暴露端口
|
| 115 |
-
EXPOSE 7860
|
| 116 |
-
|
| 117 |
-
# 健康检查
|
| 118 |
-
# 定期检查应用是否正常运行
|
| 119 |
-
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 120 |
-
CMD curl -f http://localhost:7860/health || exit 1
|
| 121 |
-
|
| 122 |
-
# 容器启动命令
|
| 123 |
-
# 使用 uvicorn 作为 ASGI 服务器运行 FastAPI 应用
|
| 124 |
-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile.optimized
DELETED
|
@@ -1,92 +0,0 @@
|
|
| 1 |
-
# ============================================
|
| 2 |
-
# FastOC - FastAPI + Node.js + Python 多语言环境 (优化多阶段构建)
|
| 3 |
-
# ============================================
|
| 4 |
-
|
| 5 |
-
# 阶段 1: 基础环境构建
|
| 6 |
-
# =====================
|
| 7 |
-
FROM ubuntu:22.04 AS base
|
| 8 |
-
|
| 9 |
-
# 设置非交互式环境
|
| 10 |
-
ENV DEBIAN_FRONTEND=noninteractive
|
| 11 |
-
ENV PYTHONUNBUFFERED=1
|
| 12 |
-
|
| 13 |
-
# 安装系统基础工具和 Python 3.12
|
| 14 |
-
RUN apt-get update && apt-get install -y \
|
| 15 |
-
software-properties-common \
|
| 16 |
-
curl \
|
| 17 |
-
ca-certificates \
|
| 18 |
-
build-essential \
|
| 19 |
-
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
| 20 |
-
&& apt-get update \
|
| 21 |
-
&& apt-get install -y \
|
| 22 |
-
python3.12 \
|
| 23 |
-
python3.12-dev \
|
| 24 |
-
python3.12-venv \
|
| 25 |
-
python3.12-distutils \
|
| 26 |
-
python3-pip \
|
| 27 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 28 |
-
|
| 29 |
-
# 阶段 2: Python 环境配置
|
| 30 |
-
# =========================
|
| 31 |
-
FROM base AS python-builder
|
| 32 |
-
|
| 33 |
-
# 创建并配置 Python 虚拟环境
|
| 34 |
-
RUN python3.12 -m venv /opt/venv
|
| 35 |
-
ENV PATH="/opt/venv/bin:$PATH"
|
| 36 |
-
|
| 37 |
-
# 升级 pip 和安装构建工具
|
| 38 |
-
RUN pip install --no-cache-dir --upgrade pip setuptools wheel
|
| 39 |
-
|
| 40 |
-
WORKDIR /app
|
| 41 |
-
|
| 42 |
-
# 安装 Python 依赖
|
| 43 |
-
COPY requirements.txt .
|
| 44 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 45 |
-
|
| 46 |
-
# 阶段 3: Node.js 环境构建
|
| 47 |
-
# ========================
|
| 48 |
-
FROM base AS node-builder
|
| 49 |
-
|
| 50 |
-
# 安装 Node.js 22.x LTS
|
| 51 |
-
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
| 52 |
-
&& apt-get install -y nodejs \
|
| 53 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 54 |
-
|
| 55 |
-
# 验证 Node.js 安装
|
| 56 |
-
RUN node --version && npm --version
|
| 57 |
-
|
| 58 |
-
# 阶段 4: 最终运行时镜像
|
| 59 |
-
# =====================
|
| 60 |
-
FROM python-builder AS final
|
| 61 |
-
|
| 62 |
-
# 从 Node.js 构建阶段复制 Node.js 运行时
|
| 63 |
-
COPY --from=node-builder /usr/bin/node /usr/bin/node
|
| 64 |
-
COPY --from=node-builder /usr/bin/npm /usr/bin/npm
|
| 65 |
-
COPY --from=node-builder /usr/lib/node_modules /usr/lib/node_modules
|
| 66 |
-
|
| 67 |
-
# 设置工作目录
|
| 68 |
-
WORKDIR /app
|
| 69 |
-
|
| 70 |
-
# 确保虚拟环境激活
|
| 71 |
-
ENV PATH="/opt/venv/bin:$PATH"
|
| 72 |
-
|
| 73 |
-
# 复制应用代码
|
| 74 |
-
COPY main.py .
|
| 75 |
-
|
| 76 |
-
# 创建一个简单的 package.json 用于可能的 Node.js 扩展
|
| 77 |
-
RUN echo '{"name": "fastoc", "version": "1.0.0", "description": "FastAPI + Node.js + Python"}' > package.json
|
| 78 |
-
|
| 79 |
-
# 验证完整环境
|
| 80 |
-
RUN python3.12 --version && node --version && npm --version \
|
| 81 |
-
&& python3.12 -c "import fastapi; print('✅ FastAPI 导入成功')" \
|
| 82 |
-
&& python3.12 -c "import uvicorn; print('✅ Uvicorn 导入成功')"
|
| 83 |
-
|
| 84 |
-
# 暴露端口
|
| 85 |
-
EXPOSE 7860
|
| 86 |
-
|
| 87 |
-
# 健康检查(可选,适合生产环境)
|
| 88 |
-
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 89 |
-
CMD curl -f http://localhost:7860/health || exit 1
|
| 90 |
-
|
| 91 |
-
# 启动命令
|
| 92 |
-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
main.py
DELETED
|
@@ -1,20 +0,0 @@
|
|
| 1 |
-
from fastapi import FastAPI
|
| 2 |
-
|
| 3 |
-
app = FastAPI(title="FastOC - FastAPI + Node.js + Python", version="1.0.0")
|
| 4 |
-
|
| 5 |
-
@app.get("/")
|
| 6 |
-
async def root():
|
| 7 |
-
return {"message": "Hello World from FastOC!", "tech_stack": "FastAPI + Node.js 22.x + Python 3.12"}
|
| 8 |
-
|
| 9 |
-
@app.get("/health")
|
| 10 |
-
async def health_check():
|
| 11 |
-
return {"status": "healthy", "service": "FastOC", "version": "1.0.0"}
|
| 12 |
-
|
| 13 |
-
@app.get("/info")
|
| 14 |
-
async def get_info():
|
| 15 |
-
return {
|
| 16 |
-
"service": "FastOC",
|
| 17 |
-
"description": "多语言开发环境",
|
| 18 |
-
"features": ["FastAPI", "Node.js 22.x", "Python 3.12"],
|
| 19 |
-
"endpoints": ["/", "/health", "/info"]
|
| 20 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
push.sh
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# OS_TYPE=$(uname -s)
|
| 4 |
+
|
| 5 |
+
# case "$OS_TYPE" in
|
| 6 |
+
# Darwin*)
|
| 7 |
+
# echo "当前系统是 macOS。"
|
| 8 |
+
# ssh-add ~/.ssh/id_ed25519_airsltd_mac
|
| 9 |
+
# ;;
|
| 10 |
+
# Linux*)
|
| 11 |
+
# echo "当前系统是 Linux。"
|
| 12 |
+
# ;;
|
| 13 |
+
# CYGWIN*|MINGW*|MSYS*)
|
| 14 |
+
# echo "当前系统是 Windows。"
|
| 15 |
+
# ;;
|
| 16 |
+
# *)
|
| 17 |
+
# echo "未知的系统: $OS_TYPE"
|
| 18 |
+
# ;;
|
| 19 |
+
# esac
|
| 20 |
+
git add .
|
| 21 |
+
git commit -m "update"
|
| 22 |
+
git push
|
| 23 |
+
|
requirements.txt
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
fastapi
|
| 2 |
-
uvicorn[standard]
|
|
|
|
|
|
|
|
|
script/backup.sh
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# 备份 /.system 文件
|
| 4 |
+
tar -czvf /.backup/.system.tar.gz --exclude-from=/.system/script/exclude_list_system.txt -C /.system .
|
| 5 |
+
|
| 6 |
+
# 备份 /root 文件
|
| 7 |
+
tar -czvf /.backup/root.tar.gz --exclude-from=/.system/script/exclude_list_root.txt -C /root .
|
script/exclude_list_root.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# exclude_list_root.txt
|
| 2 |
+
.bun
|
| 3 |
+
.cache
|
| 4 |
+
# .claude
|
| 5 |
+
.config/opencode/node_modules
|
| 6 |
+
.local
|
| 7 |
+
.npm
|
script/exclude_list_system.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# exclude_list_system.txt
|
| 2 |
+
# 暂时没有需要排除的文件或目录
|
script/restore.sh
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# 恢复脚本 - 将backup.sh备份的内容恢复到对应目录
|
| 4 |
+
|
| 5 |
+
set -e # 遇到错误时立即退出
|
| 6 |
+
|
| 7 |
+
echo "开始恢复备份内容..."
|
| 8 |
+
|
| 9 |
+
# 检查备份文件是否存在
|
| 10 |
+
if [ ! -f "/.backup/.system.tar.gz" ]; then
|
| 11 |
+
echo "错误:/.backup/.system.tar.gz 备份文件不存在"
|
| 12 |
+
exit 1
|
| 13 |
+
fi
|
| 14 |
+
|
| 15 |
+
if [ ! -f "/.backup/root.tar.gz" ]; then
|
| 16 |
+
echo "错误:/.backup/root.tar.gz 备份文件不存在"
|
| 17 |
+
exit 1
|
| 18 |
+
fi
|
| 19 |
+
|
| 20 |
+
# 创建目标目录(如果不存在)
|
| 21 |
+
mkdir -p /.system
|
| 22 |
+
mkdir -p /root
|
| 23 |
+
|
| 24 |
+
echo "正在恢复 /.system 文件..."
|
| 25 |
+
# 恢复 .system 文件,自动覆盖现有文件
|
| 26 |
+
tar -xzf /.backup/.system.tar.gz -C /.system --overwrite
|
| 27 |
+
|
| 28 |
+
echo "正在恢复 /root 文件..."
|
| 29 |
+
# 恢复 root 文件,自动覆盖现有文件
|
| 30 |
+
tar -xzf /.backup/root.tar.gz -C /root --overwrite
|
| 31 |
+
|
| 32 |
+
echo "恢复完成!"
|
| 33 |
+
echo "已将备份内容恢复到 /.system 和 /root 目录"
|
script/upload-example.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Google Drive 文件上传示例脚本
|
| 5 |
+
* 演示如何使用 /.system/service/gdrive 中的脚本上传文件
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
const path = require('path');
|
| 9 |
+
const fs = require('fs');
|
| 10 |
+
|
| 11 |
+
// 设置环境变量指向 gdrive 服务目录
|
| 12 |
+
process.env.GDRIVE_SERVICE_PATH = '/.system/service/gdrive';
|
| 13 |
+
|
| 14 |
+
// 导入 gdrive-hf 模块 (支持 HuggingFace Space 环境变量)
|
| 15 |
+
const gdriveModule = require(path.join(process.env.GDRIVE_SERVICE_PATH, 'gdrive-hf.js'));
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* 创建示例文件
|
| 19 |
+
*/
|
| 20 |
+
function createSampleFile(filePath) {
|
| 21 |
+
const content = `这是一个示例文件,用于测试 Google Drive 上传功能。
|
| 22 |
+
|
| 23 |
+
创建时间: ${new Date().toLocaleString('zh-CN')}
|
| 24 |
+
文件路径: ${filePath}
|
| 25 |
+
测试内容: Google Drive API 上传示例
|
| 26 |
+
|
| 27 |
+
此文件由 /.system/script/upload-example.js 自动生成。
|
| 28 |
+
`;
|
| 29 |
+
|
| 30 |
+
fs.writeFileSync(filePath, content, 'utf8');
|
| 31 |
+
console.log(`示例文件已创建: ${filePath}`);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
/**
|
| 35 |
+
* 上传示例文件到 Google Drive
|
| 36 |
+
*/
|
| 37 |
+
async function uploadSampleFile() {
|
| 38 |
+
try {
|
| 39 |
+
// 示例文件路径
|
| 40 |
+
const sampleFileName = `example_${Date.now()}.txt`;
|
| 41 |
+
const sampleFilePath = path.join(process.cwd(), sampleFileName);
|
| 42 |
+
|
| 43 |
+
// 创建示例文件
|
| 44 |
+
createSampleFile(sampleFilePath);
|
| 45 |
+
|
| 46 |
+
console.log('开始上传到 Google Drive...');
|
| 47 |
+
|
| 48 |
+
// 调用 gdrive 模块的 uploadFile 函数
|
| 49 |
+
const result = await gdriveModule.uploadFile(sampleFilePath);
|
| 50 |
+
|
| 51 |
+
if (result) {
|
| 52 |
+
console.log('\n=== 上传成功 ===');
|
| 53 |
+
console.log('文件 ID:', result.id);
|
| 54 |
+
console.log('文件名:', result.name);
|
| 55 |
+
console.log('文件大小:', result.size, '字节');
|
| 56 |
+
console.log('查看链接:', result.webViewLink);
|
| 57 |
+
|
| 58 |
+
// 清理本地示例文件
|
| 59 |
+
fs.unlinkSync(sampleFilePath);
|
| 60 |
+
console.log(`\n本地示例文件已删除: ${sampleFilePath}`);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
} catch (error) {
|
| 64 |
+
console.error('上传失败:', error.message);
|
| 65 |
+
|
| 66 |
+
// 提供错误解决建议
|
| 67 |
+
if (error.message.includes('环境变量') || error.message.includes('GOOGLE_')) {
|
| 68 |
+
console.log('\n建议解决方案:');
|
| 69 |
+
console.log('1. 在 HuggingFace Space 的 Settings > Environment Variables 中设置:');
|
| 70 |
+
console.log(' - GOOGLE_CLIENT_ID: Google OAuth 客户端 ID');
|
| 71 |
+
console.log(' - GOOGLE_CLIENT_SECRET: Google OAuth 客户端密钥');
|
| 72 |
+
console.log(' - GOOGLE_REFRESH_TOKEN: Google OAuth 刷新令牌');
|
| 73 |
+
console.log('2. 运行检查配置: cd /.system/service/gdrive && node gdrive-hf.js check');
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
/**
|
| 79 |
+
* 上传指定文件到 Google Drive
|
| 80 |
+
* @param {string} filePath - 要上传的文件路径
|
| 81 |
+
* @param {string} folderId - 目标文件夹 ID (可选)
|
| 82 |
+
*/
|
| 83 |
+
async function uploadSpecificFile(filePath, folderId = null) {
|
| 84 |
+
try {
|
| 85 |
+
if (!fs.existsSync(filePath)) {
|
| 86 |
+
console.error(`文件不存在: ${filePath}`);
|
| 87 |
+
return;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
console.log(`上传文件: ${filePath}`);
|
| 91 |
+
|
| 92 |
+
// 调用 gdrive 模块的 uploadFile 函数
|
| 93 |
+
const result = await gdriveModule.uploadFile(filePath, folderId);
|
| 94 |
+
|
| 95 |
+
if (result) {
|
| 96 |
+
console.log('\n=== 上传成功 ===');
|
| 97 |
+
console.log('文件 ID:', result.id);
|
| 98 |
+
console.log('文件名:', result.name);
|
| 99 |
+
console.log('文件大小:', result.size, '字节');
|
| 100 |
+
console.log('查看链接:', result.webViewLink);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
} catch (error) {
|
| 104 |
+
console.error('上传失败:', error.message);
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/**
|
| 109 |
+
* 显示使用说明
|
| 110 |
+
*/
|
| 111 |
+
function showUsage() {
|
| 112 |
+
console.log('使用方法:');
|
| 113 |
+
console.log(' node upload-example.js - 上传示例文件');
|
| 114 |
+
console.log(' node upload-example.js <文件路径> - 上传指定文件');
|
| 115 |
+
console.log(' node upload-example.js <文件路径> <文件夹ID> - 上传到指定文件夹');
|
| 116 |
+
console.log('');
|
| 117 |
+
console.log('示例:');
|
| 118 |
+
console.log(' node upload-example.js');
|
| 119 |
+
console.log(' node upload-example.js ./test.txt');
|
| 120 |
+
console.log(' node upload-example.js ./document.pdf 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms');
|
| 121 |
+
console.log('');
|
| 122 |
+
console.log('注意:');
|
| 123 |
+
console.log('1. 需要在 HuggingFace Space 环境变量中设置 Google Drive 凭据');
|
| 124 |
+
console.log('2. 检查配置: cd /.system/service/gdrive && node gdrive-hf.js check');
|
| 125 |
+
console.log('3. 环境变量: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN');
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
// 主函数
|
| 129 |
+
async function main() {
|
| 130 |
+
const args = process.argv.slice(2);
|
| 131 |
+
|
| 132 |
+
if (args.length === 0) {
|
| 133 |
+
// 没有参数,上传示例文件
|
| 134 |
+
await uploadSampleFile();
|
| 135 |
+
} else if (args.length === 1) {
|
| 136 |
+
// 上传指定文件
|
| 137 |
+
await uploadSpecificFile(args[0]);
|
| 138 |
+
} else if (args.length === 2) {
|
| 139 |
+
// 上��到指定文件夹
|
| 140 |
+
await uploadSpecificFile(args[0], args[1]);
|
| 141 |
+
} else {
|
| 142 |
+
console.error('参数错误');
|
| 143 |
+
showUsage();
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
// 如果直接运行此脚本
|
| 148 |
+
if (require.main === module) {
|
| 149 |
+
main().catch(error => {
|
| 150 |
+
console.error('程序执行失败:', error.message);
|
| 151 |
+
process.exit(1);
|
| 152 |
+
});
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
module.exports = {
|
| 156 |
+
uploadSampleFile,
|
| 157 |
+
uploadSpecificFile,
|
| 158 |
+
createSampleFile
|
| 159 |
+
};
|
service/cron-service.sh
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "安装cron 服务..."
|
| 4 |
+
apt-get update && apt-get install -y cron
|
| 5 |
+
echo "⏰ 启动 cron 服务..."
|
| 6 |
+
cron -f & # 在后台启动 cron 守护进程,并将其移入后台,以便脚本可以继续执行
|
| 7 |
+
echo "✅ cron 服务状态:$(pgrep -l cron || echo 'cron 服务未运行')"
|
service/gdrive/README.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Google Drive OAuth2.0 桌面客户端
|
| 2 |
+
|
| 3 |
+
这是一个使用 Google Drive API 进行文件操作的 Node.js 工具,支持 OAuth2.0 桌面客户端认证,提供完整的文件管理功能。
|
| 4 |
+
|
| 5 |
+
## 🚀 快速开始
|
| 6 |
+
|
| 7 |
+
### 1. 安装依赖
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
cd /.system/service/gdrive
|
| 11 |
+
npm install
|
| 12 |
+
|
| 13 |
+
2. 获取 Google Cloud 凭据
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
访问 Google Cloud Console
|
| 17 |
+
|
| 18 |
+
创建新项目或选择现有项目
|
| 19 |
+
|
| 20 |
+
启用 Google Drive API
|
| 21 |
+
|
| 22 |
+
创建凭据:
|
| 23 |
+
|
| 24 |
+
选择"OAuth 客户端 ID"
|
| 25 |
+
|
| 26 |
+
选择"桌面应用程序"
|
| 27 |
+
|
| 28 |
+
下载 JSON 文件
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
将 JSON 文件重命名为 credentials.json 并放在当前目录下
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
3. 进行认证
|
| 36 |
+
|
| 37 |
+
node gdrive-cli.js auth
|
| 38 |
+
|
| 39 |
+
按照提示在浏览器中完成认证,认证成功后会生成 token.json 文件。
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
📋 功能特性
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
✅ OAuth2.0 认证 - 安全的桌面客户端认证
|
| 46 |
+
|
| 47 |
+
✅ 文件列举 - 列出文件和文件夹
|
| 48 |
+
|
| 49 |
+
✅ 文件上传 - 上传本地文件到 Google Drive
|
| 50 |
+
|
| 51 |
+
✅ 文件下载 - 下载 Google Drive 文件到本地
|
| 52 |
+
|
| 53 |
+
✅ 文件删除 - 删除文件和文件夹
|
| 54 |
+
|
| 55 |
+
✅ 文件夹管理 - 创建文件夹
|
| 56 |
+
|
| 57 |
+
✅ 文件搜索 - 按条件搜索文件
|
| 58 |
+
|
| 59 |
+
✅ 文件信息 - 获取详细文件信息
|
| 60 |
+
|
| 61 |
+
✅ 令牌管理 - 自动刷新访问令牌
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
🛠️ 使用方法
|
| 65 |
+
|
| 66 |
+
认证命令
|
| 67 |
+
|
| 68 |
+
# 进行OAuth2认证
|
| 69 |
+
node gdrive-cli.js auth
|
| 70 |
+
|
| 71 |
+
# 刷新访问令牌
|
| 72 |
+
node gdrive-cli.js refresh
|
| 73 |
+
|
| 74 |
+
# 测试连接
|
| 75 |
+
node gdrive-cli.js test
|
| 76 |
+
|
| 77 |
+
文件操作
|
| 78 |
+
|
| 79 |
+
# 列出根目录文件
|
| 80 |
+
node gdrive-cli.js list
|
| 81 |
+
|
| 82 |
+
# 列出指定文件夹的文件
|
| 83 |
+
node gdrive-cli.js list <文件夹ID> <每页数量>
|
| 84 |
+
|
| 85 |
+
# 上传文件到根目录
|
| 86 |
+
node gdrive-cli.js upload /path/to/file.txt
|
| 87 |
+
|
| 88 |
+
# 上传文件到指定文件夹
|
| 89 |
+
node gdrive-cli.js upload /path/to/file.txt <文件夹ID>
|
| 90 |
+
|
| 91 |
+
# 下载文件到当前目录
|
| 92 |
+
node gdrive-cli.js download <文件ID>
|
| 93 |
+
|
| 94 |
+
# 下载文件到指定路径
|
| 95 |
+
node gdrive-cli.js download <文件ID> /path/to/save/location.txt
|
| 96 |
+
|
| 97 |
+
# 删除文件
|
| 98 |
+
node gdrive-cli.js delete <文件ID>
|
| 99 |
+
|
| 100 |
+
# 创建文件夹
|
| 101 |
+
node gdrive-cli.js mkdir "新文件夹"
|
| 102 |
+
|
| 103 |
+
# 在指定文件夹中创建子文件夹
|
| 104 |
+
node gdrive-cli.js mkdir "子文件夹" <父文件夹ID>
|
| 105 |
+
|
| 106 |
+
# 搜索文件
|
| 107 |
+
node gdrive-cli.js search "name contains '.txt'"
|
| 108 |
+
|
| 109 |
+
# 获取文件信息
|
| 110 |
+
node gdrive-cli.js info <文件ID>
|
| 111 |
+
|
| 112 |
+
📝 使用示例
|
| 113 |
+
|
| 114 |
+
基本工作流
|
| 115 |
+
|
| 116 |
+
# 1. 认证(首次使用)
|
| 117 |
+
node gdrive-cli.js auth
|
| 118 |
+
|
| 119 |
+
# 2. 测试连接
|
| 120 |
+
node gdrive-cli.js test
|
| 121 |
+
|
| 122 |
+
# 3. 创建文件夹
|
| 123 |
+
node gdrive-cli.js mkdir "我的文档"
|
| 124 |
+
|
| 125 |
+
# 4. 上传文件
|
| 126 |
+
node gdrive-cli.js upload ./document.txt
|
| 127 |
+
|
| 128 |
+
# 5. 列出文件查看上传结果
|
| 129 |
+
node gdrive-cli.js list
|
| 130 |
+
|
| 131 |
+
# 6. 搜索特定文件
|
| 132 |
+
node gdrive-cli.js search "name contains 'document'"
|
| 133 |
+
|
| 134 |
+
# 7. 下载文件测试
|
| 135 |
+
node gdrive-cli.js download <文件ID> ./downloaded_document.txt
|
| 136 |
+
|
| 137 |
+
高级搜索示例
|
| 138 |
+
|
| 139 |
+
# 搜索所有PDF文件
|
| 140 |
+
node gdrive-cli.js search "mimeType = 'application/pdf'"
|
| 141 |
+
|
| 142 |
+
# 搜索最近修改的文件
|
| 143 |
+
node gdrive-cli.js search "modifiedTime > '2024-01-01T00:00:00'"
|
| 144 |
+
|
| 145 |
+
# 搜索特定名称的文件
|
| 146 |
+
node gdrive-cli.js search "name = '重要文档'"
|
| 147 |
+
|
| 148 |
+
📁 项目结构
|
| 149 |
+
|
| 150 |
+
/.system/service/gdrive/
|
| 151 |
+
├── gdrive-service.js # 核心服务类(OAuth2.0 + 文件操作)
|
| 152 |
+
├── gdrive-cli.js # 命令行接口
|
| 153 |
+
├── credentials.json # Google Cloud 凭据文件
|
| 154 |
+
├── token.json # 访问令牌文件(认证后生成)
|
| 155 |
+
├── package.json # 项目配置
|
| 156 |
+
├── package-lock.json # 依赖锁定文件
|
| 157 |
+
├── node_modules/ # 依赖包
|
| 158 |
+
└── README.md # 说明文档
|
| 159 |
+
|
| 160 |
+
🔧 技术实现
|
| 161 |
+
|
| 162 |
+
OAuth2.0 认证流程
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
使用 Google Cloud 凭据创建 OAuth2 客户端
|
| 166 |
+
|
| 167 |
+
生成认证 URL,用户在浏览器中授权
|
| 168 |
+
|
| 169 |
+
获取授权码,交换访问令牌
|
| 170 |
+
|
| 171 |
+
保存令牌到本地文件,支持自动刷新
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
文件操作 API
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
列举文件: drive.files.list() - 支持分页和过滤
|
| 178 |
+
|
| 179 |
+
上传文件: drive.files.create() - 支持大文件上传
|
| 180 |
+
|
| 181 |
+
下载文件: drive.files.get() - 流式下载
|
| 182 |
+
|
| 183 |
+
删除文件: drive.files.delete() - 软删除到回收站
|
| 184 |
+
|
| 185 |
+
创建文件夹: drive.files.create() - 指定 MIME 类型
|
| 186 |
+
|
| 187 |
+
搜索文件: drive.files.list() - 使用查询语法
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
⚠️ 注意事项
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
安全性:
|
| 194 |
+
|
| 195 |
+
credentials.json 和 token.json 包含敏感信息,请勿分享
|
| 196 |
+
|
| 197 |
+
建议将这些文件添加到 .gitignore
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
令牌管理:
|
| 202 |
+
|
| 203 |
+
访问令牌会过期,系统会自动刷新
|
| 204 |
+
|
| 205 |
+
如遇到认证错误,可运行 node gdrive-cli.js refresh
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
API 限制:
|
| 210 |
+
|
| 211 |
+
Google Drive API 有使用配额限制
|
| 212 |
+
|
| 213 |
+
大量操作时请注意控制频率
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
文件大小:
|
| 218 |
+
|
| 219 |
+
单文件上传限制为 5GB
|
| 220 |
+
|
| 221 |
+
超大文件需要使用分块上传
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
🐛 错误处理
|
| 227 |
+
|
| 228 |
+
常见错误及解决方法:
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
错误信息 解决方法
|
| 232 |
+
"凭据文件不存在" 确保 credentials.json 文件存在
|
| 233 |
+
"认证失败" 删除 token.json 并重新运行 node gdrive-cli.js auth
|
| 234 |
+
"文件不存在" 检查文件路径或文件ID是否正确
|
| 235 |
+
"权限不足" 检查 Google Cloud Console 中的 API 权限设置
|
| 236 |
+
"令牌过期" 运行 node gdrive-cli.js refresh 刷新令牌
|
| 237 |
+
|
| 238 |
+
🔄 API 查询语法
|
| 239 |
+
|
| 240 |
+
���索功能支持 Google Drive API 查询语法:
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
# 按名称搜索
|
| 244 |
+
name contains '关键词'
|
| 245 |
+
|
| 246 |
+
# 按类型搜索
|
| 247 |
+
mimeType = 'application/pdf'
|
| 248 |
+
mimeType contains 'image/'
|
| 249 |
+
|
| 250 |
+
# 按时间搜索
|
| 251 |
+
modifiedTime > '2024-01-01T00:00:00'
|
| 252 |
+
createdTime < '2024-12-31T23:59:59'
|
| 253 |
+
|
| 254 |
+
# 组合条件
|
| 255 |
+
name contains '报告' and modifiedTime > '2024-01-01T00:00:00'
|
| 256 |
+
|
| 257 |
+
📚 开发参考
|
| 258 |
+
|
| 259 |
+
作为模块使用
|
| 260 |
+
|
| 261 |
+
const GoogleDriveOAuth2Service = require('./gdrive-service');
|
| 262 |
+
|
| 263 |
+
async function example() {
|
| 264 |
+
const service = new GoogleDriveOAuth2Service();
|
| 265 |
+
await service.initialize();
|
| 266 |
+
|
| 267 |
+
// 列出文件
|
| 268 |
+
const files = await service.listFiles({ pageSize: 20 });
|
| 269 |
+
console.log(files.files);
|
| 270 |
+
|
| 271 |
+
// 上传文件
|
| 272 |
+
const result = await service.uploadFile('./test.txt');
|
| 273 |
+
console.log('上传成功:', result);
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
支持的方法
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
initialize() - 初始化认证
|
| 280 |
+
|
| 281 |
+
listFiles(options) - 列出文件
|
| 282 |
+
|
| 283 |
+
uploadFile(filePath, fileName, folderId) - 上传文件
|
| 284 |
+
|
| 285 |
+
downloadFile(fileId, destinationPath) - 下载文件
|
| 286 |
+
|
| 287 |
+
createFolder(folderName, parentFolderId) - 创建文件夹
|
| 288 |
+
|
| 289 |
+
deleteFile(fileId) - 删除文件
|
| 290 |
+
|
| 291 |
+
searchFiles(searchQuery) - 搜索文件
|
| 292 |
+
|
| 293 |
+
getFileInfo(fileId) - 获取文件信息
|
| 294 |
+
|
| 295 |
+
testConnection() - 测试连接
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
📄 许可证
|
| 299 |
+
|
| 300 |
+
MIT License
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
🤝 贡献
|
| 304 |
+
|
| 305 |
+
欢迎提交 Issue 和 Pull Request!
|
service/gdrive/config.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"defaultFolder": {
|
| 3 |
+
"name": "myoc",
|
| 4 |
+
"id": "1UCRmKH3yfMq4u6NjMMW9av30oyNjTgFk"
|
| 5 |
+
}
|
| 6 |
+
}
|
service/gdrive/credentials.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"installed":{"client_id":"769815209820-s0vsl27t45etplsjigcctb3bo8j05599.apps.googleusercontent.com","project_id":"molten-nirvana-445614-b4","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}
|
service/gdrive/example.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
|
| 5 |
+
Google Drive 使用示例脚本
|
| 6 |
+
演示如何使用 Google Drive OAuth2 服务 */
|
| 7 |
+
const GoogleDriveOAuth2Service = require('./gdrive-service'); const path = require('path'); const fs = require('fs');
|
| 8 |
+
|
| 9 |
+
async function runExample() { console.log('🚀 Google Drive OAuth2.0 示例演示\n');
|
| 10 |
+
|
| 11 |
+
const service = new GoogleDriveOAuth2Service();
|
| 12 |
+
|
| 13 |
+
try {
|
| 14 |
+
// 1. 初始化服务
|
| 15 |
+
console.log('📡 正在初始化 Google Drive 服务...');
|
| 16 |
+
await service.initialize();
|
| 17 |
+
console.log('✅ 服务初始化成功!\n');
|
| 18 |
+
|
| 19 |
+
// 2. 测试连接
|
| 20 |
+
console.log('🔍 正在测试连接...');
|
| 21 |
+
const connectionInfo = await service.testConnection();
|
| 22 |
+
console.log('✅ 连接成功!');
|
| 23 |
+
console.log(`👤 用户: ${connectionInfo.user.emailAddress}\n`);
|
| 24 |
+
|
| 25 |
+
// 3. 列出文件
|
| 26 |
+
console.log('📋 正在列出根目录文件...');
|
| 27 |
+
const listResult = await service.listFiles({ pageSize: 5 });
|
| 28 |
+
|
| 29 |
+
if (listResult.files && listResult.files.length > 0) {
|
| 30 |
+
console.log(`找到 ${listResult.files.length} 个文件:`);
|
| 31 |
+
listResult.files.forEach((file, index) => {
|
| 32 |
+
const size = file.size ? `${(parseInt(file.size) / 1024).toFixed(2)} KB` : 'N/A';
|
| 33 |
+
const type = file.mimeType.includes('folder') ? '📁' : '📄';
|
| 34 |
+
console.log(` ${index + 1}. ${type} ${file.name} (${size})`);
|
| 35 |
+
});
|
| 36 |
+
} else {
|
| 37 |
+
console.log('📭 根目录为空');
|
| 38 |
+
}
|
| 39 |
+
console.log('');
|
| 40 |
+
|
| 41 |
+
// 4. 创建测试文件夹
|
| 42 |
+
console.log('📁 正在创建测试文件夹...');
|
| 43 |
+
const folderName = `测试文件夹_${new Date().getTime()}`;
|
| 44 |
+
const folderResult = await service.createFolder(folderName);
|
| 45 |
+
console.log(`✅ 文件夹创建成功: ${folderResult.name} (ID: ${folderResult.id})\n`);
|
| 46 |
+
|
| 47 |
+
// 5. 创建测试文件并上传
|
| 48 |
+
console.log('📤 正在创建测试文件并上传...');
|
| 49 |
+
const testContent = `这是一个测试文件\n创建时间: ${new Date().toISOString()}\n此文件由 Google Drive OAuth2.0 示例脚本创建`;
|
| 50 |
+
const testFileName = `test_file_${Date.now()}.txt`;
|
| 51 |
+
const testFilePath = path.join(__dirname, testFileName);
|
| 52 |
+
|
| 53 |
+
// 创建本地测试文件
|
| 54 |
+
fs.writeFileSync(testFilePath, testContent, 'utf8');
|
| 55 |
+
console.log(`📝 本地测试文件已创建: ${testFileName}`);
|
| 56 |
+
|
| 57 |
+
// 上传到测试文件夹
|
| 58 |
+
const uploadResult = await service.uploadFile(testFilePath, testFileName, folderResult.id);
|
| 59 |
+
console.log(`✅ 文件上传成功: ${uploadResult.name} (ID: ${uploadResult.id})\n`);
|
| 60 |
+
|
| 61 |
+
// 6. 搜索文件
|
| 62 |
+
console.log('🔍 正在搜索测试文件...');
|
| 63 |
+
const searchResult = await service.searchFiles(`name = '${testFileName}'`);
|
| 64 |
+
if (searchResult.files && searchResult.files.length > 0) {
|
| 65 |
+
console.log(`✅ 找到 ${searchResult.files.length} 个匹配的文件:`);
|
| 66 |
+
searchResult.files.forEach(file => {
|
| 67 |
+
console.log(` 📄 ${file.name} (ID: ${file.id})`);
|
| 68 |
+
});
|
| 69 |
+
}
|
| 70 |
+
console.log('');
|
| 71 |
+
|
| 72 |
+
// 7. 获取文件信息
|
| 73 |
+
if (uploadResult.id) {
|
| 74 |
+
console.log('ℹ️ 正在获取文件详细信息...');
|
| 75 |
+
const fileInfo = await service.getFileInfo(uploadResult.id);
|
| 76 |
+
console.log(`📄 文件名: ${fileInfo.name}`);
|
| 77 |
+
console.log(`📏 大小: ${fileInfo.size} 字节`);
|
| 78 |
+
console.log(`📋 类型: ${fileInfo.mimeType}`);
|
| 79 |
+
console.log(`📅 创建时间: ${fileInfo.createdTime}`);
|
| 80 |
+
console.log(`🔄 修改时间: ${fileInfo.modifiedTime}\n`);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// 8. 下载文件
|
| 84 |
+
if (uploadResult.id) {
|
| 85 |
+
console.log('📥 正在下载文件...');
|
| 86 |
+
const downloadPath = path.join(__dirname, `downloaded_${testFileName}`);
|
| 87 |
+
const downloadResult = await service.downloadFile(uploadResult.id, downloadPath);
|
| 88 |
+
console.log(`✅ 文件下载成功: ${downloadResult.destinationPath}`);
|
| 89 |
+
console.log(`📄 原文件名: ${downloadResult.fileName}`);
|
| 90 |
+
console.log(`📏 文件大小: ${downloadResult.fileSize} 字节\n`);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
// 9. 清理测试文件和文件夹
|
| 94 |
+
console.log('🧹 正在清理测试文件...');
|
| 95 |
+
|
| 96 |
+
// 删除上传的文件
|
| 97 |
+
if (uploadResult.id) {
|
| 98 |
+
await service.deleteFile(uploadResult.id);
|
| 99 |
+
console.log(`🗑️ 已删除上传的文件: ${uploadResult.name}`);
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
// 删除下载的文件
|
| 103 |
+
if (fs.existsSync(path.join(__dirname, `downloaded_${testFileName}`))) {
|
| 104 |
+
fs.unlinkSync(path.join(__dirname, `downloaded_${testFileName}`));
|
| 105 |
+
console.log(`🗑️ 已删除下载的文件: downloaded_${testFileName}`);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
// 删除本地测试文件
|
| 109 |
+
if (fs.existsSync(testFilePath)) {
|
| 110 |
+
fs.unlinkSync(testFilePath);
|
| 111 |
+
console.log(`🗑️ 已删除本地测试文件: ${testFileName}`);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
// 删除测试文件夹
|
| 115 |
+
if (folderResult.id) {
|
| 116 |
+
await service.deleteFile(folderResult.id);
|
| 117 |
+
console.log(`🗑️ 已删除测试文件夹: ${folderResult.name}`);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
console.log('\n✅ 所有测试��成! 示例运行成功。');
|
| 121 |
+
console.log('\n💡 提示: 你现在可以使用 "node gdrive-cli.js help" 查看所有可用命令。');
|
| 122 |
+
|
| 123 |
+
} catch (error) {
|
| 124 |
+
console.error('❌ 示例运行失败:', error.message);
|
| 125 |
+
|
| 126 |
+
// 提供解决建议
|
| 127 |
+
if (error.message.includes('credentials') || error.message.includes('认证')) {
|
| 128 |
+
console.log('\n💡 解决方案: 请先运行以下命令进行认证:');
|
| 129 |
+
console.log(' node gdrive-cli.js auth');
|
| 130 |
+
} else if (error.message.includes('ENOTFOUND') || error.message.includes('network')) {
|
| 131 |
+
console.log('\n💡 解决方案: 请检查网络连接');
|
| 132 |
+
} else {
|
| 133 |
+
console.log('\n💡 请检查凭据配置和网络连接');
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
process.exit(1);
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
// 如果直接运行此脚本 if (require.main === module) { runExample(); }
|
| 141 |
+
|
| 142 |
+
module.exports = runExample;
|
service/gdrive/folder-client.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
|
| 5 |
+
Google Drive 文件夹专用客户端
|
| 6 |
+
限定操作到指定文件夹 */
|
| 7 |
+
const GoogleDriveOAuth2Service = require('./gdrive-service'); const path = require('path'); const fs = require('path');
|
| 8 |
+
|
| 9 |
+
/**
|
| 10 |
+
|
| 11 |
+
显示使用帮助 */ function showUsage() { console.log(''); console.log('📁 Google Drive 文件夹专用客户端'); console.log('当前配置文件夹: myoc'); console.log(''); console.log('📄 文件操作:'); console.log(' node folder-client.js list [数量] - 列出文件夹内文件'); console.log(' node folder-client.js upload <文件路径> - 上传文件到文件夹'); console.log(' node folder-client.js download <文件ID> - 下载文件'); console.log(' node folder-client.js delete <文件ID> - 删除文件'); console.log(' node folder-client.js mkdir <文件夹名> - 在当前文件夹下创建子文件夹'); console.log(' node folder-client.js search <搜索条件> - 在当前文件夹下搜索'); console.log(' node folder-client.js info <文件ID> - 获取文件信息'); console.log(' node folder-client.js test - 测试连接'); console.log(''); }
|
| 12 |
+
/**
|
| 13 |
+
|
| 14 |
+
格式化文件信息显示 */ function formatFileInfo(file) { const size = file.size ? ${(parseInt(file.size) / 1024).toFixed(2)} KB : 'N/A'; const type = file.mimeType.includes('folder') ? '📁' : file.mimeType.includes('google-apps') ? '📄' : '📎'; return ${type} ${file.name}\n ID: ${file.id}\n 大小: ${size}\n 修改时间: ${file.modifiedTime}\n;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
async function main() { const args = process.argv.slice(2);
|
| 18 |
+
|
| 19 |
+
if (args.length === 0) {
|
| 20 |
+
showUsage();
|
| 21 |
+
return;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
const command = args[0];
|
| 25 |
+
|
| 26 |
+
// myoc 文件夹 ID
|
| 27 |
+
const MYOC_FOLDER_ID = '1UCRmKH3yfMq4u6NjMMW9av30oyNjTgFk';
|
| 28 |
+
const service = new GoogleDriveOAuth2Service(MYOC_FOLDER_ID);
|
| 29 |
+
|
| 30 |
+
try {
|
| 31 |
+
switch (command) {
|
| 32 |
+
case 'list':
|
| 33 |
+
await service.initialize();
|
| 34 |
+
const pageSize = args[1] ? parseInt(args[1]) : 10;
|
| 35 |
+
const result = await service.listFiles({ pageSize });
|
| 36 |
+
|
| 37 |
+
console.log(`📋 myoc 文件夹内容 (${result.files.length} 个):`);
|
| 38 |
+
console.log('');
|
| 39 |
+
result.files.forEach((file, index) => {
|
| 40 |
+
console.log(`${index + 1}. ${formatFileInfo(file)}`);
|
| 41 |
+
});
|
| 42 |
+
break;
|
| 43 |
+
|
| 44 |
+
case 'upload':
|
| 45 |
+
if (!args[1]) {
|
| 46 |
+
console.error('❌ 请指定要上传的文件路径');
|
| 47 |
+
break;
|
| 48 |
+
}
|
| 49 |
+
await service.initialize();
|
| 50 |
+
const uploadResult = await service.uploadFile(args[1]);
|
| 51 |
+
console.log('✅ 文件上传成功!');
|
| 52 |
+
console.log(`📎 文件名: ${uploadResult.name}`);
|
| 53 |
+
console.log(`🆔 文件ID: ${uploadResult.id}`);
|
| 54 |
+
console.log(`🔗 链接: ${uploadResult.webViewLink}`);
|
| 55 |
+
break;
|
| 56 |
+
|
| 57 |
+
case 'download':
|
| 58 |
+
if (!args[1]) {
|
| 59 |
+
console.error('❌ 请指定要下载的文件ID');
|
| 60 |
+
break;
|
| 61 |
+
}
|
| 62 |
+
await service.initialize();
|
| 63 |
+
const destPath = args[2] || './' + args[1] + '.downloaded';
|
| 64 |
+
const downloadResult = await service.downloadFile(args[1], destPath);
|
| 65 |
+
console.log('✅ 文件下载成功!');
|
| 66 |
+
console.log(`📎 文件名: ${downloadResult.fileName}`);
|
| 67 |
+
console.log(`💾 保存路径: ${downloadResult.destinationPath}`);
|
| 68 |
+
break;
|
| 69 |
+
|
| 70 |
+
case 'delete':
|
| 71 |
+
if (!args[1]) {
|
| 72 |
+
console.error('❌ 请指定要删除的文件ID');
|
| 73 |
+
break;
|
| 74 |
+
}
|
| 75 |
+
await service.initialize();
|
| 76 |
+
await service.deleteFile(args[1]);
|
| 77 |
+
console.log('✅ 文件删除成功!');
|
| 78 |
+
break;
|
| 79 |
+
|
| 80 |
+
case 'mkdir':
|
| 81 |
+
if (!args[1]) {
|
| 82 |
+
console.error('❌ 请指定文件夹名称');
|
| 83 |
+
break;
|
| 84 |
+
}
|
| 85 |
+
await service.initialize();
|
| 86 |
+
const folderResult = await service.createFolder(args[1]);
|
| 87 |
+
console.log('✅ 文件夹创建成功!');
|
| 88 |
+
console.log(`📁 文件夹名: ${folderResult.name}`);
|
| 89 |
+
console.log(`🆔 文件夹ID: ${folderResult.id}`);
|
| 90 |
+
console.log(`🔗 链接: ${folderResult.webViewLink}`);
|
| 91 |
+
break;
|
| 92 |
+
|
| 93 |
+
case 'search':
|
| 94 |
+
if (!args[1]) {
|
| 95 |
+
console.error('❌ 请指定搜索条件');
|
| 96 |
+
break;
|
| 97 |
+
}
|
| 98 |
+
await service.initialize();
|
| 99 |
+
const searchResult = await service.listFiles({ query: args[1] });
|
| 100 |
+
console.log(`🔍 搜索结果 (${searchResult.files.length} 个):`);
|
| 101 |
+
console.log('');
|
| 102 |
+
searchResult.files.forEach((file, index) => {
|
| 103 |
+
console.log(`${index + 1}. ${formatFileInfo(file)}`);
|
| 104 |
+
});
|
| 105 |
+
break;
|
| 106 |
+
|
| 107 |
+
case 'info':
|
| 108 |
+
if (!args[1]) {
|
| 109 |
+
console.error('❌ 请指定文件ID');
|
| 110 |
+
break;
|
| 111 |
+
}
|
| 112 |
+
await service.initialize();
|
| 113 |
+
const info = await service.getFileInfo(args[1]);
|
| 114 |
+
console.log('📋 文件信息:');
|
| 115 |
+
console.log(`📎 名称: ${info.name}`);
|
| 116 |
+
console.log(`🆔 ID: ${info.id}`);
|
| 117 |
+
console.log(`📊 类型: ${info.mimeType}`);
|
| 118 |
+
console.log(`💾 大小: ${info.size || 'N/A'}`);
|
| 119 |
+
console.log(`📅 创建时间: ${info.createdTime}`);
|
| 120 |
+
console.log(`🔄 修改时间: ${info.modifiedTime}`);
|
| 121 |
+
break;
|
| 122 |
+
|
| 123 |
+
case 'test':
|
| 124 |
+
await service.initialize();
|
| 125 |
+
const testInfo = await service.testConnection();
|
| 126 |
+
console.log('✅ 连接成功!');
|
| 127 |
+
console.log(`👤 用户: ${testInfo.user.emailAddress}`);
|
| 128 |
+
console.log(`📊 存储配额: ${testInfo.storageQuota.usage} / ${testInfo.storageQuota.limit}`);
|
| 129 |
+
break;
|
| 130 |
+
|
| 131 |
+
default:
|
| 132 |
+
console.error(`❌ 未知命令: ${command}`);
|
| 133 |
+
showUsage();
|
| 134 |
+
}
|
| 135 |
+
} catch (error) {
|
| 136 |
+
console.error('❌ 操作失败:', error.message);
|
| 137 |
+
process.exit(1);
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
if (require.main === module) { main(); }
|
| 142 |
+
|
| 143 |
+
module.exports = { main };
|
service/gdrive/gdrive-cli.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Google Drive 命令行接口
|
| 5 |
+
* 统一的命令行工具,支持所有基本操作
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
const GoogleDriveOAuth2Service = require('./gdrive-service');
|
| 9 |
+
const path = require('path');
|
| 10 |
+
const fs = require('fs');
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* 显示使用帮助
|
| 14 |
+
*/
|
| 15 |
+
function showUsage() {
|
| 16 |
+
console.log('');
|
| 17 |
+
console.log('📁 Google Drive 命令行工具');
|
| 18 |
+
console.log('');
|
| 19 |
+
console.log('🔧 认证命令:');
|
| 20 |
+
console.log(' node gdrive-cli.js auth - 进行OAuth2认证');
|
| 21 |
+
console.log(' node gdrive-cli.js refresh - 刷新访问令牌');
|
| 22 |
+
console.log(' node gdrive-cli.js test - 测试连接');
|
| 23 |
+
console.log('');
|
| 24 |
+
console.log('📄 文件操作:');
|
| 25 |
+
console.log(' node gdrive-cli.js list [文件夹ID] [数量] - 列出文件');
|
| 26 |
+
console.log(' node gdrive-cli.js upload <文件路径> [文件夹ID] - 上传文件');
|
| 27 |
+
console.log(' node gdrive-cli.js download <文件ID> [保存路径] - 下载文件');
|
| 28 |
+
console.log(' node gdrive-cli.js delete <文件ID> - 删除文件');
|
| 29 |
+
console.log(' node gdrive-cli.js mkdir <文件夹名> [父文件夹ID] - 创建文件夹');
|
| 30 |
+
console.log(' node gdrive-cli.js search <搜索条件> - 搜索文件');
|
| 31 |
+
console.log(' node gdrive-cli.js info <文件ID> - 获取文件信息');
|
| 32 |
+
console.log('');
|
| 33 |
+
console.log('📋 示例:');
|
| 34 |
+
console.log(' node gdrive-cli.js list # 列出根目录文件');
|
| 35 |
+
console.log(' node gdrive-cli.js upload ./test.txt # 上传文件');
|
| 36 |
+
console.log(' node gdrive-cli.js download 1Bxi... ./save.txt # 下载文件');
|
| 37 |
+
console.log(' node gdrive-cli.js search "name contains \\".txt\\"" # 搜索txt文件');
|
| 38 |
+
console.log('');
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
/**
|
| 42 |
+
* 格式化文件信息显示
|
| 43 |
+
*/
|
| 44 |
+
function formatFileInfo(file) {
|
| 45 |
+
const size = file.size ? `${(parseInt(file.size) / 1024).toFixed(2)} KB` : 'N/A';
|
| 46 |
+
const type = file.mimeType.includes('folder') ? '📁 文件夹' :
|
| 47 |
+
file.mimeType.includes('google-apps') ? '📄 Google文档' : '📎 文件';
|
| 48 |
+
|
| 49 |
+
return `${type} ${file.name} (${size})`;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/**
|
| 53 |
+
* 格式化文件列表显示
|
| 54 |
+
*/
|
| 55 |
+
function formatFileList(files) {
|
| 56 |
+
if (!files || files.length === 0) {
|
| 57 |
+
console.log('📭 没有找到文件');
|
| 58 |
+
return;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
console.log(`📋 找到 ${files.length} 个文件:`);
|
| 62 |
+
console.log('');
|
| 63 |
+
|
| 64 |
+
files.forEach((file, index) => {
|
| 65 |
+
const size = file.size ? `${(parseInt(file.size) / 1024).toFixed(2)} KB` : 'N/A';
|
| 66 |
+
const type = file.mimeType.includes('folder') ? '📁' :
|
| 67 |
+
file.mimeType.includes('google-apps') ? '📄' : '📎';
|
| 68 |
+
|
| 69 |
+
console.log(`${index + 1}. ${type} ${file.name}`);
|
| 70 |
+
console.log(` ID: ${file.id}`);
|
| 71 |
+
console.log(` 大小: ${size}`);
|
| 72 |
+
console.log(` 修改时间: ${file.modifiedTime || file.createdTime}`);
|
| 73 |
+
console.log('');
|
| 74 |
+
});
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
/**
|
| 78 |
+
* 主函数
|
| 79 |
+
*/
|
| 80 |
+
async function main() {
|
| 81 |
+
const args = process.argv.slice(2);
|
| 82 |
+
|
| 83 |
+
if (args.length === 0) {
|
| 84 |
+
showUsage();
|
| 85 |
+
return;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
const command = args[0];
|
| 89 |
+
|
| 90 |
+
// 读取配置文件
|
| 91 |
+
let defaultFolderId = null;
|
| 92 |
+
const configPath = path.join(__dirname, 'config.json');
|
| 93 |
+
if (fs.existsSync(configPath)) {
|
| 94 |
+
try {
|
| 95 |
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
| 96 |
+
defaultFolderId = config.defaultFolder?.id || null;
|
| 97 |
+
} catch (error) {
|
| 98 |
+
console.warn('配置文件读取失败,使用默认设置');
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
const service = new GoogleDriveOAuth2Service(defaultFolderId);
|
| 103 |
+
|
| 104 |
+
try {
|
| 105 |
+
switch (command) {
|
| 106 |
+
case 'auth':
|
| 107 |
+
await GoogleDriveOAuth2Service.getNewToken();
|
| 108 |
+
break;
|
| 109 |
+
|
| 110 |
+
case 'refresh':
|
| 111 |
+
await GoogleDriveOAuth2Service.refreshToken();
|
| 112 |
+
break;
|
| 113 |
+
|
| 114 |
+
case 'test':
|
| 115 |
+
await service.initialize();
|
| 116 |
+
const info = await service.testConnection();
|
| 117 |
+
console.log('✅ 连接成功!');
|
| 118 |
+
console.log(`👤 用户: ${info.user.emailAddress}`);
|
| 119 |
+
console.log(`📊 存储空间: ${info.storageQuota.usage || 'N/A'} / ${info.storageQuota.limit || 'N/A'}`);
|
| 120 |
+
break;
|
| 121 |
+
|
| 122 |
+
case 'list':
|
| 123 |
+
await service.initialize();
|
| 124 |
+
const folderId = args[1] || null;
|
| 125 |
+
const pageSize = args[2] ? parseInt(args[2]) : 10;
|
| 126 |
+
const listResult = await service.listFiles({ folderId, pageSize });
|
| 127 |
+
formatFileList(listResult.files);
|
| 128 |
+
break;
|
| 129 |
+
|
| 130 |
+
case 'upload':
|
| 131 |
+
if (args.length < 2) {
|
| 132 |
+
console.error('❌ 请指定要上传的文件路径');
|
| 133 |
+
return;
|
| 134 |
+
}
|
| 135 |
+
await service.initialize();
|
| 136 |
+
const uploadResult = await service.uploadFile(args[1], null, args[2]);
|
| 137 |
+
console.log('✅ 上传成功!');
|
| 138 |
+
console.log(`📄 文件名: ${uploadResult.name}`);
|
| 139 |
+
console.log(`🆔 文件ID: ${uploadResult.id}`);
|
| 140 |
+
console.log(`🔗 查看链接: ${uploadResult.webViewLink}`);
|
| 141 |
+
break;
|
| 142 |
+
|
| 143 |
+
case 'download':
|
| 144 |
+
if (args.length < 2) {
|
| 145 |
+
console.error('❌ 请指定要下载的文件ID');
|
| 146 |
+
return;
|
| 147 |
+
}
|
| 148 |
+
await service.initialize();
|
| 149 |
+
const savePath = args[2] || path.join(process.cwd(), 'downloaded_file');
|
| 150 |
+
const downloadResult = await service.downloadFile(args[1], savePath);
|
| 151 |
+
console.log('✅ 下载成功!');
|
| 152 |
+
console.log(`📄 文件名: ${downloadResult.fileName}`);
|
| 153 |
+
console.log(`💾 保存路径: ${downloadResult.destinationPath}`);
|
| 154 |
+
console.log(`📏 文件大小: ${downloadResult.fileSize} 字节`);
|
| 155 |
+
break;
|
| 156 |
+
|
| 157 |
+
case 'delete':
|
| 158 |
+
if (args.length < 2) {
|
| 159 |
+
console.error('❌ 请指定要删除的文件ID');
|
| 160 |
+
return;
|
| 161 |
+
}
|
| 162 |
+
await service.initialize();
|
| 163 |
+
await service.deleteFile(args[1]);
|
| 164 |
+
console.log('✅ 文件删除成功!');
|
| 165 |
+
break;
|
| 166 |
+
|
| 167 |
+
case 'mkdir':
|
| 168 |
+
if (args.length < 2) {
|
| 169 |
+
console.error('❌ 请指定文件夹名称');
|
| 170 |
+
return;
|
| 171 |
+
}
|
| 172 |
+
await service.initialize();
|
| 173 |
+
const folderResult = await service.createFolder(args[1], args[2]);
|
| 174 |
+
console.log('✅ 文件夹创建成功!');
|
| 175 |
+
console.log(`📁 文件夹名: ${folderResult.name}`);
|
| 176 |
+
console.log(`🆔 文件夹ID: ${folderResult.id}`);
|
| 177 |
+
console.log(`🔗 查看链接: ${folderResult.webViewLink}`);
|
| 178 |
+
break;
|
| 179 |
+
|
| 180 |
+
case 'search':
|
| 181 |
+
if (args.length < 2) {
|
| 182 |
+
console.error('❌ 请指定搜索条件');
|
| 183 |
+
return;
|
| 184 |
+
}
|
| 185 |
+
await service.initialize();
|
| 186 |
+
const searchResult = await service.searchFiles(args[1]);
|
| 187 |
+
formatFileList(searchResult.files);
|
| 188 |
+
break;
|
| 189 |
+
|
| 190 |
+
case 'info':
|
| 191 |
+
if (args.length < 2) {
|
| 192 |
+
console.error('❌ 请指定文件ID');
|
| 193 |
+
return;
|
| 194 |
+
}
|
| 195 |
+
await service.initialize();
|
| 196 |
+
const fileInfo = await service.getFileInfo(args[1]);
|
| 197 |
+
console.log('📄 文件信息:');
|
| 198 |
+
console.log(`📝 名称: ${fileInfo.name}`);
|
| 199 |
+
console.log(`🆔 ID: ${fileInfo.id}`);
|
| 200 |
+
console.log(`📋 类型: ${fileInfo.mimeType}`);
|
| 201 |
+
console.log(`📏 大小: ${fileInfo.size || 'N/A'} 字节`);
|
| 202 |
+
console.log(`📅 创建时间: ${fileInfo.createdTime}`);
|
| 203 |
+
console.log(`🔄 修改时间: ${fileInfo.modifiedTime}`);
|
| 204 |
+
if (fileInfo.parents && fileInfo.parents.length > 0) {
|
| 205 |
+
console.log(`📁 父文件夹: ${fileInfo.parents.join(', ')}`);
|
| 206 |
+
}
|
| 207 |
+
break;
|
| 208 |
+
|
| 209 |
+
case 'help':
|
| 210 |
+
case '--help':
|
| 211 |
+
case '-h':
|
| 212 |
+
showUsage();
|
| 213 |
+
break;
|
| 214 |
+
|
| 215 |
+
default:
|
| 216 |
+
console.error(`❌ 未知命令: ${command}`);
|
| 217 |
+
showUsage();
|
| 218 |
+
}
|
| 219 |
+
} catch (error) {
|
| 220 |
+
console.error('❌ 操作失败:', error.message);
|
| 221 |
+
|
| 222 |
+
// 提供解决建议
|
| 223 |
+
if (error.message.includes('credentials') || error.message.includes('认证')) {
|
| 224 |
+
console.log('💡 建议: 请先运行 "node gdrive-cli.js auth" 进行认证');
|
| 225 |
+
} else if (error.message.includes('file not found') || error.message.includes('文件不存在')) {
|
| 226 |
+
console.log('💡 建议: 请检查文件路径或文件ID是否正确');
|
| 227 |
+
} else if (error.message.includes('permission') || error.message.includes('权限')) {
|
| 228 |
+
console.log('💡 建议: 请检查Google Drive API权限设置');
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
process.exit(1);
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
// 如果直接运行此脚本
|
| 236 |
+
if (require.main === module) {
|
| 237 |
+
main();
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
module.exports = { main, showUsage };
|
service/gdrive/gdrive-service.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env node
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* Google Drive OAuth2.0 桌面客户端统一服务
|
| 5 |
+
* 支持列举、下载、上传、删除等基本操作
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
const fs = require('fs');
|
| 9 |
+
const path = require('path');
|
| 10 |
+
const { google } = require('googleapis');
|
| 11 |
+
const readline = require('readline');
|
| 12 |
+
|
| 13 |
+
// 配置信息
|
| 14 |
+
const SCOPES = ['https://www.googleapis.com/auth/drive'];
|
| 15 |
+
const TOKEN_PATH = path.join(__dirname, 'token.json');
|
| 16 |
+
const CREDENTIALS_PATH = path.join(__dirname, 'credentials.json');
|
| 17 |
+
|
| 18 |
+
class GoogleDriveOAuth2Service {
|
| 19 |
+
constructor(defaultFolderId = null) {
|
| 20 |
+
this.auth = null;
|
| 21 |
+
this.drive = null;
|
| 22 |
+
this.defaultFolderId = defaultFolderId;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
/**
|
| 26 |
+
* 初始化认证
|
| 27 |
+
*/
|
| 28 |
+
async initialize() {
|
| 29 |
+
try {
|
| 30 |
+
if (!fs.existsSync(CREDENTIALS_PATH)) {
|
| 31 |
+
throw new Error('凭据文件不存在: ' + CREDENTIALS_PATH);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
const credentials = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf8'));
|
| 35 |
+
const { client_secret, client_id, redirect_uris } = credentials.web || credentials.installed;
|
| 36 |
+
|
| 37 |
+
this.auth = new google.auth.OAuth2(
|
| 38 |
+
client_id,
|
| 39 |
+
client_secret,
|
| 40 |
+
redirect_uris ? redirect_uris[0] : 'http://localhost:8080'
|
| 41 |
+
);
|
| 42 |
+
|
| 43 |
+
// 检查是否已有token
|
| 44 |
+
if (fs.existsSync(TOKEN_PATH)) {
|
| 45 |
+
const token = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8'));
|
| 46 |
+
this.auth.setCredentials(token);
|
| 47 |
+
} else {
|
| 48 |
+
throw new Error('需要先进行认证,请运行: node gdrive-cli.js auth');
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
this.drive = google.drive({ version: 'v3', auth: this.auth });
|
| 52 |
+
return this;
|
| 53 |
+
} catch (error) {
|
| 54 |
+
console.error('初始化失败:', error.message);
|
| 55 |
+
throw error;
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* 获取新的访问令牌
|
| 61 |
+
*/
|
| 62 |
+
static async getNewToken() {
|
| 63 |
+
try {
|
| 64 |
+
if (!fs.existsSync(CREDENTIALS_PATH)) {
|
| 65 |
+
console.error('凭据文件不存在:', CREDENTIALS_PATH);
|
| 66 |
+
console.log('请从 Google Cloud Console 下载凭据文件并保存为 credentials.json');
|
| 67 |
+
process.exit(1);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
const credentials = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf8'));
|
| 71 |
+
const { client_secret, client_id, redirect_uris } = credentials.web || credentials.installed;
|
| 72 |
+
const oAuth2Client = new google.auth.OAuth2(
|
| 73 |
+
client_id,
|
| 74 |
+
client_secret,
|
| 75 |
+
redirect_uris ? redirect_uris[0] : 'http://localhost:8080'
|
| 76 |
+
);
|
| 77 |
+
|
| 78 |
+
const authUrl = oAuth2Client.generateAuthUrl({
|
| 79 |
+
access_type: 'offline',
|
| 80 |
+
scope: SCOPES,
|
| 81 |
+
});
|
| 82 |
+
|
| 83 |
+
console.log('请在浏览器中打开以下链接进行认证:');
|
| 84 |
+
console.log(authUrl);
|
| 85 |
+
console.log('');
|
| 86 |
+
|
| 87 |
+
const rl = readline.createInterface({
|
| 88 |
+
input: process.stdin,
|
| 89 |
+
output: process.stdout
|
| 90 |
+
});
|
| 91 |
+
|
| 92 |
+
const code = await new Promise((resolve) => {
|
| 93 |
+
rl.question('请输入认证代码: ', (code) => {
|
| 94 |
+
rl.close();
|
| 95 |
+
resolve(code);
|
| 96 |
+
});
|
| 97 |
+
});
|
| 98 |
+
|
| 99 |
+
const token = await oAuth2Client.getAccessToken(code);
|
| 100 |
+
|
| 101 |
+
// 保存令牌
|
| 102 |
+
fs.writeFileSync(TOKEN_PATH, JSON.stringify({
|
| 103 |
+
access_token: token.token,
|
| 104 |
+
refresh_token: oAuth2Client.credentials.refresh_token,
|
| 105 |
+
scope: SCOPES.join(' '),
|
| 106 |
+
token_type: 'Bearer',
|
| 107 |
+
expiry_date: oAuth2Client.credentials.expiry_date
|
| 108 |
+
}));
|
| 109 |
+
|
| 110 |
+
console.log('认证成功! 令牌已保存到 token.json');
|
| 111 |
+
console.log('现在可以使用 gdrive-cli.js 脚本了');
|
| 112 |
+
|
| 113 |
+
} catch (error) {
|
| 114 |
+
console.error('认证失败:', error.message);
|
| 115 |
+
process.exit(1);
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
/**
|
| 120 |
+
* 刷新访问令牌
|
| 121 |
+
*/
|
| 122 |
+
static async refreshToken() {
|
| 123 |
+
try {
|
| 124 |
+
if (!fs.existsSync(TOKEN_PATH)) {
|
| 125 |
+
console.error('令牌文件不存在,请先进行认证');
|
| 126 |
+
return;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
const token = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8'));
|
| 130 |
+
const credentials = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf8'));
|
| 131 |
+
|
| 132 |
+
const { client_secret, client_id } = credentials.web || credentials.installed;
|
| 133 |
+
const oAuth2Client = new google.auth.OAuth2(
|
| 134 |
+
client_id,
|
| 135 |
+
client_secret
|
| 136 |
+
);
|
| 137 |
+
|
| 138 |
+
oAuth2Client.setCredentials(token);
|
| 139 |
+
|
| 140 |
+
// 刷新令牌
|
| 141 |
+
({ credentials } = await oAuth2Client.refreshAccessToken());
|
| 142 |
+
|
| 143 |
+
// 更新令牌文件
|
| 144 |
+
fs.writeFileSync(TOKEN_PATH, JSON.stringify({
|
| 145 |
+
access_token: credentials.access_token,
|
| 146 |
+
refresh_token: credentials.refresh_token || token.refresh_token,
|
| 147 |
+
scope: credentials.scope,
|
| 148 |
+
token_type: credentials.token_type,
|
| 149 |
+
expiry_date: credentials.expiry_date
|
| 150 |
+
}));
|
| 151 |
+
|
| 152 |
+
console.log('令牌刷新成功!');
|
| 153 |
+
} catch (error) {
|
| 154 |
+
console.error('刷新令牌失败:', error.message);
|
| 155 |
+
console.log('请重新进行认证');
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
/**
|
| 160 |
+
* 列出文件和文件夹
|
| 161 |
+
* @param {Object} options - 选项参数
|
| 162 |
+
* @param {string} options.folderId - 文件夹ID (可选)
|
| 163 |
+
* @param {number} options.pageSize - 每页文件数量 (默认10)
|
| 164 |
+
* @param {string} options.query - 搜索查询条件 (可选)
|
| 165 |
+
*/
|
| 166 |
+
async listFiles(options = {}) {
|
| 167 |
+
try {
|
| 168 |
+
let { folderId = null, pageSize = 10, query = null } = options;
|
| 169 |
+
|
| 170 |
+
// 使用默认文件夹或指定的文件夹
|
| 171 |
+
folderId = folderId || this.defaultFolderId;
|
| 172 |
+
|
| 173 |
+
let searchQuery = "trashed=false";
|
| 174 |
+
if (folderId) {
|
| 175 |
+
searchQuery += ` and '${folderId}' in parents`;
|
| 176 |
+
}
|
| 177 |
+
if (query) {
|
| 178 |
+
searchQuery += ` and ${query}`;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
const response = await this.drive.files.list({
|
| 182 |
+
q: searchQuery,
|
| 183 |
+
pageSize: pageSize,
|
| 184 |
+
fields: 'nextPageToken, files(id, name, size, mimeType, createdTime, modifiedTime)',
|
| 185 |
+
});
|
| 186 |
+
|
| 187 |
+
return response.data;
|
| 188 |
+
} catch (error) {
|
| 189 |
+
console.error('列出文件失败:', error.message);
|
| 190 |
+
throw error;
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
/**
|
| 195 |
+
* 上传文件
|
| 196 |
+
* @param {string} filePath - 本地文件路径
|
| 197 |
+
* @param {string} fileName - 云端文件名 (可选,默认使用本地文件名)
|
| 198 |
+
* @param {string} folderId - 目标文件夹ID (可选)
|
| 199 |
+
*/
|
| 200 |
+
async uploadFile(filePath, fileName = null, folderId = null) {
|
| 201 |
+
try {
|
| 202 |
+
if (!fs.existsSync(filePath)) {
|
| 203 |
+
throw new Error(`文件不存在: ${filePath}`);
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
const finalFileName = fileName || path.basename(filePath);
|
| 207 |
+
const fileMetadata = {
|
| 208 |
+
name: finalFileName,
|
| 209 |
+
};
|
| 210 |
+
|
| 211 |
+
// 使用默认文件夹或指定的文件夹
|
| 212 |
+
const targetFolderId = folderId || this.defaultFolderId;
|
| 213 |
+
if (targetFolderId) {
|
| 214 |
+
fileMetadata.parents = [targetFolderId];
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
const media = {
|
| 218 |
+
mimeType: 'application/octet-stream',
|
| 219 |
+
body: fs.createReadStream(filePath),
|
| 220 |
+
};
|
| 221 |
+
|
| 222 |
+
const response = await this.drive.files.create({
|
| 223 |
+
resource: fileMetadata,
|
| 224 |
+
media: media,
|
| 225 |
+
fields: 'id,name,size,mimeType,webViewLink',
|
| 226 |
+
});
|
| 227 |
+
|
| 228 |
+
return response.data;
|
| 229 |
+
} catch (error) {
|
| 230 |
+
console.error('上传文件失败:', error.message);
|
| 231 |
+
throw error;
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
/**
|
| 236 |
+
* 下载文件
|
| 237 |
+
* @param {string} fileId - 文件ID
|
| 238 |
+
* @param {string} destinationPath - 本地保存路径
|
| 239 |
+
*/
|
| 240 |
+
async downloadFile(fileId, destinationPath) {
|
| 241 |
+
try {
|
| 242 |
+
// 获取文件信息
|
| 243 |
+
const fileMeta = await this.drive.files.get({
|
| 244 |
+
fileId: fileId,
|
| 245 |
+
fields: 'name,size',
|
| 246 |
+
});
|
| 247 |
+
|
| 248 |
+
// 确保目标目录存在
|
| 249 |
+
const targetDir = path.dirname(destinationPath);
|
| 250 |
+
if (!fs.existsSync(targetDir)) {
|
| 251 |
+
fs.mkdirSync(targetDir, { recursive: true });
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
// 下载文件
|
| 255 |
+
const response = await this.drive.files.get({
|
| 256 |
+
fileId: fileId,
|
| 257 |
+
alt: 'media',
|
| 258 |
+
}, { responseType: 'stream' });
|
| 259 |
+
|
| 260 |
+
const dest = fs.createWriteStream(destinationPath);
|
| 261 |
+
response.data.pipe(dest);
|
| 262 |
+
|
| 263 |
+
return new Promise((resolve, reject) => {
|
| 264 |
+
dest.on('finish', () => {
|
| 265 |
+
resolve({
|
| 266 |
+
success: true,
|
| 267 |
+
fileName: fileMeta.data.name,
|
| 268 |
+
fileSize: fileMeta.data.size,
|
| 269 |
+
destinationPath: destinationPath
|
| 270 |
+
});
|
| 271 |
+
});
|
| 272 |
+
dest.on('error', reject);
|
| 273 |
+
});
|
| 274 |
+
} catch (error) {
|
| 275 |
+
console.error('下载文件失败:', error.message);
|
| 276 |
+
throw error;
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
/**
|
| 281 |
+
* 创建文件夹
|
| 282 |
+
* @param {string} folderName - 文件夹名称
|
| 283 |
+
* @param {string} parentFolderId - 父文件夹ID (可选)
|
| 284 |
+
*/
|
| 285 |
+
async createFolder(folderName, parentFolderId = null) {
|
| 286 |
+
try {
|
| 287 |
+
const fileMetadata = {
|
| 288 |
+
name: folderName,
|
| 289 |
+
mimeType: 'application/vnd.google-apps.folder',
|
| 290 |
+
};
|
| 291 |
+
|
| 292 |
+
// 使用默认文件夹或指定的文件夹
|
| 293 |
+
const targetParentId = parentFolderId || this.defaultFolderId;
|
| 294 |
+
if (targetParentId) {
|
| 295 |
+
fileMetadata.parents = [targetParentId];
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
const response = await this.drive.files.create({
|
| 299 |
+
resource: fileMetadata,
|
| 300 |
+
fields: 'id,name,webViewLink',
|
| 301 |
+
});
|
| 302 |
+
|
| 303 |
+
return response.data;
|
| 304 |
+
} catch (error) {
|
| 305 |
+
console.error('创建文件夹失败:', error.message);
|
| 306 |
+
throw error;
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
/**
|
| 311 |
+
* 删除文件或文件夹
|
| 312 |
+
* @param {string} fileId - 文件或文件夹ID
|
| 313 |
+
*/
|
| 314 |
+
async deleteFile(fileId) {
|
| 315 |
+
try {
|
| 316 |
+
await this.drive.files.delete({
|
| 317 |
+
fileId: fileId,
|
| 318 |
+
});
|
| 319 |
+
return { success: true, fileId: fileId };
|
| 320 |
+
} catch (error) {
|
| 321 |
+
console.error('删除文件失败:', error.message);
|
| 322 |
+
throw error;
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
/**
|
| 327 |
+
* 搜索文件
|
| 328 |
+
* @param {string} searchQuery - 搜索条件
|
| 329 |
+
*/
|
| 330 |
+
async searchFiles(searchQuery) {
|
| 331 |
+
try {
|
| 332 |
+
const response = await this.drive.files.list({
|
| 333 |
+
q: searchQuery,
|
| 334 |
+
fields: 'nextPageToken, files(id, name, mimeType, size, createdTime, modifiedTime)',
|
| 335 |
+
});
|
| 336 |
+
|
| 337 |
+
return response.data;
|
| 338 |
+
} catch (error) {
|
| 339 |
+
console.error('搜索文件失败:', error.message);
|
| 340 |
+
throw error;
|
| 341 |
+
}
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
/**
|
| 345 |
+
* 获取文件信息
|
| 346 |
+
* @param {string} fileId - 文件ID
|
| 347 |
+
*/
|
| 348 |
+
async getFileInfo(fileId) {
|
| 349 |
+
try {
|
| 350 |
+
const response = await this.drive.files.get({
|
| 351 |
+
fileId: fileId,
|
| 352 |
+
fields: 'id,name,mimeType,size,createdTime,modifiedTime,parents',
|
| 353 |
+
});
|
| 354 |
+
|
| 355 |
+
return response.data;
|
| 356 |
+
} catch (error) {
|
| 357 |
+
console.error('获取文件信息失败:', error.message);
|
| 358 |
+
throw error;
|
| 359 |
+
}
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
/**
|
| 363 |
+
* 测试连接
|
| 364 |
+
*/
|
| 365 |
+
async testConnection() {
|
| 366 |
+
try {
|
| 367 |
+
const response = await this.drive.about.get({
|
| 368 |
+
fields: 'user, storageQuota'
|
| 369 |
+
});
|
| 370 |
+
return response.data;
|
| 371 |
+
} catch (error) {
|
| 372 |
+
console.error('连接测试失败:', error.message);
|
| 373 |
+
throw error;
|
| 374 |
+
}
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
module.exports = GoogleDriveOAuth2Service;
|
| 379 |
+
|
| 380 |
+
// 如果直接运行此脚本
|
| 381 |
+
if (require.main === module) {
|
| 382 |
+
const args = process.argv.slice(2);
|
| 383 |
+
|
| 384 |
+
if (args.length === 0) {
|
| 385 |
+
console.log('使用方法:');
|
| 386 |
+
console.log(' node gdrive-oauth.js auth - 进行OAuth2认证');
|
| 387 |
+
console.log(' node gdrive-oauth.js refresh - 刷新令牌');
|
| 388 |
+
console.log(' node gdrive-oauth.js test - 测试连接');
|
| 389 |
+
return;
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
const command = args[0];
|
| 393 |
+
|
| 394 |
+
switch (command) {
|
| 395 |
+
case 'auth':
|
| 396 |
+
GoogleDriveOAuth2Service.getNewToken();
|
| 397 |
+
break;
|
| 398 |
+
case 'refresh':
|
| 399 |
+
GoogleDriveOAuth2Service.refreshToken();
|
| 400 |
+
break;
|
| 401 |
+
case 'test':
|
| 402 |
+
(async () => {
|
| 403 |
+
try {
|
| 404 |
+
const service = new GoogleDriveOAuth2Service();
|
| 405 |
+
await service.initialize();
|
| 406 |
+
const info = await service.testConnection();
|
| 407 |
+
console.log('连接成功!');
|
| 408 |
+
console.log('用户信息:', info.user);
|
| 409 |
+
console.log('存储配额:', info.storageQuota);
|
| 410 |
+
} catch (error) {
|
| 411 |
+
console.error('测试失败:', error.message);
|
| 412 |
+
}
|
| 413 |
+
})();
|
| 414 |
+
break;
|
| 415 |
+
default:
|
| 416 |
+
console.error(`未知命令: ${command}`);
|
| 417 |
+
console.log('可用命令: auth, refresh, test');
|
| 418 |
+
}
|
| 419 |
+
}
|
service/gdrive/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "gdrive-oauth2-client",
|
| 3 |
+
"version": "2.0.0",
|
| 4 |
+
"description": "Google Drive OAuth2.0 桌面客户端,支持完整的文件管理功能",
|
| 5 |
+
"main": "gdrive-service.js",
|
| 6 |
+
"bin": {
|
| 7 |
+
"gdrive": "./gdrive-cli.js"
|
| 8 |
+
},
|
| 9 |
+
"scripts": {
|
| 10 |
+
"start": "node gdrive-cli.js",
|
| 11 |
+
"auth": "node gdrive-cli.js auth",
|
| 12 |
+
"refresh": "node gdrive-cli.js refresh",
|
| 13 |
+
"test": "node gdrive-cli.js test",
|
| 14 |
+
"list": "node gdrive-cli.js list",
|
| 15 |
+
"help": "node gdrive-cli.js help"
|
| 16 |
+
},
|
| 17 |
+
"dependencies": {
|
| 18 |
+
"googleapis": "^128.0.0"
|
| 19 |
+
},
|
| 20 |
+
"keywords": [
|
| 21 |
+
"google-drive",
|
| 22 |
+
"oauth2",
|
| 23 |
+
"desktop-client",
|
| 24 |
+
"file-management",
|
| 25 |
+
"upload",
|
| 26 |
+
"download",
|
| 27 |
+
"api"
|
| 28 |
+
],
|
| 29 |
+
"author": "",
|
| 30 |
+
"license": "MIT",
|
| 31 |
+
"engines": {
|
| 32 |
+
"node": ">=14.0.0"
|
| 33 |
+
}
|
| 34 |
+
}
|
service/gdrive/token.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"access_token":"ya29.a0AUMWg_IvqTCIDaHD0TMLzWiW7gAa_wYf54vy9nORhlt7HPcyiLc5cWZskA8xVuE3gQTarhZpgFdFoihm48kYOwuZV_XUSgp5HqY5ISRnywhGMLDiOKAsYHOmU-sB0Z-mlkWXpU4MaqgR17o8cFqAhaT8bDT5x3FmT7Q7xDIo4k9aIub4OUuDCvp9Zyvj8VqC0Bar67saCgYKAX4SARESFQHGX2MijXx1s6MfwNHC_LQdkREqmw0206","refresh_token":"1//05BwoYmQl_5QHCgYIARAAGAUSNwF-L9IrmMQGNo9ep1SQs6vgkIMlncy_8v795g0Die4_Hq6ui92608Gpa33AOey4tMIGrM51RFc","scope":"https://www.googleapis.com/auth/drive","token_type":"Bearer","expiry_date":1769651589942}
|
service/nodejs-service.sh
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
echo "将要安装 nodejs 20"
|
| 4 |
+
echo ""
|
| 5 |
+
# 安装 Node.js 和必要依赖
|
| 6 |
+
apt-get update
|
| 7 |
+
apt-get install -y curl
|
| 8 |
+
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
| 9 |
+
apt-get install -y nodejs
|
| 10 |
+
apt-get clean
|
| 11 |
+
rm -rf /var/lib/apt/lists/*
|
service/opencode-service.sh
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# # 将 /root/.config 目录及子目录下所有的 .md 文件权限修改为:644
|
| 4 |
+
# find /root/.config -type f -name "*.md" -exec chmod 644 {} \;
|
| 5 |
+
|
| 6 |
+
# 全局安装 OpenCode AI
|
| 7 |
+
npm install -g opencode-ai
|
| 8 |
+
|
| 9 |
+
# 验证安装
|
| 10 |
+
which opencode
|
| 11 |
+
opencode --version
|
service/start-services.sh
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -e
|
| 3 |
+
|
| 4 |
+
/.system/service/nodejs-service.sh
|
| 5 |
+
/.system/service/cron-service.sh
|
| 6 |
+
/.system/service/opencode-service.sh
|
| 7 |
+
|
| 8 |
+
/.system/script/restore.sh || true
|
| 9 |
+
|
| 10 |
+
echo ""
|
| 11 |
+
echo ""
|
| 12 |
+
echo "安装 gdrive 依赖..."
|
| 13 |
+
cd /.system/service/gdrive && npm install
|
| 14 |
+
|
| 15 |
+
# echo ""
|
| 16 |
+
# echo ""
|
| 17 |
+
# echo "node /.system/service/gdrive/gdrive-cli.js auth"
|
| 18 |
+
# node /.system/service/gdrive/gdrive-cli.js auth
|
| 19 |
+
|
| 20 |
+
echo ""
|
| 21 |
+
echo ""
|
| 22 |
+
echo "node /.system/service/gdrive/gdrive-cli.js list"
|
| 23 |
+
node /.system/service/gdrive/gdrive-cli.js list
|
| 24 |
+
|
| 25 |
+
# echo ""
|
| 26 |
+
# echo ""
|
| 27 |
+
# echo "node /.system/service/gdrive/quick-auth-setup.js"
|
| 28 |
+
# node /.system/service/gdrive/quick-auth-setup.js
|
| 29 |
+
|
| 30 |
+
# echo ""
|
| 31 |
+
# echo ""
|
| 32 |
+
# echo "node /.system/service/gdrive/list-files.js"
|
| 33 |
+
# node /.system/service/gdrive/list-files.js
|
| 34 |
+
|
| 35 |
+
# echo ""
|
| 36 |
+
# echo ""
|
| 37 |
+
# echo "node /.system/service/gdrive/get-hf-token.js"
|
| 38 |
+
# node /.system/service/gdrive/get-hf-token.js
|
| 39 |
+
|
| 40 |
+
# echo ""
|
| 41 |
+
# echo ""
|
| 42 |
+
# echo "node /.system/service/gdrive/gdrive-hf.js check"
|
| 43 |
+
# node /.system/service/gdrive/gdrive-hf.js check
|
| 44 |
+
|
| 45 |
+
# echo ""
|
| 46 |
+
# echo ""
|
| 47 |
+
# echo "/.system/script/upload-example.js"
|
| 48 |
+
# node /.system/script/upload-example.js
|
| 49 |
+
|
| 50 |
+
echo "🚀 启动 OpenCode AI Web Interface 服务..."
|
| 51 |
+
|
| 52 |
+
# 明确禁用服务器认证,确保公开访问
|
| 53 |
+
export OPENCODE_SERVER_PASSWORD=""
|
| 54 |
+
export OPENCODE_SERVER_USERNAME=""
|
| 55 |
+
export OPENCODE_AUTH_REQUIRED=false
|
| 56 |
+
|
| 57 |
+
echo "🔓 认证已禁用:OPENCODE_AUTH_REQUIRED=false"
|
| 58 |
+
echo "🌐 启动 OpenCode 服务..."
|
| 59 |
+
exec opencode serve --port 7860 --hostname 0.0.0.0 --cors "*"
|