Skip to content

Commit 663f543

Browse files
committed
feat: 添加服务条款/隐私政策/退款政策页面(中英双语),修复插件web.md规则(:contains禁止+localStorage陷阱),修正Footer公司地址
1 parent 9c249a7 commit 663f543

160 files changed

Lines changed: 6998 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

deploy/README.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# TestPilot AI — 腾讯云服务器部署备忘录
2+
3+
> 最后更新:2026-03-17
4+
5+
---
6+
7+
## 一、服务器信息
8+
9+
| 项目 ||
10+
|------|---|
11+
| 云厂商 | 腾讯云轻量服务器 |
12+
| 公网 IP | `82.157.97.179` |
13+
| 域名 | `testpilop.xinzao.com` |
14+
| 操作系统 | Ubuntu 24.04 LTS |
15+
| SSH 密钥 | `D:\Projects\TestPilotAI\key\TestPilotAi.pem` |
16+
| 应用目录 | `/opt/testpilot/app` |
17+
| 前端静态目录 | `/var/www/testpilot` |
18+
| 引擎端口 | `8900`(仅内网,Nginx 代理) |
19+
| 社区网页 | `http://testpilop.xinzao.com` |
20+
21+
---
22+
23+
## 二、SSH 登录
24+
25+
```bash
26+
# Windows PowerShell
27+
ssh -i "D:\Projects\TestPilotAI\key\TestPilotAi.pem" ubuntu@82.157.97.179
28+
29+
# macOS / Linux
30+
ssh -i ~/.ssh/TestPilotAi.pem ubuntu@82.157.97.179
31+
```
32+
33+
> 首次连接时 pem 文件权限需设为只读:
34+
> - Windows:右键 pem → 属性 → 安全 → 仅保留自己账户的读取权限
35+
> - Linux/Mac:`chmod 600 TestPilotAi.pem`
36+
37+
---
38+
39+
## 三、首次部署(从零开始)
40+
41+
```bash
42+
# 1. 登录服务器
43+
ssh -i "D:\Projects\TestPilotAI\key\TestPilotAi.pem" ubuntu@82.157.97.179
44+
45+
# 2. 上传并运行初始化脚本
46+
scp -i "D:\Projects\TestPilotAI\key\TestPilotAi.pem" \
47+
D:\Projects\TestPilotAI\deploy\setup-server.sh \
48+
ubuntu@82.157.97.179:/tmp/setup-server.sh
49+
50+
ssh -i "D:\Projects\TestPilotAI\key\TestPilotAi.pem" ubuntu@82.157.97.179 \
51+
"sudo bash /tmp/setup-server.sh"
52+
53+
# 3. 配置环境变量
54+
sudo cp /opt/testpilot/app/.env.example /opt/testpilot/app/.env
55+
sudo nano /opt/testpilot/app/.env
56+
# 必填项见第四节
57+
```
58+
59+
---
60+
61+
## 四、.env 必填配置项
62+
63+
```env
64+
# 数据库(PostgreSQL,社区功能必须)
65+
TP_DATABASE_URL=postgresql+psycopg2://testpilot:密码@localhost:5432/testpilot
66+
67+
# JWT 密钥(随机字符串,生产环境必须修改)
68+
JWT_SECRET_KEY=换成随机字符串至少32位
69+
70+
# 豆包 AI API 密钥
71+
TP_AI_API_KEY=你的方舟平台密钥
72+
TP_AI_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
73+
TP_AI_MODEL=doubao-seed-1-8-251228
74+
75+
# 禁用热重载(服务器上必须为 false)
76+
TP_SERVER_RELOAD=false
77+
78+
# 仅监听本地(Nginx 代理,不对外暴露)
79+
TP_SERVER_HOST=127.0.0.1
80+
TP_SERVER_PORT=8900
81+
```
82+
83+
---
84+
85+
## 五、Nginx 配置
86+
87+
```bash
88+
# 复制配置文件
89+
sudo cp /opt/testpilot/app/deploy/nginx-testpilot.conf \
90+
/etc/nginx/sites-available/testpilot
91+
92+
# 启用站点
93+
sudo ln -sf /etc/nginx/sites-available/testpilot \
94+
/etc/nginx/sites-enabled/testpilot
95+
96+
# 删除默认站点(避免冲突)
97+
sudo rm -f /etc/nginx/sites-enabled/default
98+
99+
# 测试配置
100+
sudo nginx -t
101+
102+
# 重载
103+
sudo systemctl reload nginx
104+
```
105+
106+
---
107+
108+
## 六、systemd 服务(引擎守护进程)
109+
110+
```bash
111+
# 复制服务文件
112+
sudo cp /opt/testpilot/app/deploy/testpilot-api.service \
113+
/etc/systemd/system/testpilot-api.service
114+
115+
# 启用并启动
116+
sudo systemctl daemon-reload
117+
sudo systemctl enable testpilot-api
118+
sudo systemctl start testpilot-api
119+
120+
# 查看状态
121+
sudo systemctl status testpilot-api
122+
123+
# 查看日志(实时)
124+
sudo journalctl -u testpilot-api -f
125+
```
126+
127+
---
128+
129+
## 七、日常更新部署
130+
131+
```bash
132+
# 本地:推送代码到 GitHub
133+
git add . && git commit -m "更新说明" && git push
134+
135+
# 服务器:拉取并重建
136+
ssh -i "D:\Projects\TestPilotAI\key\TestPilotAi.pem" ubuntu@82.157.97.179 << 'EOF'
137+
cd /opt/testpilot/app
138+
git pull
139+
140+
# 更新 Python 依赖(如有新库)
141+
source venv/bin/activate
142+
poetry install --no-interaction --only main
143+
144+
# 重建前端
145+
cd web && npm ci && npm run build
146+
sudo rm -rf /var/www/testpilot
147+
sudo cp -r dist /var/www/testpilot
148+
sudo chown -R www-data:www-data /var/www/testpilot
149+
150+
# 重启后端
151+
sudo systemctl restart testpilot-api
152+
EOF
153+
```
154+
155+
---
156+
157+
## 八、SSL 证书(HTTPS)
158+
159+
```bash
160+
# 使用 Let's Encrypt 申请免费证书
161+
sudo certbot --nginx -d testpilopai.xinzao.com
162+
163+
# 自动续期(certbot 安装后已自动配置 cron)
164+
sudo certbot renew --dry-run
165+
```
166+
167+
> ⚠️ 申请证书前确保:
168+
> - 域名 `testpilopai.xinzao.com` 已在 DNS 解析到 `82.157.97.179`
169+
> - 服务器 80 端口未被防火墙屏蔽
170+
171+
---
172+
173+
## 九、PostgreSQL 数据库安装
174+
175+
```bash
176+
# 安装
177+
sudo apt-get install -y postgresql
178+
179+
# 创建数据库和用户
180+
sudo -u postgres psql << 'EOF'
181+
CREATE USER testpilot WITH PASSWORD '设置一个强密码';
182+
CREATE DATABASE testpilot OWNER testpilot;
183+
GRANT ALL PRIVILEGES ON DATABASE testpilot TO testpilot;
184+
EOF
185+
186+
# 运行数据库迁移
187+
cd /opt/testpilot/app
188+
source venv/bin/activate
189+
alembic upgrade head
190+
```
191+
192+
---
193+
194+
## 十、常用排查命令
195+
196+
```bash
197+
# 引擎健康检查
198+
curl http://127.0.0.1:8900/api/v1/health
199+
200+
# 查看引擎日志
201+
sudo journalctl -u testpilot-api -n 100
202+
203+
# 查看 Nginx 错误日志
204+
sudo tail -f /var/log/nginx/error.log
205+
206+
# 查看磁盘空间
207+
df -h
208+
209+
# 查看内存
210+
free -h
211+
212+
# 重启所有服务
213+
sudo systemctl restart testpilot-api nginx postgresql
214+
```
215+
216+
---
217+
218+
## 十一、防火墙端口
219+
220+
腾讯云控制台 → 防火墙规则,需开放:
221+
222+
| 端口 | 协议 | 说明 |
223+
|------|------|------|
224+
| 22 | TCP | SSH |
225+
| 80 | TCP | HTTP(Nginx) |
226+
| 443 | TCP | HTTPS(SSL) |
227+
228+
> 8900 端口**不需要**对外开放,Nginx 内部代理即可。

