feat: one account can bind both netease + skin identities
- db: user_identities table (UNIQUE user_id+source), migrate backfill from users - auth: GET/POST/DELETE /api/auth/identities - bind/unbind/list identities (netease requires UID, one per source, name uniqueness, keep >=1) - register/external/plugin/admin-created users auto-write primary identity - tickets: submit uses selected identity (validated belongs to user) - dashboard: 我的身份 card with bind/unbind UI - ticket-create: identity selector when >1 identity
This commit is contained in:
@@ -79,10 +79,14 @@ router.post('/register', validateLengths({
|
||||
}
|
||||
|
||||
const hashed = await bcrypt.hash(password, 10);
|
||||
await query(
|
||||
const r = await query(
|
||||
'INSERT INTO users(username, password, email, game_name, game_uid, active, email_verified, source) VALUES (?,?,?,?,?,?,?,?)',
|
||||
[username, hashed, email, game_name, game_uid||'', 1, 1, source || 'netease']
|
||||
);
|
||||
try {
|
||||
await query('INSERT INTO user_identities(user_id, source, game_name, game_uid) VALUES (?,?,?,?)',
|
||||
[r.insertId, source || 'netease', game_name, game_uid || '']);
|
||||
} catch {}
|
||||
res.status(201).json({ message: '注册成功,请登录' });
|
||||
} catch (err) {
|
||||
console.error('[auth]', err);
|
||||
@@ -148,10 +152,11 @@ router.post('/login', async (req, res) => {
|
||||
router.get('/me', authenticate, async (req, res) => {
|
||||
try {
|
||||
const user = await getRow(
|
||||
'SELECT id, username, email, game_name, game_uid, role, email_verified, created_at FROM users WHERE id = ?',
|
||||
'SELECT id, username, email, game_name, game_uid, role, email_verified, source, created_at FROM users WHERE id = ?',
|
||||
[req.user.id]
|
||||
);
|
||||
if (!user) return res.status(404).json({ error: '用户不存在' });
|
||||
user.identities = await query('SELECT id, source, game_name, game_uid, created_at FROM user_identities WHERE user_id = ? ORDER BY id', [req.user.id]);
|
||||
res.json(user);
|
||||
} catch (err) {
|
||||
console.error('[auth]', err);
|
||||
@@ -159,6 +164,54 @@ router.get('/me', authenticate, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ---- 多来源身份管理(一个账号可同时绑定网易端 + 皮肤站) ----
|
||||
router.get('/identities', authenticate, async (req, res) => {
|
||||
try {
|
||||
const rows = await query('SELECT id, source, game_name, game_uid, created_at FROM user_identities WHERE user_id = ? ORDER BY id', [req.user.id]);
|
||||
res.json(rows);
|
||||
} catch (err) {
|
||||
console.error('[auth]', err);
|
||||
res.status(500).json({ error: '服务器内部错误' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/identities', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { source, game_name, game_uid } = req.body;
|
||||
if (!['netease','skin'].includes(source)) return res.status(400).json({ error: '无效的来源' });
|
||||
if (!game_name) return res.status(400).json({ error: '游戏名不能为空' });
|
||||
if (source === 'netease' && !game_uid) return res.status(400).json({ error: '网易端必须填写UID' });
|
||||
|
||||
const dup = await getRow('SELECT id FROM user_identities WHERE user_id = ? AND source = ?', [req.user.id, source]);
|
||||
if (dup) return res.status(400).json({ error: '该来源已绑定,可先删除再重新绑定' });
|
||||
|
||||
const dupName = await getRow('SELECT id FROM user_identities WHERE source = ? AND game_name = ?', [source, game_name]);
|
||||
if (dupName) return res.status(400).json({ error: '该游戏名已绑定其他账号' });
|
||||
|
||||
const r = await query('INSERT INTO user_identities(user_id, source, game_name, game_uid) VALUES (?,?,?,?)',
|
||||
[req.user.id, source, game_name, game_uid || '']);
|
||||
res.status(201).json({ id: r.insertId, message: '绑定成功' });
|
||||
} catch (err) {
|
||||
console.error('[auth]', err);
|
||||
res.status(500).json({ error: '服务器内部错误' });
|
||||
}
|
||||
});
|
||||
|
||||
router.delete('/identities/:id', authenticate, async (req, res) => {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
const ident = await getRow('SELECT * FROM user_identities WHERE id = ? AND user_id = ?', [id, req.user.id]);
|
||||
if (!ident) return res.status(404).json({ error: '身份不存在' });
|
||||
const count = await getRow('SELECT COUNT(*) as c FROM user_identities WHERE user_id = ?', [req.user.id]);
|
||||
if (count.c <= 1) return res.status(400).json({ error: '至少保留一个身份' });
|
||||
await query('DELETE FROM user_identities WHERE id = ?', [id]);
|
||||
res.json({ message: '已解绑' });
|
||||
} catch (err) {
|
||||
console.error('[auth]', err);
|
||||
res.status(500).json({ error: '服务器内部错误' });
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/profile', authenticate, async (req, res) => {
|
||||
try {
|
||||
const fields = {};
|
||||
|
||||
Reference in New Issue
Block a user