From aac5ac4a9b2386470654ad07a25ff86374dbd5ed Mon Sep 17 00:00:00 2001 From: lansonsam <2952678374@qq.com> Date: Sun, 15 Mar 2026 21:06:58 +0800 Subject: [PATCH] Add scheduled GitHub Action task --- .github/workflows/daily-task.yml | 34 +++++++++++ README.md | 39 +++++++++++++ scripts/daily-task.js | 97 ++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 .github/workflows/daily-task.yml create mode 100644 scripts/daily-task.js diff --git a/.github/workflows/daily-task.yml b/.github/workflows/daily-task.yml new file mode 100644 index 0000000..2138a44 --- /dev/null +++ b/.github/workflows/daily-task.yml @@ -0,0 +1,34 @@ +name: Daily Task + +on: + schedule: + - cron: '0 2 * * *' + workflow_dispatch: + +jobs: + run-daily-task: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Run scheduled task + env: + TARGET_URL: ${{ secrets.TARGET_URL }} + HTTP_METHOD: ${{ vars.HTTP_METHOD }} + REQUEST_BODY: ${{ secrets.REQUEST_BODY }} + AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }} + HEADERS_JSON: ${{ secrets.HEADERS_JSON }} + EXPECTED_STATUS: ${{ vars.EXPECTED_STATUS }} + RETRY_COUNT: ${{ vars.RETRY_COUNT }} + RETRY_DELAY_MS: ${{ vars.RETRY_DELAY_MS }} + run: node scripts/daily-task.js \ No newline at end of file diff --git a/README.md b/README.md index 54b2ac1..9eb3369 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,41 @@ # card-api +## GitHub Action: daily task + +This repository includes a scheduled GitHub Action at .github/workflows/daily-task.yml. + +### Trigger mode + +- Runs every day at 02:00 UTC +- Can also be started manually from the GitHub Actions page + +### Required configuration + +Configure these repository secrets before enabling the workflow: + +- TARGET_URL: target API endpoint to call + +Optional secrets: + +- AUTH_TOKEN: bearer token appended to the Authorization header +- REQUEST_BODY: raw JSON string sent as the request body +- HEADERS_JSON: JSON object string for extra request headers + +Optional repository variables: + +- HTTP_METHOD: defaults to GET +- EXPECTED_STATUS: defaults to 200 +- RETRY_COUNT: defaults to 3 +- RETRY_DELAY_MS: defaults to 5000 + +### Behavior + +The workflow runs scripts/daily-task.js, which: + +- calls the configured endpoint +- validates the returned status code +- retries on failure +- exits with a non-zero code when all retries fail + +If you want this action to do something more specific, such as syncing data, generating reports, or updating files in the repository, the script can be extended from the same entry point. + diff --git a/scripts/daily-task.js b/scripts/daily-task.js new file mode 100644 index 0000000..46a3561 --- /dev/null +++ b/scripts/daily-task.js @@ -0,0 +1,97 @@ +const DEFAULT_METHOD = 'GET'; +const DEFAULT_EXPECTED_STATUS = 200; +const DEFAULT_RETRY_COUNT = 3; +const DEFAULT_RETRY_DELAY_MS = 5000; + +function readNumber(value, fallback) { + if (!value) { + return fallback; + } + + const parsed = Number.parseInt(value, 10); + return Number.isNaN(parsed) ? fallback : parsed; +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function buildHeaders() { + const headers = { + 'content-type': 'application/json', + }; + + if (process.env.HEADERS_JSON) { + const customHeaders = JSON.parse(process.env.HEADERS_JSON); + Object.assign(headers, customHeaders); + } + + if (process.env.AUTH_TOKEN) { + headers.authorization = `Bearer ${process.env.AUTH_TOKEN}`; + } + + if (!process.env.REQUEST_BODY) { + delete headers['content-type']; + } + + return headers; +} + +async function runTask() { + const targetUrl = process.env.TARGET_URL; + if (!targetUrl) { + throw new Error('Missing TARGET_URL secret.'); + } + + const method = (process.env.HTTP_METHOD || DEFAULT_METHOD).toUpperCase(); + const expectedStatus = readNumber(process.env.EXPECTED_STATUS, DEFAULT_EXPECTED_STATUS); + const retryCount = readNumber(process.env.RETRY_COUNT, DEFAULT_RETRY_COUNT); + const retryDelayMs = readNumber(process.env.RETRY_DELAY_MS, DEFAULT_RETRY_DELAY_MS); + const headers = buildHeaders(); + + const requestOptions = { + method, + headers, + }; + + if (process.env.REQUEST_BODY) { + requestOptions.body = process.env.REQUEST_BODY; + } + + for (let attempt = 1; attempt <= retryCount; attempt += 1) { + console.log(`Attempt ${attempt}/${retryCount}: ${method} ${targetUrl}`); + + try { + const response = await fetch(targetUrl, requestOptions); + const responseText = await response.text(); + + console.log(`Response status: ${response.status}`); + if (responseText) { + console.log(`Response body: ${responseText.slice(0, 1000)}`); + } + + if (response.status !== expectedStatus) { + throw new Error(`Expected status ${expectedStatus}, received ${response.status}.`); + } + + console.log('Scheduled task completed successfully.'); + return; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`Attempt ${attempt} failed: ${message}`); + + if (attempt === retryCount) { + throw error; + } + + console.log(`Waiting ${retryDelayMs}ms before retry.`); + await sleep(retryDelayMs); + } + } +} + +runTask().catch((error) => { + console.error('Scheduled task failed.'); + console.error(error); + process.exit(1); +}); \ No newline at end of file