前后端第一版提交

This commit is contained in:
2025-11-11 17:21:59 +08:00
commit 96e9a6d396
241 changed files with 197906 additions and 0 deletions

274
hertz_demo/README.md Normal file
View File

@@ -0,0 +1,274 @@
# Hertz Demo 演示模块
## 📋 模块概述
Hertz Demo 模块是一个功能演示和测试模块,提供了完整的示例代码和交互式演示页面,帮助开发者快速了解和使用 Hertz Server Django 框架的各项功能特性。
## ✨ 功能特性
- **验证码演示**: 展示多种验证码类型的生成、刷新和验证功能
- **邮件系统演示**: 提供邮件模板预览和发送测试功能
- **WebSocket演示**: 实时通信功能演示和测试
- **交互式界面**: 美观的Web界面支持实时操作和反馈
- **完整示例代码**: 提供可直接参考的实现代码
## 📁 模块结构
```
hertz_demo/
├── __init__.py # 模块初始化
├── apps.py # Django应用配置
├── models.py # 数据模型(预留)
├── views.py # 视图函数和业务逻辑
├── urls.py # URL路由配置
├── tests.py # 单元测试
├── consumers.py # WebSocket消费者
├── routing.py # WebSocket路由
└── templates/ # 模板文件
├── captcha_demo.html # 验证码演示页面
├── email_demo.html # 邮件系统演示页面
└── websocket_demo.html # WebSocket演示页面
```
## 🎯 核心功能详解
### 1. 验证码演示功能
验证码演示页面提供三种验证码类型:
- **随机字符验证码**: 随机生成的字母数字组合
- **数学运算验证码**: 简单的数学计算验证
- **单词验证码**: 英文单词验证
**主要功能**:
- 验证码实时生成和刷新
- 前端Ajax验证
- 后端表单验证
- 验证码类型切换
### 2. 邮件系统演示功能
邮件演示页面提供多种邮件模板:
- **欢迎邮件**: 用户注册欢迎邮件模板
- **系统通知**: 系统消息通知模板
- **邮箱验证**: 邮箱验证邮件模板
- **自定义邮件**: 支持自定义主题和内容
**主要功能**:
- 邮件模板实时预览
- 邮件发送测试
- 收件人邮箱验证
- 发送状态反馈
### 3. WebSocket演示功能
WebSocket演示页面提供实时通信功能
- **连接状态管理**: 显示WebSocket连接状态
- **消息发送接收**: 实时消息通信
- **广播功能**: 消息广播演示
- **错误处理**: 连接异常处理
## 🚀 API接口
### 演示页面路由
| 路由 | 方法 | 描述 |
|------|------|------|
| `/demo/captcha/` | GET | 验证码演示页面 |
| `/demo/email/` | GET | 邮件系统演示页面 |
| `/demo/websocket/` | GET | WebSocket演示页面 |
| `/websocket/test/` | GET | WebSocket测试页面 |
### Ajax接口
**验证码相关**:
- `POST /demo/captcha/` (Ajax): 验证码刷新和验证
- 请求体: `{"action": "refresh/verify", "captcha_id": "...", "user_input": "..."}`
**邮件发送**:
- `POST /demo/email/` (Ajax): 发送演示邮件
- 请求体: 邮件类型、收件人邮箱、自定义内容等
## ⚙️ 配置参数
### 邮件配置settings.py
```python
# 邮件服务器配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your-email@gmail.com'
EMAIL_HOST_PASSWORD = 'your-app-password'
DEFAULT_FROM_EMAIL = 'noreply@yourdomain.com'
```
### WebSocket配置
```python
# ASGI配置
ASGI_APPLICATION = 'hertz_server_django.asgi.application'
# Channel layers配置
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [('127.0.0.1', 6379)],
},
},
}
```
## 🛠️ 快速开始
### 1. 访问演示页面
启动开发服务器后访问以下URL
```bash
# 验证码演示
http://localhost:8000/demo/captcha/
# 邮件系统演示
http://localhost:8000/demo/email/
# WebSocket演示
http://localhost:8000/demo/websocket/
```
### 2. 测试验证码功能
1. 打开验证码演示页面
2. 选择验证码类型
3. 点击验证码图片可刷新
4. 输入验证码进行验证
5. 观察验证结果反馈
### 3. 测试邮件功能
1. 打开邮件演示页面
2. 选择邮件模板类型
3. 输入收件人邮箱
4. 点击发送测试邮件
5. 查看发送状态
### 4. 测试WebSocket功能
1. 打开WebSocket演示页面
2. 点击"连接"按钮建立连接
3. 在输入框中发送消息
4. 观察消息接收和广播
5. 测试断开重连功能
## 🔧 高级用法
### 自定义邮件模板
`views.py` 中的 `generate_email_content` 函数中添加新的邮件模板:
```python
def generate_email_content(email_type, recipient_name, custom_subject='', custom_message=''):
email_templates = {
'your_template': {
'subject': '您的邮件主题',
'html_template': '''
<html>
<!-- 您的HTML模板内容 -->
</html>
'''
}
}
# ...
```
### 扩展验证码类型
在验证码演示中扩展新的验证码类型:
```python
# 在 captcha_demo 函数中添加新的验证码类型
captcha_types = {
'random_char': '随机字符验证码',
'math': '数学运算验证码',
'word': '单词验证码',
'new_type': '您的新验证码类型' # 新增类型
}
```
### WebSocket消息处理
`consumers.py` 中扩展WebSocket消息处理逻辑
```python
class DemoConsumer(WebsocketConsumer):
async def receive(self, text_data):
data = json.loads(text_data)
message_type = data.get('type')
if message_type == 'custom_message':
# 处理自定义消息类型
await self.handle_custom_message(data)
```
## 🧪 测试
### 运行单元测试
```bash
python manage.py test hertz_demo
```
### 测试覆盖范围
- 验证码功能测试
- 邮件发送测试
- WebSocket连接测试
- 页面渲染测试
## 🔒 安全考虑
### 验证码安全
- 验证码有效期限制
- 验证次数限制
- 防止暴力破解
### 邮件安全
- 收件人邮箱验证
- 发送频率限制
- 防止邮件滥用
### WebSocket安全
- 连接认证
- 消息内容过滤
- 防止DDoS攻击
## ❓ 常见问题
### Q: 邮件发送失败怎么办?
A: 检查邮件服务器配置确保SMTP设置正确邮箱密码为应用专用密码。
### Q: WebSocket连接失败怎么办
A: 检查Redis服务是否运行确保CHANNEL_LAYERS配置正确。
### Q: 验证码验证总是失败?
A: 检查验证码存储后端Redis是否正常运行。
### Q: 如何添加新的演示功能?
A: 在views.py中添加新的视图函数在urls.py中配置路由在templates中添加模板文件。
## 📝 更新日志
### v1.0.0 (2024-01-01)
- 初始版本发布
- 包含验证码、邮件、WebSocket演示功能
- 提供完整的示例代码和文档
## 🔗 相关链接
- [🏠 返回主项目](../README.md) - Hertz Server Django 主项目文档
- [🔐 认证授权模块](../hertz_studio_django_auth/README.md) - 用户管理和权限控制
- [🛠️ 工具类模块](../hertz_studio_django_utils/README.md) - 加密、邮件和验证工具
- [📋 代码风格指南](../docs/CODING_STYLE.md) - 开发规范和最佳实践
---
💡 **提示**: 此模块主要用于功能演示和学习参考,生产环境请根据实际需求进行适当调整和优化。

