<<<<<<< HEAD
<<<<<<< HEAD
#!/usr/bin/env python
# encoding: utf-8


# 导入必要模块：日志、文件操作、随机数生成、加密、HTTP请求等
import logging
import os
import random
import string
import uuid
from hashlib import sha256

# 导入第三方库：HTML过滤、Markdown转换、HTTP请求
import bleach
import markdown
import requests
# 导入Django核心模块：配置、缓存、站点模型、静态文件工具
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.cache import cache
from django.templatetags.static import static

# 创建当前模块的日志记录器
logger = logging.getLogger(__name__)


def get_max_articleid_commentid():
    """
    获取最大的文章ID和评论ID
    用于系统统计或数据同步场景，快速获取最新数据的ID边界
    """
    # 延迟导入模型（避免循环导入问题）
    from blog.models import Article
    from comments.models import Comment
    # 返回最新文章和评论的主键（ID）
    return (Article.objects.latest().pk, Comment.objects.latest().pk)


def get_sha256(str):
    """
    对字符串进行SHA256加密
    用于密码加密、数据校验等场景（如生成唯一标识）
    """
    m = sha256(str.encode('utf-8'))  # 编码为UTF-8后加密
    return m.hexdigest()  # 返回十六进制加密结果


def cache_decorator(expiration=3 * 60):
    """
    缓存装饰器：为函数添加缓存功能，减少重复计算或数据库查询
    默认缓存时间为3分钟（180秒），可通过参数调整

    Args:
        expiration: 缓存过期时间（秒）
    """

    def wrapper(func):
        def news(*args, **kwargs):
            # 尝试从请求对象中获取缓存键（适用于视图函数）
            try:
                view = args[0]
                key = view.get_cache_key()
            except:
                key = None  # 非视图函数则生成自定义键

            # 生成自定义缓存键（基于函数和参数的唯一标识）
            if not key:
                unique_str = repr((func, args, kwargs))  # 序列化函数和参数
                m = sha256(unique_str.encode('utf-8'))
                key = m.hexdigest()  # 生成唯一哈希键

            # 从缓存获取数据
            value = cache.get(key)
            if value is not None:
                # 处理空值标记（避免缓存None导致的重复计算）
                if str(value) == '__default_cache_value__':
                    return None
                else:
                    return value  # 返回缓存数据
            else:
                # 缓存未命中，执行原函数并缓存结果
                logger.debug(f'cache_decorator set cache:{func.__name__} key:{key}')
                value = func(*args, **kwargs)
                # 缓存空值时用特殊标记，避免缓存穿透
                if value is None:
                    cache.set(key, '__default_cache_value__', expiration)
                else:
                    cache.set(key, value, expiration)
                return value

        return news

    return wrapper


def expire_view_cache(path, servername, serverport, key_prefix=None):
    '''
    主动刷新视图缓存：删除指定URL路径的缓存
    用于数据更新后同步清理缓存，确保用户看到最新内容

    Args:
        path: URL路径（如'/article/1/'）
        servername: 服务器域名/主机名
        serverport: 服务器端口
        key_prefix: 缓存键前缀

    Returns:
        bool: 缓存是否成功删除
    '''
    from django.http import HttpRequest
    from django.utils.cache import get_cache_key

    # 构造模拟请求对象（用于生成缓存键）
    request = HttpRequest()
    request.META = {'SERVER_NAME': servername, 'SERVER_PORT': serverport}
    request.path = path

    # 获取缓存键并删除缓存
    key = get_cache_key(request, key_prefix=key_prefix, cache=cache)
    if key:
        logger.info(f'expire_view_cache:get key:{path}')
        if cache.get(key):
            cache.delete(key)
        return True
    return False


@cache_decorator()
def get_current_site():
    """
    获取当前站点信息（缓存装饰器确保高效获取）
    基于Django的sites框架，用于生成绝对URL等场景
    """
    site = Site.objects.get_current()
    return site


class CommonMarkdown:
    """
    Markdown转换工具类：将Markdown文本转换为HTML，并支持生成目录（TOC）
    集成代码高亮、表格等扩展功能
    """

    @staticmethod
    def _convert_markdown(value):
        """内部转换方法：执行Markdown到HTML的转换，返回内容和目录"""
        md = markdown.Markdown(
            extensions=[
                'extra',  # 基础扩展（表格、脚注等）
                'codehilite',  # 代码高亮
                'toc',  # 目录生成
                'tables',  # 表格支持
            ]
        )
        body = md.convert(value)  # 转换正文为HTML
        toc = md.toc  # 提取目录
        return body, toc

    @staticmethod
    def get_markdown_with_toc(value):
        """获取带目录的HTML内容"""
        body, toc = CommonMarkdown._convert_markdown(value)
        return body, toc

    @staticmethod
    def get_markdown(value):
        """仅获取转换后的HTML正文（不含目录）"""
        body, toc = CommonMarkdown._convert_markdown(value)
        return body