desktop-demo/after_input.png

7.58 KB
Loading

desktop-demo/after_login.png

8.82 KB
Loading

desktop-demo/before.png

7.17 KB
Loading

desktop-demo/check_ui.ps1

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Add-Type -AssemblyName UIAutomationClient
2+
Add-Type -AssemblyName UIAutomationTypes
3+
4+
$root = [System.Windows.Automation.AutomationElement]::RootElement
5+
$cond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::NameProperty, "NoteApp")
6+
$win = $root.FindFirst([System.Windows.Automation.TreeScope]::Children, $cond)
7+
8+
function Get-Children($el, $depth) {
9+
if ($depth -gt 4) { return }
10+
$children = $el.FindAll([System.Windows.Automation.TreeScope]::Children, [System.Windows.Automation.Condition]::TrueCondition)
11+
foreach ($c in $children) {
12+
$indent = " " * $depth
13+
$n = $c.Current.Name
14+
$cls = $c.Current.ClassName
15+
$aid = $c.Current.AutomationId
16+
$ct = $c.Current.ControlType.ProgrammaticName
17+
Write-Output "$indent[$ct] Name='$n' Class='$cls' AutomationId='$aid'"
18+
Get-Children $c ($depth + 1)
19+
}
20+
}
21+
22+
if ($null -ne $win) {
23+
Write-Output "Found: $($win.Current.Name) hwnd=$($win.Current.NativeWindowHandle)"
24+
Get-Children $win 1
25+
}
26+
else {
27+
Write-Output "NOT FOUND - NoteApp window not detected"
28+
}

