Django + UWSGI + NGINX 部署上线

原本简单部署后,日志里发现了好多漏扫以及各种报错,然后跟着AI折腾了大半天,终于重新部署完成,记录下。 1. UWSGI 参考配置文件 # UWSGI.INI 参考配置文件 [uwsgi] # --- 用户权限 --- uid = www-data gid = www-data # Django 项目配置 chdir = /srv/www/codenotes/ # Django 的 WSGI 入口文件 module = codenotes.wsgi # 虚拟环境路径 virtualenv = /srv/work_env/codenotes_env # python 插件 plugins = /usr/lib/uwsgi/plugins/python3 # 主进程 master = true # 工作进程数量:通常设置为 CPU 核心数 processes = 2 # 每个进程的线程数 threads = 2 # 显式启用线程支持 enable-threads = true # --- 通信配置 (Unix Socket 高性能)--- socket = /var/run/uwsgi/codenotes.sock # 设置 socket 权限,确保 Nginx 用户(通常是 www-data)可以读取 chmod-socket = 660 # 兜底权限 chown-socket = www-data:www-data # 当 uwsgi 停止时,删除 socket 文件 vacuum = true # --- 资源限制与稳定性 (防止内存泄漏) --- # 每个工作进程在处理指定数量的请求后自动重启 max-requests = 5000 # 重启进程时的平滑过渡,避免请求丢失 max-requests-delta = 500 # 如果单个请求超过 60 秒,杀死该进程 harakiri = 90 # 内存超1024MB自动重启 reload-on-rss = 1024 # 防止请求队列堆积导致 OOM listen = 100 # --- 日志配置 --- # 日志文件路径 logto = /srv/log/uwsgi.log # 日志轮转大小 30M,防止日志文件无限增长 log-maxsize = 31457280 # 日志级别 log-level = warning # 记录客户端IP log-x-forwarded-for = true # 建议:开启日志轮转时的信号处理,平滑切割 log-reopen = true # --- 其他性能优化 --- # 主进程加载应用,节省内存 lazy-apps = true buffer-size = 32768 # 静态文件转发缓冲区 post-buffering = 4096 ignore-sigpipe = true ignore-write-errors = true # --- 进程管理 --- # pidfile = /var/run/uwsgi/codenotes_uwsgi.pid # 优雅退出 die-on-term = true disable-logging = false # 【新增】优雅处理中断 thunder-lock = true 2. UWSGI 配置文件链接 # 配置文件链接至 etc/uwsgi/apps-enabled 目录 ln -s /srv/www/codenotes/uwsgi.ini /etc/uwsgi/apps-enabled/uwsgi.ini 3. 配置 UWSGI 自启动服务, 文件目录: /etc/systemd/system/codenotes.service  创建并配置完成后,重新加载下: systemctl daemon-reload 参考配置如下: [Unit] Description=uWSGI Emperor for CodeNotes Django Project Documentation=man:uwsgi(1) # 确保在网络就绪后启动,且必须在 Nginx 之前启动 After=network.target network-online.target syslog.target Wants=network-online.target [Service] # 强制服务以 root 启动 (这样 ExecStartPre 和主进程都有权限) User=root Group=root # 允许写入 /var/run ReadWritePaths=/var/run # 预执行命令 ExecStartPre=/bin/mkdir -p /var/run/uwsgi ExecStartPre=/bin/chown -R www-data:www-data /var/run/uwsgi ExecStartPre=/bin/chmod 755 /var/run/uwsgi # --- 启动命令 --- # 有链接配置文件到 /etc/uwsgi/apps-enabled 目录时,可以不需要下面方式,要不然会多启动一个。 # 方式 A: 直接指定 ini 文件路径 (推荐) # ExecStart=/srv/work_env/codenotes_env/bin/uwsgi --ini /srv/www/codenotes/uwsgi.ini # 方式 B: 如果使用全局安装的 uwsgi (非虚拟环境),则改为: # ExecStart=/usr/bin/uwsgi --ini /srv/www/codenotes/uwsgi.ini # 注意:请确保 ExecStart 指向的是真实的 uwsgi 二进制文件路径。 # 如果不确定,可以在终端运行 'which uwsgi' 或激活虚拟环境后运行 'which uwsgi' 查看路径。 # --- 重启策略 --- # 当进程意外退出时自动重启 Restart=always RestartSec=5s # --- 环境变量 (可选) --- # 如果 Django 需要特定的环境变量,可以在这里添加 Environment="DJANGO_SETTINGS_MODULE=codenotes.settings" Environment="PATH=/srv/work_env/codenotes_env/bin" # --- 资源限制 (可选,防止内存泄漏拖垮系统) --- # LimitNOFILE=65535 # LimitNPROC=65535 # --- 日志处理 --- # 将输出发送到 systemd journal (可以通过 journalctl -u uwsgi-codenotes 查看) StandardOutput=journal StandardError=journal # 如果你更倾向于 uwsgi.ini 中配置的 logto 文件,可以注释掉上面两行,或者保留它们作为备份 # --- 安全加固 --- NoNewPrivileges=true ProtectSystem=strict ProtectHome=true # 允许写入日志目录和 socket 目录 (需要根据实际路径调整 ReadWritePaths) ReadWritePaths=/srv/log /var/run/uwsgi /srv/www/codenotes [Install] # 开机自启目标 WantedBy=multi-user.target 4. Nginx 参考配置 # -------------------------- # 全局上游定义 (Upstream) # -------------------------- upstream django_codenotes { # 修改为 Unix Socket 路径 # 必须与你 uwsgi.ini 中的 socket 路径完全一致 server unix:/var/run/uwsgi/codenotes.sock; } # 定义恶意路径匹配规则 (大小写不敏感) # 如果 URI 匹配到以下任意正则,$block_scanner 变为 1,否则为 0 map $request_uri $block_scanner { default 0; # 常见后台/管理路径 ~*^/(wp-admin|wp-login|administrator|phpmyadmin|pma|mysql|webadmin|shell|cmd|console|manager|login|signin|auth|api) 1; # 常见敏感文件/备份 ~*^/.(php|git|svn|htaccess|htpasswd|env|config|bak|backup|sql|tar|gz|zip|rar|7z) 1; # 常见漏洞利用路径 ~*^/(solr|actuator|eureka|swagger|api-docs) 1; ~*^/(cgi-bin|scripts|bin|tmp) 1; ~*\.(asp|aspx|jsp|jspx|php5|phtml|pl|py|rb|sh|exe|bat|cmd)$ 1; # 特定漏洞探测字符串 ~*\$(\{|%24\{) 1; # Log4j JNDI 特征 ~*union.*select 1; # 基础 SQL 注入探测 ~*(\.\.\/|\.\.%2f|\%2e\%2e\/|\%2e\%2e\%2f) 1; # 目录遍历 } # -------------------------- # 1. HTTP 80 端口:强制跳转 HTTPS + 去除 www # -------------------------- server { listen 80; listen [::]:80; server_name codenotes.cn www.codenotes.cn ; # 逻辑: # 1. 如果是 www,跳转到非 www 的 https # 2. 如果不是 www,直接跳转到 https if ($host = www.codenotes.cn) { return 301 https://$host$request_uri; } # managed by Certbot if ($host = codenotes.cn) { return 301 https://$host$request_uri; } # managed by Certbot # 关闭日志(跳转请求无需记录) access_log off; log_not_found off; } # -------------------------- # HTTPS: codenotes.cn # -------------------------- server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name codenotes.cn; # 拦截恶意扫描路径 if ($block_scanner) { return 444; # 444表示直接关闭连接,不返回任何响应 } # 限制 HTTP 方法 (只允许常用方法) if ($request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE|PATCH)$) { return 405; } # SSL 证书 ssl_certificate /etc/letsencrypt/live/codenotes.cn/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/codenotes.cn/privkey.pem; # managed by Certbot # 引入通用 SSL 优化配置 (也可写在里面) ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; # OpenSSL 3.0 兼容的加密套件(优先 TLS1.3 套件,兼顾 TLS1.2) ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; # 曲线名 ssl_ecdh_curve X25519:prime256v1:secp384r1; ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_session_tickets off; # HSTS add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # 安全隐藏版本号 server_tokens off; # --- 性能优化:静态资源处理 --- # 开启零拷贝 sendfile on; tcp_nopush on; tcp_nodelay on; # 开启 Gzip 压缩 (显著减少传输体积) gzip on; gzip_vary on; gzip_min_length 1024; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; # 静态文件 (Static) location /static/ { alias /srv/www/codenotes/static/; expires 30d; add_header Cache-Control "public, max-age=2592000" always; add_header X-Content-Type-Options nosniff always; access_log off; # 静态文件不记录日志,提升 IO 性能 } # 媒体文件 (Media) location /media/ { alias /srv/www/codenotes/media/; expires 30d; add_header Cache-Control "public, max-age=2592000" always; access_log off; } # 动态请求 (Django/uWSGI) location / { # 使用 uWSGI 协议 uwsgi_pass django_codenotes; include uwsgi_params; # uWSGI 专用参数设置 (比 proxy_set_header 更准确) uwsgi_param Host $host; uwsgi_param X-Real-IP $remote_addr; uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for; uwsgi_param X-Forwarded-Proto $scheme; # 优化 Buffer,避免大请求写入临时文件 uwsgi_buffer_size 128k; uwsgi_buffers 8 256k; uwsgi_busy_buffers_size 512k; # 超时设置 (根据业务调整,防止长任务被切断) uwsgi_read_timeout 60s; uwsgi_send_timeout 60s; uwsgi_connect_timeout 60s; } # 基础配置 client_max_body_size 5M; # 适当增大上传限制 charset utf-8; access_log /srv/log/codenotes.access.log main if=$block_scanner=0; error_log /srv/log/codenotes.error.log warn; } # -------------------------- # HTTPS: www 子域名跳转 # -------------------------- # www.codenotes.cn -> codenotes.cn server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name www.codenotes.cn; # 拦截恶意扫描路径 if ($block_scanner) { return 444; # 444表示直接关闭连接,不返回任何响应 } # 限制 HTTP 方法 (只允许常用方法) if ($request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE|PATCH)$) { return 405; } # 必须加载 codenotes 的证书,否则浏览器会报证书不安全,无法完成跳转 ssl_protocols TLSv1.2 TLSv1.3; ssl_certificate /etc/letsencrypt/live/codenotes.cn/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/codenotes.cn/privkey.pem; # managed by Certbot # 301 永久跳转到非 www return 301 https://codenotes.cn$request_uri; } 请一定注意各文件及文件夹的用户及权限,尤其像 /var/run/、 日志文件及文件夹等等,在这块折腾了好久… 发现 /var/share/uwsgi/conf 下还有个default.ini ,依据上述配置,会把这个也起来,删除后,需要把自启动服务里的 # ExecStart=/usr/bin/uwsgi --ini /srv/www/codenotes/uwsgi.ini 备注取消!!要不然会异常!! 验证各服务后完成部署。