0
hertz_demo/__init__.py Normal file
View File

6
hertz_demo/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class DemoConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'hertz_demo'

120
hertz_demo/consumers.py Normal file
View File

@@ -0,0 +1,120 @@
import json
from datetime import datetime
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = f'chat_{self.room_name}'
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message_type = text_data_json.get('type', 'chat_message')
message = text_data_json.get('message', '')
username = text_data_json.get('username', 'Anonymous')
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': message_type,
'message': message,
'username': username,
'timestamp': datetime.now().strftime('%H:%M:%S')
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
username = event['username']
timestamp = event['timestamp']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'type': 'chat_message',
'message': message,
'username': username,
'timestamp': timestamp
}))
async def user_join(self, event):
username = event['username']
timestamp = event['timestamp']
await self.send(text_data=json.dumps({
'type': 'user_notification',
'message': f'{username} 加入了聊天室',
'username': username,
'timestamp': timestamp
}))
async def user_leave(self, event):
username = event['username']
timestamp = event['timestamp']
await self.send(text_data=json.dumps({
'type': 'user_notification',
'message': f'{username} 离开了聊天室',
'username': username,
'timestamp': timestamp
}))
class EchoConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
try:
# 解析接收到的JSON数据
data = json.loads(text_data)
message = data.get('message', '').strip()
if message:
# 返回回声消息
response = {
'type': 'echo_message',
'original_message': message,
'echo_message': f'回声: {message}',
'timestamp': datetime.now().strftime('%H:%M:%S')
}
else:
# 如果消息为空
response = {
'type': 'error',
'message': '消息不能为空',
'timestamp': datetime.now().strftime('%H:%M:%S')
}
await self.send(text_data=json.dumps(response, ensure_ascii=False))
except json.JSONDecodeError:
# JSON解析错误
error_response = {
'type': 'error',
'message': '无效的JSON格式',
'timestamp': datetime.now().strftime('%H:%M:%S')
}
await self.send(text_data=json.dumps(error_response, ensure_ascii=False))

View File

3
hertz_demo/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

7
hertz_demo/routing.py Normal file
View File

@@ -0,0 +1,7 @@
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
re_path(r'ws/echo/$', consumers.EchoConsumer.as_asgi()),
]

View File

