Files
app_03_pregnancy/docs/crmeb-internal-api-plan.md

248 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CRMEB 后端新增内部接口方案
## 一、背景
当前 H5 登录需要小程序端传多个 URL 参数:
```
?uid=7&openid=oauWC...&phone=13693687880&name=王小明&avatar=https://...
```
改为只传 `openid`,由 app_03 后端主动向 CRMEB 查询用户信息。
---
## 二、方案架构
```
H5: ?openid=xxx
↓
app_03 后端 → GET /api/internal/user-info?openid=xxx → CRMEB 后端
← { nickname, phone, avatar, uid }
↓
创建/更新 User → 返回 token → 登录完成
```
---
## 三、CRMEB 端改动
### 3.1 新增控制器
`app/controller/api/v1/InternalController.php`:
```php
<?php
namespace app\controller\api\v1;
use app\BaseController;
use app\services\wechat\WechatUserServices;
use think\Response;
class InternalController extends BaseController
{
/**
* 内部接口:根据 openid 获取用户信息
* 用于 app_03 H5 登录时自动补齐用户数据
*
* @param WechatUserServices $wechatUserServices
* @return Response
*/
public function userInfo(WechatUserServices $wechatUserServices): Response
{
// 1. 验证内部 API Key
$apiKey = $this->request->header('X-Internal-Key');
$configKey = sys_config('internal_api_key', '');
if (!$apiKey || $apiKey !== $configKey) {
return json(['code' => 403, 'msg' => 'Forbidden']);
}
// 2. 获取 openid 参数
$openid = $this->request->param('openid', '');
if (!$openid) {
return json(['code' => 400, 'msg' => 'openid is required']);
}
// 3. 查询微信用户信息
$wechatUser = $wechatUserServices->getOne(['openid' => $openid]);
if (!$wechatUser) {
return json(['code' => 404, 'msg' => 'user not found']);
}
// 4. 获取用户主表信息(nickname, phone, avatar 可能在用户表或微信表)
$uid = $wechatUser['uid'];
$userServices = app()->make(\app\services\user\UserServices::class);
$user = $userServices->getUserInfo($uid);
return json([
'code' => 200,
'data' => [
'uid' => $uid,
'nickname' => $user['nickname'] ?? $wechatUser['nickname'] ?? '',
'phone' => $user['phone'] ?? $wechatUser['phone'] ?? '',
'avatar' => $user['avatar'] ?? $wechatUser['headimgurl'] ?? '',
],
]);
}
}
```
### 3.2 新增路由
`config/route.php` 或 `route/app.php` 中增加:
```php
// 内部接口(无需用户登录验证)
Route::get('api/internal/user-info', 'api/v1.Internal/userInfo');
```
> CRMEB 的路由文件通常在 `route/app.php`、`route/api.php` 或 `route/v1.php` 中,根据实际项目结构添加。
### 3.3 配置内部分享密钥
在 CRMEB 后台 → 系统设置 → 系统配置 中添加配置项 `internal_api_key`:
```
键名: internal_api_key
键值: 生成一个随机字符串,如:sk_crmeb_app03_xxxxxxxx
说明: 内部服务间通信密钥
```
或者在 `.env` 中直接添加:
```ini
INTERNAL_API_KEY=sk_crmeb_app03_xxxxxxxx
```
然后在代码中读取:
```php
$configKey = env('INTERNAL_API_KEY', '');
```
### 3.4 验证 WechatUserServices 是否有 getOne 方法
```php
// 如果没有,WechatUserServices 中需添加
public function getOne(array $where): array
{
return $this->dao->getOne($where);
}
```
---
## 四、app_03 后端改动
### 4.1 `.env` 新增配置
```ini
CRMEB_BASE_URL=https://your-crmeb-domain.com
CRMEB_API_KEY=sk_crmeb_app03_xxxxxxxx
```
### 4.2 `config.py` 新增字段
```python
class Settings(BaseSettings):
# ... 原有字段
crmeb_base_url: str = ""
crmeb_api_key: str = ""
```
### 4.3 新增 `routers/auth.py` 接口
```python
class OpenidRequest(BaseModel):
openid: str
@router.post("/openid-login")
async def openid_login(req: OpenidRequest) -> TokenResponse:
"""仅传 openid → 调 CRMEB 获取用户信息 → 创建/更新 User → 返回 token"""
# 1. 调 CRMEB 内部接口
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{settings.crmeb_base_url}/api/internal/user-info",
params={"openid": req.openid},
headers={"X-Internal-Key": settings.crmeb_api_key},
)
data = resp.json()
if data.get("code") != 200:
raise HTTPException(status_code=400, detail="获取用户信息失败")
info = data["data"]
# 2. 查找或创建用户
user = await User.find_one(User.openid == req.openid)
if not user:
user = User(
openid=req.openid,
name=info.get("nickname", "") or "",
phone=info.get("phone") or None,
avatar_url=info.get("avatar") or None,
)
await user.insert()
else:
user.name = info.get("nickname", "") or user.name
if info.get("phone"):
user.phone = info["phone"]
if info.get("avatar"):
user.avatar_url = info["avatar"]
await user.save()
# 3. 返回 token
token = _build_token(user)
return TokenResponse(
token=token,
user_id=str(user.id),
openid=user.openid,
mobile=user.phone or "",
)
```
### 4.4 H5 前端 LoginPage
```typescript
// 检测 openid 参数 → 调后端 openid-login
const openidParam = params.get('openid');
if (openidParam && openidParam.trim()) {
const res = await fetchApi("/auth/openid-login", {
method: "POST",
body: JSON.stringify({ openid: openidParam }),
});
setTokenAndUser(res.token, { id: res.user_id, openid: res.openid, phone: res.mobile });
// fetchProfile 自动拿到 name/avatar_url
}
```
---
## 五、部署步骤
| 步骤 | 操作 | 负责 |
|------|------|------|
| 1 | CRMEB 加 `InternalController` | 小程序端 |
| 2 | CRMEB 加路由 | 小程序端 |
| 3 | CRMEB 配置 `internal_api_key` | 小程序端 |
| 4 | app_03 `.env` 加 `CRMEB_BASE_URL` + `CRMEB_API_KEY` | H5 端 |
| 5 | app_03 `config.py` 加字段 | H5 端 |
| 6 | app_03 加 `openid-login` 接口 | H5 端 |
| 7 | H5 前端 LoginPage 改为调 `openid-login` | H5 端 |
---
## 六、最终效果
小程序端 web-view URL 简化为一行:
```javascript
// 之前(5个参数)
const url = 'https://mp.yizhezhe.com/app_03/?uid=' + uid + '&openid=' + encodeURIComponent(openid) + '&phone=' + phone + '&name=' + encodeURIComponent(name) + '&avatar=' + encodeURIComponent(avatar);
// 之后(1个参数)
const url = 'https://mp.yizhezhe.com/app_03/?openid=' + encodeURIComponent(openid);
```