diff --git a/src/servermanager/MemcacheStorage.py b/src/servermanager/MemcacheStorage.py new file mode 100644 index 0000000..5610ccd --- /dev/null +++ b/src/servermanager/MemcacheStorage.py @@ -0,0 +1,88 @@ +# 导入WeRoBot框架的会话存储基类和工具函数 +from werobot.session import SessionStorage +from werobot.utils import json_loads, json_dumps + +# 导入Django项目的缓存工具 +from djangoblog.utils import cache + + +class MemcacheStorage(SessionStorage): + """ + 基于Memcached的WeRoBot会话存储实现 + 继承自WeRoBot的SessionStorage基类,用于将会话数据存储到Memcached中 + """ + + def __init__(self, prefix='ws_'): + """ + 初始化方法 + + Args: + prefix (str): 缓存键的前缀,默认为'ws_',用于区分其他缓存数据 + """ + self.prefix = prefix # 设置缓存键前缀 + self.cache = cache # Django缓存实例(通常是Memcached) + + @property + def is_available(self): + """ + 检查缓存存储是否可用的属性 + + Returns: + bool: 如果缓存系统工作正常返回True,否则返回False + """ + value = "1" # 测试值 + # 尝试设置一个测试键值对 + self.set('checkavaliable', value=value) + # 读取测试值并比较,验证缓存系统是否正常工作 + return value == self.get('checkavaliable') + + def key_name(self, s): + """ + 生成完整的缓存键名 + + Args: + s (str): 原始会话ID + + Returns: + str: 添加前缀后的完整缓存键名 + """ + return '{prefix}{s}'.format(prefix=self.prefix, s=s) + + def get(self, id): + """ + 根据会话ID获取会话数据 + + Args: + id (str): 会话ID + + Returns: + dict: 解析后的会话数据字典,如果不存在返回空字典 + """ + id = self.key_name(id) # 生成完整缓存键 + # 从缓存获取数据,如果不存在则返回空JSON对象 + session_json = self.cache.get(id) or '{}' + # 将JSON字符串解析为Python字典 + return json_loads(session_json) + + def set(self, id, value): + """ + 设置会话数据 + + Args: + id (str): 会话ID + value (dict): 要存储的会话数据字典 + """ + id = self.key_name(id) # 生成完整缓存键 + # 将会话数据序列化为JSON字符串并存储到缓存 + self.cache.set(id, json_dumps(value)) + + def delete(self, id): + """ + 删除指定会话ID的数据 + + Args: + id (str): 要删除的会话ID + """ + id = self.key_name(id) # 生成完整缓存键 + # 从缓存中删除对应的数据 + self.cache.delete(id) \ No newline at end of file diff --git a/src/servermanager/__init__.py b/src/servermanager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/servermanager/__pycache__/MemcacheStorage.cpython-314.pyc b/src/servermanager/__pycache__/MemcacheStorage.cpython-314.pyc new file mode 100644 index 0000000..264ee58 Binary files /dev/null and b/src/servermanager/__pycache__/MemcacheStorage.cpython-314.pyc differ diff --git a/src/servermanager/__pycache__/MemcacheStorage.cpython-38.pyc b/src/servermanager/__pycache__/MemcacheStorage.cpython-38.pyc new file mode 100644 index 0000000..68cfdc0 Binary files /dev/null and b/src/servermanager/__pycache__/MemcacheStorage.cpython-38.pyc differ diff --git a/src/servermanager/__pycache__/__init__.cpython-314.pyc b/src/servermanager/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..fbaf8ef Binary files /dev/null and b/src/servermanager/__pycache__/__init__.cpython-314.pyc differ diff --git a/src/servermanager/__pycache__/__init__.cpython-38.pyc b/src/servermanager/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..fb4faef Binary files /dev/null and b/src/servermanager/__pycache__/__init__.cpython-38.pyc differ diff --git a/src/servermanager/__pycache__/admin.cpython-314.pyc b/src/servermanager/__pycache__/admin.cpython-314.pyc new file mode 100644 index 0000000..e49d82a Binary files /dev/null and b/src/servermanager/__pycache__/admin.cpython-314.pyc differ diff --git a/src/servermanager/__pycache__/admin.cpython-38.pyc b/src/servermanager/__pycache__/admin.cpython-38.pyc new file mode 100644 index 0000000..0f7c0f7 Binary files /dev/null and b/src/servermanager/__pycache__/admin.cpython-38.pyc differ diff --git a/src/servermanager/__pycache__/apps.cpython-314.pyc b/src/servermanager/__pycache__/apps.cpython-314.pyc new file mode 100644 index 0000000..20c51d0 Binary files /dev/null and b/src/servermanager/__pycache__/apps.cpython-314.pyc differ diff --git a/src/servermanager/__pycache__/apps.cpython-38.pyc b/src/servermanager/__pycache__/apps.cpython-38.pyc new file mode 100644 index 0000000..784347e Binary files /dev/null and b/src/servermanager/__pycache__/apps.cpython-38.pyc differ diff --git a/src/servermanager/__pycache__/models.cpython-314.pyc b/src/servermanager/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000..8ed5a0c Binary files /dev/null and b/src/servermanager/__pycache__/models.cpython-314.pyc differ diff --git a/src/servermanager/__pycache__/models.cpython-38.pyc b/src/servermanager/__pycache__/models.cpython-38.pyc new file mode 100644 index 0000000..1b44f1b Binary files /dev/null and b/src/servermanager/__pycache__/models.cpython-38.pyc differ diff --git a/src/servermanager/__pycache__/robot.cpython-314.pyc b/src/servermanager/__pycache__/robot.cpython-314.pyc new file mode 100644 index 0000000..f120130 Binary files /dev/null and b/src/servermanager/__pycache__/robot.cpython-314.pyc differ diff --git a/src/servermanager/__pycache__/robot.cpython-38.pyc b/src/servermanager/__pycache__/robot.cpython-38.pyc new file mode 100644 index 0000000..46f3054 Binary files /dev/null and b/src/servermanager/__pycache__/robot.cpython-38.pyc differ diff --git a/src/servermanager/__pycache__/urls.cpython-314.pyc b/src/servermanager/__pycache__/urls.cpython-314.pyc new file mode 100644 index 0000000..96307d2 Binary files /dev/null and b/src/servermanager/__pycache__/urls.cpython-314.pyc differ diff --git a/src/servermanager/__pycache__/urls.cpython-38.pyc b/src/servermanager/__pycache__/urls.cpython-38.pyc new file mode 100644 index 0000000..22ca2cb Binary files /dev/null and b/src/servermanager/__pycache__/urls.cpython-38.pyc differ diff --git a/src/servermanager/admin.py b/src/servermanager/admin.py new file mode 100644 index 0000000..b8cdfe8 --- /dev/null +++ b/src/servermanager/admin.py @@ -0,0 +1,63 @@ +# 导入Django管理模块 +from django.contrib import admin +# 导入自定义模型(假设已在同一目录的models.py中定义) +from .models import commands, EmailSendLog + + +# 注册Commands模型到Django管理后台 +class CommandsAdmin(admin.ModelAdmin): + """ + 命令模型的管理界面配置类 + 用于自定义commands模型在Django admin中的显示和行为 + """ + + # 设置在管理列表页面显示的字段 + list_display = ('title', 'command', 'describe') + # 字段说明: + # - title: 命令标题 + # - command: 具体命令内容 + # - describe: 命令描述 + + +# 注册EmailSendLog模型到Django管理后台 +class EmailSendLogAdmin(admin.ModelAdmin): + """ + 邮件发送日志模型的管理界面配置类 + 用于自定义EmailSendLog模型在Django admin中的显示和行为 + """ + + # 设置在管理列表页面显示的字段 + list_display = ('title', 'emailto', 'send_result', 'creation_time') + # 字段说明: + # - title: 邮件标题 + # - emailto: 收件人 + # - send_result: 发送结果 + # - creation_time: 创建时间 + + # 设置只读字段,这些字段在编辑页面不可修改 + readonly_fields = ( + 'title', # 邮件标题(只读) + 'emailto', # 收件人(只读) + 'send_result', # 发送结果(只读) + 'creation_time', # 创建时间(只读) + 'content' # 邮件内容(只读) + ) + + def has_add_permission(self, request): + """ + 重写添加权限方法,禁止在管理后台手动添加邮件日志记录 + + Args: + request: HTTP请求对象 + + Returns: + bool: 总是返回False,禁止添加操作 + """ + return False + # 这样设计是因为邮件发送日志应该由系统自动创建 + # 而不是手动添加,保证日志的真实性和完整性 + + +# 将模型注册到Django管理后台 +admin.site.register(commands, CommandsAdmin) +admin.site.register(EmailSendLog, EmailSendLogAdmin) \ No newline at end of file diff --git a/src/servermanager/api/__init__.py b/src/servermanager/api/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/servermanager/api/__init__.py @@ -0,0 +1 @@ + diff --git a/src/servermanager/api/__pycache__/__init__.cpython-314.pyc b/src/servermanager/api/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..f04b8ff Binary files /dev/null and b/src/servermanager/api/__pycache__/__init__.cpython-314.pyc differ diff --git a/src/servermanager/api/__pycache__/__init__.cpython-38.pyc b/src/servermanager/api/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..3f9bbc7 Binary files /dev/null and b/src/servermanager/api/__pycache__/__init__.cpython-38.pyc differ diff --git a/src/servermanager/api/__pycache__/blogapi.cpython-314.pyc b/src/servermanager/api/__pycache__/blogapi.cpython-314.pyc new file mode 100644 index 0000000..d6bf824 Binary files /dev/null and b/src/servermanager/api/__pycache__/blogapi.cpython-314.pyc differ diff --git a/src/servermanager/api/__pycache__/blogapi.cpython-38.pyc b/src/servermanager/api/__pycache__/blogapi.cpython-38.pyc new file mode 100644 index 0000000..6beae76 Binary files /dev/null and b/src/servermanager/api/__pycache__/blogapi.cpython-38.pyc differ diff --git a/src/servermanager/api/__pycache__/commonapi.cpython-314.pyc b/src/servermanager/api/__pycache__/commonapi.cpython-314.pyc new file mode 100644 index 0000000..1664c95 Binary files /dev/null and b/src/servermanager/api/__pycache__/commonapi.cpython-314.pyc differ diff --git a/src/servermanager/api/__pycache__/commonapi.cpython-38.pyc b/src/servermanager/api/__pycache__/commonapi.cpython-38.pyc new file mode 100644 index 0000000..4784a47 Binary files /dev/null and b/src/servermanager/api/__pycache__/commonapi.cpython-38.pyc differ diff --git a/src/servermanager/api/blogapi.py b/src/servermanager/api/blogapi.py new file mode 100644 index 0000000..d9e4a7d --- /dev/null +++ b/src/servermanager/api/blogapi.py @@ -0,0 +1,78 @@ +# 导入Haystack搜索查询集 +from haystack.query import SearchQuerySet + +# 导入博客模型 +from blog.models import Article, Category + + +class BlogApi: + """ + 博客API类 + 提供博客文章的搜索、分类获取和最近文章等功能 + """ + + def __init__(self): + """ + 初始化方法 + 创建搜索查询集实例并设置最大返回数量 + """ + # 创建搜索查询集实例 + self.searchqueryset = SearchQuerySet() + # 初始化空查询,准备后续搜索 + self.searchqueryset.auto_query('') + # 设置最大返回文章数量 + self.__max_takecount__ = 8 + + def search_articles(self, query): + """ + 搜索文章方法 + + Args: + query (str): 搜索关键词 + + Returns: + SearchQuerySet: 包含搜索结果的查询集,最多返回__max_takecount__条结果 + """ + # 使用Haystack进行自动搜索查询 + sqs = self.searchqueryset.auto_query(query) + # 加载所有相关对象(避免N+1查询问题) + sqs = sqs.load_all() + # 返回前__max_takecount__条结果 + return sqs[:self.__max_takecount__] + + def get_category_lists(self): + """ + 获取所有分类列表 + + Returns: + QuerySet: 包含所有分类的查询集 + """ + return Category.objects.all() + + def get_category_articles(self, categoryname): + """ + 根据分类名称获取该分类下的文章 + + Args: + categoryname (str): 分类名称 + + Returns: + QuerySet or None: 包含该分类文章的查询集(最多__max_takecount__条),如果分类不存在返回None + """ + # 根据分类名称过滤文章 + articles = Article.objects.filter(category__name=categoryname) + # 如果找到文章,返回前__max_takecount__条 + if articles: + return articles[:self.__max_takecount__] + # 没有找到文章返回None + return None + + def get_recent_articles(self): + """ + 获取最近的文章 + + Returns: + QuerySet: 包含最近文章的查询集,最多返回__max_takecount__条 + """ + # 获取所有文章并按时间排序(假设Article模型的默认排序是按时间倒序) + return Article.objects.all()[:self.__max_takecount__] \ No newline at end of file diff --git a/src/servermanager/api/commonapi.py b/src/servermanager/api/commonapi.py new file mode 100644 index 0000000..98fbc0e --- /dev/null +++ b/src/servermanager/api/commonapi.py @@ -0,0 +1,129 @@ +# 导入必要的模块 +import logging +import os + +import openai # OpenAI API客户端 + +# 导入自定义的Django模型 +from servermanager.models import commands + +# 获取当前模块的日志记录器 +logger = logging.getLogger(__name__) + +# 配置OpenAI API密钥,从环境变量中获取 +openai.api_key = os.environ.get('OPENAI_API_KEY') +# 如果设置了HTTP代理,配置OpenAI使用代理 +if os.environ.get('HTTP_PROXY'): + openai.proxy = os.environ.get('HTTP_PROXY') + + +class ChatGPT: + """ + ChatGPT API封装类 + 提供与OpenAI GPT模型交互的静态方法 + """ + + @staticmethod + def chat(prompt): + """ + 与ChatGPT进行对话的静态方法 + + Args: + prompt (str): 用户输入的提示词 + + Returns: + str: ChatGPT的回复内容,如果出错返回错误信息 + """ + try: + # 调用OpenAI ChatCompletion API创建对话完成 + completion = openai.ChatCompletion.create( + model="gpt-3.5-turbo", # 指定使用gpt-3.5-turbo模型 + messages=[{"role": "user", "content": prompt}] # 设置对话消息 + ) + # 返回第一个选择中的消息内容 + return completion.choices[0].message.content + except Exception as e: + # 记录错误日志 + logger.error(e) + # 返回用户友好的错误信息 + return "服务器出错了" + + +class CommandHandler: + """ + 命令处理器类 + 用于管理和执行预定义的系统命令 + """ + + def __init__(self): + """ + 初始化方法 + 从数据库加载所有预定义命令 + """ + # 从数据库获取所有命令对象 + self.commands = commands.objects.all() + + def run(self, title): + """ + 运行指定标题的命令 + + Args: + title (str): 命令标题 + + Returns: + str: 命令执行结果或帮助信息 + """ + # 使用filter和lambda函数查找标题匹配的命令(不区分大小写) + cmd = list( + filter( + lambda x: x.title.upper() == title.upper(), # 不区分大小写比较 + self.commands)) + + # 如果找到匹配的命令 + if cmd: + # 执行找到的第一个命令 + return self.__run_command__(cmd[0].command) + else: + # 未找到命令时返回帮助提示 + return "未找到相关命令,请输入helpme获得帮助。" + + def __run_command__(self, cmd): + """ + 私有方法:实际执行系统命令 + + Args: + cmd (str): 要执行的系统命令 + + Returns: + str: 命令执行结果或错误信息 + """ + try: + # 使用os.popen执行系统命令并读取输出 + res = os.popen(cmd).read() + return res + except BaseException: + # 捕获所有异常,返回错误信息 + return '命令执行出错!' + + def get_help(self): + """ + 获取所有命令的帮助信息 + + Returns: + str: 格式化的命令帮助信息,包含所有命令的标题和描述 + """ + rsp = '' + # 遍历所有命令,格式化输出 + for cmd in self.commands: + rsp += '{c}:{d}\n'.format(c=cmd.title, d=cmd.describe) + return rsp + + +# 主程序入口 +if __name__ == '__main__': + # 创建ChatGPT实例 + chatbot = ChatGPT() + # 测试提示词 + prompt = "写一篇1000字关于AI的论文" + # 调用chat方法并打印结果 + print(chatbot.chat(prompt)) \ No newline at end of file diff --git a/src/servermanager/apps.py b/src/servermanager/apps.py new file mode 100644 index 0000000..8098071 --- /dev/null +++ b/src/servermanager/apps.py @@ -0,0 +1,31 @@ +# 导入Django的应用配置基类 +from django.apps import AppConfig + + +class ServermanagerConfig(AppConfig): + """ + Servermanager应用的配置类 + 继承自Django的AppConfig基类,用于配置servermanager应用的相关设置 + + 这个类主要用于: + 1. 定义应用的配置信息 + 2. 在Django启动时执行应用初始化代码 + 3. 配置应用的元数据 + """ + + # 指定应用的Python路径(必需字段) + # 这应该与settings.INSTALLED_APPS中使用的路径匹配 + name = 'servermanager' + # 说明: + # - 'servermanager' 表示应用的Python导入路径 + # - Django使用这个名称来识别和应用配置 + + # 可选配置字段示例(当前代码中未使用): + # + # verbose_name = '服务器管理器' # 应用的可读名称 + # default_auto_field = 'django.db.models.BigAutoField' # 默认主键字段类型 + # + # 可以在ready()方法中添加应用启动时的初始化代码: + # def ready(self): + # # 应用启动时执行的初始化代码 + # import servermanager.signals # 例如:导入信号处理器 \ No newline at end of file diff --git a/src/servermanager/migrations/0001_initial.py b/src/servermanager/migrations/0001_initial.py new file mode 100644 index 0000000..0cfdd13 --- /dev/null +++ b/src/servermanager/migrations/0001_initial.py @@ -0,0 +1,67 @@ +# Generated by Django 4.1.7 on 2023-03-02 07:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + """ + Django数据库迁移类 + 迁移用于创建数据库表结构,这是初始迁移(initial=True) + """ + + initial = True # 标记这是初始迁移 + + dependencies = [ + # 依赖关系列表,此迁移不依赖其他迁移 + ] + + operations = [ + # 迁移操作列表 + + # 创建命令表 + migrations.CreateModel( + name='commands', # 模型名称 + fields=[ + # 主键字段,自增BigInteger类型 + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + # 命令标题字段,最大长度300字符 + ('title', models.CharField(max_length=300, verbose_name='命令标题')), + # 命令字段,存储具体命令内容,最大长度2000字符 + ('command', models.CharField(max_length=2000, verbose_name='命令')), + # 命令描述字段,最大长度300字符 + ('describe', models.CharField(max_length=300, verbose_name='命令描述')), + # 创建时间字段,自动设置为记录创建时的时间 + ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + # 最后修改时间字段,每次保存时自动更新为当前时间 + ('last_mod_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), + ], + options={ + 'verbose_name': '命令', # 单数形式的可读名称 + 'verbose_name_plural': '命令', # 复数形式的可读名称 + }, + ), + + # 创建邮件发送日志表 + migrations.CreateModel( + name='EmailSendLog', # 模型名称 + fields=[ + # 主键字段,自增BigInteger类型 + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + # 收件人字段,存储收件人邮箱地址,最大长度300字符 + ('emailto', models.CharField(max_length=300, verbose_name='收件人')), + # 邮件标题字段,最大长度2000字符 + ('title', models.CharField(max_length=2000, verbose_name='邮件标题')), + # 邮件内容字段,文本类型,无长度限制 + ('content', models.TextField(verbose_name='邮件内容')), + # 发送结果字段,布尔类型,默认值为False(发送失败) + ('send_result', models.BooleanField(default=False, verbose_name='结果')), + # 创建时间字段,自动设置为记录创建时的时间 + ('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), + ], + options={ + 'verbose_name': '邮件发送log', # 单数形式的可读名称 + 'verbose_name_plural': '邮件发送log', # 复数形式的可读名称 + 'ordering': ['-created_time'], # 默认按创建时间降序排列 + }, + ), + ] \ No newline at end of file diff --git a/src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py b/src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py new file mode 100644 index 0000000..67e8c70 --- /dev/null +++ b/src/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py @@ -0,0 +1,50 @@ +# Generated by Django 4.2.5 on 2023-09-06 13:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + """ + Django数据库迁移类 + 这是一个数据模型变更的迁移文件,用于修改已有表结构 + """ + + dependencies = [ + # 依赖关系:此迁移依赖于servermanager应用的0001_initial初始迁移 + ('servermanager', '0001_initial'), + ] + + operations = [ + # 迁移操作列表 + + # 修改EmailSendLog模型的元选项 + migrations.AlterModelOptions( + name='emailsendlog', # 模型名称 + options={ + 'ordering': ['-creation_time'], # 改为按creation_time字段降序排列 + 'verbose_name': '邮件发送log', # 单数形式的可读名称 + 'verbose_name_plural': '邮件发送log', # 复数形式的可读名称 + }, + ), + + # 重命名commands模型的created_time字段为creation_time + migrations.RenameField( + model_name='commands', # 模型名称 + old_name='created_time', # 原字段名 + new_name='creation_time', # 新字段名 + ), + + # 重命名commands模型的last_mod_time字段为last_modify_time + migrations.RenameField( + model_name='commands', # 模型名称 + old_name='last_mod_time', # 原字段名 + new_name='last_modify_time', # 新字段名 + ), + + # 重命名emailsendlog模型的created_time字段为creation_time + migrations.RenameField( + model_name='emailsendlog', # 模型名称 + old_name='created_time', # 原字段名 + new_name='creation_time', # 新字段名 + ), + ] \ No newline at end of file diff --git a/src/servermanager/migrations/__init__.py b/src/servermanager/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/servermanager/migrations/__pycache__/0001_initial.cpython-314.pyc b/src/servermanager/migrations/__pycache__/0001_initial.cpython-314.pyc new file mode 100644 index 0000000..d4b8c2b Binary files /dev/null and b/src/servermanager/migrations/__pycache__/0001_initial.cpython-314.pyc differ diff --git a/src/servermanager/migrations/__pycache__/0001_initial.cpython-38.pyc b/src/servermanager/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000..39e38ec Binary files /dev/null and b/src/servermanager/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/src/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-314.pyc b/src/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-314.pyc new file mode 100644 index 0000000..df5b56b Binary files /dev/null and b/src/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-314.pyc differ diff --git a/src/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-38.pyc b/src/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-38.pyc new file mode 100644 index 0000000..116663c Binary files /dev/null and b/src/servermanager/migrations/__pycache__/0002_alter_emailsendlog_options_and_more.cpython-38.pyc differ diff --git a/src/servermanager/migrations/__pycache__/__init__.cpython-314.pyc b/src/servermanager/migrations/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..37386eb Binary files /dev/null and b/src/servermanager/migrations/__pycache__/__init__.cpython-314.pyc differ diff --git a/src/servermanager/migrations/__pycache__/__init__.cpython-38.pyc b/src/servermanager/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..2a76714 Binary files /dev/null and b/src/servermanager/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/src/servermanager/models.py b/src/servermanager/models.py new file mode 100644 index 0000000..8539cf2 --- /dev/null +++ b/src/servermanager/models.py @@ -0,0 +1,82 @@ +# 导入Django的模型模块 +from django.db import models + + +# 创建命令模型(数据库表) +class commands(models.Model): + """ + 命令模型类 + 用于存储系统命令的相关信息 + """ + + # 命令标题字段,CharField类型,最大长度300,在admin中显示为'命令标题' + title = models.CharField('命令标题', max_length=300) + + # 命令内容字段,CharField类型,最大长度2000,在admin中显示为'命令' + command = models.CharField('命令', max_length=2000) + + # 命令描述字段,CharField类型,最大长度300,在admin中显示为'命令描述' + describe = models.CharField('命令描述', max_length=300) + + # 创建时间字段,DateTimeField类型,自动设置为对象创建时的时间 + creation_time = models.DateTimeField('创建时间', auto_now_add=True) + + # 最后修改时间字段,DateTimeField类型,自动更新为对象最后修改的时间 + last_modify_time = models.DateTimeField('修改时间', auto_now=True) + + def __str__(self): + """ + 定义对象的字符串表示形式 + 在Django admin和shell中显示时使用 + + Returns: + str: 命令标题 + """ + return self.title + + class Meta: + """ + 模型的元数据配置类 + 用于定义模型的其他选项 + """ + verbose_name = '命令' # 单数形式的可读名称 + verbose_name_plural = verbose_name # 复数形式的可读名称(与单数相同) + + +class EmailSendLog(models.Model): + """ + 邮件发送日志模型类 + 用于记录邮件发送的历史记录和结果 + """ + + # 收件人字段,CharField类型,最大长度300,在admin中显示为'收件人' + emailto = models.CharField('收件人', max_length=300) + + # 邮件标题字段,CharField类型,最大长度2000,在admin中显示为'邮件标题' + title = models.CharField('邮件标题', max_length=2000) + + # 邮件内容字段,TextField类型,无长度限制,在admin中显示为'邮件内容' + content = models.TextField('邮件内容') + + # 发送结果字段,BooleanField类型,默认值为False(发送失败),在admin中显示为'结果' + send_result = models.BooleanField('结果', default=False) + + # 创建时间字段,DateTimeField类型,自动设置为对象创建时的时间 + creation_time = models.DateTimeField('创建时间', auto_now_add=True) + + def __str__(self): + """ + 定义对象的字符串表示形式 + + Returns: + str: 邮件标题 + """ + return self.title + + class Meta: + """ + 模型的元数据配置类 + """ + verbose_name = '邮件发送log' # 单数形式的可读名称 + verbose_name_plural = verbose_name # 复数形式的可读名称 + ordering = ['-creation_time'] # 默认按创建时间降序排列(最新的在前) \ No newline at end of file diff --git a/src/servermanager/robot.py b/src/servermanager/robot.py new file mode 100644 index 0000000..1762ed8 --- /dev/null +++ b/src/servermanager/robot.py @@ -0,0 +1,255 @@ +# 导入必要的模块 +import logging +import os +import re + +import jsonpickle # 用于对象序列化 +from django.conf import settings +from werobot import WeRoBot # 微信机器人框架 +from werobot.replies import ArticlesReply, Article # 微信图文回复 +from werobot.session.filestorage import FileStorage # 文件会话存储 + +from djangoblog.utils import get_sha256 # 加密工具函数 +from servermanager.api.blogapi import BlogApi # 博客API +from servermanager.api.commonapi import ChatGPT, CommandHandler # 聊天和命令处理 +from .MemcacheStorage import MemcacheStorage # 自定义缓存存储 + +# 初始化微信机器人,从环境变量获取token,默认使用'lylinux' +robot = WeRoBot(token=os.environ.get('DJANGO_WEROBOT_TOKEN') or 'lylinux', enable_session=True) + +# 初始化Memcached会话存储 +memstorage = MemcacheStorage() +# 检查Memcached是否可用,如果可用则使用,否则使用文件存储 +if memstorage.is_available: + robot.config['SESSION_STORAGE'] = memstorage +else: + # 如果存在旧的会话文件则删除 + if os.path.exists(os.path.join(settings.BASE_DIR, 'werobot_session')): + os.remove(os.path.join(settings.BASE_DIR, 'werobot_session')) + robot.config['SESSION_STORAGE'] = FileStorage(filename='werobot_session') + +# 初始化API处理器 +blogapi = BlogApi() +cmd_handler = CommandHandler() +logger = logging.getLogger(__name__) + + +def convert_to_article_reply(articles, message): + """ + 将文章列表转换为微信图文回复格式 + + Args: + articles: 文章对象列表 + message: 微信消息对象 + + Returns: + ArticlesReply: 图文回复对象 + """ + reply = ArticlesReply(message=message) + from blog.templatetags.blog_tags import truncatechars_content # 导入内容截断过滤器 + + for post in articles: + # 使用正则表达式从文章内容中提取第一张图片 + imgs = re.findall(r'(?:http\:|https\:)?\/\/.*\.(?:png|jpg)', post.body) + imgurl = '' + if imgs: + imgurl = imgs[0] # 使用第一张图片作为封面 + + # 创建图文消息条目 + article = Article( + title=post.title, # 文章标题 + description=truncatechars_content(post.body), # 截断后的文章内容作为描述 + img=imgurl, # 封面图片URL + url=post.get_full_url() # 文章完整URL + ) + reply.add_article(article) + return reply + + +@robot.filter(re.compile(r"^\?.*")) +def search(message, session): + """ + 文章搜索功能 + 触发方式: ?关键字 + 例如: ?python + """ + s = message.content + searchstr = str(s).replace('?', '') # 移除问号获取搜索关键词 + result = blogapi.search_articles(searchstr) # 调用搜索API + + if result: + # 将搜索结果转换为文章对象列表 + articles = list(map(lambda x: x.object, result)) + reply = convert_to_article_reply(articles, message) + return reply + else: + return '没有找到相关文章。' + + +@robot.filter(re.compile(r'^category\s*$', re.I)) +def category(message, session): + """ + 获取所有文章分类 + 触发方式: category + """ + categorys = blogapi.get_category_lists() + content = ','.join(map(lambda x: x.name, categorys)) + return '所有文章分类目录:' + content + + +@robot.filter(re.compile(r'^recent\s*$', re.I)) +def recents(message, session): + """ + 获取最新文章 + 触发方式: recent + """ + articles = blogapi.get_recent_articles() + if articles: + reply = convert_to_article_reply(articles, message) + return reply + else: + return "暂时还没有文章" + + +@robot.filter(re.compile('^help$', re.I)) +def help(message, session): + """ + 获取帮助信息 + 触发方式: help + """ + return '''欢迎关注! + 默认会与图灵机器人聊天~~ + 你可以通过下面这些命令来获得信息 + ?关键字搜索文章. + 如?python. + category获得文章分类目录及文章数. + category-***获得该分类目录文章 + 如category-python + recent获得最新文章 + help获得帮助. + weather:获得天气 + 如weather:西安 + idcard:获得身份证信息 + 如idcard:61048119xxxxxxxxxx + music:音乐搜索 + 如music:阴天快乐 + PS:以上标点符号都不支持中文标点~~ + ''' + + +@robot.filter(re.compile(r'^weather\:.*$', re.I)) +def weather(message, session): + """天气查询功能(建设中)""" + return "建设中..." + + +@robot.filter(re.compile(r'^idcard\:.*$', re.I)) +def idcard(message, session): + """身份证查询功能(建设中)""" + return "建设中..." + + +@robot.handler +def echo(message, session): + """ + 默认消息处理器 + 处理所有未匹配到其他过滤器的消息 + """ + handler = MessageHandler(message, session) + return handler.handler() + + +class MessageHandler: + """消息处理器类,负责处理用户会话和权限验证""" + + def __init__(self, message, session): + userid = message.source + self.message = message + self.session = session + self.userid = userid + try: + # 从会话中获取用户信息 + info = session[userid] + self.userinfo = jsonpickle.decode(info) + except Exception as e: + # 如果会话中没有用户信息,创建新的用户信息对象 + userinfo = WxUserInfo() + self.userinfo = userinfo + + @property + def is_admin(self): + """检查用户是否为管理员""" + return self.userinfo.isAdmin + + @property + def is_password_set(self): + """检查管理员密码是否已设置""" + return self.userinfo.isPasswordSet + + def save_session(self): + """保存用户信息到会话""" + info = jsonpickle.encode(self.userinfo) + self.session[self.userid] = info + + def handler(self): + """主消息处理方法""" + info = self.message.content + + # 管理员退出命令 + if self.userinfo.isAdmin and info.upper() == 'EXIT': + self.userinfo = WxUserInfo() + self.save_session() + return "退出成功" + + # 进入管理员模式 + if info.upper() == 'ADMIN': + self.userinfo.isAdmin = True + self.save_session() + return "输入管理员密码" + + # 管理员密码验证 + if self.userinfo.isAdmin and not self.userinfo.isPasswordSet: + passwd = settings.WXADMIN # 从设置获取管理员密码 + if settings.TESTING: # 测试环境下使用简单密码 + passwd = '123' + # 验证密码(双重SHA256加密) + if passwd.upper() == get_sha256(get_sha256(info)).upper(): + self.userinfo.isPasswordSet = True + self.save_session() + return "验证通过,请输入命令或者要执行的命令代码:输入helpme获得帮助" + else: + # 密码错误次数限制 + if self.userinfo.Count >= 3: + self.userinfo = WxUserInfo() + self.save_session() + return "超过验证次数" + self.userinfo.Count += 1 + self.save_session() + return "验证失败,请重新输入管理员密码:" + + # 管理员命令执行 + if self.userinfo.isAdmin and self.userinfo.isPasswordSet: + # 确认执行命令 + if self.userinfo.Command != '' and info.upper() == 'Y': + return cmd_handler.run(self.userinfo.Command) + else: + # 获取命令帮助 + if info.upper() == 'HELPME': + return cmd_handler.get_help() + # 保存命令等待确认 + self.userinfo.Command = info + self.save_session() + return "确认执行: " + info + " 命令?" + + # 默认使用ChatGPT回复 + return ChatGPT.chat(info) + + +class WxUserInfo(): + """微信用户信息类,用于存储用户会话状态""" + + def __init__(self): + self.isAdmin = False # 是否为管理员 + self.isPasswordSet = False # 是否通过密码验证 + self.Count = 0 # 密码尝试次数 + self.Command = '' # 待确认的命令 \ No newline at end of file diff --git a/src/servermanager/tests.py b/src/servermanager/tests.py new file mode 100644 index 0000000..2f57a6c --- /dev/null +++ b/src/servermanager/tests.py @@ -0,0 +1,121 @@ +# 导入Django测试相关模块 +from django.test import Client, RequestFactory, TestCase +from django.utils import timezone +# 导入WeRoBot文本消息类 +from werobot.messages.messages import TextMessage + +# 导入应用模型 +from accounts.models import BlogUser +from blog.models import Category, Article +# 导入要测试的API类 +from servermanager.api.commonapi import ChatGPT +from .models import commands +# 导入要测试的机器人处理类和方法 +from .robot import MessageHandler, CommandHandler +from .robot import search, category, recents + + +# 创建服务器管理器测试类 +class ServerManagerTest(TestCase): + """ + 服务器管理器功能测试类 + 测试servermanager应用的各项功能 + """ + + def setUp(self): + """ + 测试初始化方法 + 在每个测试方法执行前运行,用于设置测试环境 + """ + # 创建测试客户端,用于模拟HTTP请求 + self.client = Client() + # 创建请求工厂,用于构建请求对象 + self.factory = RequestFactory() + + def test_chat_gpt(self): + """ + 测试ChatGPT功能 + 验证ChatGPT API是否能正常返回响应 + """ + content = ChatGPT.chat("你好") # 发送测试消息 + self.assertIsNotNone(content) # 断言响应内容不为空 + + def test_validate_comment(self): + """ + 综合测试方法 + 测试博客系统和微信机器人的完整功能链 + """ + # 创建超级用户 + user = BlogUser.objects.create_superuser( + email="liangliangyy1@gmail.com", + username="liangliangyy1", + password="liangliangyy1") + + # 使用创建的超级用户登录 + self.client.login(username='liangliangyy1', password='liangliangyy1') + + # 创建分类 + c = Category() + c.name = "categoryccc" + c.save() + + # 创建文章 + article = Article() + article.title = "nicetitleccc" + article.body = "nicecontentccc" + article.author = user + article.category = c + article.type = 'a' # 文章类型 + article.status = 'p' # 发布状态 + article.save() + + # 测试搜索功能 + s = TextMessage([]) # 创建空的文本消息对象 + s.content = "nice" # 设置消息内容 + rsp = search(s, None) # 调用搜索函数 + # 注意:这里没有对搜索结果进行断言,可能需要补充 + + # 测试分类功能 + rsp = category(None, None) # 调用分类函数 + self.assertIsNotNone(rsp) # 断言响应不为空 + + # 测试最近文章功能 + rsp = recents(None, None) # 调用最近文章函数 + self.assertTrue(rsp != '暂时还没有文章') # 断言有文章返回 + + # 测试命令功能 + # 创建测试命令 + cmd = commands() + cmd.title = "test" + cmd.command = "ls" # Linux列表命令 + cmd.describe = "test" + cmd.save() + + # 测试命令处理器 + cmdhandler = CommandHandler() + rsp = cmdhandler.run('test') # 执行测试命令 + self.assertIsNotNone(rsp) # 断言命令执行结果不为空 + + # 测试消息处理器 + s.source = 'u' # 设置消息来源 + s.content = 'test' # 设置消息内容 + msghandler = MessageHandler(s, {}) # 创建消息处理器 + + # 注释掉的管理员权限设置 + # msghandler.userinfo.isPasswordSet = True + # msghandler.userinfo.isAdmin = True + + # 测试各种消息处理场景 + msghandler.handler() # 处理'test'消息 + s.content = 'y' # 确认执行命令 + msghandler.handler() + s.content = 'idcard:12321233' # 身份证查询(建设中功能) + msghandler.handler() + s.content = 'weather:上海' # 天气查询(建设中功能) + msghandler.handler() + s.content = 'admin' # 进入管理员模式 + msghandler.handler() + s.content = '123' # 输入管理员密码 + msghandler.handler() + s.content = 'exit' # 退出管理员模式 + msghandler.handler() \ No newline at end of file diff --git a/src/servermanager/urls.py b/src/servermanager/urls.py new file mode 100644 index 0000000..f3b73f5 --- /dev/null +++ b/src/servermanager/urls.py @@ -0,0 +1,22 @@ +# 导入Django的URL路由模块 +from django.urls import path +# 导入WeRoBot的Django视图创建工具 +from werobot.contrib.django import make_view + +# 从当前目录的robot模块导入robot实例 +from .robot import robot + +# 定义应用的命名空间,用于URL反向解析 +app_name = "servermanager" + +# 定义URL模式列表 +urlpatterns = [ + # 微信机器人消息接收接口 + # 路径: /servermanager/robot + # 使用WeRoBot的make_view将robot实例转换为Django视图 + path(r'robot', make_view(robot)), + # 说明: + # - r'robot': URL路径模式,匹配"robot"路径 + # - make_view(robot): 将WeRoBot机器人实例包装成Django视图函数 + # - 这个端点用于接收微信服务器发送的消息和事件 +] \ No newline at end of file diff --git a/src/servermanager/views.py b/src/servermanager/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/src/servermanager/views.py @@ -0,0 +1 @@ +# Create your views here.