@@ -0,0 +1,499 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hertz验证码演示 - Hertz Server Django</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 2rem;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.demo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.demo-card {
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
overflow: hidden;
}
.demo-header {
background: #667eea;
color: white;
padding: 1rem;
text-align: center;
}
.demo-header h3 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
}
.demo-content {
padding: 1.5rem;
}
@media (max-width: 768px) {
.demo-grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2rem;
}
body {
padding: 1rem;
}
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #333;
}
.form-group input {
width: 100%;
padding: 0.8rem;
border: 2px solid #e1e5e9;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
}
.captcha-container {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.captcha-image {
border: 2px solid #e1e5e9;
border-radius: 8px;
cursor: pointer;
transition: border-color 0.3s ease;
}
.captcha-image:hover {
border-color: #667eea;
}
.btn {
padding: 0.8rem 1.5rem;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: background 0.3s ease;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
}
.btn-success {
background: #4CAF50;
color: white;
}
.btn-success:hover {
background: #45a049;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.refresh-btn {
background: #667eea;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.3s ease;
}
.refresh-btn:hover {
background: #5a6fd8;
}
.submit-btn {
width: 100%;
background: #667eea;
color: white;
border: none;
padding: 1rem;
border-radius: 5px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: background 0.3s ease;
}
.submit-btn:hover {
background: #5a6fd8;
}
.back-link {
text-align: center;
margin-top: 2rem;
}
.back-link a {
color: white;
text-decoration: none;
font-weight: 500;
font-size: 1.1rem;
}
.back-link a:hover {
text-decoration: underline;
}
.info-box {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 1rem;
margin-bottom: 2rem;
border-radius: 0 8px 8px 0;
}
.info-box h3 {
color: #667eea;
margin-bottom: 0.5rem;
}
.info-box ul {
margin-left: 1.5rem;
color: #666;
}
.info-box li {
margin-bottom: 0.3rem;
}
.info-box code {
background: #e9ecef;
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
}
.captcha-types {
background: #f8f9fa;
border-left: 4px solid #28a745;
padding: 1rem;
margin-bottom: 2rem;
border-radius: 0 8px 8px 0;
}
.type-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 0.5rem;
}
.type-btn {
background: #e9ecef;
color: #495057;
border: 2px solid #dee2e6;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s ease;
flex: 1;
min-width: 120px;
}
.type-btn:hover {
background: #dee2e6;
border-color: #adb5bd;
}
.type-btn.active {
background: #28a745;
color: white;
border-color: #28a745;
}
.demo-section {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 1rem;
margin-bottom: 2rem;
border-radius: 0 8px 8px 0;
}
.demo-section h4 {
color: #856404;
margin-bottom: 0.5rem;
}
.demo-section p {
color: #856404;
margin-bottom: 0.3rem;
}
.success-message {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔐 Hertz验证码演示</h1>
<p>{{ demo_description }}</p>
</div>
<div class="demo-grid">
<!-- 验证码功能介绍 -->
<div class="demo-card">
<div class="demo-header">
<h3>🎯 Hertz验证码功能特性</h3>
<p>自定义验证码系统支持Redis缓存</p>
</div>
<div class="demo-content">
<div class="info-box">
<ul>
<li>🔤 随机字符验证码 - 生成随机字母数字组合</li>
<li>🎨 自定义样式配置 - 支持颜色、字体、噪声等设置</li>
<li>⚡ Ajax刷新功能 - 无需刷新页面</li>
<li>💾 Redis缓存 - 高性能数据存储</li>
<li>⏰ 超时自动失效 - 可配置过期时间</li>
<li>🔧 灵活配置 - 通过settings.py进行配置</li>
</ul>
</div>
<div class="captcha-types">
<h3 style="margin-bottom: 1rem; color: #667eea;">配置信息</h3>
<p style="color: #666; font-size: 0.9rem; margin-bottom: 0.5rem;">• 验证码长度: 可通过HERTZ_CAPTCHA_LENGTH配置</p>
<p style="color: #666; font-size: 0.9rem; margin-bottom: 0.5rem;">• 图片尺寸: 可通过HERTZ_CAPTCHA_WIDTH/HEIGHT配置</p>
<p style="color: #666; font-size: 0.9rem; margin-bottom: 0.5rem;">• 过期时间: 可通过HERTZ_CAPTCHA_TIMEOUT配置</p>
<p style="color: #666; font-size: 0.9rem;">• Redis前缀: 可通过HERTZ_CAPTCHA_REDIS_KEY_PREFIX配置</p>
</div>
</div>
</div>
<!-- 验证码测试 -->
<div class="demo-card">
<div class="demo-header">
<h3>🔒 Hertz验证码测试</h3>
<p>输入验证码进行功能测试</p>
</div>
<div class="demo-content">
{% if messages %}
{% for message in messages %}
<div class="success-message" style="{% if message.tags == 'error' %}background: #f8d7da; color: #721c24; border-color: #f5c6cb;{% endif %}">
{% if message.tags == 'success' %}✅{% elif message.tags == 'error' %}❌{% endif %} {{ message }}
</div>
{% endfor %}
{% endif %}
<div class="demo-section">
<h4>📋 Hertz验证码说明</h4>
<p>• 随机字符验证码:生成随机字母和数字组合</p>
<p>• 特点自定义样式支持噪声干扰Redis缓存存储</p>
<p>• 功能支持Ajax刷新自动过期失效</p>
</div>
<form method="post" id="captcha-form">
{% csrf_token %}
<div class="form-group">
<label for="captcha_input">验证码:</label>
<div class="captcha-container">
<img id="captcha-image" src="{{ initial_captcha.image_data }}"
alt="验证码" class="captcha-image" onclick="refreshCaptcha()"
style="cursor: pointer; border: 2px solid #e1e5e9; border-radius: 8px;">
<button type="button" class="refresh-btn" onclick="refreshCaptcha()">🔄 刷新</button>
</div>
<input type="text" id="captcha_input" name="captcha_input"
placeholder="请输入验证码" required
style="width: 100%; padding: 0.8rem; border: 2px solid #e1e5e9; border-radius: 8px; font-size: 1rem; margin-top: 0.5rem;">
<input type="hidden" id="captcha_id" name="captcha_id" value="{{ initial_captcha.captcha_id }}">
</div>
<button type="submit" class="submit-btn">验证提交</button>
</form>
<div style="margin-top: 1rem; padding: 1rem; background: #e7f3ff; border-radius: 8px; border-left: 4px solid #007bff;">
<h4 style="color: #007bff; margin-bottom: 0.5rem;">💡 使用提示</h4>
<p style="color: #004085; margin-bottom: 0.3rem;">• 点击验证码图片可以刷新</p>
<p style="color: #004085; margin-bottom: 0.3rem;">• 验证码不区分大小写</p>
<p style="color: #004085;">• 验证码有效期为5分钟</p>
</div>
</div>
</div>
</div>
<div class="back-link">
<a href="/" style="color: white; text-decoration: none; font-weight: 500; font-size: 1.1rem;">← 返回首页</a>
</div>
</div>
<script>
let currentCaptchaId = '{{ initial_captcha.captcha_id }}';
function refreshCaptcha() {
fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify({
action: 'refresh',
captcha_id: currentCaptchaId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('captcha-image').src = data.data.image_data;
document.getElementById('captcha_id').value = data.data.captcha_id;
document.getElementById('captcha_input').value = '';
currentCaptchaId = data.data.captcha_id;
} else {
console.error('刷新验证码失败:', data.error);
alert('刷新验证码失败,请重试');
}
})
.catch(error => {
console.error('刷新验证码失败:', error);
alert('刷新验证码失败,请重试');
});
}
// 表单提交处理
const captchaForm = document.getElementById('captcha-form');
if (captchaForm) {
captchaForm.addEventListener('submit', function(e) {
e.preventDefault();
const captchaId = document.getElementById('captcha_id').value;
const userInput = document.getElementById('captcha_input').value;
if (!userInput.trim()) {
alert('请输入验证码');
return;
}
fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify({
action: 'verify',
captcha_id: captchaId,
user_input: userInput
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.valid) {
alert('✅ ' + data.message);
document.getElementById('captcha_input').value = '';
refreshCaptcha();
} else {
alert('❌ ' + data.message);
document.getElementById('captcha_input').value = '';
refreshCaptcha();
}
} else {
alert('❌ 验证失败: ' + (data.error || '未知错误'));
refreshCaptcha();
}
})
.catch(error => {
console.error('提交失败:', error);
alert('❌ 提交失败,请重试');
refreshCaptcha();
});
});
}
// 回车键提交
document.getElementById('captcha_input').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
captchaForm.dispatchEvent(new Event('submit'));
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,520 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邮件系统演示 - Hertz Server Django</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 2rem;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.back-link {
margin-bottom: 1rem;
}
.back-link a {
color: white;
text-decoration: none;
font-size: 1.1rem;
opacity: 0.9;
transition: opacity 0.3s ease;
}
.back-link a:hover {
opacity: 1;
text-decoration: underline;
}
.demo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.demo-card {
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.demo-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 40px rgba(0,0,0,0.15);
}
.demo-card h3 {
color: #667eea;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.demo-card p {
color: #666;
margin-bottom: 1rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 0.8rem;
border: 2px solid #e1e5e9;
border-radius: 5px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
outline: none;
border-color: #667eea;
}
.form-group textarea {
resize: vertical;
min-height: 120px;
}
.btn {
display: inline-block;
padding: 0.8rem 1.5rem;
border: none;
border-radius: 5px;
font-size: 1rem;
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
margin-right: 0.5rem;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
transform: translateY(-2px);
}
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover {
background: #218838;
transform: translateY(-2px);
}
.btn:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
}
.alert {
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
font-weight: 500;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.alert-info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.email-types {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.type-btn {
padding: 0.5rem 1rem;
border: 2px solid #667eea;
background: white;
color: #667eea;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.type-btn.active,
.type-btn:hover {
background: #667eea;
color: white;
}
.email-preview {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 5px;
padding: 1rem;
margin-top: 1rem;
}
.email-preview h4 {
color: #495057;
margin-bottom: 0.5rem;
}
.email-preview .preview-content {
background: white;
padding: 1rem;
border-radius: 3px;
border-left: 4px solid #667eea;
}
.loading {
display: none;
text-align: center;
padding: 1rem;
}
.loading.show {
display: block;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 0.5rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.demo-grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2rem;
}
body {
padding: 1rem;
}
.email-types {
justify-content: center;
}
}
</style>
</head>
<body>
<div class="container">
<div class="back-link">
<a href="/">← 返回首页</a>
</div>
<div class="header">
<h1>📧 邮件系统演示</h1>
<p>体验Django邮件发送功能支持多种邮件类型和模板</p>
</div>
<div class="demo-grid">
<div class="demo-card">
<h3>📮 邮件发送功能</h3>
<p>本演示展示了Django邮件系统的核心功能</p>
<ul style="color: #666; margin-left: 1.5rem; margin-bottom: 1rem;">
<li>支持HTML和纯文本邮件</li>
<li>多种邮件模板类型</li>
<li>SMTP配置和发送状态</li>
<li>邮件预览和验证</li>
</ul>
<div class="email-types">
<button class="type-btn active" data-type="welcome">欢迎邮件</button>
<button class="type-btn" data-type="notification">通知邮件</button>
<button class="type-btn" data-type="verification">验证邮件</button>
<button class="type-btn" data-type="custom">自定义邮件</button>
</div>
</div>
<div class="demo-card">
<h3>✉️ 发送邮件测试</h3>
<div id="message-area"></div>
<form id="email-form" method="post">
{% csrf_token %}
<input type="hidden" id="email-type" name="email_type" value="welcome">
<div class="form-group">
<label for="recipient_email">收件人邮箱 *</label>
<input type="email" id="recipient_email" name="recipient_email" required
placeholder="请输入收件人邮箱地址">
</div>
<div class="form-group" id="subject-group">
<label for="subject">邮件主题</label>
<input type="text" id="subject" name="subject"
placeholder="邮件主题将根据类型自动生成">
</div>
<div class="form-group" id="message-group" style="display: none;">
<label for="message">邮件内容</label>
<textarea id="message" name="message"
placeholder="请输入自定义邮件内容"></textarea>
</div>
<div class="form-group">
<label for="recipient_name">收件人姓名</label>
<input type="text" id="recipient_name" name="recipient_name"
placeholder="收件人姓名(可选)">
</div>
<button type="submit" class="btn btn-primary" id="send-btn">
📤 发送邮件
</button>
<button type="button" class="btn btn-success" id="preview-btn">
👁️ 预览邮件
</button>
</form>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>正在发送邮件,请稍候...</p>
</div>
</div>
</div>
<div class="demo-card" id="preview-card" style="display: none;">
<h3>📋 邮件预览</h3>
<div class="email-preview">
<h4>邮件内容预览:</h4>
<div class="preview-content" id="preview-content">
<!-- 预览内容将在这里显示 -->
</div>
</div>
</div>
</div>
<script>
// 邮件类型切换
document.querySelectorAll('.type-btn').forEach(btn => {
btn.addEventListener('click', function() {
// 移除所有active类
document.querySelectorAll('.type-btn').forEach(b => b.classList.remove('active'));
// 添加active类到当前按钮
this.classList.add('active');
const type = this.dataset.type;
document.getElementById('email-type').value = type;
// 根据类型显示/隐藏字段
const subjectGroup = document.getElementById('subject-group');
const messageGroup = document.getElementById('message-group');
const subjectInput = document.getElementById('subject');
if (type === 'custom') {
messageGroup.style.display = 'block';
subjectInput.placeholder = '请输入邮件主题';
subjectInput.required = true;
} else {
messageGroup.style.display = 'none';
subjectInput.placeholder = '邮件主题将根据类型自动生成';
subjectInput.required = false;
}
// 更新预览
updatePreview();
});
});
// 表单提交
document.getElementById('email-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const sendBtn = document.getElementById('send-btn');
const loading = document.getElementById('loading');
const messageArea = document.getElementById('message-area');
// 显示加载状态
sendBtn.disabled = true;
loading.classList.add('show');
messageArea.innerHTML = '';
fetch('/demo/email/', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
loading.classList.remove('show');
sendBtn.disabled = false;
if (data.success) {
messageArea.innerHTML = `
<div class="alert alert-success">
${data.message}
</div>
`;
} else {
messageArea.innerHTML = `
<div class="alert alert-error">
${data.message}
</div>
`;
}
})
.catch(error => {
loading.classList.remove('show');
sendBtn.disabled = false;
messageArea.innerHTML = `
<div class="alert alert-error">
❌ 发送失败:网络错误
</div>
`;
});
});
// 预览功能
document.getElementById('preview-btn').addEventListener('click', function() {
updatePreview();
const previewCard = document.getElementById('preview-card');
previewCard.style.display = previewCard.style.display === 'none' ? 'block' : 'none';
});
function updatePreview() {
const type = document.getElementById('email-type').value;
const recipientName = document.getElementById('recipient_name').value || '用户';
const subject = document.getElementById('subject').value;
const message = document.getElementById('message').value;
let previewContent = '';
switch(type) {
case 'welcome':
previewContent = `
<h4>🎉 欢迎加入我们!</h4>
<p>亲爱的 ${recipientName}</p>
<p>欢迎您注册成为我们的用户!我们很高兴您能加入我们的大家庭。</p>
<p>在这里,您可以享受到优质的服务和丰富的功能。如果您有任何问题,请随时联系我们。</p>
<p>祝您使用愉快!</p>
<p>此致<br>Hertz Server Django 团队</p>
`;
break;
case 'notification':
previewContent = `
<h4>🔔 系统通知</h4>
<p>亲爱的 ${recipientName}</p>
<p>您有一条新的系统通知:</p>
<div style="background: #f8f9fa; padding: 1rem; border-left: 4px solid #007bff; margin: 1rem 0;">
<p>您的账户设置已更新,如果这不是您的操作,请立即联系我们。</p>
</div>
<p>如有疑问,请联系客服。</p>
<p>此致<br>Hertz Server Django 团队</p>
`;
break;
case 'verification':
previewContent = `
<h4>🔐 邮箱验证</h4>
<p>亲爱的 ${recipientName}</p>
<p>请点击下面的链接验证您的邮箱地址:</p>
<div style="text-align: center; margin: 2rem 0;">
<a href="#" style="background: #667eea; color: white; padding: 1rem 2rem; text-decoration: none; border-radius: 5px;">验证邮箱</a>
</div>
<p>如果您没有注册账户,请忽略此邮件。</p>
<p>此致<br>Hertz Server Django 团队</p>
`;
break;
case 'custom':
previewContent = `
<h4>${subject || '自定义邮件'}</h4>
<p>亲爱的 ${recipientName}</p>
<div style="margin: 1rem 0;">
${message ? message.replace(/\n/g, '<br>') : '请输入邮件内容'}
</div>
<p>此致<br>Hertz Server Django 团队</p>
`;
break;
}
document.getElementById('preview-content').innerHTML = previewContent;
}
// 初始化预览
updatePreview();
</script>
</body>
</html>

