python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python生成电子邮件签名

使用Python生成个性化的电子邮件签名

作者:闲人编程

在数字通信时代,电子邮件仍然是商务沟通和个人交流的重要工具,本文将详细介绍如何使用Python构建一个智能的个性化电子邮件签名生成系统,希望对大家有所帮助

1. 引言

在数字通信时代,电子邮件仍然是商务沟通和个人交流的重要工具。据统计,全球每天发送的电子邮件数量超过3000亿封,而专业的电子邮件签名不仅能够提供必要的联系信息,还能增强品牌形象、建立信任关系并促进业务增长。

一个精心设计的电子邮件签名具有以下重要作用:

然而,手动创建和维护电子邮件签名存在诸多挑战:

本文将详细介绍如何使用Python构建一个智能的个性化电子邮件签名生成系统。通过这个系统,用户可以快速生成美观、专业且一致的电子邮件签名,支持动态内容、响应式设计和多平台兼容。

2. 系统架构设计

2.1 整体架构概述

电子邮件签名生成系统的核心架构采用模块化设计,确保代码的可维护性和扩展性。

2.2 核心模块设计

系统包含以下关键模块:

2.3 技术选型理由

选择Python作为开发语言的主要原因:

3. 环境配置与依赖安装

3.1 系统要求

3.2 依赖包安装

创建requirements.txt文件:

Jinja2==3.1.2
Pillow==10.0.0
python-dotenv==1.0.0
click==8.1.4
colorama==0.4.6
requests==2.28.2
beautifulsoup4==4.12.2
lxml==4.9.2
qrcode==7.4.2

安装依赖:

pip install -r requirements.txt

3.3 项目结构设计

email_signature_generator/
├── src/
│   ├── __init__.py
│   ├── models/
│   │   ├── __init__.py
│   │   ├── signature_data.py
│   │   └── validators.py
│   ├── generators/
│   │   ├── __init__.py
│   │   ├── html_generator.py
│   │   ├── text_generator.py
│   │   └── image_processor.py
│   ├── templates/
│   │   ├── html/
│   │   │   ├── corporate.html
│   │   │   ├── modern.html
│   │   │   └── minimal.html
│   │   └── css/
│   │       ├── corporate.css
│   │       ├── modern.css
│   │       └── minimal.css
│   ├── utils/
│   │   ├── __init__.py
│   │   ├── helpers.py
│   │   └── config.py
│   └── cli.py
├── tests/
├── examples/
├── docs/
├── requirements.txt
└── README.md

4. 数据模型设计

4.1 签名数据模型

创建基础数据模型来存储签名信息:

# src/models/signature_data.py
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Any
from datetime import datetime
import re
from enum import Enum

class SocialPlatform(Enum):
    """支持的社交媒体平台枚举"""
    LINKEDIN = "linkedin"
    TWITTER = "twitter"
    FACEBOOK = "facebook"
    INSTAGRAM = "instagram"
    GITHUB = "github"
    WEBSITE = "website"
    YOUTUBE = "youtube"

class ThemeStyle(Enum):
    """主题样式枚举"""
    CORPORATE = "corporate"
    MODERN = "modern"
    MINIMAL = "minimal"
    CREATIVE = "creative"

@dataclass
class SocialMedia:
    """社交媒体链接数据类"""
    platform: SocialPlatform
    url: str
    username: Optional[str] = None
    
    @property
    def display_name(self) -> str:
        """获取平台显示名称"""
        platform_names = {
            SocialPlatform.LINKEDIN: "LinkedIn",
            SocialPlatform.TWITTER: "Twitter",
            SocialPlatform.FACEBOOK: "Facebook",
            SocialPlatform.INSTAGRAM: "Instagram",
            SocialPlatform.GITHUB: "GitHub",
            SocialPlatform.WEBSITE: "Website",
            SocialPlatform.YOUTUBE: "YouTube"
        }
        return platform_names.get(self.platform, self.platform.value)

@dataclass
class ContactInfo:
    """联系信息数据类"""
    phone: Optional[str] = None
    mobile: Optional[str] = None
    email: Optional[str] = None
    address: Optional[str] = None
    website: Optional[str] = None
    
    def get_display_phone(self) -> Optional[str]:
        """格式化显示电话号码"""
        if not self.phone:
            return None
        # 简单的电话号码格式化
        cleaned = re.sub(r'\D', '', self.phone)
        if len(cleaned) == 10:
            return f"({cleaned[:3]}) {cleaned[3:6]}-{cleaned[6:]}"
        return self.phone

