frontend-security-integration.md 15 KB

前端对接:兑换码/优惠券权限与登录日志

日期:2026-10-21。本文对应当前本地后端改动,尚未完成编译、发布及在线联调。 接口均为应用路径,不含网关 /api 等前缀,沿用现有 baseURL 和后台鉴权请求配置。

1. 发布前准备

后端/运维执行:

  • docs/sql/20261021-coupon-exchange-permissions.sql:补齐 9 个权限,默认仅授权 root。 父菜单无法自动识别时按脚本填写父菜单 id。已有权限不重复添加。
  • docs/sql/20261021-login-audit.sql:创建登录日志表。
  • 在角色管理中显式授权所需权限,重新保存角色刷新权限缓存,再重新登录。

前端不直接操作权限表。后台登录返回的 AdminInfo 已有 permissions 和 roles, 可沿用现有登录流程:/manage/admin/get/loginInfo 或 /manage/user/get/loginInfo。 /manage/menu/get/self/menu 只返回非 button 菜单,不能从这里推断完整按钮权限。

2. 兑换码页面(优先接入)

入口 显示/请求条件 后端接口
兑换码页、查询按钮、首次列表加载、翻页 exchangeCode:view GET /manage/exchange/code/get
生成兑换码按钮及提交 同时有 exchangeCode:add 和 exchangeCode:export GET /manage/exchange/code/createAndExport
导出按钮及提交 exchangeCode:export GET /manage/exchange/code/export

没有 view 时不请求列表、不渲染码明文及复制按钮,展示无权限提示。 权限异步加载完成后才决定是否首次请求;退出登录/切换账号清空列表、表单和已缓存码。 路由守卫、按钮和点击处理函数都检查权限,不能只隐藏按钮。后端仍为最终授权边界。 不要仅凭 root 字样在前端忽略权限码;应使用项目现有、与后端一致的权限匹配逻辑。

// 概念示例,接到项目现有权限工具,不另造登录或 token 存储。
const canView = hasPermission('exchangeCode:view');
const canGenerate = hasPermission('exchangeCode:add') && hasPermission('exchangeCode:export');
const canExport = hasPermission('exchangeCode:export');

2.1 查询/导出参数白名单

参数 类型/规则
goodsId 商品 Long,可选
skuId 规格 Long,可选
code 完整兑换码,精确匹配,不再模糊查询
userId 用户 Long,可选
useStatus true/false 或 1/0;全部时省略,未使用必须显式传 false
codeType 1 兑换码、2 兑换链接;全部时省略
start 从 1 开始的页码,默认 1
limit 1–100,默认 10

只发送上述字段。不要传 condition、operator、字段 -op、逻辑分组或动态排序。 不再支持透传其他历史 BeanSearcher 参数;若现有页面依赖,请列清单再联调,不恢复通用参数透传。 兑换链接筛选先解析 URL 的 code 查询参数,再传完整码本身;不要把整个 URL 直接作为 code。 查询列表返回 Result<SearchResult<ExchangeCodeView>>,沿用现有 BeanSearcher 列表解析。 导出为 xlsx,最多 10000 条,不按分页截断;超限提示缩小筛选范围。 非 root 只能查当前创建人范围;历史创建人昵称为空或重名时后端会拒绝,前端显示后端提示。

2.2 生成参数

沿用 GET 查询参数(兼容现有页面,后续联合迁移 POST,不要预加载或自动重试该请求):

skuIds=[823]
number=10
type=0
codeType=1
  • skuIds 是 JSON 数组字符串,包含 1–100 个有效规格 ID,不传 goodsIds/operator。
  • number:1–500 的整数;提交期间禁用按钮,超时后先查询核实,避免重复生成。
  • type:0 平台(默认)、1 抽奖;codeType:1 兑换码(默认)、2 链接。
  • 当前后端 number > 1 才返回 Excel;number = 1 生成后无文件响应,应刷新列表确认, 不能把“无下载文件”当成失败并重试。
  • 外层请求检查权限失败、参数异常时仍可能返回 JSON,不能全部保存成 Excel 文件。

2.3 文件与错误处理

查询沿用 Result:code、msg、flag、data,成功以现有统一拦截器判断。 不要只依据 HTTP 200 判定业务成功。二进制下载先区分 Content-Type 是否为 JSON, JSON 错误解析后展示 msg,不生成错误 Excel。鉴权失效走现有重新登录流程; 无权限展示提示,不自动重试,不降级到 external 接口。

3. 优惠券页面

行为 权限
查询券、分类、发放记录及统计 coupon:view
POST /manage/coupon 新增券 coupon:add
PUT /manage/coupon 及推荐、推广码、分类、活动等配置 coupon:edit
删除券、分类、推荐、活动等记录 coupon:delete
POST /manage/coupon/send 或 /corp/send coupon:send