View File

@@ -0,0 +1,556 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket演示 - Hertz Server Django</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 2rem;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.demo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.demo-card {
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
overflow: hidden;
}
.demo-header {
background: #667eea;
color: white;
padding: 1rem;
text-align: center;
}
.demo-header h3 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
}
.demo-content {
padding: 1.5rem;
}
.connection-status {
display: inline-block;
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: bold;
margin-bottom: 1rem;
}
.status-disconnected {
background: #f44336;
color: white;
}
.status-connecting {
background: #ff9800;
color: white;
}
.status-connected {
background: #4CAF50;
color: white;
}
.message-area {
border: 1px solid #ddd;
border-radius: 5px;
height: 300px;
overflow-y: auto;
padding: 1rem;
margin-bottom: 1rem;
background: #f9f9f9;
font-family: monospace;
font-size: 0.9rem;
}
.message {
margin-bottom: 0.5rem;
padding: 0.3rem;
border-radius: 3px;
}
.message-system {
background: #e3f2fd;
color: #1976d2;
}
.message-user {
background: #e8f5e8;
color: #2e7d32;
}
.message-echo {
background: #fff3e0;
color: #f57c00;
}
.message-error {
background: #ffebee;
color: #c62828;
}
.message-notification {
background: #f3e5f5;
color: #7b1fa2;
}
.input-group {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.input-field {
flex: 1;
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1rem;
}
.btn {
padding: 0.8rem 1.5rem;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: background 0.3s ease;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
}
.btn-success {
background: #4CAF50;
color: white;
}
.btn-success:hover {
background: #45a049;
}
.btn-danger {
background: #f44336;
color: white;
}
.btn-danger:hover {
background: #da190b;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.chat-controls {
margin-bottom: 1rem;
}
.chat-controls input {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.back-link {
text-align: center;
margin-top: 2rem;
}
.back-link a {
color: white;
text-decoration: none;
font-weight: 500;
font-size: 1.1rem;
}
.back-link a:hover {
text-decoration: underline;
}
@media (max-width: 768px) {
.demo-grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2rem;
}
body {
padding: 1rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🌐 WebSocket演示</h1>
<p>实时通信功能展示 - 支持聊天室和回声测试</p>
</div>
<div class="demo-grid">
<!-- 回声测试 -->
<div class="demo-card">
<div class="demo-header">
<h3>🔄 回声测试</h3>
<p>发送消息,服务器会回声返回</p>
</div>
<div class="demo-content">
<div class="connection-status status-disconnected" id="echo-status">未连接</div>
<div class="message-area" id="echo-messages"></div>
<div class="input-group">
<input type="text" class="input-field" id="echo-input" placeholder="输入消息..." disabled>
<button class="btn btn-primary" id="echo-send" disabled>发送</button>
</div>
<div class="input-group">
<button class="btn btn-success" id="echo-connect">连接</button>
<button class="btn btn-danger" id="echo-disconnect" disabled>断开</button>
<button class="btn btn-primary" id="echo-clear">清空</button>
</div>
</div>
</div>
<!-- 聊天室 -->
<div class="demo-card">
<div class="demo-header">
<h3>💬 聊天室</h3>
<p>多用户实时聊天功能</p>
</div>
<div class="demo-content">
<div class="connection-status status-disconnected" id="chat-status">未连接</div>
<div class="chat-controls">
<input type="text" class="input-field" id="username" placeholder="用户名" value="用户" style="width: 120px;">
<input type="text" class="input-field" id="room-name" placeholder="房间名" value="general" style="width: 120px;">
<button class="btn btn-success" id="chat-connect">加入聊天室</button>
<button class="btn btn-danger" id="chat-disconnect" disabled>离开聊天室</button>
</div>
<div class="message-area" id="chat-messages"></div>
<div class="input-group">
<input type="text" class="input-field" id="chat-input" placeholder="输入消息..." disabled>
<button class="btn btn-primary" id="chat-send" disabled>发送</button>
<button class="btn btn-primary" id="chat-clear">清空</button>
</div>
</div>
</div>
</div>
<div class="back-link">
<a href="/">← 返回首页</a>
</div>
</div>
<script>
// WebSocket连接管理
let echoSocket = null;
let chatSocket = null;
// DOM元素
const echoStatus = document.getElementById('echo-status');
const echoMessages = document.getElementById('echo-messages');
const echoInput = document.getElementById('echo-input');
const echoSendBtn = document.getElementById('echo-send');
const echoConnectBtn = document.getElementById('echo-connect');
const echoDisconnectBtn = document.getElementById('echo-disconnect');
const echoClearBtn = document.getElementById('echo-clear');
const chatStatus = document.getElementById('chat-status');
const chatMessages = document.getElementById('chat-messages');
const chatInput = document.getElementById('chat-input');
const chatSendBtn = document.getElementById('chat-send');
const chatConnectBtn = document.getElementById('chat-connect');
const chatDisconnectBtn = document.getElementById('chat-disconnect');
const chatClearBtn = document.getElementById('chat-clear');
const usernameInput = document.getElementById('username');
const roomNameInput = document.getElementById('room-name');
// 工具函数
function addMessage(container, message, type = 'system') {
const messageDiv = document.createElement('div');
messageDiv.className = `message message-${type}`;
messageDiv.innerHTML = `<strong>[${message.timestamp}]</strong> ${message.message}`;
container.appendChild(messageDiv);
container.scrollTop = container.scrollHeight;
}
function updateStatus(statusElement, status, text) {
statusElement.className = `connection-status status-${status}`;
statusElement.textContent = text;
}
// 回声测试WebSocket
function connectEcho() {
updateStatus(echoStatus, 'connecting', '连接中...');
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/echo/`;
echoSocket = new WebSocket(wsUrl);
echoSocket.onopen = function(e) {
updateStatus(echoStatus, 'connected', '已连接');
echoInput.disabled = false;
echoSendBtn.disabled = false;
echoConnectBtn.disabled = true;
echoDisconnectBtn.disabled = false;
};
echoSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
console.log('收到WebSocket消息:', data); // 调试信息
let messageType = 'system';
let displayMessage = data;
if (data.type === 'echo_message') {
messageType = 'echo';
// 解码Unicode字符并创建显示用的消息对象
let echoText = data.echo_message;
if (typeof echoText === 'string') {
// 处理可能的Unicode编码
try {
echoText = decodeURIComponent(escape(echoText));
} catch (e) {
// 如果解码失败,使用原始文本
console.log('Unicode解码失败使用原始文本');
}
}
displayMessage = {
message: echoText || data.echo_message || '回声消息',
timestamp: data.timestamp || new Date().toLocaleTimeString()
};
console.log('处理回声消息:', displayMessage); // 调试信息
} else if (data.type === 'error') {
messageType = 'error';
displayMessage = {
message: data.message || '发生错误',
timestamp: data.timestamp || new Date().toLocaleTimeString()
};
} else {
// 处理其他类型的消息
displayMessage = {
message: data.message || JSON.stringify(data),
timestamp: data.timestamp || new Date().toLocaleTimeString()
};
}
addMessage(echoMessages, displayMessage, messageType);
};
echoSocket.onclose = function(e) {
updateStatus(echoStatus, 'disconnected', '已断开');
echoInput.disabled = true;
echoSendBtn.disabled = true;
echoConnectBtn.disabled = false;
echoDisconnectBtn.disabled = true;
};
echoSocket.onerror = function(e) {
updateStatus(echoStatus, 'disconnected', '连接错误');
addMessage(echoMessages, {
message: 'WebSocket连接发生错误',
timestamp: new Date().toLocaleTimeString()
}, 'error');
};
}
function disconnectEcho() {
if (echoSocket) {
echoSocket.close();
}
}
function sendEchoMessage() {
const message = echoInput.value.trim();
if (message && echoSocket && echoSocket.readyState === WebSocket.OPEN) {
echoSocket.send(JSON.stringify({
'message': message
}));
echoInput.value = '';
}
}
// 聊天室WebSocket
function connectChat() {
const username = usernameInput.value.trim() || '匿名用户';
const roomName = roomNameInput.value.trim() || 'general';
updateStatus(chatStatus, 'connecting', '连接中...');
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/chat/${roomName}/`;
chatSocket = new WebSocket(wsUrl);
chatSocket.onopen = function(e) {
updateStatus(chatStatus, 'connected', `已连接到房间: ${roomName}`);
chatInput.disabled = false;
chatSendBtn.disabled = false;
chatConnectBtn.disabled = true;
chatDisconnectBtn.disabled = false;
usernameInput.disabled = true;
roomNameInput.disabled = true;
// 发送加入通知
chatSocket.send(JSON.stringify({
'type': 'user_join',
'username': username
}));
};
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
let messageType = 'system';
if (data.type === 'chat_message') {
messageType = 'user';
data.message = `${data.username}: ${data.message}`;
} else if (data.type === 'user_notification') {
messageType = 'notification';
} else if (data.type === 'error') {
messageType = 'error';
}
addMessage(chatMessages, data, messageType);
};
chatSocket.onclose = function(e) {
updateStatus(chatStatus, 'disconnected', '已断开');
chatInput.disabled = true;
chatSendBtn.disabled = true;
chatConnectBtn.disabled = false;
chatDisconnectBtn.disabled = true;
usernameInput.disabled = false;
roomNameInput.disabled = false;
};
chatSocket.onerror = function(e) {
updateStatus(chatStatus, 'disconnected', '连接错误');
addMessage(chatMessages, {
message: 'WebSocket连接发生错误',
timestamp: new Date().toLocaleTimeString()
}, 'error');
};
}
function disconnectChat() {
if (chatSocket) {
const username = usernameInput.value.trim() || '匿名用户';
// 发送离开通知
chatSocket.send(JSON.stringify({
'type': 'user_leave',
'username': username
}));
setTimeout(() => {
chatSocket.close();
}, 100);
}
}
function sendChatMessage() {
const message = chatInput.value.trim();
const username = usernameInput.value.trim() || '匿名用户';
if (message && chatSocket && chatSocket.readyState === WebSocket.OPEN) {
chatSocket.send(JSON.stringify({
'type': 'chat_message',
'message': message,
'username': username
}));
chatInput.value = '';
}
}
// 事件监听器
echoConnectBtn.addEventListener('click', connectEcho);
echoDisconnectBtn.addEventListener('click', disconnectEcho);
echoSendBtn.addEventListener('click', sendEchoMessage);
echoClearBtn.addEventListener('click', () => {
echoMessages.innerHTML = '';
});
echoInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendEchoMessage();
}
});
chatConnectBtn.addEventListener('click', connectChat);
chatDisconnectBtn.addEventListener('click', disconnectChat);
chatSendBtn.addEventListener('click', sendChatMessage);
chatClearBtn.addEventListener('click', () => {
chatMessages.innerHTML = '';
});
chatInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendChatMessage();
}
});
// 页面卸载时断开连接
window.addEventListener('beforeunload', function() {
if (echoSocket) {
echoSocket.close();
}
if (chatSocket) {
disconnectChat();
}
});
</script>
</body>
</html>

