一、開發環境準備 (Development Environment Setup)
-
Python 環境 (Python Environment)
- 安裝 Python 3.8 或更新版本。建議使用
pyenv
或conda
來管理 Python 版本。 - 創建並激活虛擬環境:
Bash
python -m venv venv source venv/bin/activate # macOS/Linux .\venv\Scripts\activate # Windows
- 安裝 Python 3.8 或更新版本。建議使用
-
安裝必要的套件 (Install Required Packages)
Bashpip install django mysqlclient waitress beautifulsoup4 Pillow markdown
django
: Django 框架本身。mysqlclient
: Django 連接 MySQL 的驅動。waitress
: 用於部署 Django 應用程式的 WSGI 伺服器。beautifulsoup4
: 如果您需要從富文本編輯器中清理 HTML 內容,這個庫很有用。Pillow
: 用於處理圖片,例如用戶頭像或文章封面圖。markdown
: 如果您希望用戶使用 Markdown 撰寫文章。
-
MySQL 資料庫 (MySQL Database)
- 安裝 MySQL 伺服器並創建一個資料庫,例如
myblog_db
。 - 創建一個用戶並賦予其對
myblog_db
的權限。 - 記下資料庫名稱、用戶名和密碼,稍後會在 Django 設定中使用。
- 安裝 MySQL 伺服器並創建一個資料庫,例如
Django 專案初始化 (Django Project Initialization)
-
創建 Django 專案 (Create Django Project)
Bashdjango-admin startproject myblog_project .
這會在當前目錄下創建
myblog_project
專案。 -
創建 Django 應用程式 (Create Django App)
Bashpython manage.py startapp blog
這會創建一個名為
blog
的應用程式,用於實現部落格功能。 -
註冊應用程式 (Register App)
打開 myblog_project/settings.py,在 INSTALLED_APPS 中添加 'blog':
Python# myblog_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog', # Add your app here ]
-
配置資料庫 (Configure Database)
在 myblog_project/settings.py 中,修改 DATABASES 配置以連接到 MySQL:
Python# myblog_project/settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'myblog_db', # 你的資料庫名稱 'USER': 'your_mysql_user', # 你的 MySQL 用戶名 'PASSWORD': 'your_mysql_password', # 你的 MySQL 密碼 'HOST': 'localhost', # 資料庫主機,通常是 localhost 'PORT': '3306', # MySQL 預設端口 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", } } }
-
設置靜態檔案 (Configure Static Files)
在 myblog_project/settings.py 中添加或修改靜態檔案配置:
Python# myblog_project/settings.py STATIC_URL = 'static/' STATIC_ROOT = BASE_DIR / 'staticfiles' # 部署時收集靜態檔案的路徑 MEDIA_URL = 'media/' MEDIA_ROOT = BASE_DIR / 'media' # 用戶上傳檔案的路徑
確保在
myblog_project/urls.py
中引入STATIC_ROOT
和MEDIA_ROOT
的配置,以便在開發模式下提供服務:Python# myblog_project/urls.py from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('', include('blog.urls')), # Include your app's URLs ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
-
執行資料庫遷移 (Run Migrations)
Bashpython manage.py makemigrations python manage.py migrate
這會創建 Django 內建應用程式和
blog
應用程式的資料庫表。 -
創建超級用戶 (Create Superuser)
Bashpython manage.py createsuperuser
這將允許您訪問 Django 管理後台。
部落格應用程式開發 (Blog App Development)
- 定義模型 (Define Models)
打開
blog/models.py
,定義Post
和Category
模型:
```python
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.template.defaultfilters import slugify # 用於生成 URL 友好的 slug
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models1.SlugField(max_length=100, unique=True, blank=True)
class Meta:
verbose_name_plural = "Categories" # 在管理後台顯示的名稱
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def __str__(self):
return self.name
class Post(models.Model):
2 STATUS_CHOICES = (
('draft', '草稿'),
('published', '已發布'),
)
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250, unique_for_date='publish', blank=True)
author = models.ForeignKey(User, on_del3ete=models.CASCADE, related_name='blog_posts')
body = models.TextField()
publish = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
category = models.ForeignKey(Category, on_delete=models.SET4_NULL, null=True, blank=True)
# 可以添加圖片欄位,例如封面圖
# cover_image = models.ImageField(upload_to='post_covers/', blank=True, null=True)
class Meta:
ordering = ('-publish',) # 預設按發布日期倒序排列
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super().save(*args, **kwargs)
def __str__(self):
def get_absolute_url(self):
return reverse('blog:post_detail', args=[self.publish.year,
self.publish.month,
self.publish.day,
self.slug])
```
* 重新執行資料庫遷移:
```bash
python manage.py makemigrations blog
python manage.py migrate
```
-
註冊模型到管理後台 (Register Models in Admin)
打開 blog/admin.py:
Python# blog/admin.py from django.contrib import admin from .models import Post, Category @admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display = ('name', 'slug',) prepopulated_fields = {'slug': ('name',)} @admin.register(Post) class PostAdmin(admin.ModelAdmin): list_display = ('title', 'slug', 'author', 'publish', 'status', 'category') list_filter = ('status', 'created', 'publish', 'author', 'category') search_fields = ('title', 'body') prepopulated_fields = {'slug': ('title',)} raw_id_fields = ('author',) # 使用 ID 選擇作者,適合大量用戶 date_hierarchy = 'publish' ordering = ('status', 'publish')
現在您可以訪問
http://127.0.0.1:8000/admin/
並使用超級用戶登錄,開始管理部落格文章和分類。 -
定義視圖 (Define Views)
打開 blog/views.py,定義部落格文章列表和詳情視圖:
Python# blog/views.py from django.shortcuts import render, get_object_or_404 from .models import Post, Category from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
6
def post_list(request, category_slug=None):
object_list = Post.objects.filter(status='published')
category = None
if category_slug:
category = get_object_or_404(Category, slug=category_slug)
object_list = object_list.filter(category=cate7gory)
paginator = Paginator(object_list, 5) # 每頁顯示 5 篇文章
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
# 如果頁碼不是整數,則顯示第一頁
posts = paginator.page(1)
except EmptyPage:
# 如果頁碼超出範圍 (例如,100 頁),則顯示最後一頁
posts = paginator.page(paginator.num_pages)
categories = Category.objects.all() # 獲取所有分類,用於導航
return render(request, 'blog/post/list.html', {'page': page,
'posts': posts,
'category': category,
'categories': categories})
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post, slug=post,
status='published',
publish__year=year,
publish__month=month,
publish__day=day)
return render(request, 'blog/post/detail.html', {'post': post})
```
-
定義 URL 路由 (Define URL Routes)
在 blog 應用程式目錄下創建 urls.py 檔案:
Python# blog/urls.py from django.urls import path from . import views app_name = 'blog' # 定義應用程式命名空間 urlpatterns = [ # 文章列表 path('', views.post_list, name='post_list'), path('category/<slug:category_slug>/', views.post_list, name='post_list_by_category'), # 文章詳情 path('<int:year>/<int:month>/<int:day>/<slug:post>/', views.post_detail, name='post_detail'), ]
確保在
myblog_project/urls.py
中包含了blog
應用程式的 URL(前面已經做過)。
整合 Bootstrap v5.0 (Integrate Bootstrap v5.0)
-
下載 Bootstrap 檔案 (Download Bootstrap Files)
訪問 Bootstrap 官方網站 (https://getbootstrap.com/docs/5.0/) 下載編譯好的 CSS 和 JS 檔案。
-
創建靜態檔案目錄 (Create Static Files Directory)
在 blog 應用程式目錄下創建 static/blog/css/ 和 static/blog/js/ 目錄。
將 Bootstrap 的 bootstrap.min.css 放入 static/blog/css/。
將 Bootstrap 的 bootstrap.bundle.min.js (包含 Popper) 放入 static/blog/js/。
-
創建基礎模板 (Create Base Template)
在 blog 應用程式目錄下創建 templates/blog/base.html 檔案:
HTML{% load static %} <!DOCTYPE html> <html lang="zh-Hant"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}我的部落格{% endblock %}</title> <link href="{% static 'blog/css/bootstrap.min.css' %}" rel="stylesheet"> <style> body { padding-top: 56px; } /* For fixed navbar */ </style> {% block extra_css %}{% endblock %} </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> <div class="container"> <a class="navbar-brand" href="{% url 'blog:post_list' %}">我的部落格</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav ms-auto"> <li class="nav-item"> <a class="nav-link active" aria-current="page" href="{% url 'blog:post_list' %}">首頁</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> 分類 </a> <ul class="dropdown-menu" aria-labelledby="navbarDropdown"> {% for category in categories %} <li><a class="dropdown-item" href="{% url 'blog:post_list_by_category' category.slug %}">{{ category.name }}</a></li> {% endfor %} <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="{% url 'blog:post_list' %}">所有文章</a></li> </ul> </li> {% if user.is_authenticated %} <li class="nav-item"> <a class="nav-link" href="{% url 'admin:index' %}">管理後台</a> </li> <li class="nav-item"> <a class="nav-link" href="#">登出</a> {# 實際登出功能需要您自己實現 #} </li> {% else %} <li class="nav-item"> <a class="nav-link" href="{% url 'admin:index' %}">登入</a> </li> {% endif %} </ul> </div> </div> </nav> <div class="container mt-4"> {% block content %} {% endblock %} </div> <footer class="footer mt-auto py-3 bg-light"> <div class="container text-center"> <span class="text-muted">© 2025 我的部落格. All rights reserved.</span> </div> </footer> <script src="{% static 'blog/js/bootstrap.bundle.min.js' %}"></script> {% block extra_js %}{% endblock %} </body> </html>
- 注意
{% load static %}
在模板頂部。 static
模板標籤用於生成靜態檔案的 URL。
- 注意
-
創建文章列表模板 (Create Post List Template)
在 blog/templates/blog/post/ 目錄下創建 list.html 檔案:
HTML{% extends "blog/base.html" %} {% block title %}文章列表{% if category %} - {{ category.name }}{% endif %}{% endblock %} {% block content %} <h1>{% if category %}{{ category.name }} - {% endif %}最新文章</h1> {% for post in posts %} <div class="card mb-4"> <div class="card-body"> <h2 class="card-title"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2> <p class="card-subtitle text-muted mb-2"> 發布於 {{ post.publish|date:"Y年m月d日" }} by {{ post.author }} {% if post.category %} in <a href="{% url 'blog:post_list_by_category' post.category.slug %}">{{ post.category.name }}</a> {% endif %} </p> <p class="card-text">{{ post.body|truncatechars:300|safe }}</p> <a href="{{ post.get_absolute_url }}" class="btn btn-primary">閱讀更多 »</a> </div> </div> {% empty %} <p>目前沒有任何已發布的文章。</p> {% endfor %} {# 分頁導航 #} {% include "blog/pagination.html" with page=posts %} {% endblock %}
-
創建文章詳情模板 (Create Post Detail Template)
在 blog/templates/blog/post/ 目錄下創建 detail.html 檔案:
HTML{% extends "blog/base.html" %} {% load markdown_tags %} {# 如果您想用 markdown 呈現內容,需要自定義模板標籤 #} {% block title %}{{ post.title }}{% endblock %} {% block content %} <h1 class="mb-4">{{ post.title }}</h1> <p class="text-muted mb-3"> 發布於 {{ post.publish|date:"Y年m月d日" }} by {{ post.author }} {% if post.category %} in <a href="{% url 'blog:post_list_by_category' post.category.slug %}">{{ post.category.name }}</a> {% endif %} </p> <div class="blog-content mb-5"> {# 如果您使用 markdown 格式撰寫文章,可以這樣顯示 #} {{ post.body|markdown|safe }} {# 否則直接顯示 #} {# {{ post.body|safe }} #} </div> <p><a href="{% url 'blog:post_list' %}" class="btn btn-secondary">« 返回文章列表</a></p> {# 您可以在這裡添加評論系統等功能 #} {% endblock %}
- 關於 Markdown: 如果您想讓用戶使用 Markdown 撰寫文章,需要創建一個自定義模板標籤。
在
blog
應用程式目錄下創建templatetags
資料夾,並在其中創建__init__.py
和markdown_tags.py
: 然後在模板中使用程式碼片段# blog/templatetags/markdown_tags.py from django import template from django.template.defaultfilters import stringfilter import markdown register = template.Library() @register.filter(name='markdown') @stringfilter def markdown_filter(value): return markdown.markdown(value, extensions=['extra', 'nl2br', 'sane_lists'])
{% load markdown_tags %}
並應用|markdown
過濾器。
- 關於 Markdown: 如果您想讓用戶使用 Markdown 撰寫文章,需要創建一個自定義模板標籤。
在
-
創建分頁模板 (Create Pagination Template)
在 blog/templates/blog/ 目錄下創建 pagination.html 檔案:
HTML{% if page.has_other_pages %} <nav aria-label="Page navigation"> <ul class="pagination justify-content-center"> {% if page.has_previous %} <li class="page-item"> <a class="page-link" href="?page={{ page.previous_page_number }}" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">«</span> </li> {% endif %} {% for i in page.paginator.page_range %} {% if page.number == i %} <li class="page-item active" aria-current="page"><span class="page-link">{{ i }}</span></li> {% else %} <li class="page-item"><a class="page-link" href="?page={{ i }}">{{ i }}</a></li> {% endif %} {% endfor %} {% if page.has_next %} <li class="page-item"> <a class="page-link" href="?page={{ page.next_page_number }}" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">»</span> </li> {% endif %}
8 </ul>
</nav>
{% endif %}
```
測試開發伺服器 (Test Development Server)
在專案根目錄下運行:
python manage.py runserver
訪問 http://127.0.0.1:8000/
,您應該能看到部落格文章列表。
部署到 Waitress (Deployment with Waitress)
Waitress 是一個生產級的 WSGI 伺服器,適合在 Windows 或非同步環境中部署 Django 應用程式。
-
收集靜態檔案 (Collect Static Files)
在部署之前,需要收集所有靜態檔案到 STATIC_ROOT 指定的目錄:
Bashpython manage.py collectstatic
這會將所有應用程式的靜態檔案(包括 Django admin 和 Bootstrap)複製到
staticfiles
目錄。 -
創建 Waitress 啟動腳本 (Create Waitress Launch Script)
在專案根目錄下創建一個 run_waitress.py 檔案(或者您可以直接在命令行中執行):
Python# run_waitress.py from waitress import serve from myblog_project.wsgi import application # 確保這裡是你的項目名.wsgi if __name__ == '__main__': print("Starting Waitress server on http://127.0.0.1:8000") serve(application, host='0.0.0.0', port=8000) # 0.0.0.0 允許從任何 IP 訪問
-
運行 Waitress 伺服器 (Run Waitress Server)
Bashpython run_waitress.py
現在,您的 Django 部落格應用程式將由 Waitress 伺服器提供服務,運行在
http://127.0.0.1:8000
(或您配置的 IP 和端口)。
進一步的功能擴展 (Further Feature Expansion)
- 評論系統 (Comments System):
- 創建
Comment
模型,與Post
關聯。 - 在
post_detail
視圖中處理評論的提交和顯示。 - 在
detail.html
中添加評論表單和評論列表。 - 可以考慮使用
django-crispy-forms
讓表單更美觀。
- 創建
- 文章搜索 (Post Search):
- 在
views.py
中添加搜索邏輯,使用Q
物件進行複雜查詢。 - 在模板中添加搜索表單。
- 在
- 用戶認證與授權 (User Authentication & Authorization):
- Django 內建的
django.contrib.auth
已經提供了用戶認證功能。 - 您可以實現用戶註冊、登錄、登出、密碼重置等功能。
- 限制只有登錄用戶才能發布或編輯文章。
- Django 內建的
- 富文本編輯器 (Rich Text Editor):
- 集成
django-ckeditor
或django-tinymce
等富文本編輯器,讓用戶可以更方便地撰寫文章。
- 集成
- 標籤系統 (Tagging System):
- 使用
django-taggit
等庫為文章添加標籤。
- 使用
- 響應式設計優化 (Responsive Design Optimization):
- Bootstrap 已經是響應式的,但您可能需要根據自己的設計進一步調整 CSS。
- SEO 優化 (SEO Optimization):
- 為文章添加 meta 描述、關鍵字等。
- 生成 sitemap。
- 安全考慮 (Security Considerations):
- 確保
DEBUG = False
在生產環境中。 - 配置
SECRET_KEY
為一個複雜且保密的值。 - 使用 HTTPS。
- 處理用戶輸入的數據,防止 XSS、SQL 注入等攻擊。
- 確保
總結
這個指南為您提供了一個從零開始使用 Django、MySQL、Waitress 和 Bootstrap 5 開發部落格功能的完整路線圖。請記住,這是一個基礎框架,您需要根據自己的需求進行深入開發和功能擴展。在開發過程中,遇到問題時,查閱 Django 官方文檔、Bootstrap 官方文檔和相關社區資源是非常有幫助的。
二、關於 Django 搭配 Waitress 和 MySQL 開發部落格,在效能和安全性方面的設定是至關重要的。以下將提供詳細的建議和配置範例。
效能設定 (Performance Settings)
效能優化通常涉及多個層面,從資料庫到程式碼,再到伺服器配置。
1. 資料庫優化 (MySQL)
-
索引 (Indexes):
- 確保您的模型中,常用於查詢條件的欄位(如
Post
模型中的publish
、status
、slug
,Category
模型中的name
、slug
)都建立了索引。Django 在定義ForeignKey
,unique=True
或db_index=True
時會自動建立索引。 - 對於
Post
模型中的publish
和slug
,由於您在get_absolute_url
和post_detail
視圖中會同時使用,unique_for_date='publish'
已經暗示了複合索引,但仍然可以手動確認或添加。 - 範例 (blog/models.py):
Python
class Post(models.Model): # ... slug = models.SlugField(max_length=250, unique_for_date='publish', db_index=True) # 確保有索引 publish = models.DateTimeField(default=timezone.now, db_index=True) # 確保有索引 status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft', db_index=True) # 確保有索引 category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, db_index=True) # ...
- 使用
EXPLAIN
: 在開發環境中使用 MySQL 的EXPLAIN
語句來分析您的查詢,確保它們正在有效利用索引。
- 確保您的模型中,常用於查詢條件的欄位(如
-
連接池 (Connection Pooling):
- 對於高流量的應用,頻繁地建立和關閉資料庫連接會產生開銷。可以使用資料庫連接池,例如
django-db-connection-pool
。 - 安裝:
pip install django-db-connection-pool
- settings.py 配置:
Python
# myblog_project/settings.py DATABASES = { 'default': { 'ENGINE': 'dj_db_conn_pool.backends.mysql', # 修改這裡 'NAME': 'myblog_db', 'USER': 'your_mysql_user', 'PASSWORD': 'your_mysql_password', 'HOST': 'localhost', 'PORT': '3306', 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", }, 'CONN_MAX_AGE': 600, # 連接在池中保持活躍的最長時間 (秒) } }
- 這可以顯著減少資料庫連接的開銷。
- 對於高流量的應用,頻繁地建立和關閉資料庫連接會產生開銷。可以使用資料庫連接池,例如
-
緩存查詢 (Caching Queries):
- 對於不經常變動但頻繁查詢的數據,例如分類列表,可以使用 Django 的緩存框架。
- settings.py 配置緩存後端:
Python
# myblog_project/settings.py CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', # 內存緩存,適合小型應用 'LOCATION': 'unique-snowflake', } # 如果需要更高效的緩存,可以使用 Redis 或 Memcached # 'default': { # 'BACKEND': 'django_redis.cache.RedisCache', # 'LOCATION': 'redis://127.0.0.1:6379/1', # 'OPTIONS': { # 'CLIENT_CLASS': 'django_redis.client.DefaultClient', # } # } }
- 在視圖中使用緩存:
Python
# blog/views.py from django.core.cache import cache def post_list(request, category_slug=None): # ... (其他代碼) categories = cache.get('all_categories') if not categories: categories = Category.objects.all() cache.set('all_categories', categories, 60 * 60) # 緩存 1 小時 return render(request, 'blog/post/list.html', { # ... 'categories': categories })
2. Django ORM 優化
-
使用
select_related
和prefetch_related
:- 當您查詢一個模型時,如果需要訪問其相關聯的外鍵或多對多關係的數據,使用這些方法可以避免 N+1 查詢問題,從而大大減少資料庫查詢次數。
- 範例 (blog/views.py):
這樣在模板中訪問Pythondef post_list(request, category_slug=None): object_list = Post.objects.filter(status='published').select_related('author', 'category') # 預先加載 author 和 category # ...
post.author.username
或post.category.name
時就不會觸發額外的資料庫查詢。
-
限制查詢結果 (Limiting QuerySets):
- 只獲取您需要的數據。如果只需要某幾個欄位,可以使用
only()
或defer()
。 Post.objects.only('title', 'slug', 'publish')
- 只獲取您需要的數據。如果只需要某幾個欄位,可以使用
-
分頁 (Pagination):
- 您已經在
post_list
視圖中實現了分頁,這是處理大量數據集的標準做法,可以避免一次性加載所有數據導致的記憶體和時間開銷。
- 您已經在
3. 靜態檔案和媒體檔案 (Static and Media Files)
-
部署時的靜態檔案服務 (Production Static File Serving):
- 不要讓 Django 在生產環境中服務靜態檔案。Waitress 是一個 WSGI 伺服器,它不擅長直接服務靜態檔案。
- 在生產環境中,應該使用專門的 Web 伺服器(如 Nginx 或 Apache)來服務靜態檔案和媒體檔案。
- Nginx 配置範例:
Nginx
server { listen 80; server_name your_domain.com; location /static/ { alias /path/to/your/myblog_project/staticfiles/; # 替換為你的 STATIC_ROOT } location /media/ { alias /path/to/your/myblog_project/media/; # 替換為你的 MEDIA_ROOT } location / { proxy_pass http://127.0.0.1:8000; # Waitress 監聽的地址和端口 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
- 在部署前執行
python manage.py collectstatic
來收集所有靜態檔案到STATIC_ROOT
。
-
CDN (Content Delivery Network):
- 對於面向全球用戶的網站,使用 CDN 可以加速靜態檔案的載入,減少伺服器負載。
4. Waitress 配置
-
調整線程數 (Threads):
- Waitress 預設使用 4 個線程。您可以根據伺服器資源和預期的併發量進行調整。
- run_waitress.py:
Python
from waitress import serve from myblog_project.wsgi import application if __name__ == '__main__': print("Starting Waitress server on http://127.0.0.1:8000") # 調整為更多的線程數,例如 8 或 16,但不要超過 CPU 核數太多 serve(application, host='0.0.0.0', port=8000, threads=8)
- 過多的線程可能會導致上下文切換開銷過大,過少則無法充分利用 CPU。需要根據實際測試進行調整。
-
日誌 (Logging):
- Waitress 預設日誌級別為
INFO
,這可能會產生大量日誌。在生產環境中,可以考慮調整日誌級別,或將日誌輸出到檔案而不是控制台。 - settings.py:
Python
# myblog_project/settings.py LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'INFO', # 或 'WARNING', 'ERROR' 'class': 'logging.FileHandler', 'filename': '/path/to/your/logs/waitress.log', # 設定日誌檔案路徑 }, }, 'loggers': { 'waitress': { 'handlers': ['file'], 'level': 'INFO', 'propagate': False, }, }, }
- Waitress 預設日誌級別為
安全性設定 (Security Settings)
安全性是任何 Web 應用程式的基石。以下是針對 Django、Waitress 和 MySQL 的關鍵安全建議。
1. Django 核心安全設定
-
SECRET_KEY
(settings.py):- 這是 Django 專案中最重要的安全設定之一。用於簽名加密、會話管理等。
- 絕不能暴露給他人。
- 在生產環境中,應從環境變數中讀取,而不是直接寫在程式碼中。
- 範例:
Python
# myblog_project/settings.py import os SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'your-very-long-and-complex-secret-key-for-development') # 開發用預設值 # 在生產環境中,確保設置環境變數: # export DJANGO_SECRET_KEY='實際的超長隨機密鑰'
- 生成一個強大的
SECRET_KEY
可以使用 Python 程式碼:Pythonfrom django.core.management.utils import get_random_secret_key print(get_random_secret_key())
-
DEBUG = False
(settings.py):- 生產環境中必須設置為
False
。 - 當
DEBUG
為True
時,Django 會顯示詳細的錯誤訊息,這可能暴露敏感資訊給攻擊者。它還會禁用一些安全檢查。 - 範例:
Python
# myblog_project/settings.py DEBUG = os.environ.get('DJANGO_DEBUG', 'True') == 'True' # 可以通過環境變數控制 # 在生產環境中: export DJANGO_DEBUG='False'
- 生產環境中必須設置為
-
ALLOWED_HOSTS
(settings.py):- 當
DEBUG = False
時,Django 會強制檢查ALLOWED_HOSTS
設定。這可以防止 HTTP Host Header 攻擊。 - 必須列出允許訪問您的網站的域名或 IP 地址。
- 範例:
Python
# myblog_project/settings.py ALLOWED_HOSTS = ['your_domain.com', 'www.your_domain.com', 'your_server_ip'] # 或者從環境變數讀取 # ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',')
- 當
-
CSRF 防護 (CSRF Protection):
- Django 內建了強大的 CSRF (Cross-Site Request Forgery) 保護,通過
CsrfViewMiddleware
自動啟用。 - 確保在所有表單中包含
{% csrf_token %}
模板標籤。 - 範例 (在任何 POST 表單中):
HTML
<form method="post"> {% csrf_token %} <button type="submit">Submit</button> </form>
- Django 內建了強大的 CSRF (Cross-Site Request Forgery) 保護,通過
-
XSS 防護 (XSS Protection):
- Django 模板會自動轉義 HTML 內容,防止 XSS (Cross-Site Scripting) 攻擊。
- 除非您明確知道自己在做什麼,否則不要使用
|safe
過濾器。 - 如果您確實需要顯示用戶提交的 HTML 內容(例如富文本編輯器),請務必使用一個成熟的 HTML 清理庫(如
bleach
或BeautifulSoup
)來過濾掉惡意標籤和屬性。 - 範例 (清理用戶提交的 HTML):
安裝 bleach:Python# 在您的視圖或模型 save 方法中 from bs4 import BeautifulSoup import bleach def clean_html_content(html_content): allowed_tags = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'p', 'strong', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'br'] allowed_attrs = {'a': ['href', 'title']} soup = BeautifulSoup(html_content, 'html.parser') cleaned_html = bleach.clean(str(soup), tags=allowed_tags, attributes=allowed_attrs, strip=True) return cleaned_html # 在保存 Post.body 之前調用 clean_html_content
pip install bleach
-
SQL 注入防護 (SQL Injection Protection):
- Django ORM 已經內建了 SQL 注入防護,只要您使用 ORM 提供的查詢方法,而不是手動拼接 SQL 語句。
- 避免使用
raw()
或extra()
方法拼接用戶輸入。如果非要使用,請務必使用參數化查詢。
-
會話管理 (Session Management):
- 使用 Django 內建的會話框架。
- 確保會話 Cookie 設置了
secure
和httponly
標誌。 - settings.py:
Python
# myblog_project/settings.py SESSION_COOKIE_SECURE = True # 僅在 HTTPS 連接上發送會話 Cookie SESSION_COOKIE_HTTPONLY = True # 防止 JavaScript 訪問會話 Cookie CSRF_COOKIE_SECURE = True # 僅在 HTTPS 連接上發送 CSRF Cookie CSRF_COOKIE_HTTPONLY = True # 防止 JavaScript 訪問 CSRF Cookie
-
密碼哈希 (Password Hashing):
- Django 預設使用強大的密碼哈希算法(如 PBKDF2 with SHA256)。
- 不要更改預設值,除非您有非常充分的理由。
-
管理後台安全 (Admin Security):
- 定期更新管理後台的超級用戶密碼。
- 限制管理後台的訪問 IP(可以通過 Web 伺服器配置)。
- 使用兩因素驗證 (Two-Factor Authentication, 2FA) 可以極大地增強管理後台的安全性(例如
django-two-factor-auth
)。
-
自定義用戶模型 (Custom User Model):
- 如果您的應用程式需要擴展用戶模型,建議在專案啟動時就定義一個自定義用戶模型,即使它只是繼承了
AbstractUser
。這樣可以避免將來擴展時的複雜性。 - settings.py:
Python
AUTH_USER_MODEL = 'yourapp.CustomUser' # 例如 'users.User'
- 如果您的應用程式需要擴展用戶模型,建議在專案啟動時就定義一個自定義用戶模型,即使它只是繼承了
2. MySQL 資料庫安全
-
獨立用戶 (Dedicated User):
- 為 Django 應用程式創建一個專用的 MySQL 用戶,並只賦予其必要的權限(例如
SELECT
,INSERT
,UPDATE
,DELETE
到指定的資料庫),而不是root
或ALL PRIVILEGES
。 - 範例:
SQL
CREATE USER 'your_django_user'@'localhost' IDENTIFIED BY 'your_secure_password'; GRANT SELECT, INSERT, UPDATE, DELETE ON myblog_db.* TO 'your_django_user'@'localhost'; FLUSH PRIVILEGES;
- 為 Django 應用程式創建一個專用的 MySQL 用戶,並只賦予其必要的權限(例如
-
強密碼 (Strong Passwords):
- 為所有資料庫用戶設置強大且複雜的密碼。
-
限制遠端連接 (Limit Remote Access):
- 如果您的資料庫伺服器和應用程式伺服器在同一台機器上,將 MySQL 配置為只監聽
localhost
(bind-address = 127.0.0.1
在my.cnf
或my.ini
中)。 - 如果需要遠端連接,請限制允許連接的 IP 地址。
- 如果您的資料庫伺服器和應用程式伺服器在同一台機器上,將 MySQL 配置為只監聽
3. Waitress 和伺服器安全
-
使用 Nginx/Apache 作為反向代理 (Reverse Proxy):
- 如前所述,不應直接將 Waitress 暴露給公網。使用 Nginx 或 Apache 作為反向代理,它們提供了更好的安全性、日誌記錄、限流和 SSL/TLS 終止功能。
- SSL/TLS (HTTPS):
- 這是最重要的安全措施之一。所有生產網站都應該使用 HTTPS。
- 使用 Let's Encrypt 等工具獲取免費的 SSL 證書,並在 Nginx/Apache 中配置。
- Nginx SSL 配置範例:
Nginx
server { listen 443 ssl; server_name your_domain.com; ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; ssl_ecdh_curve secp384r1; # Nginx >= 1.7.0 ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; location /static/ { alias /path/to/your/myblog_project/staticfiles/; } location /media/ { alias /path/to/your/myblog_project/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } # HTTP to HTTPS redirect server { listen 80; server_name your_domain.com; return 301 https://$host$request_uri; }
X-Frame-Options: DENY
: 防止點擊劫持攻擊。X-Content-Type-Options: nosniff
: 防止瀏覽器 MIME 類型嗅探。X-XSS-Protection: 1; mode=block
: 啟用瀏覽器內建的 XSS 過濾器。
-
防火牆 (Firewall):
- 在伺服器層級設置防火牆(如
ufw
on Linux),只允許必要的端口(SSH, HTTP, HTTPS)對外開放。Waitress 監聽的端口 (如 8000) 應該只允許來自反向代理的連接。
- 在伺服器層級設置防火牆(如
-
系統更新 (System Updates):
- 定期更新您的操作系統、Python、Django 和所有相關庫,以修補已知的安全漏洞。
-
日誌監控 (Log Monitoring):
- 定期檢查 Django、Web 伺服器和 Waitress 的日誌,以便及早發現潛在的安全問題或異常行為。
-
生產環境的環境變數 (Production Environment Variables):
- 將所有敏感資訊(如
SECRET_KEY
、資料庫密碼)存儲在環境變數中,而不是硬編碼在settings.py
中。 - 可以使用
python-decouple
或django-environ
等庫來更方便地管理環境變數。
- 將所有敏感資訊(如
-
入侵檢測系統 (Intrusion Detection Systems):
- 考慮使用像 Fail2Ban 這樣的工具來監控日誌文件並自動封鎖惡意 IP 地址,例如針對 SSH 暴力破解或 Web 應用攻擊。
總之,效能和安全性是持續的過程,沒有一勞永逸的解決方案。您需要定期審查您的配置、更新您的軟體並監控您的系統,以確保您的部落格安全高效地運行。
沒有留言:
張貼留言