248 lines
6.5 KiB
Markdown
248 lines
6.5 KiB
Markdown
# 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);
|
||
```
|