def send_email(emailto, title, content):
    """
    发送邮件的封装函数：通过信号机制发送邮件，解耦邮件发送逻辑
    实际发送由信号接收者处理（如调用Django邮件后端）
    """
    from djangoblog.blog_signals import send_email_signal
    # 发送信号，传递邮件参数
    send_email_signal.send(
        send_email.__class__,
        emailto=emailto,
        title=title,
        content=content)


def generate_code() -> str:
    """生成6位数字随机验证码，用于邮箱验证、登录等场景"""
    return ''.join(random.sample(string.digits, 6))  # 从0-9中随机选择6个数字


def parse_dict_to_url(dict):
    """
    将字典转换为URL查询参数字符串（如{'a':1,'b':2} → 'a=1&b=2'）
    自动对键值进行URL编码，支持特殊字符

    Args:
        dict: 键值对字典

    Returns:
        str: URL查询参数字符串
    """
    from urllib.parse import quote
    return '&'.join([
        f'{quote(k, safe="/")}={quote(v, safe="/")}'
        for k, v in dict.items()
    ])


def get_blog_setting():
    """
    获取博客系统设置（单例模式），并缓存结果
    包含站点名称、描述、SEO配置等核心设置

    Returns:
        BlogSettings对象：系统设置实例
    """
    # 先从缓存获取，减少数据库查询
    value = cache.get('get_blog_setting')
    if value:
        return value
    else:
        from blog.models import BlogSettings
        # 若不存在设置记录，创建默认配置
        if not BlogSettings.objects.count():
            setting = BlogSettings()
            setting.site_name = 'djangoblog'
            setting.site_description = '基于Django的博客系统'
            setting.site_seo_description = '基于Django的博客系统'
            setting.site_keywords = 'Django,Python'
            setting.article_sub_length = 300
            setting.sidebar_article_count = 10
            setting.sidebar_comment_count = 5
            setting.show_google_adsense = False
            setting.open_site_comment = True
            setting.analytics_code = ''
            setting.beian_code = ''
            setting.show_gongan_code = False
            setting.comment_need_review = False
            setting.save()
        # 获取设置并缓存
        value = BlogSettings.objects.first()
        logger.info('set cache get_blog_setting')
        cache.set('get_blog_setting', value)
        return value


def save_user_avatar(url):
    '''
    保存用户头像到本地静态目录，并返回访问URL
    用于处理第三方登录（如GitHub）的头像保存

    Args:
        url: 头像的远程URL

    Returns:
        str: 本地头像的静态文件URL（默认返回系统默认头像）
    '''
    logger.info(url)
    try:
        # 定义本地保存路径（static/avatar目录）
        basedir = os.path.join(settings.STATICFILES, 'avatar')
        # 下载头像图片
        rsp = requests.get(url, timeout=2)
        if rsp.status_code == 200:
            # 创建目录（若不存在）
            if not os.path.exists(basedir):
                os.makedirs(basedir)

            # 验证文件类型并确定扩展名
            image_extensions = ['.jpg', '.png', 'jpeg', '.gif']
            isimage = len([i for i in image_extensions if url.endswith(i)]) > 0
            ext = os.path.splitext(url)[1] if isimage else '.jpg'
            # 生成唯一文件名（UUID避免冲突）
            save_filename = str(uuid.uuid4().hex) + ext
            logger.info(f'保存用户头像:{basedir}{save_filename}')
            # 写入文件
            with open(os.path.join(basedir, save_filename), 'wb+') as file:
                file.write(rsp.content)
            # 返回静态文件URL
            return static('avatar/' + save_filename)
    except Exception as e:
        logger.error(e)
    # 失败时返回默认头像
    return static('blog/img/avatar.png')


def delete_sidebar_cache():
    """
    删除侧边栏缓存：当侧边栏数据（如链接、文章列表）更新时调用
    确保用户看到最新的侧边栏内容
    """
    from blog.models import LinkShowType
    # 生成所有侧边栏缓存键（基于链接展示类型）
    keys = ["sidebar" + x for x in LinkShowType.values]
    for k in keys:
        logger.info(f'delete sidebar key:{k}')
        cache.delete(k)


def delete_view_cache(prefix, keys):
    """
    删除模板片段缓存：用于删除指定前缀和参数的模板缓存
    如文章详情页的评论区缓存

    Args:
        prefix: 缓存前缀（模板中定义）
        keys: 缓存键参数列表
    """
    from django.core.cache.utils import make_template_fragment_key
    key = make_template_fragment_key(prefix, keys)
    cache.delete(key)