Nginx 提高防漏扫等安全配置

没事干看了下日志,发现UWSGI里好多 NOT FOUND 的空路径,说是被漏扫记录啥的,感觉这些日志都没啥必要记录,想着完善下。 1. 定义匹配规则 # 定义恶意路径匹配规则 (大小写不敏感) # 如果 URI 匹配到以下任意正则,$block_scanner 变为 1,否则为 0 map $request_uri $block_scanner { default 0; # 常见后台/管理路径 ~*^/(wp-admin|wp-login|administrator|phpmyadmin|pma|mysql|webadmin|shell|cmd|console|manager|login|signin|auth) 1; # 常见敏感文件/备份 ~*^/.(php|git|svn|htaccess|htpasswd|env|config|bak|backup|sql|tar|gz|zip|rar|7z) 1; # 常见漏洞利用路径 (ThinkPHP, Struts2, Log4j, Shellshock 等特征) ~*^/(index.php)/?(\?s=|\\x00) 1; ~*^/(solr|actuator|eureka|swagger|api-docs) 1; ~*^/(cgi-bin|scripts|bin|tmp) 1; ~*\.(asp|aspx|jsp|jspx|php5|phtml|pl|py|rb|sh|exe|bat|cmd)$ 1; # 特定漏洞探测字符串 ~*\$(\{|%24\{) 1; # Log4j JNDI 特征 ~*union.*select 1; # 基础 SQL 注入探测 (注意:复杂SQL注入建议由WAF处理,此处仅做简单特征) ~*(\.\.\/|\.\.%2f|\%2e\%2e\/|\%2e\%2e\%2f) 1; # 目录遍历 } 2. 配置 main 日志格式,要不然后续配置检查会报错 # /etc/nginx/nginx.conf http { …… # 定义名为 "main" 的日志格式 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; …… } 3. 引用规则及配置 server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name domain.cn; # 拦截恶意扫描路径 if ($block_scanner) { return 444; # 444表示直接关闭连接,不返回任何响应,也可返回 403、404 } # 限制 HTTP 方法 (只允许常用方法) if ($request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE|PATCH)$) { return 405; } # 如果 block_scanner=1,记录日志,其它不记录 access_log /srv/log/codenotes.access.log main if=$block_scanner=0; …… } 4. 检查并重启,完成相应安全强化 # 检查配置 nginx -t # 重启 nginx nginx -s reload OK,完成…

