时地方

This commit is contained in:
coco
2026-07-21 14:53:24 +08:00
parent 8b94d76799
commit 1b8027e61c
308 changed files with 61360 additions and 10 deletions
+156
View File
@@ -0,0 +1,156 @@
我已经仔细阅读了这两个项目。让我为你总结一下:
📁 fumamxCLI - 孚盟MX 贸易数据 CLI 工具
类型: Python 命令行工具
技术栈: Python 3.11+, Click, Rich, Requests, Playwright
核心功能
公司搜索 - 搜索全球贸易企业
进口数据分析 - 获取进口企业数据、统计
出口数据分析 - 获取出口企业数据、统计
产品分析 - HS 编码分析、产品聚合、对比分析
国家分析 - 贸易来源国家、出口国家分析
贸易记录 - 查询完整贸易详情
企业详情 - 公司信息、联系人、电话
项目结构
fumamxCLI/
├── fumamx # 可执行入口
├── src/
│ ├── cli.py # 命令行接口 (21+ 命令)
│ ├── client.py # API 客户端
│ ├── config.py # 认证配置
│ ├── auth.py # 自动登录/Token 刷新
│ └── dynamic_api.py # 动态 API 调用
├── scripts/
│ ├── capture_api.js # 浏览器抓取脚本
│ └── setup_apis.py # API 代码生成器
└── fumamx_apis.json # 抓取的 API 数据
认证方式
使用浏览器 Cookie + Token
支持 Playwright 自动登录刷新
---
Token
Token 缓存机制
📁 fumamxCRM - 客户关系管理系统
类型: Web 应用 (FastAPI + React)
技术栈:
后端: FastAPI, SQLAlchemy, SQLite
前端: React, Vite, Tailwind CSS
端口: 后端 10001, 前端 10000
核心功能
客户管理
本地客户存储
潜在客户导入(从 fumamxCLI 数据源)
联系人管理
数据发现
关键词搜索企业
分页导入潜在客户
后台异步导入任务
深度分析
AI 驱动的企业商业画布分析
生成 Markdown 分析报告
支持多种大模型
系统设置
Cookie/Token 管理
系统状态监控
数据库模型
表名 说明
users 管理员用户
customers 本地客户
countries 国家信息
import_tasks 导入任务
potential_customers 潜在客户
potential_contacts 潜在客户联系人
deep_analyses 深度分析记录
system_settings 系统设置
项目结构
```
fumamxCRM/
├── backend/
│ ├── app.py # FastAPI 主应用
│ ├── models.py # SQLAlchemy 模型
│ ├── schemas.py # Pydantic 模型
│ ├── database.py # 数据库配置
│ ├── auth.py # 认证逻辑
│ ├── import_service.py # 导入服务
│ ├── fumamx_cli.py # CLI 包装器
│ └── routers/
│ ├── auth.py
│ ├── customers.py
│ ├── discover.py
│ ├── import_tasks.py
│ ├── potential_customers.py
│ ├── deep_analysis.py
│ └── settings
.py
└── frontend/
├── src/
│ ├── App.jsx
│ ├── api.js
│ ├── components/
│ └── pages/
└── node_modules/
🔗 两个项目的关系
fumamxCLI (数据获取层)
↓
fumamxCRM/backend/fumamx_cli.py (包装器)
↓
fumamxCRM (数据管理层 + Web UI)
```
fumamxCLI 负责从孚盟MX平台抓取贸易数据
fumamxCRM 提供 Web 界面管理这些数据,支持批量导入、深度分析
蔡叔叔,我已经了解了这两个项目的全貌。需要我:
继续查看某个具体文件?
运行/测试某个功能?
开发新功能?
---
搞定了!现在 fumamxCRM 也做成了 systemd 服务,**随系统启动自动运行**。
---
**创建的两个服务**
| 服务 | 端口 | 状态 |
|------|------|------|
| `fumamx-crm-backend.service` | 10001 | active (running) |
| `fumamx-crm-frontend.service` | 10000 | active (running) |
---
**常用命令**
```
# 查看状态
sudo systemctl status fumamx-crm-backend
sudo systemctl status fumamx-crm-frontend
# 重启
sudo systemctl restart fumamx-crm-backend
sudo systemctl restart fumamx-crm-frontend
# 停止
sudo systemctl stop fumamx-crm-backend
sudo systemctl stop fumamx-crm-frontend
# 查看日志
sudo journalctl -u fumamx-crm-backend -f
sudo journalctl -u fumamx-crm-frontend -f
```
---
**验证**
- 前端 `http://129.211.173.87:10000` → HTTP 200 ✓
- 后端 `http://129.211.173.87:10001/health` → `{"status":"healthy"}` ✓
现在服务器重启后,fumamxCRM 会自动启动,不需要再手动跑 `start.sh`。
+627
View File
@@ -0,0 +1,627 @@
我们开始新对话。
你使用OpenCLI(https://github.com/jackwener/OpenCLI)帮我生成网站https://www.fumamx.com的fumamxCLI
我在/home/ubuntu目录放一个网站的cookie文件名是fumamx_cookies.txt 。
我是通过URL: https://www.fumamx.com/#/login
账号和密码 showmin.zhou@163.com Kingc.83695910
然后获取到cookie的。
登录进来后,url 是:https://www.fumamx.com/#/main/workbench
在/home/ubuntu/nanocode目录建立一个fumamxCLI 做这个事情。
---
文件在:
```
/home/ubuntu/nanocode/fumamxCLI/scripts/capture_api.js
```
你可以直接复制使用。完整内容如下,**直接在浏览器控制台粘贴**:
```javascript
/**
* 孚盟MX API 抓取脚本
* 使用方法:
* 1. 登录 https://www.fumamx.com/#/login
* 2. 按 F12 打开开发者工具
* 3. 切换到 Console 面板
* 4. 粘贴此脚本并回车
* 5. 在页面上正常操作(点客户、邮件等模块)
* 6. 所有 API 请求会被记录并导出
*/
(function() {
'use strict';
window.fumamxAPIs = window.fumamxAPIs || [];
// 拦截 fetch
const originalFetch = window.fetch;
window.fetch = function(...args) {
const url = args[0];
const options = args[1] || {};
if (typeof url === 'string' && (url.includes('/api/') || url.includes('/rest/') || url.includes('/v1/'))) {
const record = {
method: options.method || 'GET',
url: url,
headers: options.headers,
body: options.body,
timestamp: new Date().toISOString()
};
window.fumamxAPIs.push(record);
console.log('📡 API:', record.method, url);
}
return originalFetch.apply(this, args);
};
// 拦截 XMLHttpRequest
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url, ...args) {
this._fumamxMethod = method;
this._fumamxUrl = url;
return originalOpen.call(this, method, url, ...args);
};
XMLHttpRequest.prototype.send = function(body) {
const url = this._fumamxUrl;
if (typeof url === 'string' && (url.includes('/api/') || url.includes('/rest/') || url.includes('/v1/'))) {
const record = {
method: this._fumamxMethod,
url: url,
body: body,
timestamp: new Date().toISOString()
};
window.fumamxAPIs.push(record);
console.log('📡 API:', record.method, url);
}
return originalSend.call(this, body);
};
// 导出功能
window.exportFumamxAPIs = function() {
const data = JSON.stringify(window.fumamxAPIs, null, 2);
const blob = new Blob([data], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'fumamx_apis.json';
a.click();
URL.revokeObjectURL(url);
console.log('✅ 已导出 ' + window.fumamxAPIs.length + ' 个 API 端点到 fumamx_apis.json');
};
console.log('✅ 孚盟MX API 抓取器已启动');
console.log('💡 操作页面后,在控制台运行 exportFumamxAPIs() 导出结果');
})();
```
**操作步骤**:
1. 登录孚盟 → F12 → Console
2. 粘贴上面的代码,回车
3. 点击「客户」「邮件」等模块
4. 看到 `📡 API: GET /api/xxx` 说明正在记录
5. 运行 `exportFumamxAPIs()` 下载 JSON
6. 把 `fumamx_apis.json` 上传到服务器
7. 运行 `python3 scripts/setup_apis.py fumamx_apis.json` 生成 CLI 代码
---
## ✅ 完成!fumamxCLI 已生成并通过验证
### 生成的文件
| 文件 | 描述 |
|------|------|
| `src/client.py` | API 客户端,20+ 个方法 |
| `src/cli.py` | 命令行接口,21 个命令 |
| `src/config.py` | 认证配置 (token) |
| `README.md` | 完整使用文档 |
### 可用命令 (21 个)
```
系统: status, cookies, call
公司: company search <keyword>
进口: import- enterprise/stats <name>
出口: export enterprise/stats <name>
产品: product hs-code/agg/analysis/compare-lose/compare-new
国家: country list/export/data <name>
贸易: trade detail <no>
```
### 验证结果
| 测试项 | 结果 |
|--------|------|
| API 连接 | ✅ 正常 |
| 用户认证 | ✅ showmin.zhou@163.com |
| 公司搜索 | ✅ 返回数据 |
| 国家列表 | ✅ 200+ 国家 |
| 通用调用 | ✅ 灵活调用任意端点 |
### 快速使用
```bash
cd /home/ubuntu/nanocode/fumamxCLI
# 检查状态
./fumamx status
# 搜索公司
./fumamx company search "Apple"
# 查看所有命令
./fumamx list-apis
# 调用任意 API
./fumamx call GET /pcapi/tradeData/api/V2/Account/GetTradeSourceCountrys
```
### 注意
Token 存储在 `src/config.py`,如果 API 返回 401,需要重新从浏览器抓取更新 token。
---
## 我们继续下一个任务,创建一个web项目,通过fumamxCLI对https://www.fumamx.com网站书籍进行管理。
### 项目目录
在/home/ubuntu/nanocode目录创建一个项目fumamxCRM(我已经创建了目录) 。
### Web项目风格
~/nanocode/personal-blog 这个项目的设计风格是新粗野风格,~/nanocode/personal-blog/DESIGN.md 是你总结的设计风格。我们使用这个风格来设计我们的项目。
### 技术栈
相同于~/nanocode/personal-blog
### 详细需求
#### 登录界面
是我们自己的fumamxCRM管理登录,类似于personal-blog的admin/abcd1234管理账号登录。但是,fumamxCRM没有前台浏览页面,登录页面是第一个页面,用户必须登录才能访问后台管理界面。
#### 后台管理主界面
后台管理主界面,类似于~/nanocode/personal-blog后台管理界面。
后台分几个模块:
1. 客户管理:客户信息是从哪里来的?是从fumamxCLI搜索并添加而来的。
2. 发现:通过fumamxCLI的search命令,搜索到客户信息列表。
客户详情:点击某个客户,进入客户详情
添加客户:可以将某个搜索到的客户,添加到本地数据库。
3. 设置:
修改管理员密码;
设置cookie, 因为cookie会过期。设置界面有地方输入cookie的文本内容,后台存储到文件fumamx_cookies.txt,然后提取信息和AccessToken
我们先实现这些基本功能,后续逐步添加更多功能。
**以上内容,你是否清晰,如果有问题请反问我。知道你清楚为止。
然后,你提出的技术方案,和实现计划,分步骤完成任务。**
![[Pasted image 20260414203040.png]]
---
**问题根源:浏览器导出时缺少 `user` 和 `pass` Cookie**
这是**浏览器导出机制的问题**,不是 CRM 的问题。你需要确认浏览器中确实存在这些 Cookie。
---
**排查步骤:**
### 1. 浏览器中验证 Cookie 是否存在
打开 https://www.fumamx.com 并登录后,按 **F12** → **Application** → **Cookies** → **https://www.fumamx.com**,检查是否有以下字段:
| Cookie 名 | 值示例 |
|-----------|--------|
| `user` | `showmin.zhou@163.com` |
| `pass` | `U2FsdGVkX1+fFGAO5Ib25ulin0hAOBVqhGjgBkmNMW4=` |
| `rememberPassword` | `true` |
### 2. 如果浏览器中没有这些 Cookie
说明你没有勾选**"记住密码"**或**"保持登录"**。请:
1. 退出登录
2. 重新登录时勾选 **"记住密码"** 或 **"保持登录状态"**
3. 再次检查 Cookies
### 3. 如果浏览器中有,但导出时丢失
尝试使用 **EditThisCookie** 扩展的 **"Export"** 功能时,选择 **"Export as JSON"** 而不是其他格式。
或者手动复制:在 EditThisCookie 中点击 `user` 和 `pass` 这两个 cookie,确认它们存在,然后手动编辑 JSON 添加进去:
```json
[
// ... 你导出的4个 ...
{
"domain": "www.fumamx.com",
"name": "user",
"value": "showmin.zhou@163.com",
"path": "/",
"secure": false,
"httpOnly": false,
"session": false,
"hostOnly": true,
"expirationDate": 1776821773,
"sameSite": "unspecified"
},
{
"domain": "www.fumamx.com",
"name": "pass",
"value": "U2FsdGVkX1+fFGAO5Ib25ulin0hAOBVqhGjgBkmNMW4=",
"path": "/",
"secure": false,
"httpOnly": false,
"session": false,
"hostOnly": true,
"expirationDate": 1776821773,
"sameSite": "unspecified"
}
]
```
---
**关键问题:fumamx API 需要 `user` + `pass` 来验证身份**,仅 `_t` token 不够。请检查浏览器中是否有这两个 cookie。
---
**还是不行** —— API 仍然返回 `"登陆令牌已失效"` (code: -2)
---
**问题分析:**
虽然 7 个 cookies 都正确加载了,但 `_t` 令牌本身已经**过期或无效**。
```
_t: ZjtDL09DbYW0mzah ← 这个 token 已经失效
```
`_t` 是一个 **session cookie**,它的有效期很短:
- 当你**关闭浏览器**时失效
- 或者**一段时间不操作**后过期
- 或者**从其他设备/浏览器登录**时旧 token 被吊销
---
**必须重新获取:**
1. 打开 https://www.fumamx.com
2. **如果已登录,先退出再重新登录**(强制刷新 token)
3. 登录后立即导出 cookies(**不要关闭浏览器**)
4. 将新的 JSON 发给我
**注意:** `_t` 是 session 级别的,**每次重新登录都会变**。必须刚登录完就导出。
---
**找到根本原因了!**
问题不在 Cookie,而在 **`config.py` 中的硬编码 token 已过期**:
```python
ACCESS_TOKEN = "Iiw4o3RoqltwY5EW"
INDIVIDUAL_ACCESS_TOKEN = "lAInwlJfiKx5FOnF"
```
fumamx API 使用**双层认证**:
1. Cookie `_t` ✅ (你从浏览器获取的,是对的)
2. URL 参数 `accessToken` + `individualAccessToken` ❌ (硬编码的,已过期)
---
**需要你在浏览器中获取这些动态 token:**
1. 打开 https://www.fumamx.com 并登录
2. 按 **F12** → **Network/网络** 标签
3. 在页面中随便搜索一个公司(比如输入 "Apple" 搜索)
4. 找到请求如 `company` 或 `ImpEnterprise`
5. 查看请求的 **URL 参数**,找到:
- `accessToken=xxxxx`
- `individualAccessToken=xxxxx`
或者:
1. 在 Console 中执行:
```javascript
localStorage.getItem('accessToken')
localStorage.getItem('individualAccessToken')
```
---
**能否帮我获取这两个 token?** 这样我就能更新 config.py 并验证连接。
---
**之前是通过浏览器脚本抓取的**:
1. 在浏览器控制台粘贴 `scripts/capture_api.js`
2. 操作页面(搜索公司等)
3. 运行 `exportFumamxAPIs()` 导出 API 请求
4. 从导出的 JSON 中提取 URL 参数里的 token
---
**但现在有更简单的方法**,直接在浏览器控制台执行:
```javascript
// 方法 1: 从 localStorage 获取
localStorage.getItem('accessToken')
localStorage.getItem('individualAccessToken')
// 方法 2: 从当前 URL 获取(如果你在 fumamx 页面)
new URLSearchParams(window.location.search).get('accessToken')
```
---
**或者手动复制**:
1. 按 **F12** → **Network**
2. 随便操作一下(比如搜索 "Apple")
3. 找一个请求,比如 `company` 或 `ImpEnterprise`
4. 查看请求的 **URL**,找到:
```
https://www.fumamx.com/pcapi/...?accessToken=xxxx&individualAccessToken=yyyy&...
```
5. 复制 `accessToken` 和 `individualAccessToken` 的值给我
---
**最快方式**:直接在浏览器控制台输入 `localStorage.getItem('accessToken')` 回车,把结果发给我。
---
试试这些方式获取 token:
**1. sessionStorage**
```javascript
sessionStorage.getItem('accessToken')
sessionStorage.getItem('individualAccessToken')
```
**2. 查看当前 URL**
```javascript
window.location.href
```
**3. Network 面板抓取(最可靠)**
- F12 → Network
- 在页面中搜索一个公司(比如输入 "Apple")
- 找到任意一个 API 请求(如 `company`、`ImpEnterprise`)
- 点击它 → Headers → Request URL
- 复制里面 `accessToken=` 和 `individualAccessToken=` 后面的值
**4. 查看所有 cookie**
```javascript
document.cookie
```
请试一下方法 2 或 3,把获取到的值发给我。
---
sessionStorage.getItem('accessToken')
'{"ownerId":11011595,"scope":"11011595","value":"71uRBqAwJWWLxWyp","createTime":1776217804534,"expireTime":1776304204534,"referURL":null,"agent":null,"agentVersion":null,"readOnly":0,"versionIteration":1,"companyTag":null,"platform":"pc","cid":1067743}'
sessionStorage.getItem('individualAccessToken')
'{"ownerId":10444522,"scope":"independent","value":"CmA5vs7xmGEYcsuD","createTime":1776217804069,"expireTime":1776304204069,"referURL":null,"agent":null,"agentVersion":null,"readOnly":0,"versionIteration":null,"companyTag":null,"platform":"pc","cid":null}'
---
**✅ 成功!API 连接正常,认证有效!**
```
✓ 已加载 7 个 cookies
✓ API 连接正常,认证有效
用户: showmin.zhou@163.com
公司ID: 1067743
```
---
**Token 已更新**:
- `ACCESS_TOKEN`: `71uRBqAwJWWLxWyp`
- `INDIVIDUAL_ACCESS_TOKEN`: `CmA5vs7xmGEYcsuD`
现在可以正常使用 fumamxCRM 了。让我也重启一下 CRM 后端服务:
---
从你提供的 `sessionStorage` 中提取的:
```javascript
sessionStorage.getItem('accessToken')
'{"ownerId":11011595,"scope":"11011595","value":"71uRBqAwJWWLxWyp",...}'
↑↑↑↑↑↑↑↑↑↑
这就是 ACCESS_TOKEN
```
我提取了 JSON 中 `value` 字段的值:
- `ACCESS_TOKEN` = `"71uRBqAwJWWLxWyp"`
- `INDIVIDUAL_ACCESS_TOKEN` = `"CmA5vs7xmGEYcsuD"` (来自 `individualAccessToken` 的 value)
---
**这两个 token 的区别**:
| Token | 来源 | 用途 |
|-------|------|------|
| `ACCESS_TOKEN` | `accessToken.value` | 公司/企业级别认证 |
| `INDIVIDUAL_ACCESS_TOKEN` | `individualAccessToken.value` | 个人用户级别认证 |
fumamx API 请求时需要同时提供这两个 token 作为 URL 参数。
对,**cookie 里没有 `ACCESS_TOKEN`**。
当前 cookie 文件里只有这 7 个:
| Cookie | 值 |
|--------|-----|
| `_t` | `CmA5vs7xmGEYcsuD` |
| `user` | `showmin.zhou@163.com` |
| `pass` | `U2FsdGVkX1...` |
| `language` | `zh-cn` |
| `timezone` | `Asia/Shanghai` |
| `AiImageEdit` | `false` |
| `rememberPassword` | `true` |
**注意**:`_t` 的值恰好和 `INDIVIDUAL_ACCESS_TOKEN` 相同(`CmA5vs7xmGEYcsuD`),但 `ACCESS_TOKEN`(`71uRBqAwJWWLxWyp`)**完全不在 cookie 中**。
---
**fumamx 的三层认证**:
| 认证层 | 存储位置 | 获取方式 |
|--------|----------|----------|
| Cookie `_t` | Cookie | 浏览器导出 |
| `INDIVIDUAL_ACCESS_TOKEN` | Cookie `_t` / sessionStorage | 相同 |
| `ACCESS_TOKEN` | **sessionStorage** | `sessionStorage.getItem('accessToken').value` |
所以之前只更新 cookie 是不够的,还必须从 `sessionStorage` 获取 `ACCESS_TOKEN`。
---
根据代码梳理,给你完整说明:
---
## 1️⃣ 现在有几个表?
**共 7 个表**(SQLite):
| 表名 | 用途 |
|------|------|
| `users` | 管理员用户(登录) |
| `customers` | 本地客户(早期表,现在用下面的) |
| `countries` | 国家信息(274条数据) |
| `import_tasks` | 潜在客户导入任务(后台任务) |
| `potential_customers` | **潜在客户主表**(核心) |
| `potential_contacts` | 潜在客户联系人 |
| `deep_analyses` | 深度分析记录 |
| `system_settings` | 系统设置(API Key等) |
---
## 2️⃣ 潜在客户列表加载逻辑
**前端请求** → **后端查询** → **直接返回**(无级联):
```python
# 1. 前端请求
GET /potential-customers/?keyword=xxx&country=US&page=1&page_size=20
# 2. 后端查询(单次查询,无JOIN)
query = db.query(PotentialCustomer)
if keyword:
query = query.filter(name包含 OR domain包含 OR description包含)
if country:
query = query.filter(country == country)
# 3. 排序分页
customers = query.order_by(source_score DESC).offset().limit().all()
# 4. 直接返回(反范式设计,状态内联在客户数据中)
return {
"results": [c.to_dict() for c in customers] # 包含 analysis_status
}
```
**关键点**:
- `analysis_status` 直接存储在 `potential_customers` 表(冗余字段)
- **无需 JOIN** `deep_analyses` 表
- **无需二次查询** 来判断"是否已分析"
---
## 3️⃣ 深度分析过程
**队列式后台处理**:
```
用户点击"深度分析"
↓
创建 DeepAnalysis 记录 (status=pending)
↓
更新 customer.analysis_status = "pending" ← 反范式同步
↓
加入内存队列 (analysis_queue)
↓
后台线程取出任务
↓
更新 analysis.status = "processing"
更新 customer.analysis_status = "processing"
↓
调用 LLM API (DeepSeek/Moonshot)
↓
生成报告文件 → 保存到 analysis_reports/
↓
更新 analysis.status = "completed"
更新 customer.analysis_status = "completed"
更新 customer.analysis_count += 1
```
**状态流转**:
```
none → pending → processing → completed
↘ failed
```
**反范式设计核心**:
- 分析状态同时存两份:
1. `deep_analyses.status` —— 详细记录
2. `potential_customers.analysis_status` —— 冗余字段(列表页直接用)
这样列表页查询时**只需查一张表**,性能提升明显。
---