feat: implement WeChat MP OAuth login

- Add wechat-mp.service.ts for MP web authorization
- Add wechat-mp.routes.ts with /api/mp endpoints
- Update EntryQRCode.vue to show H5 URL QR code
- Update HomeView.vue with WeChat auth detection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-02-03 21:33:32 +08:00
parent b53e732ffa
commit 9b11f99fed
5 changed files with 415 additions and 83 deletions

View File

@@ -0,0 +1,109 @@
import { Router, Request, Response } from 'express';
import { wechatMpService } from '../services/wechat-mp.service';
import { config } from '../config';
import { logger } from '../utils/logger';
const router = Router();
/**
* GET /api/mp/auth-url
* 获取公众号网页授权URL
* 前端在微信环境中调用此接口获取授权跳转URL
*/
router.get('/auth-url', (req: Request, res: Response) => {
try {
if (!wechatMpService.isConfigured()) {
return res.json({
success: false,
error: 'WeChat MP not configured',
});
}
// 从query获取回调地址默认使用移动端URL
const redirectUri = (req.query.redirect_uri as string) || config.mobileClientUrl;
// 生成随机state防止CSRF
const state = Math.random().toString(36).slice(2, 15);
// 使用snsapi_base静默授权只获取openid
const scope = (req.query.scope as 'snsapi_base' | 'snsapi_userinfo') || 'snsapi_base';
const authUrl = wechatMpService.generateAuthUrl(redirectUri, state, scope);
logger.info({ redirectUri, scope }, 'Generated WeChat MP auth URL');
return res.json({
success: true,
data: {
authUrl,
state,
},
});
} catch (error) {
logger.error({ error }, 'Failed to generate auth URL');
return res.status(500).json({
success: false,
error: 'Internal server error',
});
}
});
/**
* POST /api/mp/login
* 用code完成登录
* 前端在授权回调后携带code调用此接口完成登录
*/
router.post('/login', async (req: Request, res: Response) => {
try {
const { code } = req.body;
if (!code) {
return res.status(400).json({
success: false,
error: 'Missing code parameter',
});
}
if (!wechatMpService.isConfigured()) {
return res.json({
success: false,
error: 'WeChat MP not configured',
});
}
const result = await wechatMpService.login(code);
if (!result.success) {
return res.json({
success: false,
error: result.error,
});
}
return res.json({
success: true,
data: result.data,
});
} catch (error) {
logger.error({ error }, 'Failed to process MP login');
return res.status(500).json({
success: false,
error: 'Internal server error',
});
}
});
/**
* GET /api/mp/config
* 获取公众号配置状态(不返回敏感信息)
*/
router.get('/config', (_req: Request, res: Response) => {
return res.json({
success: true,
data: {
configured: wechatMpService.isConfigured(),
},
});
});
export default router;