云端部署

用 Azure Functions(Python v2 编程模型)做一个“到期日计算”接口

用 Azure Functions(Python v2 编程模型)做一个“到期日计算”接口:GET /api/due-date?start=2026-09-30&days=3 按工作日推算到期日,跳过周末和你维护的节假日表,适合合同、工单的 SLA 计算。

BINBHO · 图文教程

用 Azure Functions(Python v2 编程模型)做一个“到期日计算”接口:GET /api/due-date?start=2026-09-30&days=3 按工作日推算到期日,跳过周末和你维护的节假日表,适合合同、工单的 SLA 计算。

先看清成品

本机用 azure-functions 2.3.0 直接调用函数跑了 3 个测试,全部通过(跨国庆假期、跨周末、非法输入)。没有安装 Azure Functions Core Tools 本地启动宿主,也没有部署到 Azure,HTTP 路由和函数密钥的实际行为以你的运行为准。

注意:Azure 按用量计费,价格以官方价格页和账单为准,本文不列金额。中国大陆的 Azure 由世纪互联运营(azure.cn),账号、门户和终结点(如 *.chinacloudapi.cn)与全球版不同,本文按全球版编写。不要共享账号或把密钥提交到仓库。

节假日提醒:代码里的节假日只是示例,不代表真实放假安排。请以国务院办公厅每年发布的通知为准自行维护,包括调休上班日。

做完之后你会得到什么?

成果 说明
HTTP 函数 /api/due-date,需要函数密钥
工作日规则 周末 + 节假日表 + 调休上班日
测试 直接调用函数,不需要启动宿主

需要准备哪些环境?

项目 要求 说明
Azure 订阅 能创建函数应用 免费额度以 Azure 免费账户页为准
Azure CLI + Functions Core Tools v4 本地运行和发布 按官方文档安装
Python 函数应用支持的版本 以 Azure Functions Python 开发指南列出的版本为准

文件结构

22-azure-A-src/
├── function_app.py
├── host.json
├── local.settings.json      # 只用于本地,不要提交真实连接字符串
├── requirements.txt
└── tests/test_due.py

第 1 步:函数代码

function_app.py:

import json
from datetime import date, timedelta
import azure.functions as func

app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)

# 示例节假日:请按当年国务院办公厅发布的放假安排自行维护
HOLIDAYS = {"2026-10-01", "2026-10-02", "2026-10-05", "2026-10-06", "2026-10-07"}
EXTRA_WORKDAYS = set()  # 调休上班的周末,按官方通知填写

def is_workday(d: date) -> bool:
    s = d.isoformat()
    if s in EXTRA_WORKDAYS: return True
    if s in HOLIDAYS: return False
    return d.weekday() < 5

def add_workdays(start: date, n: int) -> date:
    d = start
    while n > 0:
        d += timedelta(days=1)
        if is_workday(d): n -= 1
    return d

def reply(code, body):
    return func.HttpResponse(json.dumps(body, ensure_ascii=False), status_code=code,
                             mimetype="application/json", charset="utf-8")

@app.route(route="due-date", methods=["GET"])
def due_date(req: func.HttpRequest) -> func.HttpResponse:
    try:
        start = date.fromisoformat(req.params.get("start", ""))
        n = int(req.params.get("days", ""))
    except ValueError:
        return reply(400, {"error": "需要 start=YYYY-MM-DD 和 days=整数"})
    if not 1 <= n <= 365:
        return reply(400, {"error": "days 应为 1-365"})
    return reply(200, {"start": start.isoformat(), "workdays": n, "due": add_workdays(start, n).isoformat()})

AuthLevel.FUNCTION 表示调用时要带函数密钥(?code=... 或 x-functions-key 头),防止接口被随便调用。

host.json:

{
  "version": "2.0",
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

local.settings.json:

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true"
  }
}

第 2 步:测试

tests/test_due.py:

import json, os, sys
import azure.functions as func
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import function_app as m

def call(**params):
    req = func.HttpRequest(method="GET", url="/api/due-date", params=params, body=b"")
    r = m.due_date.build().get_user_function()(req)
    return r.status_code, json.loads(r.get_body())

def test_skips_holiday():
    # 2026-09-30 周三,+3 个工作日:10/1-10/7 中 1、2、5、6、7 放假,3、4 为周末
    assert call(start="2026-09-30", days="3")[1]["due"] == "2026-10-12"

def test_weekend():
    assert call(start="2026-10-16", days="1")[1]["due"] == "2026-10-19"

def test_bad_input():
    assert call(start="2026/10/16", days="1")[0] == 400
    assert call(start="2026-10-16", days="999")[0] == 400
pip install -r requirements.txt pytest
python -m pytest -q tests

检查结果:3 passed。

m.due_date.build().get_user_function() 取出装饰器包装前的原函数,这样不用启动宿主也能测试。

第 3 步:本地运行(本文未执行)

func start
curl "http://localhost:7071/api/due-date?start=2026-09-30&days=3"

本地运行时不校验函数密钥。预期结果:

{"start": "2026-09-30", "workdays": 3, "due": "2026-10-12"}

第 4 步:部署(本文未执行)

az login
az group create -n rg-duedate -l eastasia
az storage account create -n 全局唯一存储名 -g rg-duedate -l eastasia --sku Standard_LRS
az functionapp create -n 全局唯一应用名 -g rg-duedate --storage-account 全局唯一存储名 \
  --flexconsumption-location eastasia --runtime python --runtime-version 3.12
func azure functionapp publish 全局唯一应用名

具体参数(托管计划、可用区域、Python 版本)以官方“用命令行创建 Python 函数”文档为准,不同区域支持情况不同。

调用时取函数密钥:

az functionapp function keys list -g rg-duedate -n 全局唯一应用名 --function-name due_date
curl "https://全局唯一应用名.azurewebsites.net/api/due-date?start=2026-10-16&days=1&code=函数密钥"

调试示例

传 start=2026/10/16,返回 400:

{"error": "需要 start=YYYY-MM-DD 和 days=整数"}

date.fromisoformat 只接受 ISO 格式;测试 test_bad_input 覆盖了这个情况。

出错了怎么办?

现象 原因 解决
线上返回 401 没带函数密钥 加 code= 参数或 x-functions-key 头
部署后找不到函数 依赖没装上或入口不是 function_app.py 检查 requirements.txt 和文件名
func start 报存储连接错误 UseDevelopmentStorage=true 需要本地 Azurite 启动 Azurite
到期日少算一天 节假日表漏了或调休没填 按官方通知维护两个集合
区域不支持所选计划 托管计划区域限制 换区域或计划

模板规则

  • 业务规则(节假日)与代码分开维护,每年更新。
  • HTTP 函数默认 FUNCTION 级别鉴权。
  • 函数逻辑写成纯函数,直接单元测试。

项目资源

用途 地址
Azure Functions Python 开发指南 https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python
用命令行创建 Python 函数 https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-python
Core Tools 本地运行 https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local
函数访问密钥 https://learn.microsoft.com/en-us/azure/azure-functions/function-keys-how-to
Functions 价格 https://azure.microsoft.com/en-us/pricing/details/functions/
Azure 中国(世纪互联) https://www.azure.cn/

到期日计算看起来简单,难点在节假日和调休。把规则放进一个函数、用测试锁住边界情况,前端、工单系统、表格都可以调用同一个接口,结果就一致了。