feat(website): 培训网站登录迁移到平台 /auth(统一账号 + 平台 JWT)
阶段2 网站侧补全(阶段3 已停用 /api/auth 登录): - services/auth.js 端点 /api/auth/*→/auth/*:sendCode/phone-login/login/update-profile 走平台, pine_token 存平台 JWT;用户端 X-Client:web→source=web;sendCode 兼容 stub_code→debugCode。 - updateProfile/phoneLogin/login 映射平台 profile→本地 user 契约 (username/name/avatar/phone/phoneBound/status/topics/source),与小程序一致。 - logout 改本地清除;业务(报名/测评/政策等)用平台 JWT(Phase3 培训 require_auth 已验证)。 - 不再产生 accounts 双套账号;手机验证码统一由平台下发校验。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,79 +1,75 @@
|
||||
/**
|
||||
* 认证服务(auth service)
|
||||
* 认证服务(auth service)——统一账号
|
||||
* -------------------------------------------------------------
|
||||
* 用户端:手机号 + 短信验证码登录 / 注册(手机号即账号,未注册自动注册)。
|
||||
* POST /api/auth/send-code 发送验证码(演示环境响应返回 debugCode)
|
||||
* POST /api/auth/phone-login 手机号 + 验证码 → 自动注册或登录 → token
|
||||
* 用户端:手机号 + 短信验证码登录(手机号即账号,未注册自动注册)。
|
||||
* POST /auth/send-code 发送验证码(stub 环境返回 stub_code/debugCode)
|
||||
* POST /auth/phone-login 手机号 + 验证码 → 自动注册或登录 → 平台 JWT
|
||||
* POST /auth/update-profile 更新资料 → 平台 profile
|
||||
* 平台端(/pine 内部管理):账号密码登录(内置 pine/123456)。
|
||||
* POST /api/auth/login
|
||||
* 迁移真实后端:只改 API_BASE 与保持 /api/auth/* 契约即可。
|
||||
* POST /auth/login 密码登录 → 平台 JWT + 身份列表
|
||||
* 统一账号:所有登录写全局 users + 平台 JWT(与小程序一致,见 分端口同步约束.md)。
|
||||
*/
|
||||
const API_BASE = 'https://opc.pinesound.cn'; // 云超服 FastAPI 后端
|
||||
const TOKEN_KEY = 'pine_token';
|
||||
const USER_KEY = 'pine_user';
|
||||
|
||||
/** 平台 profile → 本地 user 契约字段(各页面读取 name/status/topics/source/phoneBound 等) */
|
||||
function pickUser(d) {
|
||||
return {
|
||||
username: d.username,
|
||||
name: d.name || d.nickname || d.username || '用户',
|
||||
avatar: d.avatar || '',
|
||||
phone: d.phone || '',
|
||||
phoneBound: !!d.phoneBound,
|
||||
status: d.status || '',
|
||||
topics: Array.isArray(d.topics) ? d.topics : [],
|
||||
source: d.source || ''
|
||||
};
|
||||
}
|
||||
|
||||
export function saveSession(data) {
|
||||
if (!data || !data.token) return;
|
||||
localStorage.setItem(TOKEN_KEY, data.token);
|
||||
localStorage.setItem(USER_KEY, JSON.stringify({ username: data.username, name: data.name || data.username }));
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(pickUser(data)));
|
||||
}
|
||||
|
||||
async function req(path, { method = 'POST', body, auth = false } = {}) {
|
||||
const headers = { 'Content-Type': 'application/json', 'X-Client': 'web' };
|
||||
if (auth) { const t = getToken(); if (t) headers.Authorization = `Bearer ${t}`; }
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(`${API_BASE}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined });
|
||||
} catch {
|
||||
throw new Error('网络连接失败,请检查网络后重试');
|
||||
}
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new Error(data.detail || data.error || '请求失败,请重试');
|
||||
return data; // 平台契约:token/username/name/...(/auth/*)
|
||||
}
|
||||
|
||||
/* ---------------- 用户端:手机号 + 验证码 ---------------- */
|
||||
export async function sendCode(phone) {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(`${API_BASE}/api/auth/send-code`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Client': 'web' },
|
||||
body: JSON.stringify({ phone })
|
||||
});
|
||||
} catch {
|
||||
throw new Error('网络连接失败,请检查网络后重试');
|
||||
}
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || !data.ok) throw new Error(data.error || '验证码发送失败,请重试');
|
||||
return data; // { ok, sent, debugCode }
|
||||
const data = await req('/auth/send-code', { body: { phone } });
|
||||
return { ok: true, sent: true, debugCode: data.stub_code ?? data.debugCode };
|
||||
}
|
||||
|
||||
export async function phoneLogin(phone, code) {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(`${API_BASE}/api/auth/phone-login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Client': 'web' },
|
||||
body: JSON.stringify({ phone, code })
|
||||
});
|
||||
} catch {
|
||||
throw new Error('网络连接失败,请检查网络后重试');
|
||||
}
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || !data.token) throw new Error(data.error || '登录失败,请重试');
|
||||
saveSession(data);
|
||||
const data = await req('/auth/phone-login', { body: { phone, code } });
|
||||
if (data.token) saveSession(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/* ---------------- 平台端:账号密码登录(内部管理) ---------------- */
|
||||
export async function login(username, password) {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(`${API_BASE}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Client': 'web' },
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
} catch {
|
||||
throw new Error('网络连接失败,请检查网络后重试');
|
||||
}
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || !data.token) throw new Error(data.error || '登录失败,请重试');
|
||||
saveSession(data);
|
||||
const data = await req('/auth/login', { body: { username, password } });
|
||||
if (data.token) saveSession(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
localStorage.removeItem(USER_KEY);
|
||||
try { fetch(`${API_BASE}/api/auth/logout`, { method: 'POST' }).catch(() => {}); } catch { /* noop */ }
|
||||
// 平台用 /auth/revoke-token;这里本地退出即可(与小程序一致)
|
||||
}
|
||||
|
||||
export function getToken() {
|
||||
@@ -89,15 +85,9 @@ export function saveUser(user) {
|
||||
|
||||
/** 更新当前用户资料(昵称/头像 + 报名资料 status/topics/source),需登录(token) */
|
||||
export async function updateProfile(data) {
|
||||
const token = getToken();
|
||||
const res = await fetch(`${API_BASE}/api/auth/update-profile`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const r = await res.json().catch(() => ({}));
|
||||
if (res.ok && r.ok && r.user) saveUser(r.user);
|
||||
return r;
|
||||
const r = await req('/auth/update-profile', { body: data, auth: true });
|
||||
if (r.username) saveUser(pickUser(r));
|
||||
return { ok: true, user: pickUser(r) };
|
||||
}
|
||||
|
||||
export function isAuthed() {
|
||||
|
||||
Reference in New Issue
Block a user