配置管理
use-notify 提供了灵活的配置管理方式,支持环境变量、配置文件、代码配置等多种方式。本指南将详细介绍如何管理和组织通知配置。
配置方式
1. 代码直接配置
最简单的配置方式是直接在代码中设置:
python
from use_notify import useNotify, useNotifyChannel
notify = useNotify()
notify.add(useNotifyChannel.Bark({
"token": "your_bark_token",
"sound": "default"
}))
2. 环境变量配置
推荐使用环境变量管理敏感信息:
python
import os
from use_notify import useNotify, useNotifyChannel
notify = useNotify()
notify.add(useNotifyChannel.Bark({
"token": os.getenv("BARK_TOKEN"),
"server": os.getenv("BARK_SERVER", "https://api.day.app"),
"sound": os.getenv("BARK_SOUND", "default")
}))
环境变量示例:
bash
# .env 文件
BARK_TOKEN=your_bark_token
BARK_SERVER=https://api.day.app
BARK_SOUND=default
DING_TOKEN=your_ding_token
DING_SECRET=your_ding_secret
EMAIL_SMTP_SERVER=smtp.gmail.com
EMAIL_SMTP_PORT=587
EMAIL_USERNAME=[email protected]
EMAIL_PASSWORD=your_app_password
EMAIL_RECIPIENTS=[email protected],[email protected]
3. 配置文件管理
JSON 配置文件
python
import json
from use_notify import useNotify, useNotifyChannel
def load_notify_from_json(config_file):
"""从 JSON 配置文件加载通知配置"""
with open(config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
notify = useNotify()
for channel_config in config.get('channels', []):
channel_type = channel_config['type']
params = channel_config['params']
if channel_type == 'bark':
notify.add(useNotifyChannel.Bark(params))
elif channel_type == 'ding':
notify.add(useNotifyChannel.Ding(params))
elif channel_type == 'wechat':
notify.add(useNotifyChannel.WeChat(params))
elif channel_type == 'email':
notify.add(useNotifyChannel.Email(params))
elif channel_type == 'pushover':
notify.add(useNotifyChannel.Pushover(params))
elif channel_type == 'pushdeer':
notify.add(useNotifyChannel.Pushdeer(params))
elif channel_type == 'chanify':
notify.add(useNotifyChannel.Chanify(params))
return notify
# 使用配置文件
notify = load_notify_from_json('notify_config.json')
配置文件示例 (notify_config.json
):
json
{
"channels": [
{
"type": "bark",
"params": {
"token": "${BARK_TOKEN}",
"server": "https://api.day.app",
"sound": "default",
"group": "myapp"
}
},
{
"type": "ding",
"params": {
"token": "${DING_TOKEN}",
"secret": "${DING_SECRET}",
"at_all": false
}
},
{
"type": "email",
"params": {
"smtp_server": "smtp.gmail.com",
"smtp_port": 587,
"username": "${EMAIL_USERNAME}",
"password": "${EMAIL_PASSWORD}",
"to_emails": ["${EMAIL_RECIPIENT}"]
}
}
]
}
YAML 配置文件
python
import yaml
from use_notify import useNotify, useNotifyChannel
def load_notify_from_yaml(config_file):
"""从 YAML 配置文件加载通知配置"""
with open(config_file, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
notify = useNotify()
# 渠道映射
channel_map = {
'bark': useNotifyChannel.Bark,
'ding': useNotifyChannel.Ding,
'wechat': useNotifyChannel.WeChat,
'email': useNotifyChannel.Email,
'pushover': useNotifyChannel.Pushover,
'pushdeer': useNotifyChannel.Pushdeer,
'chanify': useNotifyChannel.Chanify
}
for channel_config in config.get('channels', []):
channel_type = channel_config['type']
params = channel_config['params']
if channel_type in channel_map:
notify.add(channel_map[channel_type](params))
return notify
# 使用 YAML 配置
notify = load_notify_from_yaml('notify_config.yaml')
YAML 配置文件示例 (notify_config.yaml
):
yaml
channels:
- type: bark
params:
token: ${BARK_TOKEN}
server: https://api.day.app
sound: default
group: myapp
- type: ding
params:
token: ${DING_TOKEN}
secret: ${DING_SECRET}
at_all: false
- type: email
params:
smtp_server: smtp.gmail.com
smtp_port: 587
username: ${EMAIL_USERNAME}
password: ${EMAIL_PASSWORD}
to_emails:
- ${EMAIL_RECIPIENT}
配置模板和变量替换
环境变量替换
python
import os
import re
import json
def replace_env_vars(config_str):
"""替换配置中的环境变量"""
def replace_var(match):
var_name = match.group(1)
default_value = match.group(2) if match.group(2) else ''
return os.getenv(var_name, default_value)
# 支持 ${VAR} 和 ${VAR:default} 格式
pattern = r'\$\{([^}:]+)(?::([^}]*))?\}'
return re.sub(pattern, replace_var, config_str)
def load_config_with_env_vars(config_file):
"""加载配置文件并替换环境变量"""
with open(config_file, 'r', encoding='utf-8') as f:
config_str = f.read()
# 替换环境变量
config_str = replace_env_vars(config_str)
# 解析 JSON
config = json.loads(config_str)
return config
# 使用示例
config = load_config_with_env_vars('notify_config.json')
配置模板
创建可重用的配置模板:
python
from use_notify import useNotify, useNotifyChannel
class NotifyConfigTemplate:
"""通知配置模板"""
@staticmethod
def development():
"""开发环境配置"""
notify = useNotify()
notify.add(useNotifyChannel.Bark({
"token": os.getenv("DEV_BARK_TOKEN"),
"sound": "silence" # 开发环境使用静音
}))
return notify
@staticmethod
def production():
"""生产环境配置"""
notify = useNotify()
notify.add(
useNotifyChannel.Bark({
"token": os.getenv("PROD_BARK_TOKEN"),
"sound": "default"
}),
useNotifyChannel.Ding({
"token": os.getenv("PROD_DING_TOKEN"),
"secret": os.getenv("PROD_DING_SECRET"),
"at_all": True
}),
useNotifyChannel.Email({
"smtp_server": "smtp.company.com",
"smtp_port": 587,
"username": os.getenv("PROD_EMAIL_USERNAME"),
"password": os.getenv("PROD_EMAIL_PASSWORD"),
"to_emails": os.getenv("PROD_EMAIL_RECIPIENTS", "").split(",")
})
)
return notify
@staticmethod
def testing():
"""测试环境配置"""
notify = useNotify()
# 测试环境可以使用控制台输出
from use_notify.channels import ConsoleChannel
notify.add(ConsoleChannel())
return notify
# 根据环境选择配置
env = os.getenv('ENVIRONMENT', 'development')
if env == 'production':
notify = NotifyConfigTemplate.production()
elif env == 'testing':
notify = NotifyConfigTemplate.testing()
else:
notify = NotifyConfigTemplate.development()
多环境配置管理
配置工厂模式
python
import os
from abc import ABC, abstractmethod
from use_notify import useNotify, useNotifyChannel
class NotifyConfigFactory(ABC):
"""通知配置工厂基类"""
@abstractmethod
def create_notify(self) -> useNotify:
pass
class DevelopmentConfig(NotifyConfigFactory):
"""开发环境配置"""
def create_notify(self) -> useNotify:
notify = useNotify()
# 开发环境只使用轻量级通知
if os.getenv("DEV_BARK_TOKEN"):
notify.add(useNotifyChannel.Bark({
"token": os.getenv("DEV_BARK_TOKEN"),
"sound": "silence"
}))
return notify
class ProductionConfig(NotifyConfigFactory):
"""生产环境配置"""
def create_notify(self) -> useNotify:
notify = useNotify()
# 生产环境使用多渠道确保可靠性
channels = []
# Bark 通知
if os.getenv("PROD_BARK_TOKEN"):
channels.append(useNotifyChannel.Bark({
"token": os.getenv("PROD_BARK_TOKEN"),
"sound": "default"
}))
# 钉钉通知
if os.getenv("PROD_DING_TOKEN"):
channels.append(useNotifyChannel.Ding({
"token": os.getenv("PROD_DING_TOKEN"),
"secret": os.getenv("PROD_DING_SECRET"),
"at_all": False
}))
# 邮件通知
if all([os.getenv("PROD_EMAIL_USERNAME"), os.getenv("PROD_EMAIL_PASSWORD")]):
channels.append(useNotifyChannel.Email({
"smtp_server": os.getenv("PROD_SMTP_SERVER", "smtp.gmail.com"),
"smtp_port": int(os.getenv("PROD_SMTP_PORT", "587")),
"username": os.getenv("PROD_EMAIL_USERNAME"),
"password": os.getenv("PROD_EMAIL_PASSWORD"),
"to_emails": os.getenv("PROD_EMAIL_RECIPIENTS", "").split(",")
}))
if channels:
notify.add(*channels)
return notify
class TestingConfig(NotifyConfigFactory):
"""测试环境配置"""
def create_notify(self) -> useNotify:
notify = useNotify()
# 测试环境使用模拟通知
class MockChannel:
def send(self, title, content, **kwargs):
print(f"[MOCK] {title}: {content}")
return True
async def send_async(self, title, content, **kwargs):
print(f"[MOCK ASYNC] {title}: {content}")
return True
notify.add(MockChannel())
return notify
def get_notify_config() -> useNotify:
"""根据环境获取通知配置"""
env = os.getenv('ENVIRONMENT', 'development').lower()
config_map = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig
}
config_class = config_map.get(env, DevelopmentConfig)
return config_class().create_notify()
# 使用配置工厂
notify = get_notify_config()
配置验证
python
from typing import Dict, List, Optional
from dataclasses import dataclass
@dataclass
class ChannelConfig:
"""通道配置数据类"""
type: str
params: Dict
enabled: bool = True
@dataclass
class NotifyConfig:
"""通知配置数据类"""
channels: List[ChannelConfig]
default_title: Optional[str] = None
timeout: int = 30
class ConfigValidator:
"""配置验证器"""
REQUIRED_PARAMS = {
'bark': ['token'],
'ding': ['token'],
'wechat': ['token'],
'email': ['smtp_server', 'smtp_port', 'username', 'password', 'to_emails'],
'pushover': ['token', 'user'],
'pushdeer': ['token'],
'chanify': ['token']
}
@classmethod
def validate_channel_config(cls, channel_config: ChannelConfig) -> List[str]:
"""验证单个通道配置"""
errors = []
# 检查通道类型
if channel_config.type not in cls.REQUIRED_PARAMS:
errors.append(f"不支持的通道类型: {channel_config.type}")
return errors
# 检查必需参数
required_params = cls.REQUIRED_PARAMS[channel_config.type]
for param in required_params:
if param not in channel_config.params or not channel_config.params[param]:
errors.append(f"{channel_config.type} 通道缺少必需参数: {param}")
# 特定验证
if channel_config.type == 'email':
# 验证邮箱格式
import re
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
username = channel_config.params.get('username')
if username and not re.match(email_pattern, username):
errors.append(f"邮箱用户名格式不正确: {username}")
to_emails = channel_config.params.get('to_emails', [])
for email in to_emails:
if not re.match(email_pattern, email):
errors.append(f"收件人邮箱格式不正确: {email}")
return errors
@classmethod
def validate_config(cls, config: NotifyConfig) -> List[str]:
"""验证完整配置"""
errors = []
if not config.channels:
errors.append("至少需要配置一个通知渠道")
return errors
enabled_channels = [ch for ch in config.channels if ch.enabled]
if not enabled_channels:
errors.append("至少需要启用一个通知渠道")
for channel_config in config.channels:
channel_errors = cls.validate_channel_config(channel_config)
errors.extend(channel_errors)
return errors
def load_and_validate_config(config_file: str) -> useNotify:
"""加载并验证配置文件"""
import json
# 加载配置
with open(config_file, 'r', encoding='utf-8') as f:
raw_config = json.load(f)
# 转换为数据类
channels = [
ChannelConfig(
type=ch['type'],
params=ch['params'],
enabled=ch.get('enabled', True)
)
for ch in raw_config.get('channels', [])
]
config = NotifyConfig(
channels=channels,
default_title=raw_config.get('default_title'),
timeout=raw_config.get('timeout', 30)
)
# 验证配置
errors = ConfigValidator.validate_config(config)
if errors:
raise ValueError(f"配置验证失败:\n" + "\n".join(f"- {error}" for error in errors))
# 创建通知实例
notify = useNotify()
channel_map = {
'bark': useNotifyChannel.Bark,
'ding': useNotifyChannel.Ding,
'wechat': useNotifyChannel.WeChat,
'email': useNotifyChannel.Email,
'pushover': useNotifyChannel.Pushover,
'pushdeer': useNotifyChannel.Pushdeer,
'chanify': useNotifyChannel.Chanify
}
for channel_config in config.channels:
if channel_config.enabled and channel_config.type in channel_map:
notify.add(channel_map[channel_config.type](channel_config.params))
return notify
# 使用验证配置
try:
notify = load_and_validate_config('notify_config.json')
print("配置加载成功")
except ValueError as e:
print(f"配置错误: {e}")
配置最佳实践
1. 安全性
python
# ✅ 推荐:使用环境变量存储敏感信息
notify.add(useNotifyChannel.Email({
"username": os.getenv("EMAIL_USERNAME"),
"password": os.getenv("EMAIL_PASSWORD")
}))
# ❌ 不推荐:在代码中硬编码敏感信息
notify.add(useNotifyChannel.Email({
"username": "[email protected]",
"password": "hardcoded_password" # 安全风险
}))
2. 可维护性
python
# ✅ 推荐:使用配置文件集中管理
notify = load_notify_from_json('config/notify.json')
# ✅ 推荐:使用工厂模式支持多环境
notify = get_notify_config()
# ❌ 不推荐:在多处重复配置
# 在多个文件中重复相同的配置代码
3. 灵活性
python
# ✅ 推荐:支持动态配置
class DynamicNotifyConfig:
def __init__(self):
self.notify = useNotify()
self.load_config()
def load_config(self):
"""动态加载配置"""
config_file = os.getenv('NOTIFY_CONFIG_FILE', 'notify_config.json')
if os.path.exists(config_file):
# 重新加载配置
self.notify = load_notify_from_json(config_file)
def reload_config(self):
"""重新加载配置"""
self.load_config()
# 支持配置热重载
dynamic_notify = DynamicNotifyConfig()
4. 测试友好
python
# ✅ 推荐:提供测试配置
class TestNotifyConfig:
@staticmethod
def create_test_notify():
"""创建测试用通知实例"""
notify = useNotify()
# 使用模拟通道进行测试
class TestChannel:
def __init__(self):
self.sent_messages = []
def send(self, title, content, **kwargs):
self.sent_messages.append({
'title': title,
'content': content,
'kwargs': kwargs
})
return True
async def send_async(self, title, content, **kwargs):
return self.send(title, content, **kwargs)
test_channel = TestChannel()
notify.add(test_channel)
# 添加验证方法
notify._test_channel = test_channel
return notify
# 在测试中使用
def test_notification():
notify = TestNotifyConfig.create_test_notify()
notify.publish(title="测试", content="测试消息")
# 验证消息是否发送
assert len(notify._test_channel.sent_messages) == 1
assert notify._test_channel.sent_messages[0]['title'] == "测试"
通过合理的配置管理,可以让 use-notify 在不同环境下灵活运行,同时保证安全性和可维护性。选择适合项目需求的配置方式,并遵循最佳实践,能够大大提升开发效率和系统稳定性。