From cb5847d65005e3e16d6826aad557acad5d389303 Mon Sep 17 00:00:00 2001 From: canglan Date: Fri, 21 Aug 2026 21:52:40 +0800 Subject: [PATCH] feat: game UID fully internal + remove multi-identity copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - game_uid is now an internal param: auto-generated server-side (U + 12 hex) on register/bind-identity/install/admin-create/external register; player-supplied game_uid ignored - Removed all player-facing UID inputs: register page, install page, identity bind modal, ticket-create 'your UID' field - Removed player-facing UID display: identity table column, ticket detail/tracking reporter UID - Removed '同一账号可绑定网易端 + 皮肤站身份' / '可绑定多个来源身份' copy - Kept target_game_uid (reported player) and admin panel UID management - settings-page skin-site placeholder updated --- backend/routes/auth.js | 9 ++++--- backend/routes/external.js | 8 +++--- backend/routes/install.js | 4 ++- backend/routes/users.js | 7 +++-- public/js/pages/dashboard.js | 45 ++------------------------------ public/js/pages/home.js | 2 +- public/js/pages/install.js | 2 -- public/js/pages/register.js | 8 +----- public/js/pages/settings-page.js | 2 +- public/js/pages/ticket-create.js | 15 +++-------- public/js/pages/ticket-detail.js | 2 +- 11 files changed, 29 insertions(+), 75 deletions(-) diff --git a/backend/routes/auth.js b/backend/routes/auth.js index ae37515..5b3d069 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -118,13 +118,15 @@ router.post('/register', validateLengths({ } catch {} const hashed = await bcrypt.hash(password, 10); + // 游戏UID为内置参数: 不接收玩家输入, 由系统自动生成 + const builtinUid = 'U' + uuid().replace(/-/g, '').slice(0, 12).toUpperCase(); 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 || ''] + [username, hashed, email, game_name, builtinUid, 1, 1, source || ''] ); try { await query('INSERT INTO user_identities(user_id, source, game_name, game_uid) VALUES (?,?,?,?)', - [r.insertId, source || '', game_name, game_uid || '']); + [r.insertId, source || '', game_name, builtinUid]); } catch {} res.status(201).json({ message: '注册成功,请登录' }); } catch (err) { @@ -233,7 +235,8 @@ router.post('/identities', authenticate, async (req, res) => { 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 || '']); + // 游戏UID为内置参数: 不接收玩家输入, 由系统自动生成 + [req.user.id, source || '', game_name, 'U' + uuid().replace(/-/g, '').slice(0, 12).toUpperCase()]); res.status(201).json({ id: r.insertId, message: '绑定成功' }); } catch (err) { console.error('[auth]', err); diff --git a/backend/routes/external.js b/backend/routes/external.js index 5b8b4dc..f37701e 100644 --- a/backend/routes/external.js +++ b/backend/routes/external.js @@ -166,14 +166,16 @@ router.post('/auth/register', async (req, res) => { const hashed = bcrypt.hashSync(password, 10); const verifyToken = uuid(); - const r = await query('INSERT INTO users(username,password,email,game_name,game_uid,verify_token,verify_expires,source) VALUES (?,?,?,?,?,?,DATE_ADD(NOW(), INTERVAL 24 HOUR),?)', [username, hashed, email, game_name, game_uid || '', verifyToken, source || '']); + // 游戏UID为内置参数: 不接收客户端输入, 由系统自动生成 + const builtinUid = 'U' + crypto.randomBytes(6).toString('hex').toUpperCase(); + const r = await query('INSERT INTO users(username,password,email,game_name,game_uid,verify_token,verify_expires,source) VALUES (?,?,?,?,?,?,DATE_ADD(NOW(), INTERVAL 24 HOUR),?)', [username, hashed, email, game_name, builtinUid, verifyToken, source || '']); try { await query('INSERT INTO user_identities(user_id, source, game_name, game_uid) VALUES (?,?,?,?)', - [r.insertId, source || '', game_name, game_uid || '']); + [r.insertId, source || '', game_name, builtinUid]); } catch {} const site = await getRow("SELECT v FROM settings WHERE k='site_url'"); - const sent = await sendEmail(email, 'verify_email', { username, game_name, game_uid, verify_link: `${site?.v||'http://localhost:3100'}#/verify?token=${verifyToken}` }); + const sent = await sendEmail(email, 'verify_email', { username, game_name, game_uid: builtinUid, verify_link: `${site?.v||'http://localhost:3100'}#/verify?token=${verifyToken}` }); if (!sent) { await query('UPDATE users SET email_verified=1,active=1,verify_token=NULL WHERE username=?', [username]); diff --git a/backend/routes/install.js b/backend/routes/install.js index 026e845..91ae6bf 100644 --- a/backend/routes/install.js +++ b/backend/routes/install.js @@ -85,9 +85,11 @@ router.post('/complete', async (req, res) => { await initSchema(conn); const hashed = bcrypt.hashSync(admin_password, 10); + // 游戏UID为内置参数: 不接收输入, 由系统自动生成 + const builtinUid = 'U' + crypto.randomBytes(6).toString('hex').toUpperCase(); await conn.query( 'INSERT INTO users(username, password, email, game_name, game_uid, role, active, email_verified) VALUES (?,?,?,?,?,?,1,1)', - [admin_username, hashed, admin_email, admin_game_name || admin_username, admin_game_uid || 'OWNER001', 'owner'] + [admin_username, hashed, admin_email, admin_game_name || admin_username, builtinUid, 'owner'] ); const jwtSecret = crypto.randomBytes(32).toString('hex'); diff --git a/backend/routes/users.js b/backend/routes/users.js index 1fcce30..2ccc6b0 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -19,6 +19,7 @@ const express = require('express'); const bcrypt = require('bcryptjs'); +const crypto = require('crypto'); const { query, getRow } = require('../db'); const { authenticate, requireRole } = require('../middleware/auth'); @@ -115,10 +116,12 @@ router.post('/', authenticate, requireRole('owner','admin'), async (req, res) => if (await getRow('SELECT id FROM users WHERE username = ?', [username])) return res.status(400).json({ error: '用户名已存在' }); if (await getRow('SELECT id FROM users WHERE email = ?', [email])) return res.status(400).json({ error: '邮箱已被使用' }); const hashed = bcrypt.hashSync(password, 10); - const r = await query('INSERT INTO users(username, password, email, game_name, game_uid, role, source, active, email_verified) VALUES (?,?,?,?,?,?,?,1,1)', [username, hashed, email, game_name, game_uid||'', role, source || '']); + // 游戏UID为内置参数: 管理员未指定时自动生成 + const builtinUid = game_uid || ('U' + crypto.randomBytes(6).toString('hex').toUpperCase()); + const r = await query('INSERT INTO users(username, password, email, game_name, game_uid, role, source, active, email_verified) VALUES (?,?,?,?,?,?,?,1,1)', [username, hashed, email, game_name, builtinUid, role, source || '']); try { await query('INSERT INTO user_identities(user_id, source, game_name, game_uid) VALUES (?,?,?,?)', - [r.insertId, source || '', game_name, game_uid || '']); + [r.insertId, source || '', game_name, builtinUid]); } catch {} await query("INSERT INTO audit_logs(user_id, username, action, entity_type, entity_id, details) VALUES (?,?,?,?,?,?)", [req.user.id, req.user.username, 'create_user', 'user', r.insertId, `创建用户 ${username}`]); res.status(201).json({ id: r.insertId, message: '创建成功' }); diff --git a/public/js/pages/dashboard.js b/public/js/pages/dashboard.js index fac0aa0..f992ddc 100644 --- a/public/js/pages/dashboard.js +++ b/public/js/pages/dashboard.js @@ -68,13 +68,12 @@ const Dashboard = {
我的身份 ${idents.length < 2 ? `` : ''}
- ${idents.length === 0 ? '暂无身份,点击右上角绑定' : `${idents.map(it=>` + ${idents.length === 0 ? '暂无身份,点击右上角绑定' : `
来源游戏名UID绑定时间操作
${idents.map(it=>` - + `).join('')}
来源游戏名绑定时间操作
${U.esc(it.source||'-')}${U.esc(it.game_name)}${U.esc(it.game_uid||'-')}${U.esc(it.game_name)} ${U.date(it.created_at)} ${idents.length > 1 ? `` : '主身份'}
`} -

同一账号可绑定多个来源身份,提交工单时选择使用哪个身份。

@@ -123,15 +122,6 @@ const Dashboard = {
- -
@@ -142,44 +132,13 @@ const Dashboard = { sel.innerHTML = list.length ? list.map(s => ``).join('') : ''; - toggleSource(); }).catch(()=>{}); - const toggleSource = () => { - const src = document.getElementById('ident-source').value; - const isSkin = src === 'skin'; - document.getElementById('ident-uid-group').style.display = isSkin ? 'none' : ''; - document.getElementById('ident-skin-group').classList.toggle('hidden', !isSkin); - }; - document.getElementById('ident-source').onchange = toggleSource; - // 自动带出站点配置的皮肤站地址 - API.get('/settings/skin-site').then(r => { - if (r.site && !document.getElementById('ident-site').value) document.getElementById('ident-site').value = r.site; - }).catch(()=>{}); - document.getElementById('ident-fetch-btn').onclick = async () => { - const site = document.getElementById('ident-site').value.trim(); - const name = document.getElementById('ident-name').value.trim(); - if (!site) return alert('请填写皮肤站地址'); - if (!name) return alert('请先填写游戏名(皮肤站用户名)'); - const btn = document.getElementById('ident-fetch-btn'); - btn.disabled = true; btn.innerHTML = ' 查询中'; - try { - const r = await API.get(`/settings/uuid-lookup?site=${encodeURIComponent(site)}&name=${encodeURIComponent(name)}`); - if (r.found) { - document.getElementById('ident-uid').value = r.uuid; - alert('已获取 UUID: ' + r.uuid); - } else { - alert(r.message || '未找到该用户名'); - } - } catch (ex) { alert(ex.message); } - btn.disabled = false; btn.innerHTML = ' 获取UUID'; - }; document.getElementById('ident-form').onsubmit = async e => { e.preventDefault(); try { await API.post('/auth/identities', { source: document.getElementById('ident-source').value, game_name: document.getElementById('ident-name').value, - game_uid: document.getElementById('ident-uid').value, }); App.closeModal(); this.mount(); diff --git a/public/js/pages/home.js b/public/js/pages/home.js index 5e69b69..5bdb173 100644 --- a/public/js/pages/home.js +++ b/public/js/pages/home.js @@ -65,7 +65,7 @@ const HomePage = { API.get(`/tickets/tracking?token=${token}`).then(tickets => { const t = tickets.find(x=>x.id===id); if(!t) return; - document.getElementById('tkd').innerHTML = `

基本信息

标题${U.esc(t.title)}
类型${U.badge(t.type,'type')}
状态${U.badge(t.status,'status')}
提交者${U.esc(t.reporter_game_name)} (UID: ${U.esc(t.reporter_game_uid)})
${t.description?`
详情${U.esc(t.description)}
`:''}
+ document.getElementById('tkd').innerHTML = `

基本信息

标题${U.esc(t.title)}
类型${U.badge(t.type,'type')}
状态${U.badge(t.status,'status')}
提交者${U.esc(t.reporter_game_name)}
${t.description?`
详情${U.esc(t.description)}
`:''}
`; document.getElementById('anon-reply-form').onsubmit = async e => { e.preventDefault(); diff --git a/public/js/pages/install.js b/public/js/pages/install.js index 6452f24..31c9e35 100644 --- a/public/js/pages/install.js +++ b/public/js/pages/install.js @@ -91,7 +91,6 @@ const InstallPage = {
-
注意:安装完成后需要重启服务器才能加载业务路由。请记好账号密码。
@@ -114,7 +113,6 @@ const InstallPage = { admin_password: document.getElementById('ia-password').value, admin_email: document.getElementById('ia-email').value, admin_game_name: document.getElementById('ia-game').value, - admin_game_uid: document.getElementById('ia-uid').value, }); document.getElementById('install-admin').innerHTML = '

安装成功!

系统已就绪,正在刷新并跳转登录...

'; setTimeout(() => location.reload(), 1500); diff --git a/public/js/pages/register.js b/public/js/pages/register.js index cc9b3c4..4983056 100644 --- a/public/js/pages/register.js +++ b/public/js/pages/register.js @@ -49,8 +49,6 @@ const RegisterPage = {
- - ${this.captcha ? `
@@ -85,10 +83,6 @@ const RegisterPage = { toggleSource() { const src = document.getElementById('rs').value; document.getElementById('rs-label1').textContent = '游戏名称'; - const isSkin = src === 'skin'; - document.getElementById('rs-label2').textContent = isSkin ? '皮肤站UUID(可选)' : 'UID(可选)'; - document.getElementById('uid-group').style.display = isSkin ? 'none' : ''; - document.getElementById('rgu').required = false; }, async refreshCaptcha() { @@ -124,7 +118,7 @@ const RegisterPage = { await Auth.register({ username: document.getElementById('ru').value.trim(), password: document.getElementById('rp').value, email: document.getElementById('re').value.trim(), game_name: document.getElementById('rgn').value.trim(), - game_uid: document.getElementById('rgu').value.trim(), source: document.getElementById('rs').value, + source: document.getElementById('rs').value, captcha_id: document.getElementById('captcha-id')?.value, captcha_answer: document.getElementById('captcha-a')?.value.trim(), code_id: this.codeId, code: document.getElementById('vf-code').value.trim(), diff --git a/public/js/pages/settings-page.js b/public/js/pages/settings-page.js index ed03e96..c90bdfe 100644 --- a/public/js/pages/settings-page.js +++ b/public/js/pages/settings-page.js @@ -33,7 +33,7 @@ const SettingsPage = {
-
+
diff --git a/public/js/pages/ticket-create.js b/public/js/pages/ticket-create.js index 30cbd40..57859ce 100644 --- a/public/js/pages/ticket-create.js +++ b/public/js/pages/ticket-create.js @@ -35,7 +35,7 @@ const TicketCreate = { this.identities = list || []; const sel = container.querySelector('#identity-select'); if (sel && this.identities.length > 1) { - sel.innerHTML = this.identities.map((it, i) => ``).join(''); + sel.innerHTML = this.identities.map((it, i) => ``).join(''); sel.closest('.form-group').classList.remove('hidden'); } }).catch(() => {}); @@ -68,18 +68,11 @@ const TicketCreate = { ${user ? `` : ''} -
-
- - -
-
- - -
+
+ +
${isReport ? ` diff --git a/public/js/pages/ticket-detail.js b/public/js/pages/ticket-detail.js index 19ea392..a49835d 100644 --- a/public/js/pages/ticket-detail.js +++ b/public/js/pages/ticket-detail.js @@ -42,7 +42,7 @@ const TicketDetail = {
基本信息
${U.badge(t.status,'status')} ${U.badge(t.type,'type')} ${U.badge(t.priority,'priority')}
标题${U.esc(t.title)}
-
提交者${U.esc(t.reporter_game_name)} (UID: ${U.esc(t.reporter_game_uid)})
+
提交者${U.esc(t.reporter_game_name)}
${isRep?`
被举报人${U.esc(t.target_game_name||'?')} (UID: ${U.esc(t.target_game_uid||'?')})
原因${U.esc(t.reason)}
`:''} ${isAppeal?`
申诉理由${U.esc(t.reason)}
`:''}