diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..6bd916f --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,83 @@ +name: Docker Publish + +permissions: + contents: read + packages: write + +on: + workflow_dispatch: + push: + branches: + - main + tags: + - "v*" + +env: + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.IMAGE_NAME }} + ghcr.io/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + + - name: Get version + id: get_version + run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + build-args: | + MAINTAINER=${{ github.repository_owner }} + BRANCH=${{ github.ref_name }} + BUILD_SHA=${{ github.sha }} + BUILD_TAG=${{ steps.get_version.outputs.VERSION }} + context: . + platforms: | + linux/amd64 + linux/arm64 + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Update repo description + uses: peter-evans/dockerhub-description@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + repository: ${{ env.IMAGE_NAME }} + short-description: ${{ github.event.repository.description }} + enable-url-completion: true \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4551d2d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# 使用官方 Python 镜像作为基础镜像 +FROM python:3.12.2-alpine + +# 设置工作目录 +WORKDIR /app + +# 将当前目录中的文件添加到工作目录中 +COPY . /app + +# 安装依赖 +RUN pip install --no-cache-dir -r requirements.txt \ + pip install --no-cache-dir flask + +# 时区 +ENV TZ="Asia/Shanghai" + +# 构建版本 +ARG BUILD_SHA +ARG BUILD_TAG +ENV BUILD_SHA=$BUILD_SHA +ENV BUILD_TAG=$BUILD_TAG + +ENV WHITELIST_NUM= + +# 端口 +EXPOSE 10000 + +# 运行应用程序 +CMD ["python", "./app/api_server.py"] \ No newline at end of file diff --git a/app/api_server.py b/app/api_server.py new file mode 100644 index 0000000..023e6d3 --- /dev/null +++ b/app/api_server.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# _*_ coding:utf-8 _*_ + +import os +import sys +import json +from datetime import datetime +from flask import Flask, request, jsonify + +# 导入父目录的依赖 +current_dir = os.path.dirname(os.path.abspath(__file__)) +parent_dir = os.path.dirname(current_dir) +sys.path.append(parent_dir) +from telecom_class import Telecom + +telecom = Telecom() + +app = Flask(__name__) +app.json.ensure_ascii = False +app.json.sort_keys = False + +# 登录信息存储文件 +LOGIN_INFO_FILE = os.environ.get("CONFIG_PATH", "./config/login_info.json") + + +def load_login_info(): + """加载本地登录信息""" + try: + with open(LOGIN_INFO_FILE, "r", encoding="utf-8") as f: + return json.load(f) + except FileNotFoundError: + return {} + + +def save_login_info(login_info): + """保存登录信息到本地""" + with open(LOGIN_INFO_FILE, "w", encoding="utf-8") as f: + json.dump(login_info, f, ensure_ascii=False, indent=2) + + +@app.route("/login", methods=["POST", "GET"]) +def login(): + """登录接口""" + if request.method == "POST": + data = request.get_json() or {} + else: + data = request.args + phonenum = data.get("phonenum") + password = data.get("password") + if not phonenum or not password: + return jsonify({"message": "手机号和密码不能为空"}), 400 + elif whitelist_num := os.environ.get("WHITELIST_NUM"): + if not phonenum in whitelist_num: + return jsonify({"message": "手机号不在白名单"}), 400 + + login_info = load_login_info() + data = telecom.do_login(phonenum, password) + if data.get("responseData").get("resultCode") == "0000": + login_info[phonenum] = data["responseData"]["data"]["loginSuccessResult"] + login_info[phonenum]["password"] = password + login_info[phonenum]["createTime"] = datetime.now().strftime( + "%Y-%m-%d %H:%M:%S" + ) + save_login_info(login_info) + return jsonify(data), 200 + else: + return jsonify({"message": "登录失败"}), 400 + + +def query_data(query_func): + """ + 查询数据,如果本地没有登录信息或密码不匹配,则尝试登录后再查询 + """ + if request.method == "POST": + data = request.get_json() or {} + else: + data = request.args + phonenum = data.get("phonenum") + password = data.get("password") + + login_info = load_login_info() + if phonenum in login_info and login_info[phonenum]["password"] == password: + telecom.set_login_info(login_info[phonenum]) + data = query_func() + if data: + return jsonify(data), 200 + # 重新登录 + login_data, status_code = login() + if status_code == 200: + telecom.set_login_info( + json.loads(login_data.data)["responseData"]["data"]["loginSuccessResult"] + ) + data = query_func() + if data: + return jsonify(data), 200 + else: + return jsonify({"message": "查询失败"}), 400 + else: + return jsonify({"message": "手机号或密码错误"}), 400 + + +@app.route("/qryImportantData", methods=["POST", "GET"]) +def qry_important_data(): + """查询重要数据接口""" + return query_data(telecom.qry_important_data) + + +@app.route("/userFluxPackage", methods=["POST", "GET"]) +def user_flux_package(): + """查询流量包接口""" + return query_data(telecom.user_flux_package) + + +if __name__ == "__main__": + app.run(debug=os.environ.get("DEBUG", False), host="0.0.0.0", port=10000)