实名认证提交成功后补拉一次 GET,用服务端脱敏字段替换本地草稿回显(新增61)

现象(IM-后端问题清单-2026-09-04.md"新增61"):POST /security/realname/submissions
成功响应只有 {status, submission_id},从不带 masked_name/masked_id_number,前端此前
的兜底是回显用户本次会话里刚输入的原始姓名/证件号草稿——多数情况下看起来没问题,
但那是未脱敏的原始值,不是服务端认可的脱敏展示值,且只覆盖"这次会话内",换一个
从未在本会话提交过的场景(比如同一账号换设备/清缓存后第一次看提交结果)就没有任何
草稿可回退,只能空白。

之前的复核已经确认 GET /security/realname 从这个功能最初上线的 commit 起就一直带着
masked_name/masked_id_number 这两个字段,只是提交成功后从未再调用过它。

修法:submit() 里 POST 成功后追加一次 getRealnameStatus(),用响应里的
masked_name/masked_id_number 覆盖本地状态,展示服务端脱敏后的真实值;这次追加请求
失败时静默忽略(提交本身已经成功,不应该因为这次锦上添花的请求失败而回退),保留
原有的本会话草稿兜底作为这次请求解析完成前的过渡显示。

验证:注册全新测试账号(无历史提交),提交姓名"测试用户"+证件号
110105199003077856 后,抓包确认 POST 响应确实只有 {status,submission_id},紧接着
自动触发的 GET 响应带 masked_name:"测*"、masked_id_number:"110***********7856";
页面输入框读取到的实际 value 是这两个脱敏值,不是刚输入的原始文本,证实修复生效。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-06 13:21:05 +08:00
co-authored by Claude Sonnet 5
parent ded440078a
commit 0748f30cc1
+18 -6
View File
@@ -678,6 +678,16 @@ function Realname({authenticated = false}) {
const data = await submitRealname({name: name.trim(), id_type: 'cn_id', id_number: idNumber.toUpperCase()});
setStatus(data.status || 'pending');
setError('');
// The submissions POST only ever returns {status, submission_id} -- never masked_name/
// masked_id_number -- but GET /security/realname has carried both fields since this feature's
// original commit (confirmed against server source), we just weren't calling it again right
// after a successful submit. Best-effort: the submission itself already succeeded above, so a
// failure here shouldn't take that away from the user -- it just means the pre-existing
// fallback below (this session's own typed draft) keeps covering the display like before.
getRealnameStatus().then(fresh => {
if (fresh.masked_name) setMaskedName(fresh.masked_name);
if (fresh.masked_id_number) setMaskedIdNumber(fresh.masked_id_number);
}).catch(() => {});
} catch (err) {
setError(err.message || '提交失败,请稍后重试');
} finally {
@@ -701,12 +711,14 @@ function Realname({authenticated = false}) {
一旦账号真的走到 verified 状态就会显示一句参考图里根本不存在的标题。 */}
<h2>请您使用有效身份证信息认证</h2>
<form onSubmit={submit}>
{/* submitRealname's success response never actually carries masked_name/masked_id_number
(only {status, submission_id}) -- falling back to what was just typed (still sitting in
`name`/`idNumber` local state) means a fresh submission at least shows the user what
they submitted, instead of switching straight from their typed input to two blank
fields the instant `editable` flips false. Only degrades to genuinely blank on a fresh
page load with no draft in this session, which is the pre-existing, unavoidable case. */}
{/* submitRealname's own success response never carries masked_name/masked_id_number (only
{status, submission_id}) -- submit() above now follows up with a GET /security/realname
to fetch the server's authoritative masked values right after a successful submission, so
this local-draft fallback (`name`/`idNumber`, still sitting in state from what was just
typed) only matters for the brief window before that GET resolves. It still degrades to
genuinely blank on a fresh page load with no draft in this session and no masked value
from a prior GET either (e.g. a submission made in an earlier session) -- that residual
case isn't fixable from here since it depends on the follow-up GET actually having run. */}
<label><span>姓名</span><input value={editable ? name : (maskedName || name)} onChange={e => setName(e.target.value)} readOnly={!editable} placeholder="请输入真实姓名" /></label>
<label><span>证件号码</span><input value={editable ? idNumber : (maskedIdNumber || idNumber)} onChange={e => setIdNumber(e.target.value)} readOnly={!editable} placeholder="请输入证件号码" /></label>
{error ? <div className="auth-field-error">{error}</div> : null}