使用 Certbot 向 Let's Encrypt 免费申请及自动部署 HTTPS 证书

HTTP 报文是以明文形式传输的,会被各种浏览器标记为不安全。 HTTPS 则提供了一个加密传输的通道,相比较更为安全可靠。但专业的证书要钱,不过有个免费的Let's Encrypt,使用 Certbot 方便快捷,记录下大致的过程。 1. 安装必要工具 sudo apt update # Certbot 是 Let's Encrypt 提供的证书管理工具 sudo apt install certbot # python3-certbot-nginx 是专门针对 Nginx 的插件,其可以自动修改 Nginx 配置文件 sudo apt install python3-certbot-nginx 2. 获取并安装证书 # 如果多个域名,可以执行多次,或直接在后面增加 -d 域名,但会被归属到第一个 -d 的域名中,实际不影响任何安全 sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com # 执行上述语句后,会有交互确认: # 1. 邮箱输入:输入你的邮箱用于过期提醒。 # 2. 服务条款:同意即可。Y # 3. 重定向 HTTP 到 HTTPS:Certbot 会询问是否将 HTTP 流量重定向到 HTTPS。 选择 2 (Redirect) 或 Yes(强烈推荐)。这会让 Nginx 自动把所有 http:// 请求转为 https:// # 执行成功后,Certbot 会在 /etc/letsencrypt/live/yourdomain.com/ 下生成相应的证书文件,并自动修改 Nginx 配置文件,添加 SSL 监听端口和证书路径。 # 可自行查看 Nginx 相关配置文件并确认重载。 3. 查看所有证书 sudo certbot certificates # 一般会显示证书名称、域名信息、过期时间、证书路径等信息 # Saving debug log to /var/log/letsencrypt/letsencrypt.log # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Found the following certs: # Certificate Name: yourdomain.com # Domains: yourdomain.com www.yourdomain.com # Expiry Date: 2024-01-05 12:00:00+00:00 (VALID: 89 days) # Certificate Path: /etc/letsencrypt/live/yourdomain.com/fullchain.pem # Private Key Path: /etc/letsencrypt/live/yourdomain.com/privkey.pem # Certificate Name: another-domain.com # Domains: another-domain.com # Expiry Date: 2024-01-05 08:30:00+00:00 (VALID: 64 days) # Certificate Path: /etc/letsencrypt/live/another-domain.com/fullchain.pem # Private Key Path: /etc/letsencrypt/live/another-domain.com/privkey.pem # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4. 测试自动续期 # Let's Encrypt 证书有效期为 90 天。 sudo certbot renew --dry-run # 如果输出显示 "Congratulations, all renewals succeeded",则自动续期配置正常。 5. 删除证书 # 推荐,交互式删除,根据列出的信息,选择需要删除的证书即可。 sudo certbot delete 6. Django 安全强化配置(参考) # settings.py # 强制所有请求使用 HTTPS SECURE_SSL_REDIRECT = True # 如果 Django 运行在反向代理(如 Nginx)后面,需要信任代理的 Header # 否则 Django 无法识别请求原本是 HTTP 还是 HTTPS SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # 设置 HSTS (HTTP Strict Transport Security),增强安全性 # 单位是秒,例如 31536000 表示一年 SECURE_HSTS_SECONDS = 31536000 SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True # 确保 Session ID 和 CSRF Token 只在 HTTPS 下传输 SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True # 防止 JavaScript 访问 Cookie (防 XSS) SESSION_COOKIE_HTTPONLY = True CSRF_COOKIE_HTTPONLY = True  