def get_resource_url():
    """
    获取静态资源基础URL
    优先使用settings中的STATIC_URL，否则基于当前站点域名生成

    Returns:
        str: 静态资源URL前缀（如'http://example.com/static/'）
    """
    if settings.STATIC_URL:
        return settings.STATIC_URL
    else:
        site = get_current_site()
        return f'http://{site.domain}/static/'


# HTML过滤配置：限制允许的标签和属性，防止XSS攻击
ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i',
                'li', 'ol', 'pre', 'strong', 'ul', 'h1', 'h2', 'p']
ALLOWED_ATTRIBUTES = {
    'a': ['href', 'title'],
    'abbr': ['title'],
    'acronym': ['title']
}


def sanitize_html(html):
    """
    净化HTML内容：仅保留允许的标签和属性，过滤恶意代码
    用于处理用户输入的HTML（如评论、文章内容），防止XSS攻击

    Args:
        html: 原始HTML字符串

    Returns:
        str: 净化后的安全HTML
    """
    return bleach.clean(
        html,
        tags=ALLOWED_TAGS,
        attributes=ALLOWED_ATTRIBUTES
    )
=======
import typing
from datetime import timedelta

from django.core.cache import cache
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _

from djangoblog.utils import send_email

_code_ttl = timedelta(minutes=5)


def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")):
    """发送重设密码验证码
    Args:
        to_mail: 接受邮箱
        subject: 邮件主题
        code: 验证码
    """
    html_content = _(
        "You are resetting the password, the verification code is：%(code)s, valid within 5 minutes, please keep it "
        "properly") % {'code': code}
    send_email([to_mail], subject, html_content)


def verify(email: str, code: str) -> typing.Optional[str]:
    """验证code是否有效
    Args:
        email: 请求邮箱
        code: 验证码
    Return:
        如果有错误就返回错误str
    Node:
        这里的错误处理不太合理，应该采用raise抛出
        否测调用方也需要对error进行处理
    """
    cache_code = get_code(email)
    if cache_code != code:
        return gettext("Verification code error")


def set_code(email: str, code: str):
    """设置code"""
    cache.set(email, code, _code_ttl.seconds)


def get_code(email: str) -> typing.Optional[str]:
    """获取code"""
    return cache.get(email)
>>>>>>> sh_branch
=======
# 导入logging模块，用于记录日志（如异常信息）
import logging

# 导入国际化翻译函数，用于生成多语言邮件内容
from django.utils.translation import gettext_lazy as _

# 导入项目工具函数，获取当前站点域名
from djangoblog.utils import get_current_site
# 导入项目工具函数，用于发送邮件
from djangoblog.utils import send_email

# 创建当前模块的日志记录器，用于记录该函数的运行日志
logger = logging.getLogger(__name__)


# 定义发送评论相关邮件的函数，接收评论对象作为参数
def send_comment_email(comment):
    # 获取当前站点的域名（用于拼接文章链接）
    site = get_current_site().domain
    # 定义邮件主题（多语言翻译，如中文为“感谢您的评论”）
    subject = _('Thanks for your comment')
    # 拼接评论所属文章的完整URL（HTTPS协议 + 域名 + 文章相对路径）
    article_url = f"https://{site}{comment.article.get_absolute_url()}"
    # 定义邮件HTML内容（多语言模板，包含感谢语、文章链接、链接提示）
    # 使用字符串格式化，替换{article_url}和{article_title}为实际值
    html_content = _("""<p>Thank you very much for your comments on this site</p>
                    You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
                    to review your comments,
                    Thank you again!
                    <br />
                    If the link above cannot be opened, please copy this link to your browser.
                    %(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
    tomail = comment.author.email
    # 调用send_email函数发送邮件：收件人列表、主题、HTML内容
    send_email([tomail], subject, html_content)

    # 尝试给父评论作者发送“评论被回复”的邮件（若当前评论是回复）
    try:
        # 判断当前评论是否有父评论（即是否为回复）
        if comment.parent_comment:
            # 定义回复通知的HTML邮件内容（多语言模板）
            # 包含父评论所属文章链接、父评论内容、查看提示
            html_content = _("""Your comment on <a href="%(article_url)s" rel="bookmark">%(article_title)s</a><br/> has 
                   received a reply. <br/> %(comment_body)s
                    <br/>   
                    go check it out!
                     <br/>
                     If the link above cannot be opened, please copy this link to your browser.
                     %(article_url)s
                    """) % {'article_url': article_url, 'article_title': comment.article.title,
                            'comment_body': comment.parent_comment.body}
            # 获取父评论作者的邮箱（回复通知的收件人）
            tomail = comment.parent_comment.author.email
            # 发送回复通知邮件
            send_email([tomail], subject, html_content)
    # 捕获所有异常，避免发送失败影响主流程
    except Exception as e:

        logger.error(e)
>>>>>>> zh_branch