desktop-demo/test_pywinauto.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""测试pywinauto对tkinter NoteApp的操作能力。"""
2+
import sys
3+
import time
4+
5+
def main():
6+
from pywinauto import Application
7+
from PIL import Image
8+
import ctypes, ctypes.wintypes
9+
10+
# 连接NoteApp
11+
try:
12+
app = Application(backend='win32').connect(title_re='.*NoteApp.*')
13+
except Exception:
14+
# 通过类名连接
15+
app = Application(backend='win32').connect(class_name='TkTopLevel')
16+
17+
win = app.window(title_re='.*NoteApp.*')
18+
r = win.rectangle()
19+
print(f"Window: {r.left},{r.top} -> {r.right},{r.bottom} ({r.right-r.left}x{r.bottom-r.top})")
20+
sys.stdout.flush()
21+
22+
# 截图函数
23+
user32 = ctypes.windll.user32
24+
gdi32 = ctypes.windll.gdi32
25+
26+
def capture(hwnd, filename):
27+
rect = ctypes.wintypes.RECT()
28+
user32.GetWindowRect(hwnd, ctypes.byref(rect))
29+
w = rect.right - rect.left
30+
h = rect.bottom - rect.top
31+
32+
class BITMAPINFOHEADER(ctypes.Structure):
33+
_fields_ = [
34+
('biSize', ctypes.c_uint32), ('biWidth', ctypes.c_long),
35+
('biHeight', ctypes.c_long), ('biPlanes', ctypes.c_ushort),
36+
('biBitCount', ctypes.c_ushort), ('biCompression', ctypes.c_uint32),
37+
('biSizeImage', ctypes.c_uint32), ('biXPelsPerMeter', ctypes.c_long),
38+
('biYPelsPerMeter', ctypes.c_long), ('biClrUsed', ctypes.c_uint32),
39+
('biClrImportant', ctypes.c_uint32),
40+
]
41+
class BITMAPINFO(ctypes.Structure):
42+
_fields_ = [('bmiHeader', BITMAPINFOHEADER), ('bmiColors', ctypes.c_uint32 * 3)]
43+
44+
bmi = BITMAPINFO()
45+
bmi.bmiHeader.biSize = ctypes.sizeof(BITMAPINFOHEADER)
46+
bmi.bmiHeader.biWidth = w
47+
bmi.bmiHeader.biHeight = -h
48+
bmi.bmiHeader.biPlanes = 1
49+
bmi.bmiHeader.biBitCount = 32
50+
51+
hdc = user32.GetWindowDC(hwnd)
52+
mem_dc = gdi32.CreateCompatibleDC(hdc)
53+
bitmap = gdi32.CreateCompatibleBitmap(hdc, w, h)
54+
gdi32.SelectObject(mem_dc, bitmap)
55+
user32.PrintWindow(hwnd, mem_dc, 2)
56+
buf = ctypes.create_string_buffer(w * h * 4)
57+
gdi32.GetDIBits(mem_dc, bitmap, 0, h, buf, ctypes.byref(bmi), 0)
58+
gdi32.DeleteObject(bitmap)
59+
gdi32.DeleteDC(mem_dc)
60+
user32.ReleaseDC(hwnd, hdc)
61+
62+
img = Image.frombytes('RGBA', (w, h), buf.raw, 'raw', 'BGRA')
63+
img.save(filename, 'PNG')
64+
print(f" 截图: {filename} ({w}x{h}, {img.size})")
65+
sys.stdout.flush()
66+
67+
hwnd = win.handle
68+
print(f"hwnd: {hwnd}")
69+
sys.stdout.flush()
70+
71+
# 截图1:操作前
72+
capture(hwnd, "desktop-demo/before.png")
73+
74+
# 聚焦
75+
win.set_focus()
76+
time.sleep(0.5)
77+
78+
# 点击用户名输入框(归一化坐标约0.5, 0.49)
79+
w = r.right - r.left
80+
h = r.bottom - r.top
81+
username_x = int(0.5 * w)
82+
username_y = int(0.49 * h)
83+
print(f"点击用户名: ({username_x}, {username_y}) 相对窗口")
84+
sys.stdout.flush()
85+
86+
win.click_input(coords=(username_x, username_y))
87+
time.sleep(0.3)
88+
89+
# 输入admin
90+
print("输入: admin")
91+
sys.stdout.flush()
92+
win.type_keys('admin', with_spaces=True)
93+
time.sleep(0.3)
94+
95+
# Tab切到密码框
96+
win.type_keys('{TAB}')
97+
time.sleep(0.2)
98+
99+
# 输入admin123
100+
print("输入: admin123")
101+
sys.stdout.flush()
102+
win.type_keys('admin123', with_spaces=True)
103+
time.sleep(0.3)
104+
105+
# 截图2:输入后
106+
capture(hwnd, "desktop-demo/after_input.png")
107+
108+
# 点击Login(归一化约0.5, 0.67)
109+
login_x = int(0.5 * w)
110+
login_y = int(0.67 * h)
111+
print(f"点击Login: ({login_x}, {login_y}) 相对窗口")
112+
sys.stdout.flush()
113+
114+
win.click_input(coords=(login_x, login_y))
115+
time.sleep(1.5)
116+
117+
# 截图3:登录后
118+
capture(hwnd, "desktop-demo/after_login.png")
119+
120+
print("完成!请检查 desktop-demo/ 下的3张截图")
121+
sys.stdout.flush()
122+
123+
124+
if __name__ == "__main__":
125+
main()

0 commit comments

Comments
 (0)