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