feat: add official wechat session exchange boundary
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import "reflect-metadata";
|
||||
import { Controller, Get, Injectable, Module } from "@nestjs/common";
|
||||
import { Body, Controller, Get, Injectable, Module, Post } from "@nestjs/common";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { FastifyAdapter } from "@nestjs/platform-fastify";
|
||||
import type { NestFastifyApplication } from "@nestjs/platform-fastify";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { WechatAuthService } from "./wechat-auth.js";
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient {
|
||||
@@ -20,7 +21,35 @@ export class HealthController {
|
||||
}
|
||||
}
|
||||
|
||||
@Module({ controllers: [HealthController], providers: [PrismaService], exports: [PrismaService] })
|
||||
@Controller("public")
|
||||
export class PublicController {
|
||||
@Get("brand")
|
||||
brand(): { message: string } {
|
||||
return { message: "public brand endpoint is ready" };
|
||||
}
|
||||
|
||||
@Get("services")
|
||||
services(): { message: string } {
|
||||
return { message: "public services endpoint is ready" };
|
||||
}
|
||||
}
|
||||
|
||||
@Controller("customer/session")
|
||||
export class CustomerSessionController {
|
||||
constructor(private readonly wechatAuth: WechatAuthService) {}
|
||||
|
||||
@Post("wechat")
|
||||
async wechatLogin(@Body() body: { code?: string }): Promise<{ openid: string }> {
|
||||
const session = await this.wechatAuth.exchangeCode(body.code ?? "");
|
||||
return { openid: session.openid };
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
controllers: [HealthController, PublicController, CustomerSessionController],
|
||||
providers: [PrismaService, WechatAuthService],
|
||||
exports: [PrismaService, WechatAuthService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
export async function createApp(): Promise<NestFastifyApplication> {
|
||||
|
||||
44
apps/api/src/wechat-auth.ts
Normal file
44
apps/api/src/wechat-auth.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Injectable, ServiceUnavailableException, UnauthorizedException } from "@nestjs/common";
|
||||
|
||||
interface WechatSessionResponse {
|
||||
openid?: string;
|
||||
session_key?: string;
|
||||
unionid?: string;
|
||||
errcode?: number;
|
||||
errmsg?: string;
|
||||
}
|
||||
|
||||
export interface WechatSession {
|
||||
openid: string;
|
||||
sessionKey: string;
|
||||
unionid?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class WechatAuthService {
|
||||
async exchangeCode(code: string): Promise<WechatSession> {
|
||||
if (!code.trim()) throw new UnauthorizedException("微信登录 code 不能为空");
|
||||
const appId = process.env.WECHAT_APP_ID;
|
||||
const appSecret = process.env.WECHAT_APP_SECRET;
|
||||
if (!appId || !appSecret) throw new ServiceUnavailableException("微信登录尚未配置");
|
||||
|
||||
const query = new URLSearchParams({
|
||||
appid: appId,
|
||||
secret: appSecret,
|
||||
js_code: code,
|
||||
grant_type: "authorization_code",
|
||||
});
|
||||
const response = await fetch(
|
||||
`https://api.weixin.qq.com/sns/jscode2session?${query.toString()}`,
|
||||
);
|
||||
if (!response.ok) throw new ServiceUnavailableException("微信登录服务暂时不可用");
|
||||
const result = (await response.json()) as WechatSessionResponse;
|
||||
if (!result.openid || !result.session_key)
|
||||
throw new UnauthorizedException(result.errmsg ?? "微信登录失败");
|
||||
return {
|
||||
openid: result.openid,
|
||||
sessionKey: result.session_key,
|
||||
...(result.unionid ? { unionid: result.unionid } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,10 @@ POST /api/admin/uploads/presign
|
||||
|
||||
## 5. 文件上传流程
|
||||
|
||||
## 5.1 微信客户会话
|
||||
|
||||
小程序调用 `wx.login` 获取临时 `code`,提交到 `/api/customer/session/wechat`。服务端使用微信官方 `jscode2session` 接口换取 OpenID 和 session key,并在服务端建立 `CustomerUser` 映射和登录态。当前代码将微信调用封装在 `WechatAuthService`,使用 Node 原生 `fetch`,不引入第三方微信 SDK;后续订单接口必须从服务端登录态解析当前 OpenID,不能由客户端提交或选择 `customer_user_id`。生产实现不得把 OpenID 或 session key 返回给小程序,应改为 HttpOnly 会话 Cookie 或短期签名令牌。
|
||||
|
||||
1. 后台请求上传凭证。
|
||||
2. API 校验管理员权限和文件元数据。
|
||||
3. 前端直接上传对象存储。
|
||||
|
||||
Reference in New Issue
Block a user