Django 使用 django-hosts 实现多应用多域名

Django项目下有三个应用:blog、work、yuge,然后有三个域名,codenotes.cn、 yugemetal.com 和 yugemetal.cn,希望blog对应codenotes.cn,work对应codenotes.cn/work/,yuge对应yugemetal.com和yugemetal.cn。 参考各种资料及踩坑后,完成相应配置,大致操作记录如下。 1. 安装 django-hosts pip install django-hosts 2. 添加应用及配置参数 # 非完整代码,仅参考 # codenotes/settings.py # 增加应用 INSTALLED_APPS = [ # ... 内置应用等 'django_hosts', # 新增 'blog', 'work', 'yuge', ] # 增加 django_hosts 中间件 # 'django_hosts.middleware.HostsRequestMiddleware' 必须在所有中间件最前面 # 'django_hosts.middleware.HostsResponseMiddleware' 必须在所有中间件最后面 MIDDLEWARE = [ 'django_hosts.middleware.HostsRequestMiddleware', # <--- 放在最前面 # ... 其他常规中间件 (Security, Session, Common, Csrf, Auth, Message, Clickjacking等) 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django_hosts.middleware.HostsResponseMiddleware', # <--- 放在最后面 ] # 配置 django-hosts ROOT_HOSTCONF = 'codenotes.hosts' # 指向创建的 hosts.py 文件 DEFAULT_HOST = 'blog' # 默认主机名 3. 创建 hosts 文件 # codenotes/hosts.py from django_hosts import patterns, host # 域名与应用路由的映射 host_patterns = patterns( '', # codenotes.cn 绑定 blog 应用的 urls host(r'^codenotes\.cn$', 'codenotes.urls', name='blog'), # yugemetal.com 绑定 yuge 应用的 urls,注意,此处需新增一个 urls_yuge.py 文件 host('^yugemetal\.com$', 'yuge.urls_yuge', name='yuge_com'), host('^yugemetal\.cn$', 'yuge.urls_yuge', name='yuge_cn'), ) 4. codenotes/urls.py  # 非完整代码,仅参考 # codenotes/urls.py urlpatterns = [ path('admin/', admin.site.urls), path('work/', include(('work.urls', 'work'), namespace='work')), path('', include(('blog.urls', 'blog'), namespace='blog')), ] 5. blog/urls.py # 非完整代码,仅参考 # blog/urls.py # 必须添加app_name app_name = 'blog' urlpatterns = [ path('about/', views.about, name='about'), path('timeline/', views.timeline, name='timeline'), path('article/<int:article_id>', views.article, name='article'), path('', views.index, name='index'), ] 6. work/urls.py # 非完整代码,仅参考 # work/urls.py app_name = 'work' urlpatterns = [ path('condition/core/', views.monitor_core, name='monitor_core'), ] 7. yuge/urls.py # 非完整代码,仅参考 # yuge/urls.py app_name = 'yuge' urlpatterns = [ path('', views.index, name='index'), ] 8. 新增 yuge/urls_yuge.py  # yuge/urls_yuge.py from django.urls import path, include urlpatterns = [ # yugemetal.com/ 根路径对应yuge应用 path('', include(('yuge.urls', 'yuge'), namespace='yuge')), ] 9. templates 中使用 <!-- templates/app/xxx.html --> <!-- 页面模板中使用时,必须先加载 hosts --> {% load hosts %} <ul class="nav-menu" id="navMenu"> <!-- 用 host_url 替换默认的 url,参数照常放,在最后面增加 host 对应名称 --> <li><a href="{% host_url 'yuge:index' host 'yuge' %}#home" class="nav-link active">首页</a></li> <li><a href="{% host_url 'blog:tag' tag.id host 'blog' %}" class="tag"></li> </ul> <!-- 之前只有个 .com 域名 ,后来增加了 .cn,然后上面的实现就比较局限了,修改如下 --> <ul class="nav-menu" id="navMenu"> <li><a href="https://{{ request.get_host }}#home" class="nav-link active">首页</a></li> <li><a href="https://{{ request.get_host }}/tag/{{ tag.id }} %}" class="tag"></li> </ul> 10. nginx 正常配置 # 动态请求 (Django/uWSGI) location / { # 使用 uWSGI 协议 uwsgi_pass django_codenotes; include uwsgi_params; # uWSGI 专用参数设置 (比 proxy_set_header 更准确) uwsgi_param Host $host; # !!留意此条,根据域名区分!! uwsgi_param X-Real-IP $remote_addr; uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for; uwsgi_param X-Forwarded-Proto $scheme; } OVER… 其中折腾最久的是页面提示 NoReverseMatch, 一开始是 hosts 中的 blog 的 url 未对应正确,对应到应用 blog 下的 urls.py 去了,实际需要对应项目的 urls.py,即 codenotes/urls.py;然后 yuge 用现有的 urls.py 无法正常显示页面,一直提示 NoReverseMatch,新建了 yuge/yuge_ursl.py 并配置相关内容后,页面及相关链接全部显示正常。 至此,目标达成。 https://codenotes.cn 正常显示博客, https://codenotes.cn/work/ 正常显示 work 相关内容 https://yugemetal.com  https://yugemetal.cn 都正常显示宇格金属,且能正常交互。