3
hertz_demo/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

11
hertz_demo/urls.py Normal file
View File

@@ -0,0 +1,11 @@
from django.urls import path
from . import views
app_name = 'hertz_demo'
urlpatterns = [
path('demo/captcha/', views.captcha_demo, name='captcha_demo'),
path('demo/websocket/', views.websocket_demo, name='websocket_demo'),
path('websocket/test/', views.websocket_test, name='websocket_test'),
path('demo/email/', views.email_demo, name='email_demo'),
]

331
hertz_demo/views.py Normal file
View File

@@ -0,0 +1,331 @@
from django.shortcuts import render, redirect
from django.http import JsonResponse, HttpResponse
from django.core.mail import send_mail, EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.contrib import messages
from django import forms
from hertz_studio_django_captcha.captcha_generator import HertzCaptchaGenerator
import json
from django.conf import settings
import random
import string
class HertzCaptchaForm(forms.Form):
"""Hertz验证码表单"""
captcha_input = forms.CharField(
max_length=10,
widget=forms.TextInput(attrs={
'placeholder': '请输入验证码',
'class': 'form-control',
'autocomplete': 'off'
}),
label='验证码'
)
captcha_id = forms.CharField(widget=forms.HiddenInput(), required=False)
def captcha_demo(request):
"""
验证码演示页面
展示多种验证码功能的使用方法
"""
# 获取请求的验证码类型
captcha_type = request.GET.get('type', 'random_char')
# 初始化验证码生成器
captcha_generator = HertzCaptchaGenerator()
if request.method == 'POST':
# 检查是否是Ajax请求
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
try:
data = json.loads(request.body)
action = data.get('action')
if action == 'refresh':
# 刷新验证码
captcha_data = captcha_generator.generate_captcha()
return JsonResponse({
'success': True,
'data': captcha_data
})
elif action == 'verify':
# 验证验证码
captcha_id = data.get('captcha_id', '')
user_input = data.get('user_input', '')
is_valid = captcha_generator.verify_captcha(captcha_id, user_input)
if is_valid:
return JsonResponse({
'success': True,
'valid': True,
'message': f'验证成功!验证码类型: {captcha_type}'
})
else:
return JsonResponse({
'success': True,
'valid': False,
'message': '验证码错误,请重新输入'
})
except json.JSONDecodeError:
return JsonResponse({
'success': False,
'error': '请求数据格式错误'
})
else:
# 普通表单提交处理
form = HertzCaptchaForm(request.POST)
username = request.POST.get('username', '')
captcha_id = request.POST.get('captcha_id', '')
captcha_input = request.POST.get('captcha_input', '')
# 验证验证码
is_valid = captcha_generator.verify_captcha(captcha_id, captcha_input)
if is_valid and username:
# 生成新的验证码用于显示
initial_captcha = captcha_generator.generate_captcha()
return render(request, 'captcha_demo.html', {
'form': HertzCaptchaForm(),
'success_message': f'验证成功!用户名: {username},验证码类型: {captcha_type}',
'captcha_unavailable': False,
'current_type': captcha_type,
'initial_captcha': initial_captcha,
'captcha_types': {
'random_char': '随机字符验证码',
'math': '数学运算验证码',
'word': '单词验证码'
}
})
# GET请求或表单验证失败时生成初始验证码
form = HertzCaptchaForm()
initial_captcha = captcha_generator.generate_captcha()
return render(request, 'captcha_demo.html', {
'form': form,
'captcha_unavailable': False,
'current_type': captcha_type,
'initial_captcha': initial_captcha,
'captcha_types': {
'random_char': '随机字符验证码',
'math': '数学运算验证码',
'word': '单词验证码'
}
})
def websocket_demo(request):
"""WebSocket演示页面"""
return render(request, 'websocket_demo.html')
def websocket_test(request):
"""
WebSocket简单测试页面
"""
return render(request, 'websocket_test.html')
# 测试热重启功能 - 添加注释触发文件变化
def email_demo(request):
"""邮件系统演示页面"""
if request.method == 'GET':
return render(request, 'email_demo.html')
elif request.method == 'POST':
try:
# 获取表单数据
email_type = request.POST.get('email_type', 'welcome')
recipient_email = request.POST.get('recipient_email')
recipient_name = request.POST.get('recipient_name', '用户')
custom_subject = request.POST.get('subject', '')
custom_message = request.POST.get('message', '')
if not recipient_email:
return JsonResponse({
'success': False,
'message': '请输入收件人邮箱地址'
})
# 根据邮件类型生成内容
email_content = generate_email_content(email_type, recipient_name, custom_subject, custom_message)
# 发送邮件
success = send_demo_email(
recipient_email=recipient_email,
subject=email_content['subject'],
html_content=email_content['html_content'],
text_content=email_content['text_content']
)
if success:
return JsonResponse({
'success': True,
'message': f'邮件已成功发送到 {recipient_email}'
})
else:
return JsonResponse({
'success': False,
'message': '邮件发送失败,请检查邮件配置'
})
except Exception as e:
return JsonResponse({
'success': False,
'message': f'发送失败:{str(e)}'
})
def generate_email_content(email_type, recipient_name, custom_subject='', custom_message=''):
"""根据邮件类型生成邮件内容"""
email_templates = {
'welcome': {
'subject': '🎉 欢迎加入 Hertz Server Django',
'html_template': f'''
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="margin: 0; font-size: 28px;">🎉 欢迎加入我们!</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #e1e5e9; border-radius: 0 0 10px 10px;">
<p style="font-size: 16px;">亲爱的 <strong>{recipient_name}</strong></p>
<p>欢迎您注册成为我们的用户!我们很高兴您能加入我们的大家庭。</p>
<p>在这里,您可以享受到:</p>
<ul style="color: #666;">
<li>🔐 安全的验证码系统</li>
<li>🌐 实时WebSocket通信</li>
<li>📧 完善的邮件服务</li>
<li>📚 详细的API文档</li>
</ul>
<p>如果您有任何问题,请随时联系我们。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://127.0.0.1:8000/" style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px; display: inline-block;">开始使用</a>
</div>
<p>祝您使用愉快!</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 14px;">此致<br><strong>Hertz Server Django 团队</strong></p>
</div>
</div>
</body>
</html>
'''
},
'notification': {
'subject': '🔔 系统通知 - Hertz Server Django',
'html_template': f'''
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: #007bff; color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="margin: 0; font-size: 24px;">🔔 系统通知</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #e1e5e9; border-radius: 0 0 10px 10px;">
<p style="font-size: 16px;">亲爱的 <strong>{recipient_name}</strong></p>
<p>您有一条新的系统通知:</p>
<div style="background: #f8f9fa; padding: 20px; border-left: 4px solid #007bff; margin: 20px 0;">
<p style="margin: 0; font-weight: 500;">您的账户设置已更新,如果这不是您的操作,请立即联系我们。</p>
</div>
<p>系统会持续为您提供安全保障,如有疑问请联系客服。</p>
<div style="text-align: center; margin: 30px 0;">
<a href="http://127.0.0.1:8000/" style="background: #007bff; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px; display: inline-block;">查看详情</a>
</div>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 14px;">此致<br><strong>Hertz Server Django 团队</strong></p>
</div>
</div>
</body>
</html>
'''
},
'verification': {
'subject': '🔐 邮箱验证 - Hertz Server Django',
'html_template': f'''
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: #28a745; color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="margin: 0; font-size: 24px;">🔐 邮箱验证</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #e1e5e9; border-radius: 0 0 10px 10px;">
<p style="font-size: 16px;">亲爱的 <strong>{recipient_name}</strong></p>
<p>感谢您注册 Hertz Server Django请点击下面的按钮验证您的邮箱地址</p>
<div style="text-align: center; margin: 40px 0;">
<a href="http://127.0.0.1:8000/verify?token=demo_token" style="background: #28a745; color: white; padding: 15px 40px; text-decoration: none; border-radius: 5px; display: inline-block; font-size: 16px; font-weight: 500;">验证邮箱地址</a>
</div>
<p style="color: #666; font-size: 14px;">如果按钮无法点击,请复制以下链接到浏览器:<br>
<code style="background: #f8f9fa; padding: 5px; border-radius: 3px;">http://127.0.0.1:8000/verify?token=demo_token</code></p>
<p style="color: #666;">如果您没有注册账户请忽略此邮件。此验证链接将在24小时后失效。</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 14px;">此致<br><strong>Hertz Server Django 团队</strong></p>
</div>
</div>
</body>
</html>
'''
},
'custom': {
'subject': custom_subject or '自定义邮件 - Hertz Server Django',
'html_template': f'''
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0;">
<h1 style="margin: 0; font-size: 24px;">{custom_subject or '自定义邮件'}</h1>
</div>
<div style="background: white; padding: 30px; border: 1px solid #e1e5e9; border-radius: 0 0 10px 10px;">
<p style="font-size: 16px;">亲爱的 <strong>{recipient_name}</strong></p>
<div style="margin: 20px 0; font-size: 16px;">
{custom_message.replace(chr(10), '<br>') if custom_message else '这是一封自定义邮件。'}
</div>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #666; font-size: 14px;">此致<br><strong>Hertz Server Django 团队</strong></p>
</div>
</div>
</body>
</html>
'''
}
}
template = email_templates.get(email_type, email_templates['welcome'])
html_content = template['html_template']
text_content = strip_tags(html_content)
return {
'subject': template['subject'],
'html_content': html_content,
'text_content': text_content
}
def send_demo_email(recipient_email, subject, html_content, text_content):
"""发送演示邮件"""
try:
# 检查邮件配置
if not settings.EMAIL_HOST_USER or not settings.EMAIL_HOST_PASSWORD:
print("邮件配置不完整,使用控制台输出模式")
print(f"收件人: {recipient_email}")
print(f"主题: {subject}")
print(f"内容: {text_content[:200]}...")
return True
# 创建邮件
email = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=settings.DEFAULT_FROM_EMAIL,
to=[recipient_email]
)
# 添加HTML内容
email.attach_alternative(html_content, "text/html")
# 发送邮件
email.send()
return True
except Exception as e:
print(f"邮件发送失败: {str(e)}")
return False