@dataclass
class SignatureData:
    """电子邮件签名主数据类"""
    # 基本信息
    full_name: str
    job_title: str
    company: str
    department: Optional[str] = None
    
    # 联系信息
    contact: ContactInfo = field(default_factory=ContactInfo)
    
    # 社交媒体
    social_media: List[SocialMedia] = field(default_factory=list)
    
    # 品牌信息
    logo_url: Optional[str] = None
    profile_picture_url: Optional[str] = None
    brand_color: Optional[str] = None
    secondary_color: Optional[str] = None
    
    # 营销信息
    promotional_text: Optional[str] = None
    call_to_action: Optional[str] = None
    disclaimer: Optional[str] = None
    
    # 样式配置
    theme: ThemeStyle = ThemeStyle.MODERN
    include_border: bool = True
    include_qr_code: bool = False
    qr_code_url: Optional[str] = None
    
    # 元数据
    created_at: datetime = field(default_factory=datetime.now)
    updated_at: datetime = field(default_factory=datetime.now)
    
    def add_social_media(self, platform: SocialPlatform, url: str, username: Optional[str] = None):
        """添加社交媒体链接"""
        social = SocialMedia(platform=platform, url=url, username=username)
        self.social_media.append(social)
    
    def remove_social_media(self, platform: SocialPlatform):
        """移除社交媒体链接"""
        self.social_media = [sm for sm in self.social_media if sm.platform != platform]
    
    def get_social_media_by_platform(self, platform: SocialPlatform) -> Optional[SocialMedia]:
        """根据平台获取社交媒体信息"""
        for social in self.social_media:
            if social.platform == platform:
                return social
        return None
    
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典格式"""
        return {
            'full_name': self.full_name,
            'job_title': self.job_title,
            'company': self.company,
            'department': self.department,
            'contact': {
                'phone': self.contact.phone,
                'mobile': self.contact.mobile,
                'email': self.contact.email,
                'address': self.contact.address,
                'website': self.contact.website
            },
            'social_media': [
                {
                    'platform': sm.platform.value,
                    'url': sm.url,
                    'username': sm.username
                } for sm in self.social_media
            ],
            'theme': self.theme.value,
            'brand_color': self.brand_color,
            'secondary_color': self.secondary_color,
            'promotional_text': self.promotional_text,
            'call_to_action': self.call_to_action,
            'disclaimer': self.disclaimer
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'SignatureData':
        """从字典创建实例"""
        contact_data = data.get('contact', {})
        contact = ContactInfo(
            phone=contact_data.get('phone'),
            mobile=contact_data.get('mobile'),
            email=contact_data.get('email'),
            address=contact_data.get('address'),
            website=contact_data.get('website')
        )
        
        signature = cls(
            full_name=data['full_name'],
            job_title=data['job_title'],
            company=data['company'],
            department=data.get('department'),
            contact=contact,
            brand_color=data.get('brand_color'),
            secondary_color=data.get('secondary_color'),
            promotional_text=data.get('promotional_text'),
            call_to_action=data.get('call_to_action'),
            disclaimer=data.get('disclaimer'),
            theme=ThemeStyle(data.get('theme', 'modern'))
        )
        
        # 添加社交媒体
        for sm_data in data.get('social_media', []):
            platform = SocialPlatform(sm_data['platform'])
            signature.add_social_media(platform, sm_data['url'], sm_data.get('username'))
        
        return signature

4.2 数据验证器

创建数据验证器确保输入数据的有效性:

# src/models/validators.py
import re
from typing import Optional, Tuple
from urllib.parse import urlparse
from datetime import datetime

class SignatureValidator:
    """签名数据验证器"""
    
    @staticmethod
    def validate_email(email: str) -> Tuple[bool, str]:
        """验证电子邮件地址"""
        if not email:
            return True, ""  # 空值视为有效
            
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if re.match(pattern, email):
            return True, ""
        return False, "无效的电子邮件格式"
    
    @staticmethod
    def validate_phone(phone: str) -> Tuple[bool, str]:
        """验证电话号码"""
        if not phone:
            return True, ""  # 空值视为有效
            
        # 移除所有非数字字符
        cleaned = re.sub(r'\D', '', phone)
        if 7 <= len(cleaned) <= 15:
            return True, ""
        return False, "电话号码长度应在7-15位数字之间"
    
    @staticmethod
    def validate_url(url: str) -> Tuple[bool, str]:
        """验证URL格式"""
        if not url:
            return True, ""  # 空值视为有效
            
        try:
            result = urlparse(url)
            if all([result.scheme, result.netloc]):
                return True, ""
            return False, "无效的URL格式"
        except:
            return False, "无效的URL格式"
    
    @staticmethod
    def validate_hex_color(color: str) -> Tuple[bool, str]:
        """验证十六进制颜色值"""
        if not color:
            return True, ""  # 空值视为有效
            
        pattern = r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$'
        if re.match(pattern, color):
            return True, ""
        return False, "颜色格式应为 #RGB 或 #RRGGBB"
    
    @staticmethod
    def validate_full_name(name: str) -> Tuple[bool, str]:
        """验证全名"""
        if not name or len(name.strip()) < 2:
            return False, "姓名不能少于2个字符"
        if len(name) > 100:
            return False, "姓名不能超过100个字符"
        return True, ""
    
    @staticmethod
    def validate_company(company: str) -> Tuple[bool, str]:
        """验证公司名称"""
        if not company or len(company.strip()) < 1:
            return False, "公司名称不能为空"
        if len(company) > 200:
            return False, "公司名称不能超过200个字符"
        return True, ""
    
    @staticmethod
    def validate_job_title(title: str) -> Tuple[bool, str]:
        """验证职位名称"""
        if not title or len(title.strip()) < 1:
            return False, "职位名称不能为空"
        if len(title) > 100:
            return False, "职位名称不能超过100个字符"
        return True, ""
    
    def validate_signature_data(self, data: 'SignatureData') -> Tuple[bool, Dict[str, str]]:
        """验证完整的签名数据"""
        errors = {}
        
        # 验证基本信息
        is_valid, message = self.validate_full_name(data.full_name)
        if not is_valid:
            errors['full_name'] = message
            
        is_valid, message = self.validate_job_title(data.job_title)
        if not is_valid:
            errors['job_title'] = message
            
        is_valid, message = self.validate_company(data.company)
        if not is_valid:
            errors['company'] = message
        
        # 验证联系信息
        if data.contact.email:
            is_valid, message = self.validate_email(data.contact.email)
            if not is_valid:
                errors['email'] = message
                
        if data.contact.phone:
            is_valid, message = self.validate_phone(data.contact.phone)
            if not is_valid:
                errors['phone'] = message
                
        if data.contact.website:
            is_valid, message = self.validate_url(data.contact.website)
            if not is_valid:
                errors['website'] = message
        
        # 验证社交媒体链接
        for social in data.social_media:
            is_valid, message = self.validate_url(social.url)
            if not is_valid:
                errors[f'social_{social.platform.value}'] = f"{social.display_name}: {message}"
        
        # 验证品牌颜色
        if data.brand_color:
            is_valid, message = self.validate_hex_color(data.brand_color)
            if not is_valid:
                errors['brand_color'] = message
                
        if data.secondary_color:
            is_valid, message = self.validate_hex_color(data.secondary_color)
            if not is_valid:
                errors['secondary_color'] = message
        
        return len(errors) == 0, errors

5. HTML签名生成器

5.1 基础HTML生成器

创建核心的HTML签名生成器:

# src/generators/html_generator.py
import os
import json
from typing import Dict, Any, Optional
from jinja2 import Environment, FileSystemLoader, Template
from ..models.signature_data import SignatureData, ThemeStyle
from .image_processor import ImageProcessor

class HTMLSignatureGenerator:
    """HTML电子邮件签名生成器"""
    
    def __init__(self, templates_dir: str = None):
        """
        初始化生成器
        
        参数:
            templates_dir: 模板目录路径
        """
        if templates_dir is None:
            # 默认模板目录
            current_dir = os.path.dirname(os.path.abspath(__file__))
            templates_dir = os.path.join(current_dir, '..', 'templates', 'html')
        
        self.templates_dir = templates_dir
        self.env = Environment(
            loader=FileSystemLoader(templates_dir),
            trim_blocks=True,
            lstrip_blocks=True
        )
        self.image_processor = ImageProcessor()
        
        # 注册自定义过滤器
        self.env.filters['escape_html'] = self._escape_html
        self.env.filters['format_phone'] = self._format_phone
        
    def _escape_html(self, text: str) -> str:
        """转义HTML特殊字符"""
        if not text:
            return ""
        return (text.replace('&', '&amp;')
                  .replace('<', '&lt;')
                  .replace('>', '&gt;')
                  .replace('"', '&quot;')
                  .replace("'", '&#39;'))
    
    def _format_phone(self, phone: str) -> str:
        """格式化电话号码显示"""
        if not phone:
            return ""
        # 移除所有非数字字符
        cleaned = ''.join(filter(str.isdigit, phone))
        if len(cleaned) == 10:
            return f"({cleaned[:3]}) {cleaned[3:6]}-{cleaned[6:]}"
        return phone
    
    def _get_template_context(self, data: SignatureData) -> Dict[str, Any]:
        """构建模板上下文数据"""
        # 处理品牌颜色
        brand_color = data.brand_color or '#2c5aa0'
        secondary_color = data.secondary_color or '#666666'
        
        # 处理社交媒体图标
        social_icons = self._prepare_social_media(data.social_media)
        
        # 准备联系信息
        contact_info = {
            'phone': data.contact.get_display_phone(),
            'mobile': data.contact.mobile,
            'email': data.contact.email,
            'address': data.contact.address,
            'website': data.contact.website
        }
        
        return {
            'data': data,
            'brand_color': brand_color,
            'secondary_color': secondary_color,
            'social_icons': social_icons,
            'contact_info': contact_info,
            'has_logo': bool(data.logo_url),
            'has_profile_picture': bool(data.profile_picture_url),
            'has_promotion': bool(data.promotional_text),
            'has_disclaimer': bool(data.disclaimer)
        }
    
    def _prepare_social_media(self, social_media_list) -> list:
        """准备社交媒体数据"""
        social_data = []
        
        for social in social_media_list:
            # 社交媒体平台对应的图标类和颜色
            platform_config = {
                'linkedin': {'icon': 'fab fa-linkedin', 'color': '#0077b5'},
                'twitter': {'icon': 'fab fa-twitter', 'color': '#1da1f2'},
                'facebook': {'icon': 'fab fa-facebook', 'color': '#1877f2'},
                'instagram': {'icon': 'fab fa-instagram', 'color': '#e4405f'},
                'github': {'icon': 'fab fa-github', 'color': '#333333'},
                'website': {'icon': 'fas fa-globe', 'color': '#666666'},
                'youtube': {'icon': 'fab fa-youtube', 'color': '#ff0000'}
            }
            
            config = platform_config.get(social.platform.value, {})
            social_data.append({
                'platform': social.platform.value,
                'display_name': social.display_name,
                'url': social.url,
                'username': social.username,
                'icon_class': config.get('icon', 'fas fa-link'),
                'color': config.get('color', '#666666')
            })
        
        return social_data
    
    def generate_signature(self, data: SignatureData, 
                          template_name: Optional[str] = None) -> str:
        """
        生成HTML签名
        
        参数:
            data: 签名数据
            template_name: 模板名称
            
        返回:
            HTML签名字符串
        """
        if template_name is None:
            template_name = f"{data.theme.value}.html"
        
        # 验证模板是否存在
        template_path = os.path.join(self.templates_dir, template_name)
        if not os.path.exists(template_path):
            raise FileNotFoundError(f"模板文件不存在: {template_path}")
        
        # 加载模板
        template = self.env.get_template(template_name)
        
        # 准备上下文数据
        context = self._get_template_context(data)
        
        # 渲染模板
        html_content = template.render(**context)
        
        # 清理和优化HTML
        html_content = self._clean_html(html_content)
        
        return html_content
    
    def _clean_html(self, html: str) -> str:
        """清理和优化HTML输出"""
        # 移除多余的空格和换行
        html = ' '.join(html.split())
        
        # 确保HTML格式正确
        html = html.replace('> <', '><')  # 移除标签间的空格
        
        return html
    
    def generate_with_css(self, data: SignatureData, 
                         template_name: Optional[str] = None) -> Dict[str, str]:
        """
        生成包含CSS的完整签名
        
        参数:
            data: 签名数据
            template_name: 模板名称
            
        返回:
            包含HTML和CSS的字典
        """
        html_content = self.generate_signature(data, template_name)
        
        # 加载对应的CSS文件
        css_content = self._load_css_template(data.theme)
        
        return {
            'html': html_content,
            'css': css_content,
            'inline_css': self._generate_inline_css(css_content)
        }
    
    def _load_css_template(self, theme: ThemeStyle) -> str:
        """加载CSS模板"""
        css_filename = f"{theme.value}.css"
        css_path = os.path.join(self.templates_dir, '..', 'css', css_filename)
        
        if os.path.exists(css_path):
            with open(css_path, 'r', encoding='utf-8') as f:
                return f.read()
        return ""
    
    def _generate_inline_css(self, css_content: str) -> str:
        """生成内联CSS样式"""
        # 这里可以添加CSS内联化逻辑
        # 简化版本,直接返回CSS内容
        return f"<style>{css_content}</style>"
    
    def save_signature(self, data: SignatureData, output_path: str,
                      template_name: Optional[str] = None,
                      include_css: bool = True):
        """
        保存签名到文件
        
        参数:
            data: 签名数据
            output_path: 输出文件路径
            template_name: 模板名称
            include_css: 是否包含CSS
        """
        if include_css:
            result = self.generate_with_css(data, template_name)
            content = result['html'] + '\n' + result['inline_css']
        else:
            content = self.generate_signature(data, template_name)
        
        # 确保输出目录存在
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(content)
        
        print(f"签名已保存到: {output_path}")

5.2 图像处理器

创建图像处理模块:

# src/generators/image_processor.py
import os
import requests
from PIL import Image, ImageDraw, ImageFont
import base64
from io import BytesIO
from typing import Optional, Tuple
import qrcode

class ImageProcessor:
    """图像处理器"""
    
    def __init__(self, cache_dir: str = "image_cache"):
        """
        初始化图像处理器
        
        参数:
            cache_dir: 图像缓存目录
        """
        self.cache_dir = cache_dir
        os.makedirs(cache_dir, exist_ok=True)
    
    def download_image(self, url: str, max_size: Tuple[int, int] = (200, 200)) -> Optional[str]:
        """
        下载并调整图像大小
        
        参数:
            url: 图像URL
            max_size: 最大尺寸 (宽, 高)
            
        返回:
            本地文件路径或None
        """
        try:
            # 检查缓存
            filename = self._get_filename_from_url(url)
            cache_path = os.path.join(self.cache_dir, filename)
            
            if os.path.exists(cache_path):
                return cache_path
            
            # 下载图像
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            
            # 打开并调整图像
            image = Image.open(BytesIO(response.content))
            image.thumbnail(max_size, Image.Resampling.LANCZOS)
            
            # 保存为PNG格式
            image.save(cache_path, 'PNG')
            
            return cache_path
            
        except Exception as e:
            print(f"下载图像失败: {e}")
            return None
    
    def _get_filename_from_url(self, url: str) -> str:
        """从URL生成文件名"""
        import hashlib
        return hashlib.md5(url.encode()).hexdigest() + '.png'
    
    def image_to_base64(self, image_path: str) -> Optional[str]:
        """
        将图像转换为Base64编码
        
        参数:
            image_path: 图像文件路径
            
        返回:
            Base64编码的字符串或None
        """
        try:
            with open(image_path, 'rb') as f:
                image_data = f.read()
                base64_encoded = base64.b64encode(image_data).decode('utf-8')
                return f"data:image/png;base64,{base64_encoded}"
        except Exception as e:
            print(f"图像Base64编码失败: {e}")
            return None
    
    def create_qr_code(self, data: str, size: int = 100) -> Optional[str]:
        """
        生成QR码
        
        参数:
            data: QR码数据
            size: 图像尺寸
            
        返回:
            Base64编码的QR码图像
        """
        try:
            qr = qrcode.QRCode(
                version=1,
                error_correction=qrcode.constants.ERROR_CORRECT_L,
                box_size=10,
                border=4,
            )
            qr.add_data(data)
            qr.make(fit=True)
            
            qr_image = qr.make_image(fill_color="black", back_color="white")
            qr_image = qr_image.resize((size, size))
            
            # 转换为Base64
            buffer = BytesIO()
            qr_image.save(buffer, format='PNG')
            base64_encoded = base64.b64encode(buffer.getvalue()).decode('utf-8')
            
            return f"data:image/png;base64,{base64_encoded}"
            
        except Exception as e:
            print(f"生成QR码失败: {e}")
            return None
    
    def validate_image(self, image_path: str, max_size_mb: int = 2) -> Tuple[bool, str]:
        """
        验证图像文件
        
        参数:
            image_path: 图像文件路径
            max_size_mb: 最大文件大小(MB)
            
        返回:
            (是否有效, 错误信息)
        """
        try:
            # 检查文件大小
            file_size = os.path.getsize(image_path) / (1024 * 1024)  # MB
            if file_size > max_size_mb:
                return False, f"图像文件过大 ({file_size:.1f}MB > {max_size_mb}MB)"
            
            # 检查图像格式
            with Image.open(image_path) as img:
                if img.format not in ['JPEG', 'PNG', 'GIF']:
                    return False, f"不支持的图像格式: {img.format}"
                
                # 检查图像尺寸
                width, height = img.size
                if width > 1000 or height > 1000:
                    return False, f"图像尺寸过大: {width}x{height}"
            
            return True, ""
            
        except Exception as e:
            return False, f"图像验证失败: {str(e)}"

6. 模板系统设计

6.1 企业风格模板

创建企业风格HTML模板:

<!-- src/templates/html/corporate.html -->
<table cellpadding="0" cellspacing="0" border="0" width="600" style="border-collapse: collapse; font-family: Arial, sans-serif; font-size: 12px; line-height: 1.4; color: #333333; border: {% if data.include_border %}1px solid #dddddd{% else %}none{% endif %};">
    <tr>
        {% if data.logo_url %}
        <td width="100" valign="top" style="padding: 15px;">
            <img src="{{ data.logo_url }}" alt="{{ data.company }} Logo" width="80" style="display: block; border: none;" />
        </td>
        {% endif %}
        
        <td valign="top" style="padding: 15px; {% if not data.logo_url %}padding-left: 0;{% endif %}">
            <!-- 姓名和职位 -->
            <table cellpadding="0" cellspacing="0" border="0">
                <tr>
                    <td style="padding-bottom: 5px;">
                        <strong style="font-size: 14px; color: {{ brand_color }};">{{ data.full_name|escape_html }}</strong>
                    </td>
                </tr>
                <tr>
                    <td style="padding-bottom: 8px;">
                        <span style="font-size: 12px; color: {{ secondary_color }};">{{ data.job_title|escape_html }}</span>
                        {% if data.department %}
                        <span style="color: #999999;"> | </span>
                        <span style="font-size: 12px; color: #999999;">{{ data.department|escape_html }}</span>
                        {% endif %}
                    </td>
                </tr>
            </table>
            
            <!-- 公司信息 -->
            <table cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 10px;">
                <tr>
                    <td style="font-size: 12px; color: {{ brand_color }}; font-weight: bold;">
                        {{ data.company|escape_html }}
                    </td>
                </tr>
            </table>
            
            <!-- 联系信息 -->
            <table cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 10px;">
                {% if contact_info.phone %}
                <tr>
                    <td width="20" valign="top">📞</td>
                    <td style="padding-bottom: 2px;">
                        <span style="font-size: 11px;">电话: {{ contact_info.phone|format_phone }}</span>
                    </td>
                </tr>
                {% endif %}
                
                {% if contact_info.mobile %}
                <tr>
                    <td width="20" valign="top">📱</td>
                    <td style="padding-bottom: 2px;">
                        <span style="font-size: 11px;">手机: {{ contact_info.mobile|format_phone }}</span>
                    </td>
                </tr>
                {% endif %}
                
                {% if contact_info.email %}
                <tr>
                    <td width="20" valign="top">✉️</td>
                    <td style="padding-bottom: 2px;">
                        <a href="mailto:{{ contact_info.email }}" rel="external nofollow"  rel="external nofollow"  style="font-size: 11px; color: {{ brand_color }}; text-decoration: none;">
                            {{ contact_info.email|escape_html }}
                        </a>
                    </td>
                </tr>
                {% endif %}
                
                {% if contact_info.website %}
                <tr>
                    <td width="20" valign="top">🌐</td>
                    <td style="padding-bottom: 2px;">
                        <a href="{{ contact_info.website }}" rel="external nofollow"  rel="external nofollow"  style="font-size: 11px; color: {{ brand_color }}; text-decoration: none;">
                            {{ contact_info.website|escape_html }}
                        </a>
                    </td>
                </tr>
                {% endif %}
            </table>
            
            <!-- 社交媒体 -->
            {% if social_icons %}
            <table cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 10px;">
                <tr>
                    <td style="padding-bottom: 5px;">
                        <span style="font-size: 11px; color: #999999;">关注我们:</span>
                    </td>
                </tr>
                <tr>
                    <td>
                        {% for social in social_icons %}
                        <a href="{{ social.url }}" rel="external nofollow"  rel="external nofollow"  style="text-decoration: none; margin-right: 8px; display: inline-block;">
                            <span style="color: {{ social.color }}; font-size: 14px;">[{{ social.display_name }}]</span>
                        </a>
                        {% endfor %}
                    </td>
                </tr>
            </table>
            {% endif %}
            
            <!-- 推广信息 -->
            {% if data.promotional_text %}
            <table cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 10px; background-color: #f8f9fa; padding: 8px; border-left: 3px solid {{ brand_color }};">
                <tr>
                    <td>
                        <span style="font-size: 11px; color: #666666; font-style: italic;">
                            {{ data.promotional_text|escape_html }}
                        </span>
                    </td>
                </tr>
            </table>
            {% endif %}
            
            <!-- 免责声明 -->
            {% if data.disclaimer %}
            <table cellpadding="0" cellspacing="0" border="0">
                <tr>
                    <td>
                        <span style="font-size: 9px; color: #999999; line-height: 1.2;">
                            {{ data.disclaimer|escape_html }}
                        </span>
                    </td>
                </tr>
            </table>
            {% endif %}
        </td>
        
        <!-- QR码 -->
        {% if data.include_qr_code and data.qr_code_url %}
        <td width="80" valign="middle" align="center" style="padding: 15px;">
            <img src="{{ data.qr_code_url }}" alt="QR Code" width="60" style="display: block; border: none;" />
            <span style="font-size: 9px; color: #999999;">扫描联系我</span>
        </td>
        {% endif %}
    </tr>
</table>

6.2 现代风格模板

创建现代风格HTML模板:

<!-- src/templates/html/modern.html -->
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="max-width: 550px; border-collapse: collapse; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 13px; line-height: 1.5; color: #444444; background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); border-radius: 8px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
    <tr>
        <!-- 左侧品牌区域 -->
        <td width="30%" valign="top" style="background: {{ brand_color }}; padding: 20px; color: white;">
            {% if data.logo_url %}
            <div style="margin-bottom: 15px;">
                <img src="{{ data.logo_url }}" alt="{{ data.company }} Logo" width="60" style="display: block; border: none; background: white; padding: 5px; border-radius: 4px;" />
            </div>
            {% endif %}
            
            <div style="font-size: 16px; font-weight: bold; margin-bottom: 5px;">
                {{ data.full_name|escape_html }}
            </div>
            
            <div style="font-size: 12px; opacity: 0.9;">
                {{ data.job_title|escape_html }}
            </div>
            
            {% if data.department %}
            <div style="font-size: 11px; opacity: 0.8; margin-top: 3px;">
                {{ data.department|escape_html }}
            </div>
            {% endif %}
        </td>
        
        <!-- 右侧内容区域 -->
        <td valign="top" style="padding: 20px;">
            <!-- 公司名称 -->
            <div style="font-size: 14px; font-weight: bold; color: {{ brand_color }}; margin-bottom: 15px;">
                {{ data.company|escape_html }}
            </div>
            
            <!-- 联系信息 -->
            <table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom: 15px;">
                {% if contact_info.phone %}
                <tr>
                    <td width="20" valign="top" style="padding-bottom: 6px;">
                        <span style="color: {{ brand_color }};">●</span>
                    </td>
                    <td style="padding-bottom: 6px;">
                        <span style="font-size: 12px;">{{ contact_info.phone|format_phone }}</span>
                    </td>
                </tr>
                {% endif %}
                
                {% if contact_info.email %}
                <tr>
                    <td width="20" valign="top" style="padding-bottom: 6px;">
                        <span style="color: {{ brand_color }};">●</span>
                    </td>
                    <td style="padding-bottom: 6px;">
                        <a href="mailto:{{ contact_info.email }}" rel="external nofollow"  rel="external nofollow"  style="font-size: 12px; color: {{ brand_color }}; text-decoration: none;">
                            {{ contact_info.email|escape_html }}
                        </a>
                    </td>
                </tr>
                {% endif %}
                
                {% if contact_info.website %}
                <tr>
                    <td width="20" valign="top" style="padding-bottom: 6px;">
                        <span style="color: {{ brand_color }};">●</span>
                    </td>
                    <td style="padding-bottom: 6px;">
                        <a href="{{ contact_info.website }}" rel="external nofollow"  rel="external nofollow"  style="font-size: 12px; color: {{ brand_color }}; text-decoration: none;">
                            {{ contact_info.website|escape_html }}
                        </a>
                    </td>
                </tr>
                {% endif %}
            </table>
            
            <!-- 社交媒体 -->
            {% if social_icons %}
            <div style="margin-bottom: 15px;">
                <table cellpadding="0" cellspacing="0" border="0">
                    <tr>
                        {% for social in social_icons %}
                        <td style="padding-right: 8px;">
                            <a href="{{ social.url }}" rel="external nofollow"  rel="external nofollow"  style="display: inline-block; width: 24px; height: 24px; background-color: {{ social.color }}; border-radius: 50%; text-align: center; line-height: 24px; text-decoration: none; color: white; font-size: 12px;">
                                {{ social.display_name|first|upper }}
                            </a>
                        </td>
                        {% endfor %}
                    </tr>
                </table>
            </div>
            {% endif %}
            
            <!-- 行动号召 -->
            {% if data.call_to_action %}
            <div style="background-color: {{ brand_color }}; color: white; padding: 8px 12px; border-radius: 4px; text-align: center; margin-bottom: 10px;">
                <span style="font-size: 11px; font-weight: bold;">
                    {{ data.call_to_action|escape_html }}
                </span>
            </div>
            {% endif %}
        </td>
    </tr>
    
    <!-- 底部区域 -->
    {% if data.promotional_text or data.disclaimer %}
    <tr>
        <td colspan="2" style="background-color: #f8f9fa; padding: 15px 20px; border-top: 1px solid #e9ecef;">
            {% if data.promotional_text %}
            <div style="font-size: 11px; color: #666666; margin-bottom: 8px;">
                ✨ {{ data.promotional_text|escape_html }}
            </div>
            {% endif %}
            
            {% if data.disclaimer %}
            <div style="font-size: 9px; color: #999999; line-height: 1.3;">
                {{ data.disclaimer|escape_html }}
            </div>
            {% endif %}
        </td>
    </tr>
    {% endif %}
</table>

6.3 CSS样式文件

创建对应的CSS样式文件:

/* src/templates/css/corporate.css */
.corporate-signature {
    font-family: Arial, sans-serif;
    font-size: 12px;
    line-height: 1.4;
    color: #333333;
    border-collapse: collapse;
}

.corporate-signature a {
    color: #2c5aa0;
    text-decoration: none;
}

.corporate-signature a:hover {
    text-decoration: underline;
}

.corporate-signature .brand-color {
    color: #2c5aa0;
}

.corporate-signature .secondary-color {
    color: #666666;
}

.corporate-signature .promotional-box {
    background-color: #f8f9fa;
    padding: 8px;
    border-left: 3px solid #2c5aa0;
    font-style: italic;
}

.corporate-signature .disclaimer {
    font-size: 9px;
    color: #999999;
    line-height: 1.2;
}
/* src/templates/css/modern.css */
.modern-signature {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-size: 13px;
    line-height: 1.5;
    color: #444444;
    border-collapse: collapse;
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.modern-signature a {
    text-decoration: none;
}

.modern-signature .brand-section {
    background: linear-gradient(135deg, #2c5aa0 0%, #1e3a8a 100%);
    color: white;
}

.modern-signature .social-icon {
    display: inline-block;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    text-align: center;
    line-height: 24px;
    color: white;
    font-size: 12px;
}

.modern-signature .cta-button {
    background-color: #2c5aa0;
    color: white;
    padding: 8px 12px;
    border-radius: 4px;
    text-align: center;
    font-weight: bold;
}

.modern-signature .footer-section {
    background-color: #f8f9fa;
    border-top: 1px solid #e9ecef;
}

7. 纯文本签名生成器

创建纯文本签名生成器,确保在不支持HTML的客户端中正常显示:

# src/generators/text_generator.py
from ..models.signature_data import SignatureData
from typing import List

class TextSignatureGenerator:
    """纯文本电子邮件签名生成器"""
    
    def __init__(self, line_width: int = 70):
        """
        初始化生成器
        
        参数:
            line_width: 行宽(字符数)
        """
        self.line_width = line_width
    
    def generate_signature(self, data: SignatureData) -> str:
        """
        生成纯文本签名
        
        参数:
            data: 签名数据
            
        返回:
            纯文本签名字符串
        """
        lines = []
        
        # 分隔线
        lines.append("=" * self.line_width)
        
        # 姓名和职位
        name_line = data.full_name
        if data.job_title:
            name_line += f" | {data.job_title}"
        lines.append(name_line)
        
        # 公司和部门
        company_line = data.company
        if data.department:
            company_line += f" | {data.department}"
        lines.append(company_line)
        
        lines.append("")  # 空行
        
        # 联系信息
        if data.contact.phone:
            lines.append(f"电话: {data.contact.get_display_phone()}")
        
        if data.contact.mobile:
            lines.append(f"手机: {data.contact.mobile}")
        
        if data.contact.email:
            lines.append(f"邮箱: {data.contact.email}")
        
        if data.contact.website:
            lines.append(f"网站: {data.contact.website}")
        
        if data.contact.address:
            # 地址可能较长,需要换行处理
            address_lines = self._wrap_text(f"地址: {data.contact.address}", self.line_width)
            lines.extend(address_lines)
        
        # 社交媒体
        if data.social_media:
            lines.append("")  # 空行
            lines.append("关注我:")
            for social in data.social_media:
                lines.append(f"  {social.display_name}: {social.url}")
        
        # 推广信息
        if data.promotional_text:
            lines.append("")  # 空行
            promo_lines = self._wrap_text(f"✨ {data.promotional_text}", self.line_width)
            lines.extend(promo_lines)
        
        # 行动号召
        if data.call_to_action:
            lines.append("")  # 空行
            cta_lines = self._wrap_text(f"🚀 {data.call_to_action}", self.line_width)
            lines.extend(cta_lines)
        
        # 免责声明
        if data.disclaimer:
            lines.append("")  # 空行
            disclaimer_lines = self._wrap_text(data.disclaimer, self.line_width)
            lines.extend(disclaimer_lines)
        
        # 结束分隔线
        lines.append("=" * self.line_width)
        
        return "\n".join(lines)
    
    def _wrap_text(self, text: str, width: int) -> List[str]:
        """
        文本换行处理
        
        参数:
            text: 原始文本
            width: 行宽
            
        返回:
            换行后的文本列表
        """
        words = text.split()
        lines = []
        current_line = []
        current_length = 0
        
        for word in words:
            # 计算添加这个词后的行长度
            word_length = len(word)
            if current_line:
                # 加上空格的长度
                word_length += 1
            
            if current_length + word_length <= width:
                current_line.append(word)
                current_length += word_length
            else:
                # 开始新行
                if current_line:
                    lines.append(' '.join(current_line))
                current_line = [word]
                current_length = len(word)
        
        # 添加最后一行
        if current_line:
            lines.append(' '.join(current_line))
        
        return lines
    
    def save_signature(self, data: SignatureData, output_path: str):
        """
        保存纯文本签名到文件
        
        参数:
            data: 签名数据
            output_path: 输出文件路径
        """
        text_content = self.generate_signature(data)
        
        # 确保输出目录存在
        import os
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(text_content)
        
        print(f"纯文本签名已保存到: {output_path}")

8. 命令行界面

创建用户友好的命令行界面:

# src/cli.py
import click
import json
import os
from typing import Dict, Any
from .models.signature_data import SignatureData, ThemeStyle, SocialPlatform, ContactInfo
from .models.validators import SignatureValidator
from .generators.html_generator import HTMLSignatureGenerator
from .generators.text_generator import TextSignatureGenerator

@click.group()
def cli():
    """个性化电子邮件签名生成工具"""
    pass

@cli.command()
@click.option('--name', prompt='姓名', help='您的全名')
@click.option('--title', prompt='职位', help='您的职位名称')
@click.option('--company', prompt='公司', help='公司名称')
@click.option('--department', help='部门名称')
@click.option('--email', prompt='电子邮件', help='电子邮件地址')
@click.option('--phone', help='电话号码')
@click.option('--website', help='个人或公司网站')
@click.option('--theme', type=click.Choice(['corporate', 'modern', 'minimal']), 
              default='modern', help='签名主题风格')
@click.option('--output', '-o', default='signature.html', help='输出文件路径')
@click.option('--format', '-f', type=click.Choice(['html', 'text', 'both']), 
              default='html', help='输出格式')
def create(name, title, company, department, email, phone, website, theme, output, format):
    """创建新的电子邮件签名"""
    
    # 创建联系信息
    contact = ContactInfo(
        email=email,
        phone=phone,
        website=website
    )
    
    # 创建签名数据
    signature_data = SignatureData(
        full_name=name,
        job_title=title,
        company=company,
        department=department,
        contact=contact,
        theme=ThemeStyle(theme)
    )
    
    # 验证数据
    validator = SignatureValidator()
    is_valid, errors = validator.validate_signature_data(signature_data)
    
    if not is_valid:
        click.echo("数据验证失败:")
        for field, error in errors.items():
            click.echo(f"  {field}: {error}")
        return
    
    # 生成签名
    if format in ['html', 'both']:
        html_generator = HTMLSignatureGenerator()
        html_output = output if format == 'html' else output.replace('.html', '_html.html')
        html_generator.save_signature(signature_data, html_output)
    
    if format in ['text', 'both']:
        text_generator = TextSignatureGenerator()
        text_output = output if format == 'text' else output.replace('.html', '_text.txt')
        text_generator.save_signature(signature_data, text_output)
    
    click.echo("签名创建成功!")

@cli.command()
@click.argument('config_file', type=click.File('r'))
@click.option('--output', '-o', required=True, help='输出文件路径')
@click.option('--format', '-f', type=click.Choice(['html', 'text', 'both']), 
              default='html', help='输出格式')
def from_config(config_file, output, format):
    """从配置文件创建签名"""
    
    try:
        config_data = json.load(config_file)
        signature_data = SignatureData.from_dict(config_data)
        
        # 验证数据
        validator = SignatureValidator()
        is_valid, errors = validator.validate_signature_data(signature_data)
        
        if not is_valid:
            click.echo("配置数据验证失败:")
            for field, error in errors.items():
                click.echo(f"  {field}: {error}")
            return
        
        # 生成签名
        if format in ['html', 'both']:
            html_generator = HTMLSignatureGenerator()
            html_output = output if format == 'html' else output.replace('.html', '_html.html')
            html_generator.save_signature(signature_data, html_output)
        
        if format in ['text', 'both']:
            text_generator = TextSignatureGenerator()
            text_output = output if format == 'text' else output.replace('.html', '_text.txt')
            text_generator.save_signature(signature_data, text_output)
        
        click.echo("签名创建成功!")
        
    except json.JSONDecodeError:
        click.echo("配置文件格式错误,请检查JSON格式")
    except Exception as e:
        click.echo(f"处理配置文件时出错: {e}")

@cli.command()
@click.option('--name', help='姓名')
@click.option('--title', help='职位')
@click.option('--company', help='公司')
@click.option('--output', default='signature_config.json', help='输出配置文件路径')
def create_config(name, title, company, output):
    """创建签名配置文件模板"""
    
    config_template = {
        "full_name": name or "张三",
        "job_title": title or "软件工程师",
        "company": company or "示例公司",
        "department": "技术部",
        "contact": {
            "phone": "+86-10-12345678",
            "mobile": "+86-138-0000-0000",
            "email": "zhangsan@example.com",
            "address": "北京市朝阳区示例街道123号",
            "website": "https://www.example.com"
        },
        "social_media": [
            {
                "platform": "linkedin",
                "url": "https://linkedin.com/in/zhangsan",
                "username": "zhangsan"
            },
            {
                "platform": "github",
                "url": "https://github.com/zhangsan",
                "username": "zhangsan"
            }
        ],
        "theme": "modern",
        "brand_color": "#2c5aa0",
        "secondary_color": "#666666",
        "promotional_text": "欢迎了解我们的最新产品和服务!",
        "call_to_action": "立即预约演示",
        "disclaimer": "本邮件及其附件包含保密信息,仅限指定收件人使用。"
    }
    
    with open(output, 'w', encoding='utf-8') as f:
        json.dump(config_template, f, ensure_ascii=False, indent=2)
    
    click.echo(f"配置文件模板已创建: {output}")
    click.echo("请编辑此文件后使用 'from-config' 命令生成签名")

@cli.command()
@click.argument('input_file')
@click.option('--theme', type=click.Choice(['corporate', 'modern', 'minimal']), 
              help='主题风格')
@click.option('--output', '-o', help='输出文件路径')
def preview(input_file, theme, output):
    """预览签名效果"""
    
    try:
        with open(input_file, 'r', encoding='utf-8') as f:
            if input_file.endswith('.json'):
                config_data = json.load(f)
                signature_data = SignatureData.from_dict(config_data)
            else:
                # 这里可以添加其他格式的支持
                raise click.ClickException("不支持的输入文件格式")
        
        if theme:
            signature_data.theme = ThemeStyle(theme)
        
        # 生成HTML预览
        html_generator = HTMLSignatureGenerator()
        html_content = html_generator.generate_signature(signature_data)
        
        if output:
            html_generator.save_signature(signature_data, output)
            click.echo(f"预览文件已保存: {output}")
        else:
            # 在控制台显示HTML代码
            click.echo("生成的HTML签名:")
            click.echo("=" * 80)
            click.echo(html_content)
            click.echo("=" * 80)
            click.echo("请将上述代码复制到您的电子邮件客户端中使用")
            
    except Exception as e:
        click.echo(f"预览失败: {e}")

if __name__ == '__main__':
    cli()

9. 完整示例和使用方法

9.1 基本使用示例

创建使用示例文件:

# examples/basic_usage.py
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from src.models.signature_data import SignatureData, ContactInfo, SocialPlatform, ThemeStyle
from src.generators.html_generator import HTMLSignatureGenerator
from src.generators.text_generator import TextSignatureGenerator

def create_basic_signature():
    """创建基础签名示例"""
    
    # 创建联系信息
    contact = ContactInfo(
        phone="+86-10-12345678",
        mobile="+86-138-0000-0000",
        email="zhangsan@example.com",
        website="https://www.example.com",
        address="北京市朝阳区示例街道123号"
    )
    
    # 创建签名数据
    signature_data = SignatureData(
        full_name="张三",
        job_title="高级软件工程师",
        company="创新科技有限公司",
        department="技术研发部",
        contact=contact,
        brand_color="#2c5aa0",
        secondary_color="#666666",
        theme=ThemeStyle.MODERN,
        promotional_text="专注于人工智能和云计算解决方案",
        call_to_action="查看我们的最新产品",
        disclaimer="本邮件及其附件包含保密信息,仅限指定收件人使用。"
    )
    
    # 添加社交媒体
    signature_data.add_social_media(
        SocialPlatform.LINKEDIN, 
        "https://linkedin.com/in/zhangsan",
        "zhangsan"
    )
    signature_data.add_social_media(
        SocialPlatform.GITHUB,
        "https://github.com/zhangsan",
        "zhangsan"
    )
    signature_data.add_social_media(
        SocialPlatform.WEBSITE,
        "https://www.zhangsan.com"
    )
    
    return signature_data

def generate_all_formats():
    """生成所有格式的签名"""
    signature_data = create_basic_signature()
    
    # 生成HTML签名
    html_generator = HTMLSignatureGenerator()
    html_signature = html_generator.generate_signature(signature_data)
    
    # 生成纯文本签名
    text_generator = TextSignatureGenerator()
    text_signature = text_generator.generate_signature(signature_data)
    
    # 保存文件
    with open('examples/output/signature.html', 'w', encoding='utf-8') as f:
        f.write(html_signature)
    
    with open('examples/output/signature.txt', 'w', encoding='utf-8') as f:
        f.write(text_signature)
    
    print("HTML签名已保存: examples/output/signature.html")
    print("纯文本签名已保存: examples/output/signature.txt")
    
    return html_signature, text_signature

if __name__ == '__main__':
    # 确保输出目录存在
    os.makedirs('examples/output', exist_ok=True)
    
    html, text = generate_all_formats()
    
    print("\n生成的HTML签名预览:")
    print("=" * 50)
    print(html[:500] + "..." if len(html) > 500 else html)
    
    print("\n生成的纯文本签名:")
    print("=" * 50)
    print(text)

9.2 配置文件示例

创建配置文件示例:

{
  "full_name": "李四",
  "job_title": "产品经理",
  "company": "数字创新有限公司",
  "department": "产品部",
  "contact": {
    "phone": "+86-21-87654321",
    "mobile": "+86-139-1111-2222",
    "email": "lisi@digital-innovations.com",
    "address": "上海市浦东新区创新路456号",
    "website": "https://www.digital-innovations.com"
  },
  "social_media": [
    {
      "platform": "linkedin",
      "url": "https://linkedin.com/in/lisi",
      "username": "lisi"
    },
    {
      "platform": "twitter",
      "url": "https://twitter.com/lisi",
      "username": "lisi"
    },
    {
      "platform": "website",
      "url": "https://www.lisi.blog"
    }
  ],
  "theme": "corporate",
  "brand_color": "#d35400",
  "secondary_color": "#7f8c8d",
  "promotional_text": "我们致力于打造用户体验卓越的数字产品",
  "call_to_action": "立即体验我们的产品演示",
  "disclaimer": "本邮件内容仅供参考,不构成任何承诺或保证。",
  "include_border": true,
  "include_qr_code": true,
  "qr_code_url": "https://www.digital-innovations.com/contact"
}

10. 测试和验证

单元测试

创建基础测试用例:

# tests/test_signature_generator.py
import unittest
import os
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from src.models.signature_data import SignatureData, ContactInfo, SocialPlatform
from src.models.validators import SignatureValidator
from src.generators.html_generator import HTMLSignatureGenerator
from src.generators.text_generator import TextSignatureGenerator

class TestSignatureGenerator(unittest.TestCase):
    
    def setUp(self):
        """测试前置设置"""
        self.contact = ContactInfo(
            phone="+86-10-12345678",
            email="test@example.com",
            website="https://www.example.com"
        )
        
        self.signature_data = SignatureData(
            full_name="测试用户",
            job_title="测试工程师",
            company="测试公司",
            contact=self.contact
        )
        
        self.validator = SignatureValidator()
        self.html_generator = HTMLSignatureGenerator()
        self.text_generator = TextSignatureGenerator()
    
    def test_valid_signature_data(self):
        """测试有效签名数据验证"""
        is_valid, errors = self.validator.validate_signature_data(self.signature_data)
        self.assertTrue(is_valid)
        self.assertEqual(len(errors), 0)
    
    def test_invalid_email(self):
        """测试无效电子邮件验证"""
        self.signature_data.contact.email = "invalid-email"
        is_valid, errors = self.validator.validate_signature_data(self.signature_data)
        self.assertFalse(is_valid)
        self.assertIn('email', errors)
    
    def test_html_generation(self):
        """测试HTML生成"""
        html_content = self.html_generator.generate_signature(self.signature_data)
        self.assertIsInstance(html_content, str)
        self.assertGreater(len(html_content), 0)
        self.assertIn('测试用户', html_content)
        self.assertIn('测试公司', html_content)
    
    def test_text_generation(self):
        """测试纯文本生成"""
        text_content = self.text_generator.generate_signature(self.signature_data)
        self.assertIsInstance(text_content, str)
        self.assertGreater(len(text_content), 0)
        self.assertIn('测试用户', text_content)
        self.assertIn('测试公司', text_content)
    
    def test_social_media_addition(self):
        """测试社交媒体添加"""
        initial_count = len(self.signature_data.social_media)
        self.signature_data.add_social_media(SocialPlatform.LINKEDIN, "https://linkedin.com/in/test")
        self.assertEqual(len(self.signature_data.social_media), initial_count + 1)
    
    def test_phone_formatting(self):
        """测试电话号码格式化"""
        formatted = self.signature_data.contact.get_display_phone()
        self.assertIsNotNone(formatted)
        # 检查是否包含格式化的电话号码元素

if __name__ == '__main__':
    unittest.main()

11. 部署和使用说明

11.1 安装和使用

安装依赖

pip install -r requirements.txt

基本使用

# 交互式创建签名
python -m src.cli create

# 使用配置文件创建签名
python -m src.cli from-config config.json --output signature.html

# 创建配置文件模板
python -m src.cli create-config --output my_config.json

# 预览签名
python -m src.cli preview my_config.json

在Python代码中使用

from src.models.signature_data import SignatureData, ContactInfo
from src.generators.html_generator import HTMLSignatureGenerator

# 创建签名数据
contact = ContactInfo(email="user@example.com", phone="+1234567890")
data = SignatureData("张三", "工程师", "科技公司", contact=contact)

# 生成HTML签名
generator = HTMLSignatureGenerator()
signature = generator.generate_signature(data)

11.2 最佳实践

图像优化

颜色选择

响应式设计

可访问性

12. 总结

本文详细介绍了一个完整的个性化电子邮件签名生成系统的设计和实现。通过这个系统,用户可以:

这个系统的核心优势在于其灵活性和易用性。无论是个人用户还是企业管理员,都可以通过这个工具快速创建和维护专业的电子邮件签名,提升沟通的专业性和效率。

通过模块化的设计和良好的代码结构,这个系统还具有良好的可扩展性,可以轻松添加新的模板主题、输出格式或集成其他功能。

以上就是使用Python生成个性化的电子邮件签名的详细内容,更多关于Python生成电子邮件签名的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文