Django 默认 admin 新增登录验证码

Django默认登录只有用户名密码,存在一定安全隐患,想着提高下,用 django-simple-captcha 实现。 1. 安装 django-simple-captcha # 如无 pillow,需安装才可正常使用 # pip install pillow pip install django-simple-captcha 2. 添加应用及配置 # settings INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 添加应用 'captcha', ] # 验证码图片宽度/高度 CAPTCHA_IMAGE_SIZE = (100, 40) # 验证码字符长度(数学公式时不生效) CAPTCHA_LENGTH = 4 # 验证码字符类型:字母数字混合(可选值:'numeric'纯数字 / 'alphabetic'纯字母 / 'alphanumeric'混合) CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' # 数学公式验证码(替代上述字符验证码) # CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge' # 验证码过期时间(秒) CAPTCHA_TIMEOUT = 5 * 60 # 5分钟 # 干扰线数量(0=无干扰线,数值越大干扰越强) CAPTCHA_NOISE_FUNCTIONS = ( 'captcha.helpers.noise_arcs', # 弧形干扰线 'captcha.helpers.noise_dots', # 点状干扰 ) # 验证码背景色(RGB) CAPTCHA_BACKGROUND_COLOR = '#ffffff' # 验证码文字颜色(RGB) CAPTCHA_FOREGROUND_COLOR = '#001100' 3. 执行 python manage.py migrate  4. 新增验证码表单 # 新建 project/forms.py from django import forms from django.contrib.admin.forms import AdminAuthenticationForm from captcha.fields import CaptchaField class AdminCaptchaAuthenticationForm(AdminAuthenticationForm): # 新增验证码字段,label 可自定义显示文本 captcha = CaptchaField(label='验证码') # 保留原有的 admin 登录验证逻辑,仅新增验证码校验 def clean(self): # 先执行父类的 clean 方法(验证用户名密码) cleaned_data = super().clean() # 验证码的校验由 CaptchaField 自动完成,无需额外处理 return cleaned_data 5. 新增视图 # 新建 project/views.py from django.contrib.admin.sites import AdminSite from django.contrib.auth.views import LoginView from .captcha_forms import AdminCaptchaAuthenticationForm class AdminCaptchaLoginView(LoginView): # 指定自定义表单 form_class = AdminCaptchaAuthenticationForm # 复用 admin 登录模板 template_name = 'admin/login.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) admin_site = AdminSite() context.update({ 'site_title': admin_site.site_title, 'site_header': admin_site.site_header, 'site_url': admin_site.site_url, 'title': '管理员登录', }) return context 6. 新增有验证码的 login.html 替换默认登录页面 # 新建 project/codenotes/templates/admin.py <!-- templates/admin/login.html --> {% extends "admin/base_site.html" %} {% load i18n static %} {% block extrastyle %}{{ block.super }} <link rel="stylesheet" href="{% static "admin/css/login.css" %}"> {{ form.media }} <!-- 新增:加载验证码所需的静态资源 --> <style> /* 控制验证码图片垂直对齐,与输入框居中 */ .captcha { vertical-align: middle; margin-bottom: 3px; border: none; } /* 调整验证码输入框的高度,与图片更匹配 */ input[name="captcha_1"] { height: 25px; } </style> {% endblock %} {% block bodyclass %}{{ block.super }} login{% endblock %} {% block nav-breadcrumbs %}{% endblock %} {% block content_title %}{% endblock %} {% block content %} {% if form.errors and not form.non_field_errors %} <p class="errornote"> {% blocktranslate count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktranslate %} </p> {% endif %} {% if form.non_field_errors %} {% for error in form.non_field_errors %} <p class="errornote"> {{ error }} </p> {% endfor %} {% endif %} <div id="content-main"> {% if user.is_authenticated %} <p class="errornote"> {% blocktranslate trimmed %} You are authenticated as {{ username }}, but are not authorized to access this page. Would you like to login to a different account? {% endblocktranslate %} </p> {% endif %} <form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %} <div class="form-row"> {{ form.username.errors }} {{ form.username.label_tag }} {{ form.username }} </div> <div class="form-row"> {{ form.password.errors }} {{ form.password.label_tag }} {{ form.password }} <input type="hidden" name="next" value="{{ next }}"> </div> <!-- 新增:验证码字段渲染 --> <div class="form-row"> {{ form.captcha.errors }} <!-- 验证码错误提示 --> {{ form.captcha.label_tag }} {{ form.captcha }} <!-- 验证码标签 + 输入框 + 图片 --> </div> {% url 'admin_password_reset' as password_reset_url %} {% if password_reset_url %} <div class="password-reset-link"> <a href="{{ password_reset_url }}">{% translate 'Forgotten your password or username?' %}</a> </div> {% endif %} <div class="submit-row"> <input type="submit" value="{% translate 'Log in' %}"> </div> </form> </div> {% endblock %} 7. 添加路由 # urls urlpatterns = [ # 增加路由 path('captcha/', include('captcha.urls')), ] 完成测试看是否成功。

