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

6.5 KiB
Raw Permalink Blame History

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

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 中增加:

// 内部接口(无需用户登录验证)
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 中直接添加:

INTERNAL_API_KEY=sk_crmeb_app03_xxxxxxxx

然后在代码中读取:

$configKey = env('INTERNAL_API_KEY', '');

3.4 验证 WechatUserServices 是否有 getOne 方法

// 如果没有,WechatUserServices 中需添加
public function getOne(array $where): array
{
    return $this->dao->getOne($where);
}

四、app_03 后端改动

4.1 .env 新增配置

CRMEB_BASE_URL=https://your-crmeb-domain.com
CRMEB_API_KEY=sk_crmeb_app03_xxxxxxxx

4.2 config.py 新增字段

class Settings(BaseSettings):
    # ... 原有字段
    crmeb_base_url: str = ""
    crmeb_api_key: str = ""

4.3 新增 routers/auth.py 接口

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

// 检测 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 简化为一行:

// 之前(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);