diff --git a/HTTPS配置指南.md b/HTTPS配置指南.md new file mode 100644 index 0000000..35dd952 --- /dev/null +++ b/HTTPS配置指南.md @@ -0,0 +1,316 @@ +# 微信小程序 HTTPS 后端接口配置指南 + +## 一、准备工作 + +### 1. 确认您已有: +- ✅ 域名(已备案,如果使用国内服务器) +- ✅ 服务器(云服务器,如阿里云、腾讯云等) +- ✅ 后端代码(Flask/FastAPI/Django 等) + +## 二、获取 SSL 证书 + +### 方案一:免费证书(推荐) + +#### 1. 使用 Let's Encrypt(免费,3个月有效期,可自动续期) + +**安装 Certbot:** +```bash +# Ubuntu/Debian +sudo apt-get update +sudo apt-get install certbot + +# CentOS +sudo yum install certbot +``` + +**获取证书:** +```bash +# 方式1:自动验证(需要80端口开放) +sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com + +# 方式2:手动验证(如果80端口被占用) +sudo certbot certonly --manual -d yourdomain.com +``` + +**证书位置:** +- 证书文件:`/etc/letsencrypt/live/yourdomain.com/fullchain.pem` +- 私钥文件:`/etc/letsencrypt/live/yourdomain.com/privkey.pem` + +#### 2. 使用阿里云/腾讯云免费证书 + +**阿里云:** +1. 登录阿里云控制台 +2. 进入「SSL证书」→「免费证书」 +3. 申请证书,填写域名信息 +4. 完成DNS验证或文件验证 +5. 下载证书(选择对应服务器类型) + +**腾讯云:** +1. 登录腾讯云控制台 +2. 进入「SSL证书」→「我的证书」→「申请免费证书」 +3. 填写域名信息并验证 +4. 下载证书 + +### 方案二:付费证书 +- 阿里云、腾讯云、华为云等都有付费证书服务 +- 通常提供1年有效期,自动续期服务 + +## 三、配置后端服务器 + +### Flask 示例 + +```python +from flask import Flask, jsonify +from flask_cors import CORS +import ssl + +app = Flask(__name__) +CORS(app) # 允许跨域请求 + +@app.route('/api/test', methods=['GET']) +def test(): + return jsonify({'message': 'Hello from HTTPS API'}) + +if __name__ == '__main__': + # 使用 SSL 证书启动 + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.load_cert_chain( + '/etc/letsencrypt/live/yourdomain.com/fullchain.pem', + '/etc/letsencrypt/live/yourdomain.com/privkey.pem' + ) + + app.run( + host='0.0.0.0', + port=443, + ssl_context=context, + debug=False + ) +``` + +### FastAPI 示例 + +```python +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +import uvicorn + +app = FastAPI() + +# 配置 CORS,允许微信小程序访问 +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # 生产环境建议指定域名 + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/api/test") +async def test(): + return {"message": "Hello from HTTPS API"} + +if __name__ == "__main__": + uvicorn.run( + app, + host="0.0.0.0", + port=443, + ssl_keyfile="/etc/letsencrypt/live/yourdomain.com/privkey.pem", + ssl_certfile="/etc/letsencrypt/live/yourdomain.com/fullchain.pem" + ) +``` + +### 使用 Nginx 反向代理(推荐) + +**安装 Nginx:** +```bash +# Ubuntu/Debian +sudo apt-get install nginx + +# CentOS +sudo yum install nginx +``` + +**配置 Nginx:** +编辑 `/etc/nginx/sites-available/default` 或创建新配置文件: + +```nginx +server { + listen 80; + server_name yourdomain.com www.yourdomain.com; + + # 重定向 HTTP 到 HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name yourdomain.com www.yourdomain.com; + + # SSL 证书配置 + ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; + + # SSL 优化配置 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # 反向代理到后端应用 + 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; + } + + # API 接口 + location /api/ { + 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; + } +} +``` + +**测试并重启 Nginx:** +```bash +sudo nginx -t # 测试配置 +sudo systemctl restart nginx # 重启服务 +``` + +## 四、域名解析配置 + +### 1. 添加 A 记录 +在您的域名 DNS 管理后台添加: +- **记录类型**:A +- **主机记录**:@ 或 www +- **记录值**:您的服务器公网 IP +- **TTL**:600(或默认) + +### 2. 验证解析 +```bash +# 检查域名解析 +ping yourdomain.com +nslookup yourdomain.com +``` + +## 五、防火墙配置 + +### 开放必要端口 +```bash +# Ubuntu/Debian (ufw) +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw allow 22/tcp # SSH +sudo ufw enable + +# CentOS (firewalld) +sudo firewall-cmd --permanent --add-service=http +sudo firewall-cmd --permanent --add-service=https +sudo firewall-cmd --reload +``` + +## 六、微信小程序配置 + +### 1. 在小程序后台配置服务器域名 + +1. 登录[微信公众平台](https://mp.weixin.qq.com/) +2. 进入「开发」→「开发管理」→「开发设置」 +3. 在「服务器域名」中配置: + - **request合法域名**:`https://yourdomain.com` + - **uploadFile合法域名**:`https://yourdomain.com`(如果需要上传文件) + - **downloadFile合法域名**:`https://yourdomain.com`(如果需要下载文件) + +### 2. 小程序代码示例 + +```javascript +// app.js 或页面中 +wx.request({ + url: 'https://yourdomain.com/api/test', + method: 'GET', + header: { + 'content-type': 'application/json' + }, + success: function(res) { + console.log(res.data); + }, + fail: function(err) { + console.error(err); + } +}); +``` + +## 七、证书自动续期(Let's Encrypt) + +Let's Encrypt 证书有效期3个月,需要设置自动续期: + +```bash +# 测试续期 +sudo certbot renew --dry-run + +# 设置自动续期(crontab) +sudo crontab -e + +# 添加以下行(每月1号凌晨3点检查续期) +0 3 1 * * certbot renew --quiet && systemctl reload nginx +``` + +## 八、常见问题 + +### 1. 证书验证失败 +- 确保域名已正确解析到服务器 +- 确保80端口开放(Let's Encrypt验证需要) +- 检查防火墙设置 + +### 2. 小程序无法访问接口 +- 检查域名是否在小程序后台配置 +- 确认接口返回的 Content-Type 正确 +- 检查服务器 CORS 配置 + +### 3. 证书过期 +- 设置自动续期任务 +- 定期检查证书有效期:`sudo certbot certificates` + +### 4. 混合内容错误 +- 确保所有资源都使用 HTTPS +- 检查 API 返回的链接是否为 HTTPS + +## 九、安全建议 + +1. **使用强密码和密钥** +2. **定期更新系统和依赖** +3. **配置适当的 CORS 策略**(生产环境不要使用 `allow_origins=["*"]`) +4. **使用 HTTPS 强制重定向** +5. **配置安全响应头**(HSTS 等) +6. **定期备份证书和配置** + +## 十、测试 HTTPS + +```bash +# 使用 curl 测试 +curl https://yourdomain.com/api/test + +# 使用浏览器访问 +# https://yourdomain.com/api/test + +# 检查 SSL 配置 +# https://www.ssllabs.com/ssltest/analyze.html?d=yourdomain.com +``` + +--- + +## 快速检查清单 + +- [ ] 域名已解析到服务器 IP +- [ ] SSL 证书已获取并配置 +- [ ] 后端服务已配置 HTTPS +- [ ] 防火墙端口已开放(80, 443) +- [ ] Nginx 配置正确并重启 +- [ ] 小程序后台已配置服务器域名 +- [ ] 测试接口可以正常访问 +- [ ] 证书自动续期已配置 + +完成以上步骤后,您的微信小程序就可以通过 HTTPS 访问后端接口了! diff --git a/README_HTTPS.md b/README_HTTPS.md new file mode 100644 index 0000000..b8cdc70 --- /dev/null +++ b/README_HTTPS.md @@ -0,0 +1,157 @@ +# 快速开始 - HTTPS 后端配置 + +## 一、安装依赖 + +```bash +pip install -r requirements_https.txt +``` + +## 二、获取 SSL 证书 + +### 最简单的方式 - 使用 Let's Encrypt + +```bash +# 1. 安装 certbot +sudo apt-get install certbot # Ubuntu/Debian +# 或 +sudo yum install certbot # CentOS + +# 2. 获取证书(确保80端口开放) +sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com + +# 证书将保存在: +# /etc/letsencrypt/live/yourdomain.com/fullchain.pem +# /etc/letsencrypt/live/yourdomain.com/privkey.pem +``` + +## 三、配置域名解析 + +在您的域名管理后台添加 A 记录: +- 主机记录:@ 或 www +- 记录类型:A +- 记录值:您的服务器公网 IP + +## 四、运行服务器 + +### 方式一:直接运行 Python(开发测试) + +```bash +# 设置环境变量 +export SSL_CERT=/etc/letsencrypt/live/yourdomain.com/fullchain.pem +export SSL_KEY=/etc/letsencrypt/live/yourdomain.com/privkey.pem + +# 运行 Flask 版本 +python https_server_example.py + +# 或运行 FastAPI 版本 +export FRAMEWORK=fastapi +python https_server_example.py +``` + +### 方式二:使用 Nginx 反向代理(生产环境推荐) + +1. **安装 Nginx** +```bash +sudo apt-get install nginx +``` + +2. **配置 Nginx** +编辑 `/etc/nginx/sites-available/default`: + +```nginx +server { + listen 80; + server_name yourdomain.com; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl; + server_name yourdomain.com; + + ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; + + 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; + } +} +``` + +3. **启动服务** +```bash +# 启动后端(HTTP,端口8000) +python https_server_example.py + +# 重启 Nginx +sudo systemctl restart nginx +``` + +## 五、配置微信小程序 + +1. 登录[微信公众平台](https://mp.weixin.qq.com/) +2. 进入「开发」→「开发管理」→「开发设置」 +3. 配置服务器域名: + - **request合法域名**:`https://yourdomain.com` + +## 六、测试接口 + +```bash +# 测试 HTTPS 接口 +curl https://yourdomain.com/api/test + +# 测试登录接口 +curl -X POST https://yourdomain.com/api/user/login \ + -H "Content-Type: application/json" \ + -d '{"username":"test","password":"123456"}' +``` + +## 七、小程序调用示例 + +```javascript +// 在小程序中调用 +wx.request({ + url: 'https://yourdomain.com/api/test', + method: 'GET', + success: function(res) { + console.log(res.data); + }, + fail: function(err) { + console.error('请求失败', err); + } +}); +``` + +## 常见问题 + +### 1. 证书文件找不到 +- 检查证书路径是否正确 +- 确认证书文件权限:`sudo chmod 644 /etc/letsencrypt/live/yourdomain.com/*.pem` + +### 2. 端口被占用 +- 检查端口占用:`sudo netstat -tulpn | grep :443` +- 或使用 Nginx 反向代理,后端运行在 8000 端口 + +### 3. 小程序无法访问 +- 确认域名已在小程序后台配置 +- 检查服务器防火墙是否开放 443 端口 +- 确认域名已正确解析 + +### 4. 证书过期 +Let's Encrypt 证书3个月有效期,设置自动续期: +```bash +sudo crontab -e +# 添加:0 3 1 * * certbot renew --quiet && systemctl reload nginx +``` + +## 安全建议 + +1. ✅ 使用 Nginx 反向代理(更安全、更稳定) +2. ✅ 生产环境配置具体的 CORS 域名 +3. ✅ 定期更新证书 +4. ✅ 使用环境变量管理敏感信息 +5. ✅ 配置防火墙规则 diff --git a/gui_config.py b/gui_config.py new file mode 100644 index 0000000..9823689 --- /dev/null +++ b/gui_config.py @@ -0,0 +1,383 @@ +import sys +import threading +from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, + QHBoxLayout, QTableWidget, QTableWidgetItem, + QPushButton, QLineEdit, QLabel, QDateTimeEdit, + QHeaderView, QMessageBox, QCheckBox, QTextEdit) +from PyQt5.QtCore import Qt, QDateTime, pyqtSignal, QThread +from PyQt5.QtGui import QColor +from 自动化 import Pdd + + +class TaskThread(QThread): + """任务执行线程""" + status_update = pyqtSignal(int, str) # 行号, 状态消息 + + def __init__(self, row, url, user_id, time_start): + super().__init__() + self.row = row + self.url = url + self.user_id = user_id + self.time_start = time_start + self.is_running = True + + def run(self): + try: + self.status_update.emit(self.row, "正在初始化...") + pdd = Pdd( + url=self.url, + user_id=self.user_id, + time_start=self.time_start if self.time_start else None, + ) + + if not self.is_running: + return + + self.status_update.emit(self.row, "正在执行任务...") + pdd.action() + + if self.is_running: + self.status_update.emit(self.row, "完成") + except Exception as e: + if self.is_running: + self.status_update.emit(self.row, f"错误: {str(e)}") + + def stop(self): + self.is_running = False + + +class ConfigGUI(QMainWindow): + def __init__(self): + super().__init__() + self.task_threads = {} # 存储任务线程 {row: thread} + self.init_ui() + + def init_ui(self): + self.setWindowTitle("自动化任务配置工具") + self.setGeometry(100, 100, 1200, 700) + + # 主窗口部件 + main_widget = QWidget() + self.setCentralWidget(main_widget) + main_layout = QVBoxLayout() + main_widget.setLayout(main_layout) + + # 输入区域 + input_layout = QHBoxLayout() + + # URL输入 + url_label = QLabel("URL:") + self.url_input = QLineEdit() + self.url_input.setPlaceholderText("请输入小红书链接") + input_layout.addWidget(url_label) + input_layout.addWidget(self.url_input) + + # User ID输入 + user_id_label = QLabel("User ID:") + self.user_id_input = QLineEdit() + self.user_id_input.setPlaceholderText("请输入用户ID") + input_layout.addWidget(user_id_label) + input_layout.addWidget(self.user_id_input) + + # 定时发布时间输入 + time_label = QLabel("定时发布时间:") + self.time_input = QDateTimeEdit() + self.time_input.setDateTime(QDateTime.currentDateTime()) + self.time_input.setDisplayFormat("yyyy-MM-dd HH:mm:ss") + self.time_input.setCalendarPopup(True) + input_layout.addWidget(time_label) + input_layout.addWidget(self.time_input) + + # 添加按钮 + add_btn = QPushButton("添加任务") + add_btn.clicked.connect(self.add_task) + input_layout.addWidget(add_btn) + + main_layout.addLayout(input_layout) + + # 任务列表表格 + self.table = QTableWidget() + self.table.setColumnCount(6) + self.table.setHorizontalHeaderLabels(["选择", "URL", "User ID", "定时发布时间", "状态", "操作"]) + self.table.horizontalHeader().setStretchLastSection(True) + self.table.setSelectionBehavior(QTableWidget.SelectRows) + self.table.setEditTriggers(QTableWidget.NoEditTriggers) + + # 设置列宽 + header = self.table.horizontalHeader() + header.setSectionResizeMode(0, QHeaderView.ResizeToContents) # 选择列 + header.setSectionResizeMode(1, QHeaderView.Stretch) # URL列 + header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # User ID列 + header.setSectionResizeMode(3, QHeaderView.ResizeToContents) # 时间列 + header.setSectionResizeMode(4, QHeaderView.ResizeToContents) # 状态列 + header.setSectionResizeMode(5, QHeaderView.ResizeToContents) # 操作列 + + main_layout.addWidget(self.table) + + # 操作按钮区域 + button_layout = QHBoxLayout() + + # 全选/取消全选 + select_all_btn = QPushButton("全选") + select_all_btn.clicked.connect(self.select_all) + button_layout.addWidget(select_all_btn) + + deselect_all_btn = QPushButton("取消全选") + deselect_all_btn.clicked.connect(self.deselect_all) + button_layout.addWidget(deselect_all_btn) + + button_layout.addStretch() + + # 删除选中 + delete_btn = QPushButton("删除选中") + delete_btn.clicked.connect(self.delete_selected) + button_layout.addWidget(delete_btn) + + # 运行选中 + run_btn = QPushButton("运行选中任务") + run_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;") + run_btn.clicked.connect(self.run_selected) + button_layout.addWidget(run_btn) + + # 停止所有 + stop_all_btn = QPushButton("停止所有任务") + stop_all_btn.setStyleSheet("background-color: #f44336; color: white; font-weight: bold;") + stop_all_btn.clicked.connect(self.stop_all) + button_layout.addWidget(stop_all_btn) + + main_layout.addLayout(button_layout) + + # 日志区域 + log_label = QLabel("运行日志:") + main_layout.addWidget(log_label) + + self.log_text = QTextEdit() + self.log_text.setReadOnly(True) + self.log_text.setMaximumHeight(150) + main_layout.addWidget(self.log_text) + + def add_task(self): + url = self.url_input.text().strip() + user_id = self.user_id_input.text().strip() + time_str = self.time_input.dateTime().toString("yyyy-MM-dd HH:mm:ss") + + if not url: + QMessageBox.warning(self, "警告", "请输入URL") + return + + if not user_id: + QMessageBox.warning(self, "警告", "请输入User ID") + return + + # 添加行 + row = self.table.rowCount() + self.table.insertRow(row) + + # 复选框 + checkbox = QCheckBox() + checkbox.setChecked(True) + self.table.setCellWidget(row, 0, checkbox) + + # URL + url_item = QTableWidgetItem(url) + url_item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) + self.table.setItem(row, 1, url_item) + + # User ID + user_id_item = QTableWidgetItem(user_id) + user_id_item.setTextAlignment(Qt.AlignCenter) + self.table.setItem(row, 2, user_id_item) + + # 定时发布时间 + time_item = QTableWidgetItem(time_str) + time_item.setTextAlignment(Qt.AlignCenter) + self.table.setItem(row, 3, time_item) + + # 状态 + status_item = QTableWidgetItem("待运行") + status_item.setTextAlignment(Qt.AlignCenter) + status_item.setForeground(QColor(128, 128, 128)) + self.table.setItem(row, 4, status_item) + + # 操作按钮 + delete_btn = QPushButton("删除") + delete_btn.clicked.connect(lambda: self.delete_row(row)) + self.table.setCellWidget(row, 5, delete_btn) + + # 清空输入框 + self.url_input.clear() + self.user_id_input.clear() + + self.log(f"已添加任务: User ID={user_id}, URL={url[:50]}...") + + def delete_row(self, row): + # 如果任务正在运行,先停止 + if row in self.task_threads: + self.stop_task(row) + + self.table.removeRow(row) + # 更新后续行的引用 + self.update_row_references(row) + + def update_row_references(self, deleted_row): + """更新删除行之后的所有行的引用""" + new_threads = {} + for old_row, thread in self.task_threads.items(): + if old_row < deleted_row: + new_threads[old_row] = thread + elif old_row > deleted_row: + new_threads[old_row - 1] = thread + self.task_threads = new_threads + + # 更新删除按钮的连接 + for row in range(self.table.rowCount()): + delete_btn = self.table.cellWidget(row, 5) + if delete_btn: + delete_btn.clicked.disconnect() + delete_btn.clicked.connect(lambda checked, r=row: self.delete_row(r)) + + def select_all(self): + for row in range(self.table.rowCount()): + checkbox = self.table.cellWidget(row, 0) + if checkbox: + checkbox.setChecked(True) + + def deselect_all(self): + for row in range(self.table.rowCount()): + checkbox = self.table.cellWidget(row, 0) + if checkbox: + checkbox.setChecked(False) + + def delete_selected(self): + rows_to_delete = [] + for row in range(self.table.rowCount()): + checkbox = self.table.cellWidget(row, 0) + if checkbox and checkbox.isChecked(): + rows_to_delete.append(row) + + # 从后往前删除,避免索引变化 + for row in sorted(rows_to_delete, reverse=True): + self.delete_row(row) + + if rows_to_delete: + self.log(f"已删除 {len(rows_to_delete)} 个任务") + + def get_selected_rows(self): + """获取选中的行号列表""" + selected_rows = [] + for row in range(self.table.rowCount()): + checkbox = self.table.cellWidget(row, 0) + if checkbox and checkbox.isChecked(): + selected_rows.append(row) + return selected_rows + + def run_selected(self): + selected_rows = self.get_selected_rows() + + if not selected_rows: + QMessageBox.warning(self, "警告", "请至少选择一个任务") + return + + # 检查是否有任务正在运行 + running_count = sum(1 for row in selected_rows if row in self.task_threads) + if running_count > 0: + QMessageBox.warning(self, "警告", "选中的任务中有正在运行的,请先停止") + return + + # 启动选中的任务 + for row in selected_rows: + url = self.table.item(row, 1).text() + user_id = self.table.item(row, 2).text() + time_str = self.table.item(row, 3).text() + + # 更新状态 + status_item = self.table.item(row, 4) + status_item.setText("运行中") + status_item.setForeground(QColor(255, 165, 0)) + + # 创建并启动线程 + thread = TaskThread(row, url, user_id, time_str) + thread.status_update.connect(self.update_status) + thread.start() + self.task_threads[row] = thread + + self.log(f"开始运行任务 {row+1}: User ID={user_id}") + + def update_status(self, row, status): + """更新任务状态""" + if row >= self.table.rowCount(): + return + + status_item = self.table.item(row, 4) + if status_item: + status_item.setText(status) + + if "完成" in status: + status_item.setForeground(QColor(0, 128, 0)) + elif "错误" in status: + status_item.setForeground(QColor(255, 0, 0)) + elif "运行中" in status or "正在" in status: + status_item.setForeground(QColor(255, 165, 0)) + else: + status_item.setForeground(QColor(128, 128, 128)) + + # 如果任务完成或出错,从线程字典中移除 + if "完成" in status or "错误" in status: + if row in self.task_threads: + del self.task_threads[row] + + def stop_task(self, row): + """停止指定行的任务""" + if row in self.task_threads: + thread = self.task_threads[row] + thread.stop() + thread.wait() + del self.task_threads[row] + + status_item = self.table.item(row, 4) + if status_item: + status_item.setText("已停止") + status_item.setForeground(QColor(128, 128, 128)) + + self.log(f"已停止任务 {row+1}") + + def stop_all(self): + """停止所有正在运行的任务""" + rows_to_stop = list(self.task_threads.keys()) + for row in rows_to_stop: + self.stop_task(row) + + if rows_to_stop: + self.log(f"已停止 {len(rows_to_stop)} 个任务") + + def log(self, message): + """添加日志""" + from datetime import datetime + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.log_text.append(f"[{timestamp}] {message}") + + def closeEvent(self, event): + """窗口关闭时停止所有任务""" + if self.task_threads: + reply = QMessageBox.question( + self, + "确认", + "有任务正在运行,确定要关闭吗?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) + + if reply == QMessageBox.Yes: + self.stop_all() + event.accept() + else: + event.ignore() + else: + event.accept() + + +if __name__ == '__main__': + app = QApplication(sys.argv) + window = ConfigGUI() + window.show() + sys.exit(app.exec_()) diff --git a/haha.xlsx b/haha.xlsx new file mode 100644 index 0000000..81533ed Binary files /dev/null and b/haha.xlsx differ diff --git a/https_server_example.py b/https_server_example.py new file mode 100644 index 0000000..624091b --- /dev/null +++ b/https_server_example.py @@ -0,0 +1,202 @@ +""" +微信小程序 HTTPS 后端服务器示例 +支持 Flask 和 FastAPI 两种框架 +""" + +# ==================== Flask 版本 ==================== +from flask import Flask, jsonify, request +from flask_cors import CORS +import ssl +import os + +app = Flask(__name__) +# 配置 CORS,允许微信小程序访问 +CORS(app, resources={ + r"/api/*": { + "origins": "*", # 生产环境建议指定具体域名 + "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"], + "allow_headers": ["Content-Type", "Authorization"] + } +}) + +@app.route('/api/test', methods=['GET']) +def test(): + """测试接口""" + return jsonify({ + 'code': 200, + 'message': 'HTTPS 接口测试成功', + 'data': { + 'timestamp': request.headers.get('X-Request-Time', ''), + 'user_agent': request.headers.get('User-Agent', '') + } + }) + +@app.route('/api/user/login', methods=['POST']) +def login(): + """登录接口示例""" + data = request.get_json() + username = data.get('username', '') + password = data.get('password', '') + + # 这里添加您的登录逻辑 + if username and password: + return jsonify({ + 'code': 200, + 'message': '登录成功', + 'data': { + 'token': 'example_token_12345', + 'user_id': 1 + } + }) + else: + return jsonify({ + 'code': 400, + 'message': '用户名或密码不能为空' + }), 400 + +@app.route('/api/health', methods=['GET']) +def health(): + """健康检查接口""" + return jsonify({ + 'status': 'healthy', + 'service': 'wechat-miniprogram-api' + }) + +def run_flask_server(): + """运行 Flask HTTPS 服务器""" + # SSL 证书路径(根据您的实际情况修改) + cert_file = os.getenv('SSL_CERT', '/etc/letsencrypt/live/yourdomain.com/fullchain.pem') + key_file = os.getenv('SSL_KEY', '/etc/letsencrypt/live/yourdomain.com/privkey.pem') + + # 检查证书文件是否存在 + if not os.path.exists(cert_file) or not os.path.exists(key_file): + print(f"警告:证书文件不存在!") + print(f"证书路径: {cert_file}") + print(f"私钥路径: {key_file}") + print("请先配置 SSL 证书,或使用 Nginx 反向代理") + # 开发环境可以运行 HTTP(仅用于测试) + app.run(host='0.0.0.0', port=8000, debug=True) + return + + # 配置 SSL + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.load_cert_chain(cert_file, key_file) + + print("启动 Flask HTTPS 服务器...") + print(f"访问地址: https://yourdomain.com/api/test") + + app.run( + host='0.0.0.0', + port=443, + ssl_context=context, + debug=False # 生产环境设为 False + ) + + +# ==================== FastAPI 版本 ==================== +try: + from fastapi import FastAPI, Request + from fastapi.middleware.cors import CORSMiddleware + from fastapi.responses import JSONResponse + import uvicorn + + fastapi_app = FastAPI(title="微信小程序 API", version="1.0.0") + + # 配置 CORS + fastapi_app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # 生产环境建议指定具体域名 + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + + @fastapi_app.get("/api/test") + async def test_api(request: Request): + """测试接口""" + return { + 'code': 200, + 'message': 'HTTPS 接口测试成功', + 'data': { + 'timestamp': request.headers.get('x-request-time', ''), + 'user_agent': request.headers.get('user-agent', '') + } + } + + @fastapi_app.post("/api/user/login") + async def login_api(data: dict): + """登录接口示例""" + username = data.get('username', '') + password = data.get('password', '') + + if username and password: + return { + 'code': 200, + 'message': '登录成功', + 'data': { + 'token': 'example_token_12345', + 'user_id': 1 + } + } + else: + return JSONResponse( + status_code=400, + content={ + 'code': 400, + 'message': '用户名或密码不能为空' + } + ) + + @fastapi_app.get("/api/health") + async def health_check(): + """健康检查接口""" + return { + 'status': 'healthy', + 'service': 'wechat-miniprogram-api' + } + + def run_fastapi_server(): + """运行 FastAPI HTTPS 服务器""" + cert_file = os.getenv('SSL_CERT', '/etc/letsencrypt/live/yourdomain.com/fullchain.pem') + key_file = os.getenv('SSL_KEY', '/etc/letsencrypt/live/yourdomain.com/privkey.pem') + + if not os.path.exists(cert_file) or not os.path.exists(key_file): + print(f"警告:证书文件不存在!") + print(f"证书路径: {cert_file}") + print(f"私钥路径: {key_file}") + print("请先配置 SSL 证书,或使用 Nginx 反向代理") + # 开发环境可以运行 HTTP(仅用于测试) + uvicorn.run(fastapi_app, host="0.0.0.0", port=8000) + return + + print("启动 FastAPI HTTPS 服务器...") + print(f"访问地址: https://yourdomain.com/api/test") + + uvicorn.run( + fastapi_app, + host="0.0.0.0", + port=443, + ssl_keyfile=key_file, + ssl_certfile=cert_file + ) + +except ImportError: + print("FastAPI 未安装,跳过 FastAPI 示例") + print("安装命令: pip install fastapi uvicorn") + + +# ==================== 主程序 ==================== +if __name__ == '__main__': + import sys + + # 选择框架:'flask' 或 'fastapi' + framework = os.getenv('FRAMEWORK', 'flask').lower() + + if framework == 'fastapi': + try: + run_fastapi_server() + except NameError: + print("FastAPI 未安装,使用 Flask") + run_flask_server() + else: + run_flask_server() diff --git a/pdd_gui.log b/pdd_gui.log index 8b265a0..a78c9ca 100644 --- a/pdd_gui.log +++ b/pdd_gui.log @@ -15308,3 +15308,68 @@ template { 🚇华南城C口 #深圳逛超市#超市拍照#线下购物