Django-CKeditor 代码高亮

习惯使用Django-CKeditor实现博客后台的富文本输入,但发现少个代码高亮功能,几经查找,发现其有内置的代码块功能:codesnippet,用此插件就可以实现代码高亮功能,且自带不少常见主题,原理为通过highlight.js实现。 1. 参考配置参数,调出代码按钮 CKEDITOR_CONFIGS = { 'default': { "skin": "moono-lisa", "toolbar_Basic": [["Source", "-", "Bold", "Italic"]], "toolbar_Full": [ ["Styles", "Format", "Bold", "Italic", "Underline", "Strike"], ["Blockquote", "Link", "Unlink"], ["Image", "Table", "HorizontalRule"], ["NumberedList", "BulletedList", "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock"], ["TextColor", "BGColor", ], ["CodeSnippet", "Source"], ], "toolbar": "Full", "height": 500, "width": 860, "filebrowserWindowWidth": 940, "filebrowserWindowHeight": 725, 'extraPlugins': 'codesnippet', 'tabSpaces': 4, 'allowedContent': True, # 配置后台语法高亮主题 'codeSnippet_theme': 'monokai', # 配置后台显示支持语言,格式:{语言标识: 显示名称} 'codeSnippet_languages': { 'bash': 'Bash', 'css': 'CSS', 'html': 'HTML', 'ini': 'INI', 'javascript': 'JavaScript', 'json': 'JSON', 'nginx': 'Nginx', 'python': 'Python', 'sql': 'SQL', }, } } 2. 前台使用 <!-- django-ckeditor 自带的highlight版本比较低,推荐用highlightjs官网最新的js --> <!-- 喜欢的高亮主题 --> <link href="{% static 'blog/css/atom-one-dark.min.css' %}" rel="stylesheet"> <!-- highlight.js文件 --> <script src="{% static 'blog/js/highlight.min.js' %}"></script> <!-- 调用highlight --> <script>hljs.highlightAll();</script> 3. 补充 部分超长代码不会自动换行问题,可以在.css文件中添加white-space: pre-wrap; 可实现正常显示自动换行。 .hljs { border-radius: 0.5rem; # 代码框圆角 white-space: pre-wrap; # 代码超长自动换行 }  

Debian 安装 ShadowSocks

1. 更新 # Debian系统中,已有shadowsocks应用,直接更新安装即可 sudo apt update # 安装 sudo apt install shadowsocks-libev 2. 修改完善相关配置 # 配置文件路径 vim /etc/shadowsocks-libev/config.json # 修改相关配置 { # 任意IP访问 "server":["0.0.0.0"], "mode":"tcp_and_udp", # 服务器端口,默认为8288 "server_port":8288, # 本地端口,默认为1080 "local_port":1080, # 密码,根据个人喜好修改 "password":"loveqiqi", # 连接超时(单位:秒) "timeout":36000, # 加密方式 "method":"chacha20-ietf-poly1305" } 3. 客户端下载 https://github.com/shadowsocks

Debian 安装 MySQL

1. 更新下载MySQL资源仓库 https://dev.mysql.com/get/mysql-apt-config_0.8.36-1_all.deb 2. 安装更新 sudo dpkg -i mysql-apt-config_0.8.36-1_all.deb 3. 更新升级 sudo apt update sudo apt upgrade 4. 安装MySQL sudu apt install mysql-server # 期间会提示输入密码, 并确认密码 5. 修改完善MySQL相关配置 # 配置文件 vim /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] # 允许远程访问 bind-address = 0.0.0.0 # 修改端口号 port = 13306 # 内存优化,按需 performance_schema = off # 进入MySQL后,执行相关语句允许远程访问(MySQL 5.7.40版本后) > CREATE USER 'root'@'%' IDENTIFIED BY '密码'; > GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; > FLUSH PRIVILEGES; # 进入MySQL后,执行相关语句允许远程访问(5.7.38版本之前) > GRANT ALL PRIVILEGES ON . TO 'root'@'%' IDENTIFIED BY '密码' WITH GRANT OPTION; > FLUSH PRIVILEGES; # 查看端口 netstat -tulnp | grep mysql  

广告 【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中