其他新增配置 POST(例如分类、弹窗)目前归 coupon:edit,不是 coupon:add。 所有 /manage/coupon 接口都要求后台身份,不能用前台用户 token。 普通用户领券继续使用 /applet/coupon/**,不要改调后台接口。 新增券请求剔除 id;编辑使用 PUT 且带 id。折扣 0 < discount <= 1,立减金额 > 0, 金额沿用后端原有“分”单位,表单显示“元”时做好转换。

4. 登录日志页面(新增)

只允许 roles 精确包含 root 的后台账号访问;不包含则隐藏入口且禁止发请求。 当前后端使用 root 角色限制,不需要新增 loginAudit:view 权限码。

GET /manage/login/audit/get?start=1&limit=20
GET /manage/login/audit/get?loginType=user&loginId=1533797&start=1&limit=20

筛选:loginType、loginId、account、result、loginMethod、ip、from、to;空值省略。 start 从 1 开始,limit 1–100(默认 20);from/to 为 URL 编码后的 yyyy-MM-dd HH:mm:ss。 时间沿用应用环境时区,不擅自转换两次。

注意:登录日志分页不是 BeanSearcher。 返回 Result<Page<LoginAudit>>, 列表读 data.records,总数读 data.total,当前页 data.current,页大小 data.size。

建议列表列:时间、身份类型、用户 ID、账号、登录方式、结果、连接 IP、浏览器、系统、耗时。 详情抽屉:remoteIp、forwardedFor、realIp、userAgent、device、requestPath、httpMethod、reason。

字段 展示说明
loginType user 普通用户;login 默认后台;其他值原样显示
loginMethod sms_code 短信、email_code 邮箱、google、wechat、mini_program 小程序
其他方式 password_check 后台密码预检查、admin_impersonation 管理员代登录、account_switch 切换账号、phone_or_email_code 未细分、other/unknown
result SUCCESS 会话创建成功;FAILURE 处理异常;CHECK_SUCCESS 凭据预检查通过,不能标成已登录
account/loginId 可以为空,空值展示 —,不能据此断言异常或伪造身份
remoteIp socket 对端,经过代理时可能是网关地址
forwardedFor/realIp 未验证代理头,可能被伪造,界面明确标“代理头(未验证)”
durationMs 到登录事件发生时耗时,毫秒,不是完整 HTTP 请求耗时

ip 筛选目前只精确匹配 remoteIp 或 realIp,不查询 forwardedFor 链。 日志从部署后开始记录,不能回溯历史;未抛异常的业务失败不保证计入 FAILURE。 不提供删除、编辑或导出日志接口。敏感字段仅按文本渲染,不使用 v-html/innerHTML。

5. 外部接口兼容变化

  • GET /external/app/get/mirror:后台 root + exchangeCode:view。
  • PUT /external/app/refund/{id}:后台 root + exchangeCode:refund。
  • 普通后台角色即使有上述权限也不能调用;无第三方签名方案前不恢复匿名访问。
  • 这些不是前台列表/退款的替代接口,前端不得用它们绕过管理接口权限。
  • 不把后台 root token 嵌入公开页面或第三方客户端;需要服务间调用请另行对接专用鉴权。

6. 联调验收清单

  1. 无 view:进页面、翻页、搜索均不请求;已加载列表清空,码与复制控件不可见。
  2. 只有 view:能看列表,生成/导出不可用;手动请求写接口仍被后端拒绝。
  3. 只有 add 无 export:不能生成兼导出;有 add + export 时正常提交。
  4. false 筛选未使用正常生效,全部时无 useStatus 字段;分页最多 100。
  5. 超过 500 个生成/超过 10000 条导出有明确错误提示;不误下载 JSON。
  6. root 登录日志列表可筛选、翻页、查看详情;其他角色/前台身份拒绝。
  7. 退出/切换账号不残留明文兑换码;所有 UA/账号等字段以文本展示。
  8. 新增优惠券不传 id;密码预检查通过与实际登录成功在日志界面清晰区分。

上述为待执行验收项,不表示线上已经通过;后端当前编译被既有腾讯云 SDK 依赖缺失阻断。

用户余额与权限管理补充

  • PUT /manage/user/add/balance:userBalance:add。
  • PUT /manage/user/sub/balance:userBalance:deduct。
  • GET /manage/user/get/user/balance/sys/operate/record:userBalance:view。
  • 增减请求保持 userId、deductBalance、remark;用户 ID 必须为正数,金额单位仍为分,必须为正整数且最多 10 位;备注必填且最多 255 字符。禁止连点提交,超时后先查账,不要自动重试增加余额。
  • SQL 脚本的 @balance_parent_override 必须设置为真实用户管理菜单 ID,执行后检查 12 个权限全部为 OK;未配置不会默认开放。
  • 角色、菜单和后台账号新增/修改/删除,以及后台账号停启用、重置密码,只允许精确 root 角色。普通角色隐藏对应写按钮;只读列表仍沿用原入口。登录入口不受 root 限制。
  • 优惠券编辑忽略客户端创建时间和删除状态,保留数据库原值;优惠券及规格关联在事务内更新。
  • 现有余额流水与操作日志继续保留。当前增额接口没有业务幂等键;拥有权限的操作员多次提交仍可能重复加款,前端禁连点不能替代后端幂等。

上线验收(在测试库进行)

  1. 匿名、前台用户 token、无对应权限后台账号均不能增减余额、生成/查看/导出兑换码、创建/修改优惠券;核对数据库无变化。
  2. 已授权后台账号可以执行对应动作;停用账号不得增减余额。
  3. 无 root 角色不能修改角色权限、菜单权限、后台账号或重置管理员密码。
  4. 余额金额为 0、负数、小数、超过 10 位或备注为空时拒绝;合法请求金额和流水一致,余额竞争更新失败时不产生流水。
  5. 修改优惠券同时提交伪造 createdTime,确认原创建时间不变。
  6. 执行 SQL、部署后重新登录刷新权限;历史异常优惠券/兑换码不会因部署自动作废,需要单独核查处理。

登录日志菜单权限(2026-10-21 更新)

  • 入口改为系统管理下的登录日志,移除顶部快捷按钮。
  • 路由 /setting/loginAudit,旧地址 /security/login-audit 保留别名。
  • 查询接口 GET /manage/login/audit/get 由 root 限制改为登录且持有 loginAudit:view;响应格式不变。
  • 前端路由、页面和请求拦截同步校验该权限。该权限可查询全部登录日志,应仅授权可信审计人员。
  • 执行 docs/sql/20261021-login-audit-menu.sql,核对系统管理父菜单 ID;仅默认授权 root,其他角色手工勾选菜单并刷新权限、重新登录。
  • 验收:授权角色可查询;无权限角色无法进入且直接调用接口被拒绝;未登录拒绝;旧地址同样受控。

2026-10-21 联调反馈调整

  • /sys/get?key=sys_default_shop_key 撤销额外商户读取权限检查;配置修改接口权限保留。
  • 兑换码列表和导出固定 ec.id desc,生成后前端回第一页(保留筛选条件)。
  • /manage/user/add/balance 单笔金额暂按截图边界设为小于 30000 元,接口单位为分,deductBalance >= 3000000 拒绝;前端同步限制。用户尚未另行指定业务上限。
  • 此上限仅限人工加余额,不是账户总余额上限,不限制扣款,也不替代累计风控。
  • 无数据库迁移;发布前后端。边界:29999.99 元可通过额度检查,30000/30000.01 元拒绝(其他权限/参数条件仍需满足)。

管理端错误提示(2026-10-21)

  • 普通请求及兑换码下载显示后端业务 msg;返回 data=requestId=... 时追加错误编号。
  • 普通请求错误提示保留 8 秒、允许关闭;登录失效、HTTP 错误、网络失败、超时均有提示。
  • 后端补充缺少参数、参数类型和 JSON 格式错误提示;字段绑定校验显示字段名,不回显错误输入值。
  • 未知服务异常仍使用通用提示,完整堆栈通过 requestId 在服务日志定位,不向前端开放 SQL/密钥。
  • 新增前端工具 src/utils/apiError.mjs,发布时需一起提交。无 SQL 变更。

后续调整:按用户要求撤回此次前端错误提示增强,删除 apiError.mjs,恢复 axios 和下载工具原有 msg 展示逻辑;前面的前端提示增强说明不再适用。其他安全修复保留。

后端管理接口错误提示分流(2026-10-21)

  • 仅应用内 /manage 和 /manage/** 使用管理提示策略,去除 servlet contextPath 后匹配;不匹配 /manager、/management,不信任请求头伪造路径。
  • 管理接口的业务/未知异常、参数校验错误在 msg 中附加错误编号,前端沿用原有 msg 展示即可。
  • 缺少参数、参数类型错误、JSON 格式错误的细化提示只用于管理路径;字段校验管理路径增加字段名。
  • 登录/角色/权限异常沿用原业务码及提示,不改变登录跳转协议。未知错误仍不返回原始异常、SQL或堆栈。
  • 其他路径保留原业务提示及错误编号 data 约定,不新增管理字段细节。无需 SQL、无需前端提示改动。

补差价独立权限(2026-10-21)

  • PUT /manage/order/refund/price/differ 改为独立 order:priceDiffer,不再共用 order:refund;仍要求后台登录且账号启用。
  • 管理端补差价按钮、打开弹窗、请求拦截同步检查;退款权限不变。
  • 更新统一菜单 SQL:订单列表下新增“补差价”,共 21 项。不自动授权任何角色;已执行旧版本可重跑新版补齐。
  • 发布后需要 root 在角色管理显式勾选补差价并保存刷新权限。只有退款权限不能补差价,只有补差价权限不能退款(不含通配权限)。

商品规格写接口权限补齐(2026-10-21)

  • 商品规格控制器与规格标题控制器共 10 个写入口接入现有 platform 权限:新增 add、修改/排序 update、删除 delete、批量上架 updateStatus。
  • 沿用既有角色菜单授权,无新增 SQL。平台写权限同时核验后台账号启用状态。
  • spec1.vue 的新增/编辑/删除/批量上架入口与通用 SKU/spec 写请求拦截同步校验;保留合并后的其他页面逻辑。
  • 此次覆盖这两个控制器的写入口,不代表所有商品、批发或增值服务接口已审计完成。需部署前后端,验证无权限管理员直接请求同样被拒绝。