目标与实现思路:
判定与数据来源说明:
t_patient_reminder 表中的两项开关:
is_notification_enabled(消息总开关),is_subscription_available(一次性订阅开关)。
仅当两者均为 1(开启)时,才继续处理该用户的提醒逻辑。*_times 字段(JSON 格式);t_patient_medication 表中的 times 字段(JSON 列表)。为避免遗漏原始表结构定义,下面以代码块保留原始 CREATE TABLE 内容(未改动):
CREATE TABLE t_patient_reminder (
id bigint(20) NOT NULL COMMENT '主键ID',
patient_user_id bigint(20) NOT NULL COMMENT '患者用户ID',
is_notification_enabled tinyint(1) DEFAULT '1' COMMENT '是否启用消息通知总开关 (0-禁用, 1-启用)',
is_subscription_available tinyint(1) DEFAULT '1' COMMENT '一次性订阅开关 (0-需要重新授权, 1-已授权可发送)',
is_blood_pressure_enabled tinyint(1) DEFAULT '1' COMMENT '测量血压提醒开关 (0-禁用, 1-启用)',
blood_pressure_times text COLLATE utf8mb4_unicode_ci COMMENT '测量血压的时间点(JSON格式存储)',
is_blood_sugar_enabled tinyint(1) DEFAULT '0' COMMENT '测量血糖提醒开关 (0-禁用, 1-启用)',
blood_sugar_times text COLLATE utf8mb4_unicode_ci COMMENT '测量血糖的时间点(JSON格式存储)',
is_heart_rate_enabled tinyint(1) DEFAULT '1' COMMENT '测量心率提醒开关 (0-禁用, 1-启用)',
heart_rate_times text COLLATE utf8mb4_unicode_ci COMMENT '测量心率的时间点(JSON格式存储)',
is_medication_enabled tinyint(1) DEFAULT '1' COMMENT '用药提醒开关 (0-禁用, 1-启用)',
version int(11) DEFAULT '0' COMMENT '版本号(乐观锁)',
create_user bigint(20) DEFAULT NULL COMMENT '创建者ID',
create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_user bigint(20) DEFAULT NULL COMMENT '更新者ID',
update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
remark varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (id),
UNIQUE KEY uk_patient_user_id (patient_user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='患者提醒设置表';
CREATE TABLE t_patient_medication (
id bigint(20) NOT NULL COMMENT '主键ID',
patient_user_id bigint(20) NOT NULL COMMENT '患者用户ID',
medicine_name varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '药品名称',
dosage varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '剂量规格',
frequency varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '服用频率',
times text COLLATE utf8mb4_unicode_ci COMMENT '服用时间点列表(JSON格式存储)',
note text COLLATE utf8mb4_unicode_ci COMMENT '备注信息',
version int(11) DEFAULT '0' COMMENT '版本号(乐观锁)',
create_user bigint(20) DEFAULT NULL COMMENT '创建者ID',
create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_user bigint(20) DEFAULT NULL COMMENT '更新者ID',
update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
remark varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (id),
KEY idx_patient_user_id (patient_user_id),
KEY idx_medicine_name (medicine_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='患者用药记录表';
以上内容为整理后的实现思路与原始表结构(原文信息全部保留)。后续可按该判定逻辑实现定时任务查询与消息触发。
相关资料:
""" 本文件实现了独立的微信订阅消息发送全流程。 包含从配置读取、access_token 获取到消息发送的完整逻辑。 """
import json import logging import os
概述:
设计要点:
t_patient_reminder 表中的两项开关:消息总开关(is_notification_enabled)与一次性订阅开关(is_subscription_available)。两者均为开启时,才继续处理该用户。*_times 字段(JSON),用药提醒时间点需要结合 t_patient_medication 表中的 times 字段来计算。处理流程(高层):
t_patient_reminder,筛选出 is_notification_enabled=1 且 is_subscription_available=1 的记录。调度与性能建议:
数据库字段与含义(摘录说明)
t_patient_reminder:患者提醒设置表,关键字段包含:
is_notification_enabled:总开关(0/1)is_subscription_available:一次性订阅开关(0/1)is_blood_pressure_enabled、blood_pressure_times:血压开关与时间点(JSON)is_blood_sugar_enabled、blood_sugar_times:血糖开关与时间点(JSON)is_heart_rate_enabled、heart_rate_times:心率开关与时间点(JSON)is_medication_enabled:用药提醒总开关(用药时间由 t_patient_medication 补充)t_patient_medication:患者用药记录表,关键字段包含:
medicine_name、dosage、frequency、times(服用时间点 JSON)微信订阅消息发送模块(架构说明)
access_token、执行消息发送、处理接口错误码(如 token 失效重试),并把发送结果上报给定时任务模块。错误处理与监控
errcode 表示 token 无效)触发重试逻辑。附:原始草稿(完整,未改动)
使用下方代码块可以查看并比对原始内容(未修改)。为完整保留原文,下面使用更长的代码围栏以原样嵌入原文内容:
```markdown
# 患者提醒定时任务设计文档
## 初步想法
我们这个项目还有一个核心功能没有实现,就是为微信小程序开通了提醒功能的用户发送推送消息,我打算用springtask每分钟执行一次定时任务,那么定时任务的话就是我们编写一个工具类,从数据库里获取所有用户的CREATE TABLE t_patient_reminder ( id bigint(20) NOT NULL COMMENT '主键ID', patient_user_id bigint(20) NOT NULL COMMENT '患者用户ID', is_notification_enabled tinyint(1) DEFAULT '1' COMMENT '是否启用消息通知总开关 (0-禁用, 1-启用)', is_subscription_available tinyint(1) DEFAULT '1' COMMENT '一次性订阅开关 (0-需要重新授权, 1-已授权可发送)', is_blood_pressure_enabled tinyint(1) DEFAULT '1' COMMENT '测量血压提醒开关 (0-禁用, 1-启用)', blood_pressure_times text COLLATE utf8mb4_unicode_ci COMMENT '测量血压的时间点(JSON格式存储)', is_blood_sugar_enabled tinyint(1) DEFAULT '0' COMMENT '测量血糖提醒开关 (0-禁用, 1-启用)', blood_sugar_times text COLLATE utf8mb4_unicode_ci COMMENT '测量血糖的时间点(JSON格式存储)', is_heart_rate_enabled tinyint(1) DEFAULT '1' COMMENT '测量心率提醒开关 (0-禁用, 1-启用)', heart_rate_times text COLLATE utf8mb4_unicode_ci COMMENT '测量心率的时间点(JSON格式存储)', is_medication_enabled tinyint(1) DEFAULT '1' COMMENT '用药提醒开关 (0-禁用, 1-启用)', version int(11) DEFAULT '0' COMMENT '版本号(乐观锁)', create_user bigint(20) DEFAULT NULL COMMENT '创建者ID', create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_user bigint(20) DEFAULT NULL COMMENT '更新者ID', update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', remark varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', PRIMARY KEY (id), UNIQUE KEY uk_patient_user_id (patient_user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='患者提醒设置表';,首先检索用户'是否启用消息通知总开关 和 一次性订阅开关是否都处于开启,如果都是开启的则分析用户需要推送的时间点,其中用药提醒的时间点需要联动获取CREATE TABLE t_patient_medication ( id bigint(20) NOT NULL COMMENT '主键ID', patient_user_id bigint(20) NOT NULL COMMENT '患者用户ID', medicine_name varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '药品名称', dosage varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '剂量规格', frequency varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '服用频率', times text COLLATE utf8mb4_unicode_ci COMMENT '服用时间点列表(JSON格式存储)', note text COLLATE utf8mb4_unicode_ci COMMENT '备注信息', version int(11) DEFAULT '0' COMMENT '版本号(乐观锁)', create_user bigint(20) DEFAULT NULL COMMENT '创建者ID', create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_user bigint(20) DEFAULT NULL COMMENT '更新者ID', update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', remark varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注', PRIMARY KEY (id), KEY idx_patient_user_id (patient_user_id), KEY idx_medicine_name (medicine_name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='患者用药记录表';的数据,
相关资料:
"""
本文件实现了独立的微信订阅消息发送全流程。
包含从配置读取、access_token 获取到消息发送的完整逻辑。
"""
import json
import logging
import os
import time
import requests
import certifi
class AccessTokenManager:
"""
管理微信 access_token 的获取与刷新
"""
def __init__(self, appid: str, secret: str):
self.appid = appid
self.secret = secret
def get_access_token(self) -> str:
"""获取有效的 access_token"""
try:
url = f'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={self.appid}&secret={self.secret}'
response = requests.get(url, timeout=10, verify=certifi.where())
response.raise_for_status()
data = response.json()
if 'access_token' in data:
return data['access_token']
else:
raise Exception(f'获取 access_token 失败: {data.get("errmsg", "Unknown error")}')
except Exception as e:
raise Exception(f'请求 access_token 异常: {str(e)}')
class SubscribeMessageSender:
"""
发送微信订阅消息的客户端
"""
def __init__(self, appid: str, secret: str):
self.access_token_manager = AccessTokenManager(appid, secret)
self.send_url = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send'
def send(self, payload: dict) -> dict:
"""
发送订阅消息
Args:
payload: 消息负载,必须包含 touser, template_id 等必要字段
Returns:
微信接口返回的响应数据
"""
if not isinstance(payload, dict):
raise ValueError('payload 必须为字典类型')
# 第一次尝试
access_token = self.access_token_manager.get_access_token()
result = self._do_send(payload, access_token)
# 如果 token 失效,尝试刷新后重试
if result.get('errcode') in (40001, 40014, 42001):
logging.info('检测到 access_token 可能失效,尝试刷新后重试')
access_token = self.access_token_manager.get_access_token()
result = self._do_send(payload, access_token)
return result
def _do_send(self, payload: dict, access_token: str) -> dict:
"""执行实际的发送请求"""
url = f'{self.send_url}?access_token={access_token}'
logging.info(f'发送订阅消息: {url}')
logging.debug(f'请求数据: {json.dumps(payload, ensure_ascii=False)}')
try:
response = requests.post(
url,
json=payload,
timeout=10,
verify=certifi.where()
)
result = response.json()
logging.debug(f'响应数据: {json.dumps(result, ensure_ascii=False)}')
return result
except Exception as e:
logging.error(f'发送请求异常: {str(e)}')
return {'errcode': -1, 'errmsg': str(e)}
# 使用示例
if __name__ == '__main__':
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# 创建发送器实例(直接传入凭证)
appid = "wx334b14b3d0bb1547"
secret = "0b42a3e4becb7817d08e44e91e2824dd"
sender = SubscribeMessageSender(appid, secret)
# 构造消息负载
sample_payload = {
"touser": "oMrLJ4upWlkcM8ngNnj849sF_sZg", # 用户实际openid
"template_id": "ACS7cwcbx0F0Y_YaB4GZr7rWP7BO2-7wQOtYsnUjmFI", # 模板ID 7536
"page": "pages/index/index", # 建议使用完整页面路径
"miniprogram_state": "developer",
"lang": "zh_CN",
"data": {
"phrase2": {"value": "健康打卡"}, # 打卡类型
"date3": {"value": "2025-11-21"}, # 日期
"thing4": {"value": "请按时完成每日健康上报"} # 温馨提示
}
}
# 发送消息
try:
response = sender.send(sample_payload)
print('发送结果:', json.dumps(response, ensure_ascii=False, indent=2))
except Exception as e:
print('发送失败:', str(e))
执行结果:PS D:\慢病APP\other\simple-python-server> python send_full_subscribe.py
2025-11-21 17:33:04,374 - INFO - 发送订阅消息: https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=98_0wfkclFyskJQJUWbUYVgvrXbPziTymJ4p4uI2tM-ESMJrv0f8c2smMpZAonBn9k98hfdlVxvG-Ey3flQHwD4vyGOdQH1eKVSka-EJftPTJsB7aaji4iFAmGyV0MUBWcADAYFA
发送结果: {
"errcode": 0,
"errmsg": "ok",
"msgid": 4263515288072994829
}
```