# API Reference 本文档记录当前已实现的 `web-api` 与 `manage-api` 接口。阅读顺序建议先看 [API 索引](#api-索引),再点击接口链接查看后面的 JavaDoc 风格详细说明。这里只说明调用、返回结构和业务规则;规划中的接口见 `docs/requirements/`。 ## 通用约定 | 环境 | Base URL | 说明 | | --- | --- | --- | | web-api local | `http://localhost:8601/xhzy-live/app` | APP/H5 前台接口。 | | manage-api local | `http://localhost:8602` | 管理后台接口,根 context。 | | manage-api dev | `https://test.zhinan.run/xhzy-live` | dev profile 使用 `/xhzy-live`,需部署对应版本后生效。 | | web-api dev | `https://test.zhinan.run/xhzy-live/app` | 需配置对应 Nginx 转发后生效。 | 下文列出的都是完整接口路径,在对应模块的 Base URL 后直接拼接。需要登录的接口传 `X-Token: <登录响应的 data.token>`;web-api 新签发的用户 Token 统一以 `xhlv_` 开头,manage-api 管理员登录的 `data` 本身就是 Token。APP 调用短信、一键或微信登录时可传 `X-V: <版本号>`,登录成功后保存为用户的 `appVersion`;未传或传空值时不清空已有版本。POST JSON 请求传 `Content-Type: application/json`。响应采用 `ApiResponse`: ```json {"code":0,"message":"成功","data":{}} ``` 业务码严格沿用知南既有约定:`0` 表示成功,`-1` 表示业务处理失败,`-999` 表示无效 Token。参数校验失败、账号或密码错误、数据不存在、数据冲突、没有操作权限及系统内部异常都返回 `code=-1`,具体原因读取 `message`;缺失、过期或已注销的 Token 返回 `code=-999`、`message=无效Token`。业务接口不使用 `400/401/403/404/500` 作为业务码;请求路径不存在时返回 HTTP `404`,响应体为 `{"code":-1,"message":"接口不存在"}`。时间格式为 `yyyy-MM-dd HH:mm:ss`。 分页 `data` 为 `PageVo`: ```json {"list":[],"total":0,"current":1,"size":20} ``` `page` 默认 1,`pageSize` 默认 20,页大小上限 200。成功导出用户时直接返回 `.xlsx` 文件,不包裹 `ApiResponse`。 当前应用配置了 `spring.jackson.default-property-inclusion=non_null`:示例中的可空字段在实际值为 `null` 时会被省略,包括顶层 `data`。下文用完整示例展示字段形状;前端读取可空字段时应兼容缺省。ID/计数为 JSON number,时间为字符串。 ### 公共对象结构 | 对象 | 字段 | 类型 | 说明 | | --- | --- | --- | --- | | `PageVo` | `list` | `T[]` | 当前页数据,可为空数组。 | | `PageVo` | `total` | number | 符合查询条件的总数。 | | `PageVo` | `current` | number | 当前页码,从 1 开始。 | | `PageVo` | `size` | number | 每页数量。 | | `Manager` | `id` | number | 管理员 ID。 | | `Manager` | `name` | string | 管理员名称。 | | `Manager` | `passport` | string | 登录账号。 | | `Manager` | `imageUrl` | string | 管理员头像 URL;历史账号可能缺省。 | | `Manager` | `roleId` | number | 角色 ID。 | | `Manager` | `role` | object | `{id,name}`,角色摘要。 | | `Manager` | `loginTime` | string | 最近登录时间,可缺省。 | | `Role` | `id` | number | 角色 ID。 | | `Role` | `name` | string | 角色名称。 | | `Role` | `description` | string | 角色描述,可缺省。 | | `Role` | `permission` | JSON | 旧后台兼容的权限配置,通常为权限标识字符串数组,由前端解释。 | | `Role` | `userNum` | number | 关联管理员数量。 | | `Role` | `updateTime` | string | 最近更新时间,可缺省。 | | `SysParam` | `id` | number | 配置 ID。 | | `SysParam` | `paramKey` | string | 配置键。 | | `SysParam` | `name` | string | 配置名称。 | | `SysParam` | `content` | JSON | 配置内容,可为对象、数组或其他 JSON 值。 | | `SysParam` | `showStatus` | number | 展示状态,可缺省。 | | `AuditLog` | `id` | number | 数据库记录 ID。 | | `AuditLog` | `courseId` | number | 所属课程 ID,无课程归属时为空。 | | `AuditLog` | `courseName` | string | 所属课程名称快照。 | | `AuditLog` | `detail` | string | 关键变化或批量范围,可为空;不保存密码、Token、验证码。 | | `AuditLog` | `time` | string | 操作时间。 | | `AuditLog` | `managerId` | number | 操作人 ID。 | | `AuditLog` | `managerName` | string | 操作人姓名快照。 | | `AuditLog` | `action` | string | 操作名称。 | | `AuditLog` | `targetType` | string | 操作对象类型。 | | `AuditLog` | `targetId` | string | 操作对象 ID,可缺省。 | | `AuditLog` | `targetName` | string | 操作对象名称快照或批量范围描述。 | | `AuditLog` | `ip` | string | 客户端 IP,可缺省。 | | `AuditLog` | `success` | boolean | 是否成功。 | | `LoginRecord` | `id` | number | 登录记录 ID。 | | `LoginRecord` | `deviceInfo` | string | 设备信息,可缺省。 | | `LoginRecord` | `loginType` | string | 登录类型,可缺省。 | | `LoginRecord` | `ip` | string | 登录 IP,可缺省。 | | `LoginRecord` | `loginTime` | string | 登录时间,可缺省。 | `User` 字段较多,见业务用户分组内的字段表。示例中的具体 ID、名称、时间均为形状示意,不代表测试库已有这些记录。 ## API 索引 ### web-api | 分组 | 说明 | API | | --- | --- | --- | | 公共配置 | 按键读取前台配置,并获取前台 COS 直传签名。 | [前台配置](#get-commonsys_param)
[职业列表](#get-commonoccupation)
[获取 COS 签名](#get-commoncossign) | | 微页面 | 按 ID 匿名读取后台维护的微页面。 | [微页面详情](#get-micro_pageinfo) | | 我的 | 用户资料、手机号修改、微信换绑、账号注销、反馈、商城订单和收货地址兼容接口。 | [个人资料](#get-userinfo)
[修改个人资料](#post-usermodifyinfo)
[发送修改手机号验证码](#post-usermodifyphoneverify-code)
[修改手机号](#post-usermodifyphone)
[微信绑定信息](#get-userbindlist)
[微信不可解绑](#post-userunbindwx)
[绑定/换绑微信](#post-userbindwx)
[注销账号](#post-userunregister)
[意见反馈](#post-feedbackadd)
[订单分页](#get-usershoporderpage)
[订单详情](#get-usershoporderinfo)
[地址列表](#get-userconsigneelist)
[地址详情](#get-userconsigneeinfo) | | 前台登录与身份绑定 | 发送验证码、手机号/一键/微信登录、采集 APP 版本、绑定或修改手机号、绑定微信、注销账号、退出登录。 | [发送登录验证码](#post-verify_codesendlogin)
[发送绑定验证码](#post-verify_codesendbind)
[发送修改手机号验证码](#post-usermodifyphoneverify-code)
[发送注销验证码](#post-verify_codesendunregister)
[短信验证码登录](#post-logincode)
[一键登录](#post-logincert)
[微信登录](#post-loginwx)
[绑定微信](#post-userbindwx)
[绑定手机号](#post-userbindphone)
[修改手机号](#post-usermodifyphone)
[注销账号](#post-userunregister)
[退出登录](#post-loginout) | | 商城商品与购物车 | 匿名浏览商品和分组;登录后维护购物车、记录商品浏览。 | [商城商品接口](#shop-product-api)
[购物车接口](#shop-cart-api)
[商品浏览统计](#get-stat-shopproductsee) | | 商城地址与订单 | 地址簿、结算、下单、支付及用户订单;地址和订单均归商城库。 | [地址列表](#get-userconsigneelist)
[地址详情](#get-userconsigneeinfo)
[新增地址](#post-userconsigneeadd)
[修改地址](#post-userconsigneemodify)
[删除地址](#post-userconsigneedelete)
[结算支付接口](#shop-pay-api)
[订单分页](#get-usershoporderpage)
[订单详情](#get-usershoporderinfo)
[订单物流](#get-usershoporderexpress)
[修改备注](#post-usershopordermodifynote)
[修改订单地址](#post-usershopordermodifyconsignee)
[批量修改订单地址](#post-usershopordermodifybatchconsignee)
[取消订单](#post-usershopordercancel)
[确认收货](#post-usershoporderitemsure)
[删除订单](#post-usershoporderdelete)
[支付回调](#shop-payment-callback-api) | | 课件匿名分享 | 无登录分享信息、密码验证及静态分享页;VOD 视频在密码通过后返回受保护播放参数。 | [分享信息](#get-resourcesharemeta)
[验证密码](#post-resourceshareverify)
[分享页面](#get-courseware-shareindexhtml) | | 课程学习记录 | 登录用户开始学习并上报视频播放进度,用户身份只从 Token 获取。 | [开始学习](#post-usercoursestudy)
[上报进度](#post-usercoursestudyprogress) | | 上课 Tab | 查询可选直播课程、听课码开通、选课、课程目录、场次入口和课程回放。 | [课程列表](#get-user-course-list)
[课程详情](#get-user-course-info)
[课程目录](#get-user-course-catalog)
[听课码开通](#post-user-course-code-open)
[VOD 播放签名](#get-review-vod-token) | | 渠道链接访问 | 解析课程目录、单场直播及客户专属链接;客户专属链接可直接换取绑定用户登录态。 | [课程目录链接](#get-live-channel-link-course-info)
[单场直播链接](#get-live-channel-link-session-info)
[单场直播口令](#get-live-channel-link-session-code-info)
[客户专属登录](#post-live-channel-link-customer-login) | ### manage-api | 分组 | 说明 | API | | --- | --- | --- | | 后台认证与会话 | 管理员登录、获取当前账号及权限、修改密码、退出登录。 | [登录](#post-manage-login)
[登录信息](#get-manage-login_info)
[修改密码](#post-manage-modify_pwd)
[退出登录](#post-manage-logout) | | 后台账号管理 | 获取 COS 签名,查询、新增、修改与删除管理员账号。 | [获取 COS 签名](#get-manage-commoncossign)
[公共账号列表](#get-manage-common-manager-list)
[账号分页](#get-manage-managerpage)
[账号详情](#get-manage-managerinfo)
[新增账号](#post-manage-manageradd)
[修改账号](#post-manage-managermodify)
[删除账号](#post-manage-managerdelete) | | 后台角色管理 | 查询、维护角色和前端权限配置。 | [角色分页](#get-manage-rolepage)
[角色列表](#get-manage-rolelist)
[角色详情](#get-manage-roleinfo)
[新增角色](#post-manage-roleadd)
[修改角色](#post-manage-rolemodify)
[删除角色](#post-manage-roledelete)
[账号可选角色](#get-manage-commonrolelist) | | 后台系统配置 | 查询和维护系统参数。 | [配置列表](#get-manage-sys_paramlist)
[配置详情](#get-manage-sys_paraminfo)
[修改配置](#post-manage-sys_parammodify) | | 后台操作日志 | 按条件分页查看操作记录。 | [操作日志分页](#get-manage-operation_logpage) | | 后台业务用户 | 查询与导出用户、查看验证码、登录和学习记录,维护手机号、客服备注、状态及密码。 | [用户分页](#get-manage-userpage)
[登录记录](#get-manage-userlogin_recordpage)
[学习记录](#get-manage-user-study-page)
[查看验证码](#get-manage-userverify_code)
[手机号占用检查](#post-manage-usermodifypassportcheck)
[修改手机号](#post-manage-usermodifypassport)
[修改客服备注](#post-manage-usermodifyremark)
[修改用户状态](#post-manage-usermodifystatus)
[重置用户密码](#post-manage-usermodifypassword)
[导出用户](#get-manage-userexport) | | 意见反馈 | 按处理状态查询、处理并导出用户反馈,接口与星鹤国学后台一致。 | [反馈分页](#get-manage-feedbackpage)
[处理反馈](#post-manage-feedbackprocess)
[导出反馈](#get-manage-feedbackexport) | | 微页面 | 查询、新增、修改、复制和删除微页面,接口与星鹤国学后台一致。 | [微页面分页](#get-manage-micro_pagepage)
[微页面详情](#get-manage-micro_pageinfo)
[新增微页面](#post-manage-micro_pageadd)
[修改微页面](#post-manage-micro_pagemodify)
[复制微页面](#post-manage-micro_pagecopy)
[删除微页面](#post-manage-micro_pagedelete) | | 平台管理 | 查询、新增、编辑和删除课程平台。 | [平台列表](#get-manage-platformpage)
[平台详情](#get-manage-platforminfo)
[新增平台](#post-manage-platformadd)
[编辑平台](#post-manage-platformmodify)
[删除平台](#post-manage-platformdelete) | | 商城商品管理 | 商品、分类、标签及商品关联资源。 | [商城商品管理接口](#manage-shop-product-api) | | 商城订单与履约 | 订单查询、线下单、发货、退款、导入和导出。 | [商城订单管理接口](#manage-shop-order-api) | | 商城交易与统计 | 交易账户、账单、销售统计及常用选项。 | [商城交易与统计接口](#manage-shop-statistic-api) | | 商城任务与购课记录 | 导入导出任务和阶段性购课记录查询。 | [商城任务与购课记录接口](#manage-shop-task-api) | | 课件管理、VOD与分享 | CRUD、分页、轻量选择、考试试卷与 Excel 导入、VOD 直传签名、媒资状态、受保护播放及分享。 | [试卷详情](#get-manage-exam_paperinfo)
[考试导入](#post-manage-exam_paperimport)
[VOD 上传签名](#get-manage-commonvodsign)
[VOD 文件状态](#get-manage-commonvodfile)
[VOD 播放签名](#get-manage-commonvodtoken)
[课件分页](#get-manage-resourcepage)
[详情](#get-manage-resourceinfo)
[新增](#post-manage-resourceadd)
[编辑](#post-manage-resourcemodify)
[删除](#post-manage-resourcedelete)
[轻量选择](#get-manage-commonresourcelist)
[分享](#post-manage-resourceshare)
[取消分享](#post-manage-resourcesharecancel) | | 渠道管理 | 同步企微成员与客户,维护渠道、客户关系和最早渠道归属。 | [渠道管理接口](#渠道管理) | | 渠道链接 | 按渠道查看直播课程、课程目录、单场直播及客户专属链接。 | [课程链接分页](#get-manage-channel-link-course-page)
[课程目录链接](#get-manage-channel-link-course-catalog-list)
[课程客户链接](#get-manage-channel-link-course-customer-page)
[单场直播链接](#get-manage-channel-link-session-page)
[单场客户链接](#get-manage-channel-link-session-customer-page) | | 表情包管理 | 全量查询、新增或修改图片、调整显示顺序和删除表情包。 | [全量列表](#get-manage-emoji-list)
[新增](#post-manage-emoji-add)
[编辑](#post-manage-emoji-modify)
[排序](#post-manage-emoji-sort)
[删除](#post-manage-emoji-delete) | | 视频课程 | 课程增删改查及课程、平台公共选择。 | [课程分页](#get-manage-course-page)
[课程详情](#get-manage-course-info)
[新增课程](#post-manage-course-add)
[修改课程](#post-manage-course-modify)
[删除课程](#post-manage-course-delete)
[平台选择](#get-manage-common-platform-list)
[课程选择](#get-manage-common-course-list) | | 直播课程 | 维护直播课程、目录与直播场次关联、学员状态、渠道凭证、回放和顾问配置。 | [课程分页](#get-manage-live-course-page)
[课程详情](#get-manage-live-course-info)
[新增课程](#post-manage-live-course-add)
[修改课程](#post-manage-live-course-modify)
[删除课程](#post-manage-live-course-delete)
[复制课程](#post-manage-live-course-copy)
[目录列表](#get-manage-live-course-catalog-list)
[目录详情](#get-manage-live-course-catalog-info)
[目录写操作](#live-course-catalog-write-api)
[学员分页](#get-manage-live-course-student-page)
[渠道分页](#get-manage-live-course-channel-page)
[渠道写操作](#live-course-channel-write-api) | | 课程目录与课件 | 维护课程目录、课件关联、排序及课程复制。 | [目录列表](#get-manage-course-catalog-list)
[目录详情](#get-manage-course-catalog-info)
[新增目录](#post-manage-course-catalog-add)
[修改目录](#post-manage-course-catalog-modify)
[删除目录](#post-manage-course-catalog-delete)
[目录排序](#post-manage-course-catalog-sort)
[关联课件](#post-manage-course-catalog-resource-add)
[修改关联](#post-manage-course-catalog-resource-modify)
[删除关联](#post-manage-course-catalog-resource-delete)
[关联排序](#post-manage-course-catalog-resource-sort)
[复制课程](#post-manage-course-copy) | | 学期与班级 | 维护课程学期、班级和班级教师。 | [学期分页](#get-manage-course-period-page)
[学期详情](#get-manage-course-period-info)
[学期列表](#get-manage-common-course_period-list)
[新增学期](#post-manage-course-period-add)
[修改学期](#post-manage-course-period-modify)
[删除学期](#post-manage-course-period-delete)
[班级分页](#get-manage-course-class-page)
[教师列表](#get-manage-course-class-teacher-list)
[修改教师](#post-manage-course-class-teacher-modify)
[班级列表](#get-manage-common-class-list) | | 课程学员 | 查询课程学员和学习记录、调班、批量导入记录,并导出课程或班级学习记录。 | [批量导入记录](#get-manage-course-batch_import-page)
[学员分页](#get-manage-course-student-page)
[学习记录](#get-manage-course-student-study_record-list)
[调班](#post-manage-course-student-move)
[课程学员导出](#get-manage-course-student-export)
[班级学员导出](#get-manage-course-class-student-export) | | 教师学习分析 | 按当前管理员被分配的任教班级查询课程、期班、学员与学习明细,并导出学习进度。 | [教师接口](#teacher-study-analysis-api) | ## web-api 接口 ## 公共配置 ### GET /common/version/ios #### 功能 匿名获取 iOS 版本配置。读取 `sys_param.param_key=version_ios` 的 `content`,与旧版 `XHZY_APP_Service`、`XHZY_APP_Service_V2` 保持一致:JSON 对象或数组按原结构作为 `data` 返回,普通字符串按字符串返回,不按 `show_status` 过滤。 #### 输入 无 Query、Body 或 Token 要求。 #### 输出 ```json {"code":0,"message":"成功","data":{"iosVersion":"1.0.0","iosBuildCode":"100","content":"新版本","isForced":false}} ``` 未配置 `version_ios` 时保持旧接口行为,返回 `code=0`、`message=成功`、`data=""`。 ### GET /common/version/android #### 功能 匿名获取 Android 版本配置。读取 `sys_param.param_key=version_android` 的 `content`,返回规则与 iOS 版本接口一致,不按 `show_status` 过滤。 #### 输入 无 Query、Body 或 Token 要求。 #### 输出 ```json {"code":0,"message":"成功","data":{"androidVersion":"1.0.0","androidBuildCode":"100","content":"新版本","isForced":true}} ``` 未配置 `version_android` 时保持旧接口行为,返回 `code=0`、`message=成功`、`data=""`。 ### GET /common/sys_param #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/CommonController.java` | | Service | `web-api/src/main/java/com/xhzy/live/web/service/CommonService.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/SysParamVo.java` | #### 功能 匿名按 `key` 读取后台配置管理中的单条配置。接口与后台配置管理共用 `sys_param` 数据和字段含义,不按 `show_status` 过滤;不向前台开放修改能力。`content` 是 JSON 数组或对象时按原结构返回,其他内容按字符串返回。 #### 输入 Query:`key:string`,必填,对应 `sys_param.param_key`。无 Body 或 Token 要求。 #### 输出 ```json {"code":0,"message":"成功","data":{"id":1,"paramKey":"app.customer_service","name":"客服配置","content":{"phone":"400-000-0000"},"showStatus":1}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.id | number | 配置 ID。 | | data.paramKey | string | 配置键,与后台配置管理一致。 | | data.name | string | 配置名称。 | | data.content | object、array 或 string | 配置内容;JSON 数组或对象会解析后返回。 | | data.showStatus | number | 配置的展示状态原值。 | `key` 为空时返回 `code=-1`、`message=配置键不能为空`;配置不存在时返回 `code=-1`、`message=配置不存在`。 ### GET /common/occupation #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/CommonController.java` | | Service | `web-api/src/main/java/com/xhzy/live/web/service/CommonService.java` | #### 功能 兼容旧星鹤国学 APP 的职业列表接口,用于个人资料编辑时选择职业。接口读取 `sys_param` 表中 `param_key=occupation` 的 `content`,JSON 数组或对象按原结构返回;普通字符串按字符串返回。 #### 输入 无 Query 或 Body 参数。请求头必须携带有效 `X-Token`。 #### 输出 配置为职业数组时: ```json {"code":0,"message":"成功","data":[{"label":"机关组织职员","value":"B"},{"label":"机关组织中层管理","value":"C"},{"label":"机关组织高管","value":"D"},{"label":"企事业单位职员","value":"E"},{"label":"企事业单位中层管理","value":"F"},{"label":"企事业单位高管","value":"G"},{"label":"私营企业职员","value":"H"},{"label":"私营企业中层管理","value":"I"},{"label":"私营企业高管","value":"J"},{"label":"自由职业者","value":"K"},{"label":"学生","value":"L"},{"label":"已退休","value":"A"},{"label":"其他","value":"M"}]} ``` 未配置 `occupation` 时保持旧接口行为,返回空字符串: ```json {"code":0,"message":"成功","data":""} ``` #### 错误响应 Token 缺失、过期或已注销时返回 `code=-999`、`message=无效Token`。 ### GET /common/cos/sign #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/CommonController.java` | | Service | `web-api/src/main/java/com/xhzy/live/web/service/CosService.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/CosSignVo.java` | #### 功能 为 APP/H5 获取腾讯云 COS 直传签名,完整地址为 `GET /xhzy-live/app/common/cos/sign`。接口需有效 `X-Token`,参数、签名算法、有效期和返回字段均与后台 `/manage/common/cos/sign` 一致,签名有效期为 3 小时。前后台读取同一组 `TENCENT_COS_*` 配置,使用同一个 AccessKey、SecretKey 和存储桶。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | key | string | 是 | COS 对象键,必须以 `/` 开头,例如 `/xhzy-live/avatar/0123456789abcdef.png`;按请求值原样参与签名。 | | method | string | 是 | 待签名的 HTTP 方法;不区分大小写,上传时传 `PUT`。 | 请求头必须携带有效 `X-Token`。 #### 输出 ```json {"code":0,"message":"成功","data":{"sign":"q-sign-algorithm=sha1&...","domain":"https://xhzy-live-1320544726.cos.ap-shanghai.myqcloud.com","region":"ap-shanghai","bucket":"xhzy-live-1320544726"}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.sign | string | 放入 COS 上传请求 `Authorization` 头的签名。 | | data.domain | string | COS 原生存储桶域名。 | | data.region | string | COS 地域。 | | data.bucket | string | COS 存储桶名称。 | 前端对 `domain + key` 发起 `PUT` 请求,并设置 `Authorization: `。签名和实际上传必须使用完全相同的 `key`;上传域名必须使用接口返回的原生存储桶域名。 #### 错误响应 - Token 缺失、过期或已注销:`code=-999`、`message=无效Token`。 - `key` 为空或未以 `/` 开头、`method` 为空或不合法:`code=-1` 和对应业务提示。 - COS 密钥、存储桶、地域或域名未配置完整:`code=-1`、`message=腾讯云 COS 配置不完整`。 ## 我的模块 所有接口使用 `X-Token` 请求头,响应继续兼容旧 APP 的 `{code,message,data}` 结构。新 APP 不展示卡包、积分、购物车和学员学号;`GET /user/info` 仍保留旧客户端使用的字段,包括 `studentCode`,但新页面不读取该字段。 | 方法 | 地址 | 说明 | 兼容性 | | --- | --- | --- | --- | | GET | `/user/info` | 读取个人资料 | 旧地址和旧字段兼容 | | POST | `/user/modify/info` | 修改昵称、头像、性别、生日、签名、职位和城市 | 旧地址和旧请求字段兼容 | | POST | `/user/modify/phone/verify-code` | 向新手机号发送修改验证码 | 请求字段、验证码类型和短信模板兼容 | | POST | `/user/modify/phone` | 验证新手机号并完成修改 | 旧地址、请求字段和返回结构兼容 | | POST | `/user/bind/wx` | 首次绑定或换绑微信 | 不提供解绑;跨微信应用按 `unionId` 校验归属 | | GET | `/user/bind/list` | 查询微信绑定信息 | 旧地址及 `data.wx` 返回结构兼容 | | POST | `/user/unbind/wx` | 旧客户端解绑入口 | 保留旧地址并返回“微信仅支持换绑,不可解绑” | | POST | `/user/unregister` | 验证手机号验证码并注销当前账号 | 旧地址、请求字段和验证码类型兼容 | | POST | `/feedback/add` | 提交意见反馈及资源 URL | 旧地址和旧请求字段兼容 | | GET | `/user/shop/order/page` | 分页读取商城订单 | 旧地址、筛选参数和分页响应兼容 | | GET | `/user/shop/order/info` | 读取订单详情 | 旧地址兼容 | | GET | `/user/shop/order/express` | 读取订单物流 | 商城 Starter 正式接口 | | POST | `/user/shop/order/modify/note` | 修改订单备注 | 旧地址兼容 | | POST | `/user/shop/order/modify/consignee` | 修改订单地址快照 | 商城 Starter 正式接口 | | POST | `/user/shop/order/modify/batch/consignee` | 批量修改订单地址快照 | 商城 Starter 正式接口 | | POST | `/user/shop/order/cancel` | 取消订单 | 旧地址兼容 | | POST | `/user/shop/order/item/sure` | 确认收货 | 旧地址兼容 | | POST | `/user/shop/order/delete` | 删除关闭订单 | 旧地址兼容 | | GET | `/user/consignee/list` | 收货地址列表 | 旧地址和返回字段兼容 | | GET | `/user/consignee/info` | 收货地址详情 | 旧地址和返回字段兼容 | | POST | `/user/consignee/add` | 新增收货地址 | 旧地址和请求字段兼容 | | POST | `/user/consignee/modify` | 修改收货地址 | 旧地址和请求字段兼容 | | POST | `/user/consignee/delete` | 删除收货地址 | 旧地址和请求字段兼容 | | POST | `/login/out` | 退出登录 | 旧地址兼容 | 微信换绑只有在新微信未归属其他用户时才会解除当前绑定并写入新绑定。若相同 `unionId` 已通过任一微信 `appId` 关联其他用户,响应为: ```json {"code":-1,"message":"绑定失败:您绑定的微信已关联其他账号","data":null} ``` ### POST /user/modify/phone/verify-code #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/UserController.java` | | Service | `web-api/src/main/java/com/xhzy/live/web/service/UserService.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/SendVerifyCodeVo.java` | #### 功能 向准备换绑的新手机号发送验证码。接口必须携带当前用户的有效 Token,新手机号已注册时不发送。验证码类型沿用旧 APP 的 `2`,默认有效期 300 秒;dev 测试环境固定为 `1111`。正式环境使用 `XHZY_SMS_MODIFY_PHONE_TEMPLATE_ID`,当前默认模板 ID 为 `1481820`。 #### 输入 请求头必须携带有效 `X-Token`。 ```json {"cellphone":"13800138000"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | cellphone | string | 是 | 准备绑定的 11 位中国大陆新手机号。 | #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -999 | `无效Token` | Token 缺失、过期或账号已注销。 | | 200 | -1 | `请输入手机号码` | 手机号为空。 | | 200 | -1 | `请输入正确的手机号码` | 手机号格式错误。 | | 200 | -1 | `手机号已被注册` | 新手机号已属于有效用户,包括与当前手机号相同。 | | 200 | -1 | `短信服务未配置:XHZY_SMS_MODIFY_PHONE_TEMPLATE_ID` | 正式环境未配置修改手机号短信模板。 | | 200 | -1 | `验证码发送失败,请稍后重试` | 短信供应商调用失败。 | ### POST /user/modify/phone #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/UserController.java` | | Service | `web-api/src/main/java/com/xhzy/live/web/service/UserService.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/ModifyPhoneVo.java` | #### 功能 校验新手机号收到的修改验证码并更新当前用户的手机号。星鹤直播 APP 不提供手机号码密码登录,因此接口不接收新密码,也不会修改历史密码字段。验证码类型为 `2`,成功时返回空数据;用户 ID、微信绑定和当前 Token 保持不变。 #### 输入 请求头必须携带有效 `X-Token`。 ```json {"cellphone":"13800138000","code":"1111"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | cellphone | string | 是 | 收到验证码的 11 位中国大陆新手机号。 | | code | string | 是 | `/user/modify/phone/verify-code` 发送的修改手机号验证码。 | #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -999 | `无效Token` | Token 缺失、过期或账号已注销。 | | 200 | -1 | `请输入手机号码` | 手机号为空。 | | 200 | -1 | `请输入正确的手机号码` | 手机号格式错误。 | | 200 | -1 | `请输入验证码` | 验证码为空。 | | 200 | -1 | `手机号已被注册` | 修改提交时新手机号已被其他有效用户占用。 | | 200 | -1 | `验证码不正确,请重新输入!` | 验证码错误或不是修改手机号类型。 | | 200 | -1 | `验证码已过期,请重新获取` | 验证码超过有效期。 | ### POST /user/unregister #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/UserController.java` | | Service | `web-api/src/main/java/com/xhzy/live/web/service/UserService.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/UnregisterVo.java` | #### 功能 使用 `/verify_code/send/unregister` 发送的验证码注销当前账号。接口保持旧 APP 的地址、请求字段和注销验证码类型 `4`。注销成功后账号状态改为已删除,释放原手机号、解除微信绑定并撤销该用户的全部 Token;旧 Token 立即失效,历史业务记录保留,原手机号可重新注册新账号。 #### 输入 请求头必须携带有效 `X-Token`。 ```json {"code":"1234"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | code | string | 是 | 当前账号绑定手机号收到的注销验证码。 | #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -999 | `无效Token` | Token 缺失、过期或账号已注销。 | | 200 | -1 | `请输入验证码` | 验证码为空。 | | 200 | -1 | `当前账号未绑定手机号码` | 当前账号只有微信身份,尚未绑定手机号。 | | 200 | -1 | `验证码不正确,请重新输入!` | 验证码错误或不是注销类型。 | | 200 | -1 | `验证码已过期,请重新获取` | 验证码超过有效期。 | ### GET /user/info #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/UserController.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/LoginInfoVo.java` | #### 功能 读取当前登录用户资料。请求头必须携带有效 `X-Token`。为兼容旧 APP,响应仍包含学员状态、学员学号和 IM 签名等历史字段;新 APP 的“我的”页面不展示 `studentCode`。 #### 输入 无 Query 或 Body 参数。 #### 输出 ```json {"code":0,"message":"成功","data":{"id":10001,"name":"星鹤用户","imageUrl":"https://example.com/avatar.png","sex":"女","passport":"13800138000","occupation":"讲师","city":"苏州市","cityCode":"320500","birthday":"1990-01-01","studentCode":"XH000001","personalSignature":"日日精进","studentStatus":1,"initStatus":1,"pushStatus":1,"tlsSign":"im-user-signature"}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | `data.id` | number | 用户 ID。 | | `data.name` | string | 昵称,可缺省。 | | `data.imageUrl` | string | 头像 URL,可缺省。 | | `data.sex` | string | 性别,可缺省。 | | `data.passport` | string | 已绑定手机号;微信新用户未绑定时缺省。 | | `data.occupation` | string | 职业,可缺省。 | | `data.city` | string | 城市名称,可缺省。 | | `data.cityCode` | string | 城市编码,可缺省。 | | `data.birthday` | string | 生日,可缺省。 | | `data.studentCode` | string | 旧客户端兼容学员学号;新 APP 不展示,可缺省。 | | `data.personalSignature` | string | 个性签名,可缺省。 | | `data.studentStatus` | number | 旧客户端兼容学员状态。 | | `data.initStatus` | number | 旧客户端兼容初始化状态。 | | `data.pushStatus` | number | 推送开关状态。 | | `data.tlsSign` | string | 旧客户端兼容 IM 签名,可缺省。 | #### 错误响应 Token 缺失、过期或已注销时返回 `code=-999`、`message=无效Token`。 ### POST /user/modify/info #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/UserController.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/ModifyUserInfoVo.java` | #### 功能 修改当前用户基础资料。接口按请求中提供的非 `null` 字段覆盖用户资料,不修改手机号、微信绑定、学员状态或学员学号。 #### 输入 ```json {"name":"星鹤用户","imageUrl":"https://example.com/avatar.png","sex":"女","birthday":"1990-01-01","personalSignature":"日日精进","occupation":"讲师","city":"苏州市","cityCode":"320500"} ``` 以上字段均为 string,均可缺省;请求头必须携带有效 `X-Token`。 #### 输出 ```json {"code":0,"message":"成功"} ``` ### GET /user/bind/list #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/UserController.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/UserBindVo.java` | #### 功能 查询当前用户的微信绑定信息。未绑定微信时 `data` 返回空对象;已绑定时沿用旧客户端的 `data.wx` 结构。 #### 输入 无 Query 或 Body 参数。请求头必须携带有效 `X-Token`。 #### 输出 ```json {"code":0,"message":"成功","data":{"wx":{"openId":"oExampleOpenId","nickName":"微信昵称","avatarUrl":"https://example.com/wechat-avatar.png"}}} ``` `wx.cellphone` 是旧结构保留字段,当前实现没有赋值,因此按非空序列化规则不返回。 ### POST /user/unbind/wx #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/UserController.java` | #### 功能 旧客户端兼容入口。当前规则不允许解绑微信,只允许通过 `POST /user/bind/wx` 换绑;该接口在 Token 有效时固定返回业务失败。 #### 输入 无 Query 或 Body 参数。请求头必须携带有效 `X-Token`。 #### 输出 ```json {"code":-1,"message":"微信仅支持换绑,不可解绑"} ``` ### POST /feedback/add #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/FeedbackController.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/FeedbackVo.java` | #### 功能 提交当前用户的意见反馈。`resources` 保存前端已经上传完成的资源 URL;本接口不接收文件二进制。 #### 输入 ```json {"title":"功能建议","content":"希望订单展示更简洁","isContact":1,"resources":["https://example.com/feedback-1.png"]} ``` | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `title` | string | 否 | 反馈标题。 | | `content` | string | 是 | 反馈正文,不能为空。 | | `isContact` | number | 否 | 是否愿意客服联系,默认 `0`。 | | `resources` | string[] | 否 | 图片或附件 URL;空值和空字符串会被忽略。 | #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 `content` 为空时返回 `code=-1`、`message=反馈内容不能为空`;Token 无效时返回 `code=-999`。 ## 微页面 ### GET /micro_page/info #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/MicroPageController.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/MicroPageVo.java` | #### 功能 按 ID 匿名读取状态为 `1` 的微页面,兼容旧星鹤 APP 接口路径。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 微页面 ID。 | #### 输出 ```json {"code":0,"message":"成功","data":{"id":7,"name":"审核合规页","dataUrl":"https://example.com/micro_page/7/page.json","content":{"components":[]}}} ``` #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `微页面ID不能为空` | `id` 未传。 | | 200 | -1 | `微页面不存在` | 页面不存在或状态不是 `1`。 | | 200 | -1 | `微页面内容未配置` | 微页面没有 COS 内容地址。 | | 200 | -1 | `微页面内容读取失败` | COS 文件不可访问或正文不是合法 JSON。 | ## 前台登录与身份绑定 前台登录响应统一返回 `LoginVo`。手机号登录后若 `needBindWechat=true`,APP 必须引导用户完成微信绑定;微信新用户登录后若 `needBindCellphone=true`,APP 可引导绑定手机号,也允许暂时跳过。 dev 测试环境不调用短信供应商,登录、绑定手机号、修改手机号和注销账号统一使用固定验证码 `1111`。正式环境仍按短信配置生成并发送随机验证码。 `LoginVo.info` 字段如下: | 字段 | 类型 | 说明 | | --- | --- | --- | | id | number | 用户 ID。 | | name | string | 用户名称,可缺省。 | | imageUrl | string | 用户头像 URL,可缺省。 | | sex | string | 性别,可缺省。 | | passport | string | 手机号;尚未绑定手机号的微信用户缺省。 | ### POST /verify_code/send/login #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/VerifyCodeController.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/SendVerifyCodeVo.java` | #### 功能 向手机号发送登录验证码。无需 Token。验证码有效期由 `xhzy.auth.verify-code-expire-seconds` 配置,当前默认 300 秒;有效期内重复发送时沿用原验证码,与旧 APP 行为一致。账号已被锁定时不发送验证码。 #### 输入 ```json {"cellphone":"13800138000"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | cellphone | string | 是 | 11 位中国大陆手机号,格式为 `1` 加 10 位数字。 | #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `请输入手机号码` | 手机号为空。 | | 200 | -1 | `请输入正确的手机号码` | 手机号格式错误。 | | 200 | -1 | `登录异常,如有疑问,请咨询客服` | 手机号对应账号已被锁定。 | | 200 | -1 | `验证码发送失败,请稍后重试` | 短信供应商调用失败。 | ### POST /verify_code/send/bind #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/VerifyCodeController.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/SendVerifyCodeVo.java` | #### 功能 向手机号发送微信账号绑定手机号所需验证码。当前接口无需 Token,真正绑定时由 `/user/bind/phone` 校验 Token 和验证码。 #### 输入 ```json {"cellphone":"13800138000"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | cellphone | string | 是 | 11 位中国大陆手机号。 | #### 输出 ```json {"code":0,"message":"成功"} ``` 错误响应与 `/verify_code/send/login` 相同。 ### POST /verify_code/send/unregister #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/VerifyCodeController.java` | | Service | `web-api/src/main/java/com/xhzy/live/web/service/UserService.java` | #### 功能 向当前登录账号绑定的手机号发送注销验证码。接口保持旧 APP 的地址和无 Body 调用方式,验证码类型为 `4`,默认有效期 300 秒。正式环境注销短信模板通过 `XHZY_SMS_UNREGISTER_TEMPLATE_ID` 配置,当前默认模板 ID 为 `1933320`。 #### 输入 请求头必须携带有效 `X-Token`,无 Query 或 Body 参数。 #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -999 | `无效Token` | Token 缺失、过期或账号已注销。 | | 200 | -1 | `当前账号未绑定手机号码` | 当前账号只有微信身份,尚未绑定手机号。 | | 200 | -1 | `短信服务未配置:XHZY_SMS_UNREGISTER_TEMPLATE_ID` | 正式环境未配置注销短信模板。 | | 200 | -1 | `验证码发送失败,请稍后重试` | 短信供应商调用失败。 | ### POST /login/code #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/LoginController.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/PhoneLoginVo.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/LoginVo.java` | #### 功能 使用手机号和短信验证码登录。手机号未注册时自动创建用户,昵称为手机号后四位,头像为 `https://cos.xhlive.com.cn/common/default_avatar.png`。若用户尚未绑定微信,返回 `needBindWechat=true`,前台必须继续微信绑定流程;已有用户登录时不覆盖其昵称和头像。 #### 输入 请求头: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | X-V | string | 否 | APP 版本号,例如 `1.0.0`;登录成功后保存到用户的 `appVersion`。 | ```json {"cellphone":"13800138000","code":"1111","deviceType":"android/15"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | cellphone | string | 是 | 11 位中国大陆手机号。 | | code | string | 是 | `/verify_code/send/login` 发送的验证码。 | | deviceType | string | 是 | 客户端设备及系统描述。 | #### 输出 ```json {"code":0,"message":"成功","data":{"token":"xhlv_example_token","info":{"id":10001,"name":"8000","imageUrl":"https://cos.xhlive.com.cn/common/default_avatar.png","passport":"13800138000"},"needBindCellphone":false,"needBindWechat":true}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.token | string | 登录 Token,后续通过 `X-Token` 请求头传递。 | | data.openId | string | 微信 OpenID;手机号登录时通常缺省。 | | data.info | object | 当前用户资料,字段见本分组开头。 | | data.needBindCellphone | boolean | 是否需要引导绑定手机号;手机号登录固定为 `false`。 | | data.needBindWechat | boolean | 是否必须继续绑定微信。 | #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `验证码不正确,请重新输入!` | 验证码不存在、已使用或类型不匹配。 | | 200 | -1 | `验证码已过期,请重新获取` | 验证码超过有效期。 | | 200 | -1 | `登录异常,如有疑问,请咨询客服` | 用户状态不可登录。 | ### POST /login/cert #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/LoginController.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/OneClickLoginVo.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/LoginVo.java` | #### 功能 使用极光认证 SDK 返回的登录凭证完成本机号码一键登录。服务端向极光校验凭证,并使用 2048 位 RSA `OAEPWithSHA-256AndMGF1Padding` 解密手机号;后续注册、登录和微信绑定规则与 `/login/code` 相同。 #### 输入 请求头: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | X-V | string | 否 | APP 版本号,例如 `1.0.0`;登录成功后保存到用户的 `appVersion`。 | ```json {"token":"jverify-login-token","deviceType":"ios/18.0"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | token | string | 是 | 极光认证 SDK 返回的一次性 loginToken,不是本站 `X-Token`。 | | deviceType | string | 是 | 客户端设备及系统描述。 | #### 输出 返回结构与 `/login/code` 相同。新手机号用户的昵称为手机号后四位,头像为 `https://cos.xhlive.com.cn/common/default_avatar.png`,并返回 `needBindWechat=true`;已有用户登录时不覆盖其昵称和头像。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `一键登录凭证不能为空` | loginToken 为空。 | | 200 | -1 | `一键登录未配置:{配置项}` | `XHZY_JVERIFY_APP_KEY`、`XHZY_JVERIFY_MASTER_SECRET` 或与极光控制台公钥配对的 `XHZY_JVERIFY_RSA_PRIVATE_KEY` 未配置。 | | 200 | -1 | `一键登录失败:{极光错误信息}` | 极光未返回成功码 `8000`。 | | 200 | -1 | `一键登录手机号解密失败` | RSA 私钥不匹配或手机号密文异常。 | ### POST /login/wx #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/LoginController.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/WechatLoginVo.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/LoginVo.java` | #### 功能 使用微信移动应用授权返回的 code 登录。首次登录会创建不带手机号的用户并立即签发 Token,返回 `needBindCellphone=true`;手机号绑定可跳过。 #### 输入 请求头: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | X-V | string | 否 | APP 版本号,例如 `1.0.0`;登录成功后保存到用户的 `appVersion`。 | ```json {"code":"wechat-oauth-code","deviceType":"ios/18.0"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | code | string | 是 | 微信 SDK 授权后返回的一次性 code。 | | deviceType | string | 是 | 客户端设备及系统描述。 | #### 输出 ```json {"code":0,"message":"成功","data":{"token":"xhlv_example_token","openId":"wechat-open-id","info":{"id":10002,"name":"微信用户","imageUrl":"https://example.com/avatar.png"},"needBindCellphone":true,"needBindWechat":false}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.token | string | 已登录 Token;用户跳过手机号绑定后仍可继续使用。 | | data.openId | string | 当前微信 OpenID。 | | data.info.passport | string | 已绑定手机号时返回;新微信用户缺省。 | | data.needBindCellphone | boolean | 是否显示可跳过的手机号绑定引导。 | | data.needBindWechat | boolean | 微信登录固定为 `false`。 | #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `微信授权码不能为空` | code 为空。 | | 200 | -1 | `微信登录未配置:{配置项}` | `XHZY_WECHAT_APP_ID` 或 `XHZY_WECHAT_APP_SECRET` 未配置。 | | 200 | -1 | `微信授权失败` | 微信 access token 换取失败。 | | 200 | -1 | `获取微信用户信息失败` | 微信用户资料查询失败。 | | 200 | -1 | `微信绑定用户不存在` | 历史绑定关系指向已不存在的用户。 | ### POST /user/bind/wx #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/UserIdentityController.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/BindWechatVo.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/LoginVo.java` | #### 功能 为当前手机号用户绑定微信。需有效 `X-Token`。新客户端传微信授权 `code`;兼容旧客户端传 `openId`,但该 OpenID 必须已存在于当前微信应用的身份记录中。`code` 与 `openId` 至少提供一个,优先使用 `code`。绑定成功后,当前头像仍为 `https://cos.xhlive.com.cn/common/default_avatar.png` 时替换为非空微信头像;当前昵称仍为手机号后四位时替换为非空微信昵称。用户已修改过的头像和昵称不会被覆盖,后续微信换绑也不会覆盖此前同步或自定义的资料。 #### 输入 请求头:`X-Token: xhlv_example_token` ```json {"code":"wechat-oauth-code","deviceType":"android/15"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | code | string | 条件必填 | 微信 SDK 授权 code;新客户端使用。 | | openId | string | 条件必填 | 兼容旧客户端,仅允许绑定服务端已记录的 OpenID。 | | deviceType | string | 否 | 设备描述,缺省时使用 `app`。 | #### 输出 返回完整 `LoginVo` 和新的 Token;`needBindWechat=false`。前端应使用新 Token 覆盖本地旧 Token。 ```json {"code":0,"message":"成功","data":{"token":"xhlv_reissued_token","openId":"wechat-open-id","info":{"id":10001,"name":"13800138000","passport":"13800138000"},"needBindCellphone":false,"needBindWechat":false}} ``` #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -999 | `无效Token` | Token 缺失、过期或已注销。 | | 200 | -1 | `微信授权码不能为空` | code 和 openId 都未提供。 | | 200 | -1 | `微信登录未配置:{配置项}` | 使用 code 授权时,`XHZY_WECHAT_APP_ID` 或 `XHZY_WECHAT_APP_SECRET` 未配置。 | | 200 | -1 | `微信授权失败` | 使用 code 授权时,微信 access token 换取失败。 | | 200 | -1 | `获取微信用户信息失败` | 使用 code 授权时,微信用户资料查询失败。 | | 200 | -1 | `微信身份不存在,请重新授权` | 旧客户端提交的 openId 未被服务端记录。 | | 200 | -1 | `绑定失败:您绑定的微信已关联其他账号` | 微信 OpenID 或同一 unionId 已属于另一个用户。 | ### POST /user/bind/phone #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/UserIdentityController.java` | | Request VO | `web-api/src/main/java/com/xhzy/live/web/vo/BindCellphoneVo.java` | | Response VO | `web-api/src/main/java/com/xhzy/live/web/vo/LoginVo.java` | #### 功能 为当前微信用户绑定手机号。需有效 `X-Token`。若手机号已有未绑定其他微信的账号,服务端会将当前微信身份合并到该手机号账号、注销临时微信账号并签发新 Token。 #### 输入 请求头:`X-Token: xhlv_example_token` ```json {"cellphone":"13800138000","code":"1234","deviceType":"ios/18.0"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | cellphone | string | 是 | 11 位中国大陆手机号。 | | code | string | 是 | `/verify_code/send/bind` 发送的验证码。 | | deviceType | string | 否 | 设备描述,缺省时使用 `app`。 | #### 输出 返回完整 `LoginVo` 和新的 Token,两个绑定标志均为 `false`。前端必须使用新 Token 覆盖本地旧 Token。 ```json {"code":0,"message":"成功","data":{"token":"xhlv_reissued_token","openId":"wechat-open-id","info":{"id":10001,"name":"微信用户","imageUrl":"https://example.com/avatar.png","passport":"13800138000"},"needBindCellphone":false,"needBindWechat":false}} ``` #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -999 | `无效Token` | Token 缺失、过期或已注销。 | | 200 | -1 | `当前账号未绑定微信` | 当前用户不是微信登录账号。 | | 200 | -1 | `验证码不正确,请重新输入!` | 绑定验证码无效。 | | 200 | -1 | `该手机号已绑定其他微信` | 目标手机号已有不同微信绑定。 | ### POST /login/out #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `web-api/src/main/java/com/xhzy/live/web/controller/LoginController.java` | #### 功能 注销 `X-Token`。为兼容旧 APP,Token 缺失时也返回成功。 #### 输入 请求头:`X-Token: xhlv_example_token`。无请求体。 #### 输出 ```json {"code":0,"message":"成功"} ``` ## 课程学习记录 两个接口均需有效前台 `X-Token`。`resourceId` 是 `course_catalog_resource.id`(课程目录与原课件的关联 ID),不是原始 `resource.id`。服务端从 Token 获取 `userId`,不接收前端覆盖用户身份;只有当前用户有效报名课程中的有效课件关联可以写入。 ### POST /user/course/study 开始学习。服务端更新 `course_period_student.study_catalog_id`,首次进入目录时记录 `last_catalog_id`;同一用户、同一课件关联、同一天只生成一条 `user_study_record`,并确保存在一条 `course_study_record` 进度记录。 ```json {"resourceId":15} ``` 成功返回 `{"code":0,"message":"成功"}`。Token 无效返回 `code=-999`;课件 ID 为空、课件不存在或当前用户无学习权限返回 `code=-1`。 ### POST /user/course/study/progress 上报当前播放位置、历史最大位置、本次新增观看秒数及完成状态。服务端原子累加 `duration`,只增不减 `maxProgress`,并把最大位置限制在已知视频时长内;完成状态一旦写入不会被后续未完成请求回退。 ```json {"resourceId":15,"progress":"00:50","maxProgress":50,"duration":10,"finishStatus":0} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | resourceId | Long | 是 | 课程课件关联 ID。 | | progress | string | 是 | 客户端播放位置文本,空白值拒绝。 | | maxProgress | integer | 是 | 历史最大播放秒数,必须大于等于 0。 | | duration | integer | 是 | 本次新增观看秒数,必须大于等于 0。 | | finishStatus | integer | 否 | `0` 未完成、`1` 完成。 | 成功返回 `{"code":0,"message":"成功"}`。该接口会补建尚不存在的开始学习记录,因此客户端偶发漏调开始接口时仍可保存进度。 ## 商城接口 商城接口由 `shop-web-spring-boot-starter` 提供,在本服务中仍使用 web-api 的 Base URL 和 `X-Token`。商城系统编码由服务端固定为 `xhlive`,前端传入的 `sysCode`、用户 ID、商品价格和支付金额均不作为可信数据。下单时服务端重新读取商品、规格、价格及库存。 直播间下单时,前端在创建订单请求中附带以下可选请求头;`X-Live-Id` 存在时,订单来源固定记录为 `LIVE_ROOM`: | 请求头 | 必填 | 说明 | | --- | --- | --- | | `X-Live-Id` | 否 | 直播间 ID;直播间下单必须传。 | | `X-Channel-Id` | 否 | 渠道 ID。 | | `X-Invite-Code` | 否 | 邀请码。 | | `X-Order-Source` | 否 | 来源补充标识。 | ### 商品与分组 | 方法 | 路径 | Token | 输入 | 输出 | | --- | --- | --- | --- | --- | | GET | `/shop/product/list` | 否 | `ids`,可重复传入的商品 ID。 | 商品数组。 | | GET | `/shop/product/page` | 否 | `key,page,pageSize`。 | 商品分页。 | | GET | `/shop/product/group/page` | 否 | `groupId,key,page,pageSize`;`groupId` 对应商城分类 ID。 | 分类商品分页。 | | GET | `/shop/product/ios/page` | 否 | `key,page,pageSize`。 | 兼容旧接口的商品分页。 | | GET | `/shop/product/coupon/page` | 否 | `typeId,page,pageSize`;参数与国学 APP V2 一致。 | 首期未启用优惠券,返回兼容的空分页。 | | GET | `/shop/product/report/list` | 是 | 无。 | 直播商城保留接口;国学 APP V2 无此接口,首期返回空数组。 | | GET | `/shop/product/info` | 否 | `id`;请求头 `X-Token` 可选。 | 与国学 APP 一致的完整商品详情。 | | GET | `/shop/group/info` | 否 | `id`,商城分类 ID。 | `{id,name}`。 | 金额字段单位为元,保留两位小数;库存字段为 `quantity`。 商品列表及分页元素保留国学 APP V2 的 `id`、`picUrl`、`subName`、`minPrice`、`maxPrice`、`originalPrice`、`minDiscountPrice`、`maxDiscountPrice`、`name`、`type`、`specType`、`status`、`giftStatus`、`specs`、`labels` 字段。未使用的可选字段按旧接口规则省略。 `GET /shop/product/info` 不使用列表接口的精简对象,返回字段与旧国学 APP 的商品详情保持一致: | 字段 | 类型 | 说明 | | --- | --- | --- | | `id`、`type`、`specType` | number | 商品 ID、商品类型和规格类型。 | | `barcode`、`name`、`subName`、`picUrl` | string | 条码、名称、副标题和封面。 | | `minPrice`、`maxPrice` | number | 最低价和最高价,单位元。 | | `minDiscountPrice`、`maxDiscountPrice` | number | 最低和最高折扣价,单位元;未配置时省略。 | | `description`、`tooltip` | string | 商品详情正文和购买提示。 | | `saleNum`、`status`、`giftStatus` | number | 销量、状态和赠品状态。 | | `bought`、`isStudent` | boolean | 知识商品始终返回。`bought=true` 表示当前用户已是关联课程学员或已支付购买过该商品;`isStudent=true` 表示当前用户已有该课程权限。未登录时均为 `false`;非知识商品省略。 | | `relativeItem` | object | 关联对象,字段为 `id`、`type`、`name`。 | | `attrs` | array | 规格属性;元素含 `id`、`name`、`configType`、`sortNo`、`items`。 | | `specs` | array | 完整规格列表,保留国学 APP 原有规格字段和组合商品 `subItems`。 | | `video` | object | 视频资源;字段格式与 `resources[]` 相同,未配置时省略。 | | `resources` | array | 图片资源对象数组,不是 URL 字符串数组。 | | `labels` | array | 商品标签数组。 | `resources[]` 和 `video` 的格式: ```json {"id":30,"fileUrl":"https://example.com/product/image.png","resourceType":2,"coverUrl":"https://example.com/product/cover.png"} ``` `specs[]` 保留字段:`id`、`spec`、`specIndex`、`specName`、`specPicUrl`、`price`、`originalPrice`、`quantity`、`barcode`、`saleNum`、`score`、`config`、`supplementStatus`、`giftStatus`、`subItems`。组合商品的 `subItems[]` 为 `{spec,product}`,与旧国学 APP 一致。值为 `null` 的可选字段按旧接口规则省略,`attrs`、`specs`、`resources`、`labels` 无数据时返回空数组。 ### 购物车 | 方法 | 路径 | 输入 | | --- | --- | --- | | GET | `/shop/cart/count` | 无。 | | GET | `/shop/cart/list` | 无。 | | POST | `/shop/cart/add` | `{"productId":1001,"productSpecId":2001,"num":1}` | | POST | `/shop/cart/modify` | `{"id":1,"productId":1001,"productSpecId":2001,"num":2}`;`num<=0` 时删除该项。 | | POST | `/shop/cart/delete` | `{"ids":[1,2]}` | 所有购物车操作按当前 Token 对应的商城用户隔离,不能读取或修改其他用户的数据。列表元素的 `product` 与 `productSpec` 字段保持国学 APP V2 的结构;新增、修改和删除成功均返回 `data:null`。 ### 地址簿 | 方法 | 路径 | 输入 | | --- | --- | --- | | GET | `/user/consignee/list` | 无。 | | GET | `/user/consignee/info` | Query:`id`。 | | POST | `/user/consignee/add` | 地址对象;成功返回 `data:null`。 | | POST | `/user/consignee/modify` | 含 `id` 的地址对象。 | | POST | `/user/consignee/delete` | `{"id":1}` | 地址对象示例: ```json {"id":1,"province":{"id":310000,"name":"上海市"},"city":{"id":310100,"name":"上海市"},"region":{"id":310115,"name":"浦东新区"},"name":"张三","mobile":"13800138000","address":"世纪大道 100 号","isDefault":1} ``` 每个商城用户最多一个默认地址。查询、修改和删除均校验地址归属;删除默认地址后,系统会把最近创建的剩余地址设为默认地址。下单后地址复制到订单快照,后续修改地址簿不会改变历史订单。 以下地址接口由 `shop-web-spring-boot-starter` 的 `ShopConsigneeController` 提供,全部要求有效 `X-Token`。 ### GET /user/consignee/list #### 功能 查询当前用户的收货地址,默认地址排在最前,其余地址按 ID 倒序排列。 #### 输入 无 Query 或 Body 参数。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":1,"province":{"id":310000,"name":"上海市"},"city":{"id":310100,"name":"上海市"},"region":{"id":310115,"name":"浦东新区"},"name":"张三","mobile":"13800138000","address":"世纪大道 100 号","isDefault":1}]} ``` ### GET /user/consignee/info #### 功能 读取当前用户的一条收货地址,不允许读取其他用户的地址。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | number | 是 | 收货地址 ID。 | #### 输出 `data` 为与地址列表元素相同的地址对象。地址不存在或不属于当前用户时返回 `code=-1`、`message=收货地址不存在`。 ### POST /user/consignee/add #### 功能 新增收货地址。第一条地址自动成为默认地址;传 `isDefault=1` 时会取消原默认地址。 #### 输入 请求体使用上方地址对象,新增时不传 `id`。`province`、`city`、`region`、`name`、`mobile` 和 `address` 必填;`mobile` 必须匹配 11 位中国大陆手机号格式。 #### 输出 ```json {"code":0,"message":"成功","data":null} ``` ### POST /user/consignee/modify #### 功能 修改当前用户的收货地址。传 `isDefault=1` 时将该地址设为唯一默认地址。 #### 输入 请求体使用上方完整地址对象,`id` 必填,其余地址字段校验与新增接口一致。 #### 输出 ```json {"code":0,"message":"成功"} ``` ### POST /user/consignee/delete #### 功能 删除当前用户的一条收货地址。删除默认地址后,最近创建的剩余地址自动成为默认地址。 #### 输入 ```json {"id":1} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` ### 结算与支付 | 方法 | 路径 | 说明 | | --- | --- | --- | | GET | `/shop/pay/coupon/list` | 首期返回空数组。 | | POST | `/shop/pay/order/check` | 检查是否已有相同未支付订单。 | | POST | `/shop/pay/calculate` | 服务端重新计算商品价格、优惠和应付金额。 | | POST | `/shop/pay/order/create` | 创建订单并按 `payType` 创建外部支付单。 | | POST | `/shop/pay/unified/order/create` | 创建统一订单,不立即创建外部支付单。 | | POST | `/shop/pay/unified/order/out` | 对统一订单创建微信或支付宝支付单。 | | GET | `/shop/pay/order/result` | Query:`orderNo`,查询本人订单支付结果;未支付的微信订单会向微信主动查单,确认成功后按支付回调相同流程补记到账。 | | GET | `/shop/pay/order/tooltip` | Query:`id`,读取本人订单支付提示。 | 下单请求沿用旧 `PayInfoBean` 字段。新版前端必须传唯一 `clientOrderNo`;旧请求不传时仍可解析。使用地址簿时传 `consigneeId`,服务端读取当前用户地址并生成订单地址快照。`items` 只接受商品 ID、规格 ID、数量及既有业务对象关联字段,客户端金额会被重新计算。 ```json {"clientOrderNo":"live-20260928-000001","payType":2,"consigneeId":1,"items":[{"num":1,"product":{"id":1001},"productSpec":{"id":2001}}],"note":"请尽快发货"} ``` `payType`:`2` 微信 APP、`3` 支付宝 APP、`5` 微信公众号、`6` 支付宝网页。零元订单直接进入与支付回调相同的支付成功处理链路。库存不足、优惠券或积分未开放、地址不属于当前用户等情况返回 `code=-1`。 ### 优惠券兼容接口 以下路径和参数与国学 APP V2 保持一致。直播系统当前未启用优惠券发放,用户分页返回空分页;优惠券类型查询和领取在宿主未提供实现时返回 `code=-1`、`message=优惠券功能尚未启用`,不会返回 404 或伪造数据。 | 方法 | 路径 | Token | 输入 | 输出 | | --- | --- | --- | --- | --- | | GET | `/user/coupon/page` | 是 | `key,status,page,pageSize`;状态与 V2 一致。 | `{list,total,current,size}`,元素含 `id,code,typeId,type,useTime,startTime,endTime,status`。 | | GET | `/user/coupon/info` | 是 | `id`。 | 单张优惠券对象。 | | GET | `/coupon_type/info` | 否 | `id` 或 `code`。 | 优惠券类型对象,含优惠、有效期及适用范围字段。 | | POST | `/coupon_type/draw` | 是 | `{"id":优惠券类型ID}`。 | 成功时 `data:null`。 | 优惠券类型 `type` 对象保留 V2 字段:`id,type,areaType,areaLimitNum,name,status,discountType,discount,description,minUseMoney,validType,dayNum,endTime,startTime,userLimitNum,drawNum,waitNum,areaItems`。宿主未提供的可选字段按旧接口非空序列化规则省略。 ### 用户订单 | 方法 | 路径 | 输入 | | --- | --- | --- | | GET | `/user/shop/order/page` | `status,key,page,pageSize`;状态 `1` 待付款、`2` 待发货、`3` 待收货。 | | GET | `/user/shop/order/info` | `id` 或 `orderNo`。 | | GET | `/user/shop/order/express` | Query:`id`;返回本人订单的发货单、物流内容和对应商品。 | | POST | `/user/shop/order/modify/note` | `{"id":1,"note":"备注"}` | | POST | `/user/shop/order/modify/consignee` | `{"id":1,"consignee":地址对象}`;未发货前修改订单地址快照。 | | POST | `/user/shop/order/modify/batch/consignee` | `{"consignees":[含 orderId 的地址对象]}`。 | | POST | `/user/shop/order/cancel` | `{"id":1}`;仅未支付订单可取消并恢复库存。 | | POST | `/user/shop/order/item/sure` | `{"itemId":1}` | | POST | `/user/shop/order/delete` | `{"id":1}`;逻辑删除,仅对当前用户隐藏。 | 订单返回保留国学 APP V2 的完整订单、金额、优惠、退款、时间、地址和商品快照字段;直播新增的 `clientOrderNo` 与 `source` 字段继续保留。全部接口校验订单属于当前 `xhlive` 用户。 以下订单接口由 `shop-web-spring-boot-starter` 的 `ShopUserOrderController` 提供,全部要求有效 `X-Token`。 ### GET /user/shop/order/page #### 功能 分页查询当前用户的商城订单,按订单 ID 倒序排列。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `status` | number | 否 | `1` 待付款、`2` 待发货、`3` 待收货;不传表示全部。 | | `key` | string | 否 | 按订单号模糊查询。 | | `page` | number | 否 | 页码,默认 `1`。 | | `pageSize` | number | 否 | 每页数量,默认 `20`,最大 `100`。 | #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"id":101,"orderNo":"XHLV202609290001","clientOrderNo":"app-20260929-0001","payType":2,"payStatus":1,"deliverStatus":0,"refundStatus":0,"totalMoney":99.0,"payMoney":99.0,"payPoint":0,"note":"请尽快发货","createTime":"2026-09-29 10:00:00","payTime":"2026-09-29 10:01:00","consignee":{"id":201,"province":{"id":310000,"name":"上海市"},"city":{"id":310100,"name":"上海市"},"region":{"id":310115,"name":"浦东新区"},"name":"张三","mobile":"13800138000","address":"世纪大道 100 号"},"source":{"sourceType":"LIVE_ROOM","liveId":3001,"channelId":"channel-a","inviteCode":"invite-001","source":"discover"},"items":[{"id":301,"objectId":3001,"objectType":1,"product":{"id":1001,"type":1,"name":"示例商品","picUrl":"https://example.com/product.png"},"productSpec":{"id":2001,"specName":"标准规格","price":99.0},"num":1,"totalMoney":99.0,"payMoney":99.0,"payStatus":1,"deliverStatus":0,"refundStatus":0,"needExpress":true}]}],"total":1,"current":1,"size":20}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | `data.list[].id` | number | 订单 ID。 | | `data.list[].orderNo` | string | 服务端订单号。 | | `data.list[].clientOrderNo` | string | 客户端幂等订单号,可缺省。 | | `data.list[].payType` | number | 支付方式。 | | `data.list[].payStatus` | number | 支付状态。 | | `data.list[].deliverStatus` | number | 发货状态。 | | `data.list[].refundStatus` | number | 退款状态。 | | `data.list[].totalMoney` | number | 订单原金额,单位元。 | | `data.list[].payMoney` | number | 实付金额,单位元。 | | `data.list[].payPoint` | number | 使用积分;首期为 `0`。 | | `data.list[].refundMoney`、`refundPoint` | number | 退款金额和退还积分。 | | `data.list[].discountTotalMoney` | number | 总优惠金额。 | | `data.list[].discountCouponMoney` | number | 优惠券优惠金额。 | | `data.list[].discountPointMoney` | number | 积分抵扣金额。 | | `data.list[].note` | string | 用户备注,可缺省。 | | `data.list[].createTime` | string | 创建时间,格式 `yyyy-MM-dd HH:mm:ss`。 | | `data.list[].payTime`、`refundTime`、`receiveTime`、`deliveryTime` | string | 对应业务时间,格式 `yyyy-MM-dd HH:mm:ss`,未发生时省略。 | | `data.list[].consignee` | object | 下单时的地址快照,不需要物流时可缺省。 | | `data.list[].source` | object | 直播间、渠道和邀请码来源,可缺省。 | | `data.list[].items` | array | 国学 APP V2 格式的商品快照列表,包含 `product`、`productSpec`、金额、退款、折扣、状态、时间、`childList` 和 `giftOrder`。 | ### GET /user/shop/order/info #### 功能 按订单 ID 或订单号读取当前用户的订单详情。结构与分页订单一致,并补充 `payCoupons`、赠品订单等详情字段;超时未支付订单返回旧接口状态码 `-2`。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | number | 条件必填 | 订单 ID;与 `orderNo` 至少传一个。 | | `orderNo` | string | 条件必填 | 订单号;与 `id` 至少传一个。当前代码未显式校验两者同时缺省,调用方不得均省略。 | #### 输出 `data` 为订单分页 `data.list[]` 的完整订单对象。订单不存在或不属于当前用户时返回 `code=-1`、`message=订单不存在`。 ### GET /user/shop/order/express #### 功能 查询当前用户订单的发货单、物流内容及每张发货单包含的商品。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `id` | number | 是 | 订单 ID。 | #### 输出 ```json {"code":0,"message":"成功","data":[{"id":401,"noticeSn":"DN202609290001","express":"SF1234567890","expressName":"顺丰速运","content":{},"createTime":"2026-09-29 12:00:00","items":[]}]} ``` ### POST /user/shop/order/modify/note #### 功能 修改当前用户订单备注。 #### 输入 ```json {"id":101,"note":"工作日送货"} ``` #### 输出 成功返回 `{"code":0,"message":"成功"}`。 ### POST /user/shop/order/modify/consignee #### 功能 修改一张尚未发货且需要物流的订单地址快照,不会修改用户地址簿。 #### 输入 ```json {"id":101,"consignee":{"province":{"id":310000,"name":"上海市"},"city":{"id":310100,"name":"上海市"},"region":{"id":310115,"name":"浦东新区"},"name":"张三","mobile":"13800138000","address":"世纪大道 100 号"}} ``` #### 输出 成功返回 `{"code":0,"message":"成功"}`。订单不需要地址时返回“订单不需要收货地址”,已有商品发货时返回“订单已有商品发货”。 ### POST /user/shop/order/modify/batch/consignee #### 功能 批量修改多张订单的地址快照,每条地址通过 `orderId` 指定订单。 #### 输入 ```json {"consignees":[{"orderId":101,"province":{"id":310000,"name":"上海市"},"city":{"id":310100,"name":"上海市"},"region":{"id":310115,"name":"浦东新区"},"name":"张三","mobile":"13800138000","address":"世纪大道 100 号"}]} ``` #### 输出 成功返回 `{"code":0,"message":"成功"}`。 ### POST /user/shop/order/cancel #### 功能 取消当前用户的未支付订单,并由商城订单服务恢复对应库存。 #### 输入 ```json {"id":101} ``` #### 输出 成功返回 `{"code":0,"message":"成功"}`。 ### POST /user/shop/order/item/sure #### 功能 确认本人订单中的一个待收货商品,并重新计算订单状态。 #### 输入 ```json {"itemId":301} ``` #### 输出 成功返回 `{"code":0,"message":"成功"}`。商品不存在或当前状态不可确认收货时返回 `code=-1`。 ### POST /user/shop/order/delete #### 功能 将当前用户订单逻辑删除;删除后不再出现在用户订单列表中。 #### 输入 ```json {"id":101} ``` #### 输出 成功返回 `{"code":0,"message":"成功"}`。 ### GET /stat/shop/product/see Query 参数 `id` 为商品 ID。Token 可选;登录用户会记录唯一浏览关系,匿名调用直接成功。 ### 支付回调 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/callback/wx/payNotify` | 微信 XML 回调。 | | POST | `/callback/ali/payNotify` | 支付宝表单回调。 | 回调不需要 Token,只由 web-api 暴露。服务端校验签名、应用/商户、外部支付单号、金额和第三方交易号;重复成功回调不会重复更新订单或发放权益。 web-api 必须配置 `XHZY_SHOP_PUBLIC_BASE_URL` 为可由微信访问的 HTTPS Base URL,例如 Dev 使用 `https://test.zhinan.run/xhzy-live/app`。未配置时创建微信、支付宝外部支付单返回“商城公开访问地址未配置”,不会使用 `127.0.0.1` 生成无效回调地址。 ## 课件匿名分享 前台接口均无需X-Token,不写学习记录。分享code来源于管理端share,资源库ID不能直接代替code。密码、固定链接与取消再开行为沿用学习工具;HTML页面路由是前台路径。 ### GET /resource/share/meta #### 位置 `web-api/src/main/java/com/xhzy/live/web/controller/ResourceShareController.java` #### 功能 分享页先查询标题、类型与是否要密码;不返回content或密码。 #### 输入 Query `code:string`:业务必填;控制器允许缺省,但服务对缺省/空白返回业务错误。 #### 输出 ```json {"code":0,"message":"成功","data":{"code":"Ab12Cd34Ef","name":"视频课件","resourceType":3,"passwordRequired":true}} ``` #### 错误响应 HTTP 200、code=-1:缺code/空白“分享链接无效”;未知或取消code“分享链接已失效”;关联资源不存在/已删除“课件不存在”。 ### POST /resource/share/verify #### 位置 `web-api/src/main/java/com/xhzy/live/web/controller/ResourceShareController.java` #### 功能 验证分享密码并获取内容。新密码明文比较,历史$2a$/$2b$/$2y$使用BCrypt验证;无密码分享可不传password。视频content包含fileId时,密码通过后才生成腾讯云受保护播放参数。 #### 输入 JSON `code:string`业务必填,`password?:string`在分享有非空白密码时必填;否则可省略。 ```json {"code":"Ab12Cd34Ef","password":"example-password"} ``` #### 输出 ```json {"code":0,"message":"成功","data":{"id":123,"name":"视频课件","resourceType":3,"content":{"fileId":"5280000000000000000","appId":1500000001,"pSign":"eyJ...","licenseUrl":"https://license.example.test/player.license"},"downloadStatus":0}} ``` 不会返回密码。`content.pSign`只在视频content有非空`fileId`时生成,`content.appId`为VOD应用ID,`content.licenseUrl`为绑定分享域名的Web播放器License地址;旧URL型视频仍原样返回。接口不写学习记录。 #### 错误响应 HTTP 200、code=-1:同meta;密码缺失/空白/错误“密码错误”;缺资源内容“课件内容不存在”;VOD播放配置缺失“腾讯云 VOD 播放配置不完整”;License URL缺失“腾讯云 VOD 播放器 License 未配置”。取消后再开启用相同code及当前密码。 ### GET /courseware-share/index.html #### 位置 `web-api/src/main/resources/static/courseware-share/index.html` #### 功能 匿名静态分享页;微信内提示右上角在浏览器打开;普通浏览器查询meta,无密码自动verify,有密码显示输入框。成功按content实际形状渲染。 #### 输入 Query `code:string`业务必填。分享URL由管理端返回;当前默认前台完整路由为`/xhzy-live/app/courseware-share/index.html?code=...`。 #### 输出 HTTP 200,HTML而非ApiResponse。相对调用`../resource/share/meta`与`../resource/share/verify`。带`fileId/appId/pSign/licenseUrl`的视频使用腾讯云TCPlayer,旧URL视频使用原生video;图片用img、PDF用iframe、其他URL用链接。页面不写学习进度,也不根据downloadStatus额外实施云端下载限制。 #### 错误响应 缺code或业务请求错误显示对应提示。TCPlayer脚本加载失败显示“视频播放器加载失败,请刷新后重试”;VOD应用、任务流、播放密钥、转自适应模板或播放器License配置错误时,需结合接口业务错误和腾讯播放器错误排查。 ## manage-api 接口 商城管理接口由 `shop-manage-spring-boot-starter` 提供,通过本地 Service 访问同一 MySQL 实例中的 `xhgx_shop`,不使用 Hessian、HTTP 或 RabbitMQ。所有管理接口统一挂在 `/manage` 下并使用 Manager Token;例如商品分页为 `GET /manage/shop/product/page`,订单分页为 `GET /manage/shop/order/page`,统计首页为 `GET /manage/statistic/index`。无效 Token 返回 `code=-999`。 商品、订单、发货、退款、导出和统计的相对路径、HTTP 方法及字段保持不变。订单分页追加可选 Query 参数 `liveId`、`channelId`;订单导出请求体也接受同名可选字段。订单详情与列表返回 `clientOrderNo`、`sourceType`、`liveId`、`channelId`、`inviteCode`、`source`。 后台可创建礼品、实物、知识、服务和组合商品。知识及服务商品的关联资源固定使用 `sysCode=xhlive`;`GET /manage/common/out/res/list` 查询本地 `shop_object` 快照。知识商品绑定课程时会通过 ZhiNan Course Contract 校验课程,并以 Contract 返回的名称更新快照。课程商品支付后自动开通课程权益;开通失败时保留可重试的购课记录。 商城正式配置为 `xhzy.shop.manage.enabled`、`xhzy.shop.schema`、`xhzy.shop.public-base-url` 和 `xhzy.shop.manage.temp-dir`。直播服务配置 `xhzy.shop.schema=xhgx_shop` 以启用动态表名;独立商城后台直接连接商城库,不配置 schema 改写。web-api 的支付公开地址由 `XHZY_SHOP_PUBLIC_BASE_URL` 唯一提供,只在实际发起支付时校验,未配置不再回退本机地址;后台启动不依赖该地址。临时文件只能写入独立的商城临时目录。 统计首页 `GET /statistic/index` 追加可选 Query 参数 `liveId`、`channelId`。任一来源条件存在时,销售额、退款额、订单数、支付用户数和待发货数按 `shop_order_source` 过滤;不传时保持原统计口径。 ## 商城管理接口 #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `XHZY_Live_Shop_Service/shop-manage-spring-boot-starter/src/main/java/com/xhgxshop/manage/controller/` | | 商品 Controller | `.../controller/shop/` | | 请求/响应 VO | `.../manage/vo/`、`common-entity` 和 `common-service` | #### 通用规则 下列接口均已由当前 `manage-api` 集成,完整路径以 `/manage` 开头,必须携带有效的 `X-Token`。除文件下载外,成功响应使用统一结构: ```json {"code":0,"message":"成功","data":null} ``` 分页接口的 `data` 为 `{list,total,current,size}`。表格中的 `JSON` 表示请求体使用 `Content-Type: application/json`;`Query` 参数拼接在 URL。金额输入和输出单位均为元,使用十进制 number;数据库内部以分保存。 ### 商城商品管理 #### 商品 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/shop/product/list` | Query:重复传入 `ids[]`,例如 `ids[]=4&ids[]=9` | 按 ID 批量返回商品列表,用于微页面商品回显 | | GET | `/manage/shop/product/page` | Query:`type,status,key,excludeLabelId,needSpec=false,page,pageSize`,均可选 | 商品分页 | | GET | `/manage/shop/product/info` | Query:`id` | 商品详情 | | POST | `/manage/shop/product/add` | JSON:`ShopProductVo` | `null` | | POST | `/manage/shop/product/modify` | JSON:`ShopProductVo`,必须包含 `id` | `null` | | POST | `/manage/shop/product/copy` | JSON:`{id}` | `null` | | POST | `/manage/shop/product/delete` | JSON:`{id}` | `null` | | POST | `/manage/shop/product/modify/status` | JSON:`{id,status}` | `null` | | POST | `/manage/shop/product/modify/label` | JSON:`{id,labelType,labels}` | `null` | `ShopProductVo` 的核心字段如下。`type` 支持礼品、实物、知识、服务和组合商品;知识及服务商品的 `relativeItem.sysCode` 由服务端按 `xhlive` 处理。 | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | number | 修改时必填,新增时不传。 | | `type` | number | 商品类型。 | | `specType` | number | 规格模式。 | | `barcode` | string | 商品编码。 | | `name`、`subName` | string | 商品名称和副标题。 | | `picUrl` | string | 商品主图 URL。 | | `supplier` | string | 供应商。 | | `minPrice`、`maxPrice`、`originalPrice` | number | 售价区间及原价,单位元。 | | `description`、`tooltip` | string | 商品详情和提示。 | | `status` | number | 商品状态。 | | `relativeItem` | object | 关联对象 `{sysCode,id,type,name}`;仅知识/服务商品使用。 | | `cate` | object[] | 分类摘要数组。 | | `attrs`、`specs` | object[] | 规格属性与规格明细。 | | `video`、`resources` | object/object[] | 视频与图片资源。 | | `labels` | object[] | 商品标签。 | #### 批量商品查询 微页面编辑时使用 `GET /manage/shop/product/list` 一次回显多个商品。参数名与旧后台一致,为可重复传入的 `ids[]`: ```http GET /manage/shop/product/list?ids%5B%5D=4&ids%5B%5D=9&ids%5B%5D=8&ids%5B%5D=10 X-Token: ``` 接口同时兼容原样的 `ids[]` 和 URL 编码后的 `ids%5B%5D`。未传 `ids[]` 时返回空数组。返回顺序由数据库查询决定,不保证与请求 ID 的顺序一致。 ```json {"code":0,"message":"成功","data":[{"id":4,"type":1,"specType":1,"barcode":"P0004","name":"示例商品","subName":"商品副标题","picUrl":"https://example.com/product.png","supplier":"示例供应商","minPrice":99.0,"maxPrice":99.0,"originalPrice":129.0,"saleNum":0,"status":1,"updateTime":"2026-09-29 14:00:00","cate":[],"labels":[]}]} ``` 批量接口返回商品基础字段、分类、关联对象和标签,不返回详情正文、图片资源及规格明细;值为 `null` 的字段按全局 JSON 配置省略。 #### 商品删除 删除接口固定为 `POST /manage/shop/product/delete`,JSON 请求体为 `{ "id": 商品ID }`。直播后台 dev 完整地址为 `https://test.zhinan.run/xhzy-live/manage/shop/product/delete`。只有当前端 `baseURL` 已经包含 `/manage` 时,调用代码才可以写成相对路径 `shop/product/delete`。删除为状态删除,并同步清理商品分类关联;成功返回 `code=0`。商品不存在时返回 `code=-1`、`message=商品不存在`,不会进入删除事务的写操作。 #### 分类 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/shop/cate/list` | 无 | 分类树 `ShopCateVo[]` | | GET | `/manage/shop/cate/info` | Query:`id` | 分类详情 | | POST | `/manage/shop/cate/add` | JSON:`{name,level,sortNo,pid,spec}` | `null` | | POST | `/manage/shop/cate/modify` | JSON:`{id,name,level,sortNo,pid,spec}` | `null` | | POST | `/manage/shop/cate/modify/config` | JSON:`{id,spec}` | `null` | | POST | `/manage/shop/cate/delete` | JSON:`{id}` | `null` | #### 标签与标签商品 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/shop/label/page` | Query:`key,type,page,pageSize` | 标签分页 | | GET | `/manage/shop/label/info` | Query:`id` | 标签详情 | | POST | `/manage/shop/label/add` | JSON:`{type,name,themeColor,bgColor,borderColor}` | 新增后的标签 | | POST | `/manage/shop/label/modify` | JSON:同新增并包含 `id` | 修改后的标签 | | POST | `/manage/shop/label/delete` | JSON:`{id}` | `null` | | GET | `/manage/shop/label/product/page` | Query:`labelId,status,key,page,pageSize` | 标签下商品分页 | | POST | `/manage/shop/label/product/add` | JSON:`{labelId,ids}` | `null` | | POST | `/manage/shop/label/product/delete` | JSON:`{labelId,id}` | `null` | | POST | `/manage/shop/label/clear` | JSON:`{labelId}` | `null` | ### 商城订单管理 #### 订单查询与维护 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/shop/order/page` | Query:`status,searchType,key,startTime,endTime,minMoney,maxMoney,sysCode,productName,productType,couponStatus,pointStatus,type,orderStatus,refundStatus,mchId,liveId,channelId,page,pageSize` | 订单分页 | | GET | `/manage/shop/order/info` | Query:`id` 或 `orderNo` | 订单详情 | | POST | `/manage/shop/order/add` | JSON:`{userId,data}`,`data` 为结算下单对象 | `null` | | POST | `/manage/shop/order/modify/remark` | JSON:`{id,remark}` | `null` | | POST | `/manage/shop/order/modify/consignee` | JSON:`{id,consignee}` | `null` | | POST | `/manage/shop/order/modify/price` | JSON:`{id,price}` | `null` | | POST | `/manage/shop/order/delay` | JSON 数组:`[{id,delayTime}]` | `null` | | POST | `/manage/shop/order/refund` | JSON:`RefundReqVo` | `null` | | GET | `/manage/shop/order/refund/list` | Query:`id` | 订单退款记录数组 | | GET | `/manage/shop/order/item/refund_info` | Query:`itemId` | 单个订单项退款信息 | | GET | `/manage/shop/order/item/refund_info/list` | Query:重复传入 `itemIds[]` | 订单项退款信息数组 | | POST | `/manage/shop/order/export` | Query:`sysCode`;JSON:订单筛选条件 | `null`,异步创建导出任务 | `RefundReqVo` 字段为 `id,refundMoney,refundPoint,refundCouponIds,returnDataList,close,closeAll,reason`。订单分页、详情和导出结果包含直播来源字段 `clientOrderNo,sourceType,liveId,channelId,inviteCode,source`;`liveId` 和 `channelId` 可用于筛选直播订单。 #### 发货与物流 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/shop/order/express/list` | Query:`id`,订单 ID | 订单物流单数组 | | GET | `/manage/shop/order/package/list` | Query:`id`,订单 ID | 订单包裹数组 | | GET | `/manage/shop/order/express/info` | Query:`id,deliveryNoticeId` | `null`;触发物流信息刷新 | | POST | `/manage/shop/order/modify/express` | JSON:`{id,expresses}` | `null` | | POST | `/manage/shop/order/deliver` | JSON:`{id,itemIds,productSpecId,productNum,expressName,noticeSn}` | `null` | | GET | `/manage/shop/order/deliver/page` | Query:`searchType,key,type,productName,startTime,endTime,page,pageSize` | 待发货订单分页 | | POST | `/manage/shop/order/deliver/export` | JSON:待发货筛选条件 | `null`,异步创建导出任务 | | POST | `/manage/shop/order/deliver/import` | JSON:`{fileUrl}` | `null`,异步创建回导任务 | | POST | `/manage/shop/order/gift/import` | Query:`sysCode`;JSON:`{fileUrl}` | `null`,异步创建赠品导入任务 | ### 商城交易与统计 #### 常用选项 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/common/user/list` | Query:`sysCode,key` | 商城用户摘要数组 | | GET | `/manage/common/shop/cate/list` | 无 | 可选商品分类数组 | | GET | `/manage/common/shop/label/list` | Query:`type,key` | 可选标签数组 | | GET | `/manage/common/out/res/list` | Query:`sysCode,type,key` | 本地 `shop_object` 资源快照数组;直播系统使用 `xhlive` | | GET | `/manage/common/shop/product/list` | Query:`type,key` | 可选商品数组 | | GET | `/manage/common/trade_account/list` | 无 | 支付账户数组 | | GET | `/manage/common/express_company/list` | 无 | 快递公司数组 | #### 统计 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/statistic/index` | Query:`startDate,endDate,liveId,channelId` | 销售额、退款额、订单数、支付用户数、待发货数 | | GET | `/manage/statistic/product/data` | Query:`startDate,endDate`(必填,`yyyy-MM-dd`) | 商品销售图表数据 | | GET | `/manage/statistic/product/page` | Query:`startDate,endDate,sortKey,sortType,page,pageSize` | 商品销售统计分页 | 日期参数 `startDate/endDate` 格式为 `yyyy-MM-dd`;订单、账单中的 `startTime/endTime` 格式为 `yyyy-MM-dd HH:mm:ss`。 #### 交易账户与账单 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/trade_account/list` | Query:`payType` | 交易账户数组 | | GET | `/manage/trade_account/info` | Query:`id` | 交易账户详情 | | POST | `/manage/trade_account/modify/status` | JSON:`{id,status}` | `null` | | POST | `/manage/trade_account/modify` | JSON:`{id,name,companyName,mchId,payType,type,status,areas}` | `null` | | GET | `/manage/shop/trade_record/page` | Query:`type,key,startTime,endTime,page,pageSize` | 交易账单分页 | | POST | `/manage/shop/trade_record/export` | JSON:账单筛选条件 | `null`,异步创建导出任务 | ### 商城任务与购课记录 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/task/export/page` | Query:`sysCode,type,page,pageSize` | 导出任务分页 | | GET | `/manage/task/import/page` | Query:`sysCode,type,page,pageSize` | 导入任务分页 | | GET | `/manage/task/import/download` | Query:`id` | `.xlsx` 导入结果文件,不使用 JSON 包装 | | GET | `/manage/user_course/page` | Query:`key,productId,status,synStatus,page,pageSize` | 历史购课记录分页 | | GET | `/manage/user_course/export` | Query:`key,productId,status,synStatus` | `.xlsx` 购课记录文件,不使用 JSON 包装 | | POST | `/manage/user_course/retry` | JSON:`{id}` | 按购课记录重试开通课程;成功后更新为已开通、同步成功 | `user_course` 是商城侧的课程同步台账。自动开通、后台重试和全额退课分别调用 ZhiNan Course Contract 的 `grant`、`grant` 和 `revoke`;权益来源固定为 `(sysCode, SHOP_ORDER_PRODUCT, shopOrderProductId)`,重复支付回调和重试不会重复开通。该链路不使用 Hessian、HTTP 或消息队列。 ## 后台认证与会话 ### POST /manage/login #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/LoginController.java` | | 方法 | `LoginController.login` | #### 功能 管理员登录;无需 Token。成功后将响应 `data` 字符串保存为 Token。 #### 输入 ```json {"passport":"admin","password":"登录密码"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | passport | string | 是 | 后台管理员登录账号,不能为空。 | | password | string | 是 | 后台管理员登录密码,不能为空。 | #### 输出 ```json {"code":0,"message":"成功","data":"登录Token"} ``` ### GET /manage/login_info #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/LoginController.java` | | 方法 | `LoginController.loginInfo` | #### 功能 读取当前管理员资料及其角色权限。需有效 Token。 #### 输入 无请求参数。 #### 输出 ```json {"code":0,"message":"成功","data":{"id":1,"name":"管理员","passport":"admin","imageUrl":"https://cos.example.com/xhzy-live/avatar/example.jpg","role":{"id":1,"name":"超级管理员","description":"内置全权限角色","permission":[null,"*"],"userNum":null,"updateTime":"2026-09-22 10:00:00"}}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.id | number | 当前管理员 ID。 | | data.name | string | 当前管理员名称。 | | data.passport | string | 登录账号。 | | data.imageUrl | string | 当前管理员头像 URL;历史账号未设置时缺省。 | | data.role | `Role` | 当前角色完整信息。 | | data.role.id | number | 当前角色 ID。 | | data.role.name | string | 当前角色名称。 | | data.role.description | string | 角色描述,可缺省。 | | data.role.permission | JSON | 与旧后台一致的原始权限配置,通常是权限标识字符串数组。 | | data.role.userNum | number | 登录信息中固定为 `null`。 | | data.role.updateTime | string | 角色最后更新时间。 | ### POST /manage/modify_pwd #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/LoginController.java` | | 方法 | `LoginController.modifyPassword` | #### 功能 修改当前管理员密码。需有效 Token。 #### 输入 ```json {"oldPwd":"原密码","newPwd":"6-20位新密码"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | oldPwd | string | 是 | 原登录密码。 | | newPwd | string | 是 | 新密码,长度为 6-20 位字符。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口无业务返回数据;`data` 为 `null` 时省略。 ### POST /manage/logout #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/LoginController.java` | | 方法 | `LoginController.logout` | #### 功能 撤销当前 `X-Token`。需有效 Token。 #### 输入 无需请求体。 #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口无业务返回数据;`data` 为 `null` 时省略。 ## 后台账号管理 接口需有效 Token。`Manager` 字段:`id`、`name`、`passport`、`imageUrl`、`roleId`、`role:{id,name}`、`loginTime`;写入时还可传 `password`,查询不返回密码。前端可依据当前角色的 `permission` 控制入口显示。 ### GET /manage/common/cos/sign #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/.../controller/CommonController.java` | | Service | `ZhiNan_Common_Manage/.../service/CosService.java` | | Response VO | `ZhiNan_Common_Manage/.../vo/CosSignVo.java` | #### 功能 获取腾讯云 COS 请求签名。需有效 Token,契约与星鹤国学旧后台的 `cos/sign` 一致,签名有效期为 3 小时。管理端和 APP/H5 前台共用同一组 `TENCENT_COS_*` 配置。管理员头像的对象键由前端生成为 `/xhzy-live/avatar/<唯一文件名>`,不增加日期目录。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | key | string | 是 | COS 对象键,必须以 `/` 开头,例如 `/xhzy-live/avatar/0123456789abcdef.png`;按请求值原样参与签名。 | | method | string | 是 | 待签名的 HTTP 方法;不区分大小写,上传时传 `PUT`。 | #### 输出 ```json {"code":0,"message":"成功","data":{"sign":"q-sign-algorithm=sha1&...","domain":"https://xhzy-live-1320544726.cos.ap-shanghai.myqcloud.com","region":"ap-shanghai","bucket":"xhzy-live-1320544726"}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.sign | string | 放入 COS 上传请求 `Authorization` 头的签名。 | | data.domain | string | COS 访问域名。 | | data.region | string | COS 地域。 | | data.bucket | string | COS 存储桶名称。 | 前端对 `domain + key` 发起 `PUT` 请求,并设置 `Authorization: `。`domain` 是腾讯原生桶域名,不能替换为只支持读取的 CDN 或自定义加速域名。上传成功后,同一个 URL 即为提交管理员新增或编辑接口的 `imageUrl`。浏览器直传前还需在 COS 存储桶配置允许管理端来源的 CORS 规则。 兼容约定:`key` 与旧星鹤国学接口一致,以 `/` 开头并按前端传入值原样参与签名;`method` 兼容大小写;参数或签名错误统一返回 `code=-1` 和对应业务提示。Token 仅从 `X-Token` 请求头读取。 ### GET /manage/manager/page #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/ManagerController.java` | | 方法 | `ManagerController.page` | #### 功能 分页查询管理员账号。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | key | string | 否 | 账号搜索关键字。 | | page | number | 否 | 页码,默认 1。 | | pageSize | number | 否 | 每页数量,默认 20,最大 200。 | #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"id":2,"name":"运营","passport":"operator","imageUrl":"https://cos.example.com/xhzy-live/avatar/example.jpg","roleId":2,"role":{"id":2,"name":"运营角色"},"loginTime":"2026-09-22 10:00:00"}],"total":1,"current":1,"size":20}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.list | `Manager[]` | 当前页管理员数组;空页为 `[]`。 | | data.list[].id | number | 管理员 ID。 | | data.list[].name | string | 管理员名称。 | | data.list[].passport | string | 登录账号。 | | data.list[].imageUrl | string | 管理员头像 URL;历史账号未设置时可能缺省。 | | data.list[].roleId | number | 角色 ID。 | | data.list[].role | object | `{id:number,name:string}`,角色摘要。 | | data.list[].loginTime | string | 最近登录时间,可能缺省。 | | data.total | number | 符合条件的总数。 | | data.current | number | 当前页码。 | | data.size | number | 每页数量。 | 密码字段不返回。 ### GET /manage/manager/info #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/ManagerController.java` | | 方法 | `ManagerController.info` | #### 功能 查询单个管理员账号。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 管理员 ID。 | #### 输出 ```json {"code":0,"message":"成功","data":{"id":2,"name":"运营","passport":"operator","imageUrl":"https://cos.example.com/xhzy-live/avatar/example.jpg","roleId":2,"role":{"id":2,"name":"运营角色"},"loginTime":"2026-09-22 10:00:00"}} ``` `data` 为单个 `Manager`,字段及类型同账号分页的 `data.list[]`;`loginTime` 为空时缺省,`password` 不返回。 ### POST /manage/manager/add #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/ManagerController.java` | | 方法 | `ManagerController.add` | #### 功能 新增管理员账号。 #### 输入 ```json {"name":"运营","passport":"operator","imageUrl":"https://cos.example.com/xhzy-live/avatar/example.jpg","password":"6-20位密码","roleId":2} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | name | string | 是 | 管理员名称,不能为空。 | | passport | string | 是 | 登录账号,不能为空。 | | imageUrl | string | 否 | 头像地址;使用 `data.domain + key`。省略时由前端显示默认头像。 | | password | string | 是 | 初始密码,长度为 6-20 位字符。 | | roleId | number | 是 | 已存在的角色 ID。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。接口不返回新账号 ID,需重新查询列表。 ### POST /manage/manager/modify #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/ManagerController.java` | | 方法 | `ManagerController.modify` | #### 功能 修改管理员账号;传新密码时会撤销该账号原有 Token。 #### 输入 ```json {"id":2,"name":"运营","passport":"operator","imageUrl":"https://cos.example.com/xhzy-live/avatar/example.jpg","roleId":2} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 待修改的管理员 ID。 | | name | string | 是 | 管理员名称。 | | passport | string | 是 | 登录账号。 | | imageUrl | string | 否 | 新头像地址;省略时保留原头像。 | | roleId | number | 是 | 已存在的角色 ID。 | | password | string | 否 | 传入时更新密码,长度为 6-20 位字符,并撤销该账号的现有 Token;省略时不修改密码。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 ### POST /manage/manager/delete #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/ManagerController.java` | | 方法 | `ManagerController.delete` | #### 功能 软删除管理员账号;不能删除当前登录账号。 #### 输入 ```json {"id":2} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 待删除的管理员 ID。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 ## 后台角色管理 角色权限契约与星鹤国学旧后台一致。`Role` 字段为 `id`、`name`、`description`、`permission`、`userNum`、`updateTime`;其中 `permission` 是由前端解释的任意 JSON,通常使用权限标识字符串数组。后端只校验 Token 和管理员状态,不按这些标识拦截接口。 ### GET /manage/role/page #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/RoleController.java` | | 方法 | `RoleController.page` | #### 功能 分页查询角色。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | key | string | 否 | 角色名称搜索关键字。 | | page | number | 否 | 页码,默认 1。 | | pageSize | number | 否 | 每页数量,默认 20,最大 200。 | #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"id":2,"name":"运营角色","description":"直播运营","permission":null,"userNum":3,"updateTime":"2026-09-22 10:00:00"}],"total":1,"current":1,"size":20}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.list | `Role[]` | 当前页角色数组。 | | data.list[].id | number | 角色 ID。 | | data.list[].name | string | 角色名称。 | | data.list[].description | string | 角色描述,可缺省。 | | data.list[].permission | null | 分页接口不加载权限配置,固定为 `null`。 | | data.list[].userNum | number | 关联管理员数量。 | | data.list[].updateTime | string | 最后更新时间,可缺省。 | | data.total | number | 总数。 | | data.current | number | 当前页码。 | | data.size | number | 每页数量。 | ### GET /manage/role/list #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/RoleController.java` | | 方法 | `RoleController.list` | #### 功能 查询全部可用角色。 #### 输入 无请求参数。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":2,"name":"运营角色","description":null,"permission":null,"userNum":null,"updateTime":null}]} ``` `data` 为角色摘要数组。每项只有 `id`、`name` 有值,其余 `Role` 字段为 `null`;无角色时为 `[]`。 ### GET /manage/role/info #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/RoleController.java` | | 方法 | `RoleController.info` | #### 功能 查询角色详情与前端权限配置。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 角色 ID。 | #### 输出 ```json {"code":0,"message":"成功","data":{"id":2,"name":"运营角色","description":"直播运营","permission":[null,"member_index_show","member_index_export"],"userNum":null,"updateTime":"2026-09-22 10:00:00"}} ``` `data` 为单个 `Role`。`permission` 是数据库中 `role_permission.permission` 解析后的原始 JSON;详情接口中的 `userNum` 为 `null`,与旧后台保持一致。 ### POST /manage/role/add #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/RoleController.java` | | 方法 | `RoleController.add` | #### 功能 新增角色。 #### 输入 ```json {"name":"运营","description":"直播运营","permission":[null,"member_index_show","member_index_export"]} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | name | string | 是 | 角色名称。 | | description | string | 否 | 角色描述。 | | permission | JSON | 否 | 前端权限配置,原样保存;通常传权限标识字符串数组,也可传空数组 `[]`。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。接口不返回新角色 ID。 ### POST /manage/role/modify #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/RoleController.java` | | 方法 | `RoleController.modify` | #### 功能 修改角色及前端权限配置。 #### 输入 ```json {"id":2,"name":"运营","description":"直播运营","permission":[null,"member_index_show","member_index_export"]} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 待修改的角色 ID。 | | name | string | 是 | 角色名称。 | | description | string | 否 | 角色描述。 | | permission | JSON | 否 | 修改后的完整前端权限配置,原样覆盖。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 ### POST /manage/role/delete #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/RoleController.java` | | 方法 | `RoleController.delete` | #### 功能 软删除角色。内置超级管理员角色或仍有关联账号的角色不能删除。 #### 输入 ```json {"id":2} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 待删除的角色 ID。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 ### GET /manage/common/role/list #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/CommonController.java` | | 方法 | `CommonController.roleList` | #### 功能 账号编辑页可选角色列表。需有效 Token。 #### 输入 无请求参数。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":2,"name":"运营角色","description":null,"permission":null,"userNum":null,"updateTime":null}]} ``` `data` 为 `Role[]`;每项字段与 `/manage/role/list` 相同,空列表为 `[]`。 ## 后台系统配置 `SysParam` 字段:`id`、`paramKey`、`name`、`content`(任意 JSON 值)、`showStatus`。接口需有效 Token;后端不按前端权限标识拦截。 ### GET /manage/sys_param/list #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/SysParamController.java` | | 方法 | `SysParamController.list` | #### 功能 查询系统配置列表。 #### 输入 无请求参数。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":1,"paramKey":"example.key","name":"示例配置","content":{"enabled":true},"showStatus":1}]} ``` `data` 为 `SysParam[]`,每项含 `id:number`、`paramKey:string`、`name:string`、`content:JSON`、可选 `showStatus:number`;无配置时为 `[]`。此接口只返回可见配置。 ### GET /manage/sys_param/info #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/SysParamController.java` | | 方法 | `SysParamController.info` | #### 功能 按 ID 或配置 Key 查询配置。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 二选一 | 配置 ID。 | | key | string | 二选一 | 配置键;`id` 与 `key` 至少提供一个。 | #### 输出 ```json {"code":0,"message":"成功","data":{"id":1,"paramKey":"example.key","name":"示例配置","content":{"enabled":true},"showStatus":1}} ``` `data` 为单个 `SysParam`;字段同配置列表元素。`content` 不固定为对象,前端应按具体 `paramKey` 解释其 JSON 结构。 ### POST /manage/sys_param/modify #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/SysParamController.java` | | 方法 | `SysParamController.modify` | #### 功能 新增或修改系统配置;`id` 为空时新增。 #### 输入 ```json {"id":1,"paramKey":"example.key","name":"示例配置","content":{"enabled":true},"showStatus":1} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 否 | 配置 ID;省略时新增。 | | paramKey | string | 是 | 配置键。 | | name | string | 是 | 配置名称。 | | content | JSON | 是 | 配置内容,可为任意 JSON 值。 | | showStatus | number | 否 | 展示状态。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。新增也不返回配置 ID。 ## 后台操作日志 新操作记录存入 Common_Manage 配置数据库的 `operation_record` 表,不读取历史 JSONL。表名由 `zhinan.common.manage.tables.operation-record` 配置。 ### GET /manage/operation_log/page #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `ZhiNan_Common_Manage/src/main/java/run/zhinan/common/manage/controller/OperationLogController.java` | | 方法 | `OperationLogController.page` | #### 功能 分页查询后台操作日志。需有效 Token。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | key | string | 否 | 按操作人、动作、对象名、对象 ID 或课程名搜索。 | | managerId | number | 否 | 按操作人 ID 筛选。 | | courseId | number | 否 | 按课程 ID 筛选。 | | action | string | 否 | 按动作精确筛选。 | | startTime | string | 否 | 开始时间,yyyy-MM-dd HH:mm:ss,包含边界。 | | endTime | string | 否 | 结束时间,yyyy-MM-dd HH:mm:ss,包含边界。 | | page | number | 否 | 页码,默认 1。 | | pageSize | number | 否 | 每页数量,默认 20,最大 200。 | #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"time":"2026-09-22 10:00:00","managerId":1,"managerName":"管理员","action":"登录","targetType":"manager","targetId":"1","targetName":"管理员","ip":"127.0.0.1","success":true}],"total":1,"current":1,"size":20}} ``` `data.list` 为 `AuditLog[]`,各字段类型见公共对象表;`total`、`current`、`size` 为 number,空页的 `list` 为 `[]`。 ## 后台业务用户 本组接口由 `manage-api` 的 `UserController` 提供。只有 `xhzy.live.user-datasource.enabled=true` 且业务数据库可用时注册;当前 local、dev YAML 均开启。修改手机号和备注直接写业务库。 `User` 字段:`id`、`passport`、`status`、`initStatus`、`studentStatus`、`pushStatus`、`studentCode`、`imageUrl`、`name`、`sex`、`birthday`、`city`、`occupation`、`deviceType`、`appVersion`、`loginTime`、`createTime`、`balancePoint`、`remark`、`wxNickname`、`channelName`。 | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | number | 用户 ID。 | | `passport` | string | 手机号/登录账号,可缺省。 | | `status` | number | 用户状态;取值沿用业务库。 | | `initStatus` | number | 初始化状态,可缺省。 | | `studentStatus` | number | 学员身份状态;`0` 注册用户、`1` 体验学员、`2` 学员,可缺省。 | | `pushStatus` | number | 推送状态,可缺省。 | | `studentCode` | string | 学员证号,可缺省。 | | `imageUrl` | string | 头像 URL,可缺省。 | | `name` | string | 昵称,可缺省。 | | `sex` | string | 性别,可缺省。 | | `birthday` | string | 生日,可缺省。 | | `city` | string | 城市,可缺省。 | | `occupation` | string | 职业,可缺省。 | | `deviceType` | string | 设备类型,可缺省。 | | `appVersion` | string | 最近一次登录通过 `X-V` 请求头上报的 APP 版本;从未上报时缺省。 | | `loginTime` | string | 最近登录时间,可缺省。 | | `createTime` | string | 注册时间,可缺省。 | | `balancePoint` | number | 当前积分余额,可缺省。 | | `remark` | string | 当前客服备注,可缺省。 | | `wxNickname` | string | 关联微信昵称;未关联微信时缺省。存在多条微信记录时取最新一条。 | | `channelName` | string | 所属渠道名称;未归属渠道时缺省。存在多个渠道时取最早归属渠道。 | ### GET /manage/user/page #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/UserController.java` | | 方法 | `UserController.page` | #### 功能 分页查询用户。需 `member_index_show`。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | key | string | 否 | 昵称模糊匹配,或手机号/学员证号精确匹配。 | | sortKey | string | 否 | `id`、`name`、`passport`、`studentCode`、`loginTime`、`createTime`;无有效值时按 ID 降序。 | | sortType | string | 否 | `asc` 升序;其余值按降序处理。 | | page | number | 否 | 页码,默认 1。 | | pageSize | number | 否 | 每页数量,默认 20,最大 200。 | #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"id":10001,"passport":"13800000000","status":1,"initStatus":1,"studentStatus":2,"pushStatus":1,"studentCode":"XH10001","imageUrl":"https://example.com/avatar.png","name":"测试用户","sex":"女","birthday":"1990-01-01","city":"北京","occupation":"教师","deviceType":"iOS","appVersion":"1.0.0","loginTime":"2026-09-22 10:00:00","createTime":"2026-09-01 09:00:00","balancePoint":100,"remark":"已联系","wxNickname":"微信昵称","channelName":"渠道A"}],"total":1,"current":1,"size":20}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.list | `User[]` | 当前页用户数组;元素字段及类型见本组字段表。 | | data.total | number | 符合条件的总数。 | | data.current | number | 当前页码。 | | data.size | number | 每页数量。 | 可空字段在数据库无值时可能不出现在 JSON 中。 ### GET /manage/user/login_record/page #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/UserController.java` | | 方法 | `UserController.loginRecordPage` | #### 功能 分页查询指定用户的登录记录。需 `member_archive_note_record`。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 用户 ID。 | | page | number | 否 | 页码,默认 1。 | | pageSize | number | 否 | 每页数量,默认 20,最大 200。 | #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"id":3,"deviceInfo":"iPhone","loginType":"验证码","ip":"127.0.0.1","loginTime":"2026-09-22 10:00:00"}],"total":1,"current":1,"size":20}} ``` `data.list` 为 `LoginRecord[]`;`id:number`,`deviceInfo:string`、`loginType:string`、`ip:string`、`loginTime:string` 可缺省;分页元数据为 number。 ### GET /manage/user/verify_code #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/UserController.java` | | 方法 | `UserController.verifyCode` | #### 功能 查看指定用户当前登录验证码,并记录操作日志。需 `member_index_verify_code`。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | userId | number | 是 | 用户 ID。 | #### 输出 ```json {"code":0,"message":"成功","data":{"code":"123456","expireTime":"2026-09-22 10:10:00"}} ``` `data.code:string` 为验证码;`data.expireTime:string` 为失效时间。没有有效验证码时服务返回 `data=null`,按当前 `non_null` 设置会省略顶层 `data` 字段。 ### POST /manage/user/modify/passport/check #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/UserController.java` | | 方法 | `UserController.checkPassport` | #### 功能 检查手机号是否被其他用户占用。需 `member_index_cellphone`。 #### 输入 ```json {"id":10001,"passport":"13800000000"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 当前用户 ID。 | | passport | string | 是 | 待检查手机号,不能为空。 | | changeUserId | number | 否 | 本接口忽略该字段。 | Postman 环境变量 `user_id`、`user_passport` 不能留空,否则请求体可能变成非法 JSON 或触发校验错误。 #### 输出 ```json {"code":0,"message":"成功","data":{"id":10002,"passport":"13800000000","status":1,"studentStatus":2,"name":"另一位用户","balancePoint":0}} ``` 有冲突时 `data` 为单个 `User`,字段全集与 `/manage/user/page` 的 `data.list[]` 一致;示例只展示非空字段。无冲突时 `data=null`,可能在 JSON 中省略,前端应把缺省也视为无冲突。 ### POST /manage/user/modify/passport #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/UserController.java` | | 方法 | `UserController.modifyPassport` | #### 功能 修改手机号;`changeUserId` 非空时与目标用户调换手机号。需 `member_index_cellphone`,并记录操作日志。 #### 输入 ```json {"id":10001,"passport":"13800000000","changeUserId":null} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 要修改手机号的用户 ID。 | | passport | string | 是 | 新手机号,不能为空。 | | changeUserId | number | 否 | 调换号码的另一用户 ID;不调换时省略或传 `null`。 | 写操作建议前端二次确认。 #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 ### POST /manage/user/modify/remark #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/UserController.java` | | 方法 | `UserController.modifyRemark` | #### 功能 覆盖该用户当前客服备注,记录操作日志。需 `member_archive_note_modify`。 #### 输入 ```json {"id":10001,"remark":"本次沟通后的备注"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 用户 ID。 | | remark | string | 否 | 新客服备注;省略、`null` 或空字符串均清空当前备注。 | 不提供备注历史分页接口。 #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。备注回显需重新请求用户列表。 ### POST /manage/user/modify/status #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/UserController.java` | | 方法 | `UserController.modifyStatus` | #### 功能 设置用户为正常或黑名单状态,并记录操作日志。需 `member_index_set_blacklist`。加入黑名单会同时清空该用户当前 Token,使现有登录凭证失效;恢复正常后用户需要重新登录。 #### 输入 ```json {"id":10001,"status":2} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 用户 ID。 | | status | number | 是 | `1` 表示正常,`2` 表示黑名单;不接受其他值。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。状态回显需重新请求用户列表,读取 `data.list[].status`。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `用户不存在` | `id` 不存在,或用户已被删除。 | | 200 | -1 | `用户状态只能是 1 或 2` | `status` 不是 `1` 或 `2`。 | ### POST /manage/user/modify/password #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/UserController.java` | | 方法 | `UserController.modifyPassword` | #### 功能 重置指定用户的登录密码,并记录操作日志。需 `member_index_reset_password`。密码使用 BCrypt 编码后保存,接口不返回也不保存明文密码。 #### 输入 ```json {"id":10001,"password":"NewPassword123","confirmPassword":"NewPassword123"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 用户 ID。 | | password | string | 是 | 新密码,不能为空且至少 6 位。 | | confirmPassword | string | 否 | 确认密码;为兼容旧接口允许省略,传入时必须与 `password` 一致。 | #### 输出 成功响应: ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `用户不存在` | `id` 不存在,或用户已被删除。 | | 200 | -1 | `两次输入的密码不一致` | 传入的 `confirmPassword` 与 `password` 不一致。 | ### GET /manage/user/export #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/UserController.java` | | 方法 | `UserController.export` | #### 功能 导出用户信息并记录操作日志。需 `member_index_export`。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | key | string | 否 | 与用户分页一致:昵称模糊匹配,或手机号/学员证号精确匹配。 | | sortKey | string | 否 | 与用户分页一致:`id`、`name`、`passport`、`studentCode`、`loginTime`、`createTime`。 | | sortType | string | 否 | `asc` 升序;其余值按降序处理。 | #### 输出 成功响应不是 JSON,而是 Excel 二进制流;`Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,`Content-Disposition` 携带 `.xlsx` 文件名。前端需按 blob 下载。导出列:用户 ID、昵称、关联微信、所属渠道、手机号码、注册日期、身份、学员证号、备注、最后登录日期。 ## 意见反馈 ### GET /manage/feedback/page #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/FeedbackController.java` | | 方法 | `FeedbackController.page` | #### 功能 分页查询意见反馈,返回提交用户和图片资源。接口字段、筛选和排序行为与星鹤国学后台一致;按反馈 ID 倒序排列。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | status | number | 否 | 处理状态:`0` 未处理,`1` 已处理;省略时查询全部。 | | page | number | 否 | 页码,默认 1。 | | pageSize | number | 否 | 每页数量,默认 20,最大 200。 | #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"user":{"id":10001,"passport":"13800000000","imageUrl":"https://example.com/avatar.png","name":"测试用户","deviceType":"iPhone"},"id":3,"title":"功能建议","content":"希望增加课程提醒","isContact":1,"status":0,"createTime":"2026-09-24 10:00:00","resources":["https://example.com/feedback/1.png"]}],"total":1,"current":1,"size":20}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.list | `Feedback[]` | 当前页反馈数组。 | | data.list[].user | object | 提交用户。 | | data.list[].user.id | number | 用户 ID。 | | data.list[].user.passport | string | 用户手机号码,可缺省。 | | data.list[].user.imageUrl | string | 用户头像 URL,可缺省。 | | data.list[].user.name | string | 用户昵称,可缺省。 | | data.list[].user.deviceType | string | 用户设备型号,可缺省。 | | data.list[].id | number | 反馈 ID。 | | data.list[].title | string | 问题类型,可缺省。 | | data.list[].content | string | 问题描述,可缺省。 | | data.list[].picUrl | string | 旧版保留字段;无值时省略。 | | data.list[].isContact | number | 是否允许联系:`0` 否,`1` 是。 | | data.list[].status | number | 处理状态:`0` 未处理,`1` 已处理。 | | data.list[].remark | string | 后台处理备注;未处理或无备注时省略。 | | data.list[].createTime | string | 反馈时间,格式 `yyyy-MM-dd HH:mm:ss`。 | | data.list[].resources | `string[]` | 反馈图片 URL 数组,无图片时为空数组。 | | data.total | number | 符合条件的反馈总数。 | | data.current | number | 当前页码。 | | data.size | number | 每页数量。 | ### POST /manage/feedback/process #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/FeedbackController.java` | | 方法 | `FeedbackController.process` | #### 功能 处理一条未处理反馈,写入处理备注并将状态改为 `1`。请求和幂等行为与星鹤国学后台一致:已经处理的反馈再次提交时不覆盖原备注。需 `feedback_index_handle`。 #### 输入 ```json {"id":3,"remark":"已经记录,将在后续版本改进"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 反馈 ID。 | | remark | string | 否 | 处理备注。接口不向用户推送消息。 | #### 输出 ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `反馈不存在` | `id` 对应的反馈不存在。 | ### GET /manage/feedback/export #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/FeedbackController.java` | | 方法 | `FeedbackController.export` | #### 功能 按处理状态导出反馈,行为与星鹤国学后台一致。每条反馈最多导出前三张图片。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | status | number | 否 | `0` 未处理,`1` 已处理;省略时导出全部。 | #### 输出 成功响应不是 JSON,而是 Excel 二进制流: - `Content-Type: application/vnd.ms-excel;charset=UTF-8` - `Content-Disposition` 携带 `反馈_时间戳.xlsx` 文件名 - 工作表名称:`记录` - 导出列依次为:创建人昵称、手机号码、手机型号、问题类型、问题描述、反馈日期、备注、是否可联系、图片1、图片2、图片3 ## 微页面 微页面接口与星鹤国学后台保持一致。页面正文以 JSON 字符串写入 COS,数据库保存对应的文件 URL;管理端通过详情接口读取正文。后端不解释组件协议,`content` 的 JSON 结构由微页面编辑器维护。 ### GET /manage/micro_page/page #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/MicroPageController.java` | | Response VO | `manage-api/src/main/java/com/xhzy/live/manage/vo/MicroPageVo.java` | #### 功能 分页查询状态为 `1` 的微页面,按 ID 倒序排列。`key` 按页面名称模糊匹配。需有效 Token。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | key | string | 否 | 页面名称关键字;省略或空字符串时查询全部。 | | page | number | 否 | 页码,默认 1。 | | pageSize | number | 否 | 每页数量,默认 20,最大 200。 | #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"id":7,"name":"课程报名页","createTime":"2026-09-24 15:00:00"}],"total":1,"current":1,"size":20}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.list | `MicroPage[]` | 当前页微页面数组。 | | data.list[].id | number | 微页面 ID。 | | data.list[].name | string | 微页面名称。 | | data.list[].createTime | string | 创建时间,格式 `yyyy-MM-dd HH:mm:ss`。 | | data.total | number | 符合条件的微页面总数。 | | data.current | number | 当前页码。 | | data.size | number | 每页数量。 | ### GET /manage/micro_page/info #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/MicroPageController.java` | | Response VO | `manage-api/src/main/java/com/xhzy/live/manage/vo/MicroPageVo.java` | #### 功能 查询微页面基本信息,并从 COS 读取页面正文。需有效 Token。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 微页面 ID。 | #### 输出 ```json {"code":0,"message":"成功","data":{"id":7,"name":"课程报名页","content":"{\"components\":[{\"type\":\"image\",\"url\":\"https://example.com/banner.png\"}]}"}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.id | number | 微页面 ID。 | | data.name | string | 微页面名称。 | | data.content | string | 页面组件 JSON 的原始字符串;历史记录没有正文 URL 时缺省。 | #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `微页面不存在` | `id` 不存在或状态不是 `1`。 | | 200 | -1 | `微页面内容读取失败` | COS 文件不存在、网络错误或 URL 无效。 | ### POST /manage/micro_page/add #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/MicroPageController.java` | | Request VO | `manage-api/src/main/java/com/xhzy/live/manage/vo/MicroPageVo.java` | #### 功能 新增微页面,将 `content` 作为 UTF-8 JSON 文件上传到 COS,并记录操作日志。需有效 Token。 #### 输入 ```json {"name":"课程报名页","content":"{\"components\":[]}"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | name | string | 是 | 微页面名称,不能为空。 | | content | string | 是 | 页面组件 JSON 的原始字符串。 | #### 输出 ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `微页面名称不能为空` | `name` 缺失或仅包含空白字符。 | | 200 | -1 | `微页面内容不能为空` | `content` 缺失或为 `null`。 | | 200 | -1 | `文件上传失败` | COS 上传失败。 | ### POST /manage/micro_page/modify #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/MicroPageController.java` | | Request VO | `manage-api/src/main/java/com/xhzy/live/manage/vo/MicroPageVo.java` | #### 功能 修改微页面名称和正文。正文会上传为新的 COS 文件,数据库改为保存新 URL,并记录操作日志。需有效 Token。 #### 输入 ```json {"id":7,"name":"课程报名页(新版)","content":"{\"components\":[{\"type\":\"text\",\"value\":\"立即报名\"}]}"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 微页面 ID。 | | name | string | 是 | 微页面名称,不能为空。 | | content | string | 是 | 修改后的页面组件 JSON 原始字符串。 | #### 输出 ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `微页面ID不能为空` | `id` 缺失。 | | 200 | -1 | `微页面不存在` | `id` 不存在或状态不是 `1`。 | ## 平台管理 平台用于区分课程和学员所属的业务平台,提供列表、详情、新增、编辑和删除接口。 ### GET /manage/platform/page #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/PlatformController.java` | | Response VO | `manage-api/src/main/java/com/xhzy/live/manage/vo/PlatformVo.java` | #### 功能 查询全部正常平台,按平台编号升序排列。接口名称使用 `page`,实际返回完整数组,不接受分页参数。需有效 Token。 #### 输入 无请求参数。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":1,"name":"星鹤国学","code":"01","userTotalNum":120}]} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data | `Platform[]` | 平台数组,无数据时为空数组。 | | data[].id | number | 平台 ID。 | | data[].code | string | 平台编号。 | | data[].name | string | 平台名称。 | | data[].userTotalNum | number | 平台总学员数,无学员时为 `0`。 | ### GET /manage/platform/info #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/PlatformController.java` | | Response VO | `manage-api/src/main/java/com/xhzy/live/manage/vo/PlatformVo.java` | #### 功能 查询平台编辑表单所需的信息。需有效 Token。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 平台 ID。 | #### 输出 ```json {"code":0,"message":"成功","data":{"id":1,"name":"星鹤国学","code":"01"}} ``` | 字段 | 类型 | 说明 | | --- | --- | --- | | data.id | number | 平台 ID。 | | data.code | string | 平台编号。 | | data.name | string | 平台名称。 | #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `平台不存在` | `id` 不存在或平台已删除。 | ### POST /manage/platform/add #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/PlatformController.java` | | Request VO | `manage-api/src/main/java/com/xhzy/live/manage/vo/PlatformVo.java` | #### 功能 新增平台并记录操作日志。需有效 Token。 #### 输入 ```json {"code":"01","name":"星鹤国学"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | code | string | 是 | 平台编号,正常平台之间不可重复。 | | name | string | 是 | 平台名称。 | #### 输出 ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `平台编号不能为空` | `code` 缺失或仅包含空白字符。 | | 200 | -1 | `平台名称不能为空` | `name` 缺失或仅包含空白字符。 | | 200 | -1 | `平台编号01已存在!` | 编号 `01` 已被正常平台使用。 | ### POST /manage/platform/modify #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/PlatformController.java` | | Request VO | `manage-api/src/main/java/com/xhzy/live/manage/vo/PlatformVo.java` | #### 功能 修改平台编号和名称,并记录操作日志。需有效 Token。 #### 输入 ```json {"id":1,"code":"01","name":"星鹤国学"} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 平台 ID。 | | code | string | 是 | 平台编号,正常平台之间不可重复。 | | name | string | 是 | 平台名称。 | #### 输出 ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `平台ID不能为空` | `id` 缺失。 | | 200 | -1 | `平台不存在` | `id` 不存在或平台已删除。 | | 200 | -1 | `平台编号01已存在!` | 编号 `01` 已被其他正常平台使用。 | ### POST /manage/platform/delete #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/PlatformController.java` | #### 功能 删除平台并记录操作日志。删除后平台不再出现在列表中。需有效 Token。 #### 输入 ```json {"id":1} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 平台 ID。 | #### 输出 ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `平台ID不能为空` | `id` 缺失。 | | 200 | -1 | `平台不存在` | `id` 不存在或平台已删除。 | ### POST /manage/micro_page/copy #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/MicroPageController.java` | #### 功能 复制现有微页面。新页面沿用原名称和正文,但生成新的 ID 和 COS 文件 URL,并记录操作日志。需有效 Token。 #### 输入 ```json {"id":7} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 被复制的微页面 ID。 | #### 输出 ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。复制成功后重新请求分页接口可取得新页面 ID。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `微页面ID不能为空` | `id` 缺失。 | | 200 | -1 | `微页面不存在` | `id` 不存在或状态不是 `1`。 | | 200 | -1 | `微页面内容读取失败` | 原页面正文无法从 COS 读取。 | ### POST /manage/micro_page/delete #### 位置 | 类型 | 位置 | | --- | --- | | Controller | `manage-api/src/main/java/com/xhzy/live/manage/controller/MicroPageController.java` | #### 功能 物理删除微页面数据库记录,并记录操作日志。行为与星鹤国学后台一致;历史 COS 文件不在本接口中删除。需有效 Token。 #### 输入 ```json {"id":7} ``` | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number | 是 | 微页面 ID。 | #### 输出 ```json {"code":0,"message":"成功"} ``` 该接口没有业务返回数据,`data` 为 `null` 时省略。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -1 | `微页面ID不能为空` | `id` 缺失。 | | 200 | -1 | `微页面不存在` | `id` 不存在或状态不是 `1`。 | ## 课件管理与分享 管理端全部接口需有效管理员`X-Token`。 **课件对象、内容与部署前提** 本节描述当前源码契约。管理端Base URL沿上方环境表,前台Base URL为`/xhzy-live/app`;不能拿管理Token作为分享密码。课件管理路由只有在`xhzy.live.user-datasource.enabled=true`时注册,VOD公共接口不依赖课件数据源。执行[scripts/database/V20261004.007__courseware.sql](../scripts/database/V20261004.007__courseware.sql)或最新初始化脚本后调用课件接口,数据源必须指向Live业务库。 ResourceVo字段按当前填充行为如下,null字段在JSON中省略: | 字段 | 类型 | 实际请求/返回说明 | | --- | --- | --- | | id | number(Long) | 修改/详情/删除/分享用资源库ID;不是课程目录关联ID。新增由数据库生成,写操作不返回ID。 | | name | string | 按旧代码接受空白字符串;当前数据库最多255字符,NULL边界需旧DDL核验。 | | resourceType | number(integer) | 新增/修改必填,枚举见下表。 | | downloadStatus | number(integer) | 新增可省略默认0,修改省略保留;不新增0/1枚举校验。返回给分享端,不代表供应商层面已禁止下载。 | | content | JSON | 视频须可解析为对象;视频URL保存和返回时将腾讯VOD默认域名规范化为配置的自有分发域名;其它内容按旧结构保存,NULL存储边界需旧DDL核验;修改NULL保留。 | | duration | number(integer) | 视频可保存传入值;前端应以VOD文件状态接口返回的真实时长提交。content.duration为number时优先取其int值;非视频写入时清空。 | | updateTime | string | 管理分页,yyyy-MM-dd HH:mm:ss;详情不返回。 | | useNum | number(Long) | 仅管理分页填充;有效课程引用数。 | | examType | number(integer) | 新增考试需提供类型;修改省略保留试卷原类型;管理详情从exam_paper返回。当前未增加枚举校验。 | | questions | object[] | 考试写入使用;不是当前resource/info返回的题目列表。 | | coverUrl | string | 兼容字段;VOD文件状态接口返回工作流生成的封面,课件保存时仍按content原样提交。 | | shareStatus | number(integer) | 管理分页/详情:0未分享或已取消,1已分享。 | | shareCode/shareUrl | string | 已分享时管理分页/详情返回。code为10位字母数字。 | | sharePassword | string | 已分享的管理详情可返回非空白明文密码;BCrypt历史密码不回显;列表不返回密码。 | | resourceType | 名称 | 当前规则 | | --- | --- | --- | | 1 | 图片 | 可新增;分享页URL渲染为图片。 | | 2 | PDF | 可新增;分享页URL渲染为iframe。 | | 3 | 视频 | 可新增,content必须对象;有fileId时分享页使用受保护VOD播放,旧URL仍兼容。 | | 4 | 超链接 | 可新增;分享页URL渲染为链接。 | | 5 | 直播回放 | 禁止新增及从其他类型改入;历史5可同类型编辑/查询/分享。 | | 6 | 优秀案例 | 禁止新增及从其他类型改入;历史6可同类型编辑/查询/分享。 | | 7 | 考试 | 可保存试卷及题目,分享页不是考试作答页面。 | 未发现“国学证书”独立编码,不虚构数值。课件分享未额外限制类型,与学习工具一致。新增5/6返回“不支持新增此课件类型”;历史5/6可以改为允许的新类型,不能从5改入6或反之。 content真实支持结构: ```json {"name":"视频课件","resourceType":3,"downloadStatus":0,"content":{"fileId":"5280000000000000000","url":"https://vod.xhlive.com.cn/path/source.mp4","coverUrl":"https://vod.xhlive.com.cn/path/cover.jpg","duration":120}} ``` 视频上传完成后,前端使用VOD返回的fileId调用文件状态接口,转码成功后把fileId、源URL、封面和时长随课件新增/编辑一次提交。视频`content.url/mediaUrl/coverUrl/outputUrl`中的腾讯VOD域名在保存和返回时统一替换为`XHZY_VOD_DISTRIBUTION_DOMAIN`;历史记录无需改库,读取时同样规范化。课件保存接口不重复远程查询;匿名分享在密码通过后按fileId生成临时pSign。图片/PDF/链接可传`{"url":"https://example.test/file"}`;非腾讯域名和旧非视频JSON保持原值。 考试示例: ```json {"name":"考试课件","resourceType":7,"examType":1,"questions":[{"title":"选择题","questionType":1,"score":5,"content":["选项A"],"answer":[0]}]} ``` 首次创建考试必须有非空questions,examType必填;score按旧Integer绑定转换,非法文本失败、数值小数截断。题目支持title/imageUrl/questionType/content/answer/score/analysis/analysisImageUrl,顺序由服务器生成,score汇总入试卷。未提供questionType时不填充0,按数据库NULL/默认策略保存,不宣称有完整题目合法性校验。服务器生成content=`{"examId":<试卷ID>,"type":}`,忽略传入考试content;修改考试省略questions时保留原题目,显式空数组报错。完整试卷由`GET /manage/exam_paper/info`查询,Excel导入由`POST /manage/exam_paper/import`提供;当前不提供作答、提交和批改接口。 分享URL配置:管理端优先使用`xhzy.live.courseware.share-base-url`,值须包含`{0}`占位;未配时用`xhzy.shop.public-base-url`去除尾部斜杠,再追加`/courseware-share/index.html?code={0}`。后者已有环境入口`XHZY_SHOP_PUBLIC_BASE_URL`,必须指向前台服务公开Base URL,例如`https://test.zhinan.run/xhzy-live/app`。管理端默认public-base-url为空;缺配置时share返回“分享页面地址未配置”。不要把值指向管理端或旧学习工具域名。 VOD与COS共用`TENCENT_COS_ACCESS_KEY`、`TENCENT_COS_SECRET_KEY`,该凭据必须同时具有COS和VOD权限。VOD另行配置`XHZY_VOD_SUB_APP_ID`、`XHZY_VOD_PROCEDURE`、`XHZY_VOD_PLAY_KEY`、`XHZY_VOD_RAW_ADAPTIVE_DEFINITION`、`XHZY_VOD_PLAYER_LICENSE_URL`,可选`XHZY_VOD_API_REGION`。`XHZY_VOD_DISTRIBUTION_DOMAIN`为管理端返回及保存VOD地址使用的分发域名,默认`https://vod.xhlive.com.cn`;该域名还必须在对应VOD子应用中设为默认分发域名并配置有效HTTPS证书。管理端负责直传签名、腾讯媒资实时查询和管理预览签名;不复制旧系统事件轮询与vod_file状态表。web-api只配置应用ID、播放密钥、未加密转自适应模板ID和License URL,不持有SecretId/SecretKey。License URL会返回给播放器,License Key不进入后端配置或API响应。 ### GET /manage/exam_paper/info #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ExamPaperController.java` #### 功能 查询完整试卷及题目。题目按`sortNo`升序返回,`content`和`answer`还原为保存时的JSON值。路由仅在`xhzy.live.user-datasource.enabled=true`时注册。 #### 输入 Query参数,需有效管理`X-Token`。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | id | number(Long) | 是 | `exam_paper.id`,即考试课件`content.examId`;不是课件资源ID。 | #### 输出 ```json { "code": 0, "message": "成功", "data": { "id": 608, "type": 1, "score": 5, "questions": [ { "id": 1001, "title": "选择题", "questionType": 1, "sortNo": 1, "content": ["选项A"], "answer": [0], "score": 5 } ] } } ``` 试卷不存在时返回HTTP 200、`code=-1`、`message=题目不存在`。未登录或Token无效返回HTTP 200、`code=-999`。 ### GET /manage/exam_paper/result/num 需有效管理`X-Token`。Query参数`id`为`exam_paper.id`。接口统计`exam_result.exam_paper_id`等于该ID的记录数;成功返回: ```json {"code":0,"message":"成功","data":2} ``` 没有提交记录或试卷ID不存在时返回`data=0`。未登录或Token无效返回HTTP 200、`code=-999`。 ### POST /manage/exam_paper/import #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ExamPaperController.java` #### 功能 解析考试课件的Excel题目并返回题目数组,供前端随课件新增或编辑请求一起提交。本接口只解析文件,不创建试卷、题目或课件记录。路由仅在`xhzy.live.user-datasource.enabled=true`时注册。 #### 输入 `multipart/form-data`,需有效管理`X-Token`。 | 字段 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | file | file | 是 | `.xlsx`文件;接口兼容同名多文件参数,但只解析第一个文件。 | 工作簿只识别名称完全匹配的`单选题`和`多选题`工作表;第1行为表头,从第2行读取。列顺序固定为:题目、选项A至N、正确答案、答案解析、分数。题目为空的行跳过;选项内容为`图片`时只返回选项ID,图片地址由前端补充。多选题答案按连续选项字母解析,例如`AB`。 #### 输出 ```json { "code": 0, "message": "成功", "data": [ { "title": "单选题题目", "questionType": 1, "content": { "options": [ {"id": "A", "content": "选项A"}, {"id": "B", "content": "选项B"} ] }, "answer": "A", "score": 5, "analysis": "答案解析" }, { "title": "多选题题目", "questionType": 2, "content": { "options": [ {"id": "A", "content": "选项A"}, {"id": "B", "content": "选项B"} ] }, "answer": ["A", "B"], "score": 10 } ] } ``` `questionType=1`为单选题,`questionType=2`为多选题。分数为空时返回`0`;解析阶段不会生成`id`和`sortNo`,null字段在JSON中省略。工作簿没有可识别题目时返回空数组。 #### 错误响应 | HTTP 状态 | code | message | 条件 | | --- | --- | --- | --- | | 200 | -999 | `无效Token` | 管理Token缺失或无效。 | | 200 | -1 | `上传文件不能为空` | 未上传文件或文件为空。 | | 200 | -1 | `试卷文件格式错误` | 文件不是可解析的`.xlsx`工作簿。 | | 200 | -1 | `[工作表] 第N行:选项不能为空` | 有题目但没有选项。 | | 200 | -1 | `[工作表] 第N行:正确答案不能为空` | 正确答案为空。 | | 200 | -1 | `[多选题] 第N行:正确答案不能少于两个` | 多选题答案少于两个选项。 | | 200 | -1 | `[工作表] 第N行:正确答案选项不存在` | 答案字母没有对应的选项。 | | 200 | -1 | `[工作表] 第N行:分数设置错误` | 分数不是整数。 | ### GET /manage/common/vod/sign #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/VodController.java` #### 功能 生成腾讯云客户端上传签名,有效期24小时;签名固定包含独立VOD应用ID和任务流名称,前端交给腾讯云VOD上传SDK。 #### 输入 无Query或Body;需有效管理`X-Token`。 #### 输出 ```json {"code":0,"message":"成功","data":{"sign":"base64-upload-signature"}} ``` #### 错误响应 无效Token返回`code=-999`;VOD云账号、应用ID或任务流缺失返回`code=-1,message=腾讯云 VOD 配置不完整`。 ### GET /manage/common/vod/file #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/VodController.java` #### 功能 按fileId实时查询腾讯云媒资及工作流输出,不读取本地状态镜像。返回的`mediaUrl/coverUrl/outputUrl`使用`XHZY_VOD_DISTRIBUTION_DOMAIN`配置的自有分发域名。`synStatus`:`-1`不存在、`1`处理中、`2`失败或禁用、`3`已有转自适应/转码输出。 #### 输入 | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | fileId | string | 是 | 腾讯云VOD文件ID。 | #### 输出 ```json {"code":0,"message":"成功","data":{"id":"5280000000000000000","mediaUrl":"https://vod.xhlive.com.cn/path/source.mp4","coverUrl":"https://vod.xhlive.com.cn/path/cover.jpg","outputUrl":"https://vod.xhlive.com.cn/path/output.m3u8","status":1,"synStatus":3,"duration":120}} ``` null字段按全局规则省略;处理中可能没有`coverUrl/outputUrl/duration`。 #### 错误响应 fileId空白返回“VOD 文件ID不能为空”;配置缺失返回“腾讯云 VOD 配置不完整”;腾讯API调用失败统一返回“查询 VOD 文件失败”,详细云错误仅写服务日志。 ### GET /manage/common/vod/token #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/VodController.java` #### 功能 生成10小时有效的腾讯云RawAdaptive播放器签名,兼容旧后台`token/appId`字段。 #### 输入 Query `fileId:string`必填;需有效管理`X-Token`。 #### 输出 ```json {"code":0,"message":"成功","data":{"token":"eyJ...","appId":1500000001,"licenseUrl":"https://license.example.test/player.license"}} ``` #### 错误响应 fileId空白返回“VOD 文件ID不能为空”;应用ID、播放密钥、受保护转自适应模板或播放器License URL缺失返回“腾讯云 VOD 播放配置不完整”。 ### GET /manage/resource/page #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ResourceController.java` #### 功能 查询未删除且有内容记录的课件,名称模糊匹配,ID倒序;填充有效引用数及分享状态。 #### 输入 Query: | 参数 | 类型 | 必填/默认 | 说明 | | --- | --- | --- | --- | | key | string | 可选,无默认筛选 | 名称模糊查询,不按ID搜索,不自动trim。 | | page | integer | 必传,无默认 | >=1。 | | pageSize | integer | 必传,无默认 | 非负整数,0空页,无200上限。 | 无type或视频分类筛选参数。 #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"id":123,"name":"视频课件","resourceType":3,"downloadStatus":0,"content":{"fileId":"fixture-file","url":"https://example.test/video.mp4","duration":120},"duration":120,"updateTime":"2026-10-04 12:00:00","useNum":0,"shareStatus":0}],"total":1,"current":1,"size":20}} ``` #### 错误响应 均需管理X-Token;无效Token返回HTTP 200、code=-999、message=无效Token。业务错误HTTP 200、code=-1;请求类型/绑定错误及数据库异常读取实际message,不将HTTP状态当业务码。 page/pageSize越界:“分页参数不正确”;引用关系表存在但course表缺失:“课程引用数据不完整”;损坏JSON按旧接口回退原字符串。 ### GET /manage/resource/info #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ResourceController.java` #### 功能 读取有效课件、JSON内容及分享详情;不返回useNum或完整题目数组。 #### 输入 Query `id`:number/Long,必填。 #### 输出 ```json {"code":0,"message":"成功","data":{"id":123,"name":"视频课件","resourceType":3,"downloadStatus":0,"content":{"url":"https://example.test/video.mp4"},"updateTime":"2026-10-04 12:00:00","shareStatus":1,"shareCode":"Ab12Cd34Ef","shareUrl":"https://test.zhinan.run/xhzy-live/app/courseware-share/index.html?code=Ab12Cd34Ef","sharePassword":"example-password"}} ``` sharePassword只在已分享且非空白明文时出现;历史BCrypt不回显。 #### 错误响应 均需管理X-Token;无效Token返回HTTP 200、code=-999、message=无效Token。业务错误HTTP 200、code=-1;请求类型/绑定错误及数据库异常读取实际message,不将HTTP状态当业务码。 ID不存在、已删除或没有content记录:“课件不存在”;内容损坏按旧接口回退原字符串。缺查询id属于参数绑定错误。 ### POST /manage/resource/add #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ResourceController.java` #### 功能 JSON ResourceVo;课件、内容和必要考试题目同事务保存。 #### 输入 | 字段 | 类型 | 必填/默认 | 说明 | | --- | --- | --- | --- | | name | string | 必填 | 非空白。 | | resourceType | integer | 必填 | 1/2/3/4/7可新增,历史5/6见公共说明。 | | content | JSON | 非考试必填 | 非null;视频必须对象。 | | downloadStatus | integer | 新增省略默认0,修改省略保留 | 按旧代码不新增枚举校验。 | | duration | integer | 可选 | 视频元数据,非云验证。 | | examType | integer | 考试必填 | 考试类型,非考试不使用。 | | questions | object[] | 首次考试必填 | 修改考试可省略,空数组报错。 | 示例见公共content说明。不要依赖传入id,数据库生成新ID。 #### 输出 成功不返回业务数据,null data按NON_NULL配置省略: ```json {"code":0,"message":"成功"} ``` #### 错误响应 均需管理X-Token;无效Token返回HTTP 200、code=-999、message=无效Token。业务错误HTTP 200、code=-1;请求类型/绑定错误及数据库异常读取实际message,不将HTTP状态当业务码。 新增5/6:“不支持新增此课件类型”;视频内容解析、无题新考试、非法整数及数据库/绑定异常:“系统出错”;显式空题数组:“没有导入题目”。空白名与额外downloadStatus值不增加拒绝校验。 ### POST /manage/resource/modify #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ResourceController.java` #### 功能 编辑现有课件;不是PATCH,name/resourceType/content等按新增要求提供。视频转其他类型清空fileId与duration。 #### 输入 JSON字段同add,另必填`id:number/Long`。downloadStatus省略会设为0,不保留旧值;考试questions省略保留原题。 ```json {"id":123,"name":"图片课件","resourceType":1,"downloadStatus":0,"content":{"url":"https://example.test/image.png"}} ``` #### 输出 成功不返回业务数据,null data按NON_NULL配置省略: ```json {"code":0,"message":"成功"} ``` #### 错误响应 均需管理X-Token;无效Token返回HTTP 200、code=-999、message=无效Token。业务错误HTTP 200、code=-1;请求类型/绑定错误及数据库异常读取实际message,不将HTTP状态当业务码。 同add校验;缺id:“课件ID不能为空”;无有效资源:“课件不存在”。不能从其他类型改入5/6;同类历史5/6可编辑。 ### POST /manage/resource/delete #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ResourceController.java` #### 功能 有效课程引用时拒删;否则状态改9、名称追加(删除),并将分享置9。不物理删除云资产。 #### 输入 JSON `id:number/Long`必填。 ```json {"id":123} ``` #### 输出 成功不返回业务数据,null data按NON_NULL配置省略: ```json {"code":0,"message":"成功"} ``` #### 错误响应 均需管理X-Token;无效Token返回HTTP 200、code=-999、message=无效Token。业务错误HTTP 200、code=-1;请求类型/绑定错误及数据库异常读取实际message,不将HTTP状态当业务码。 缺id:“课件ID不能为空”;资源不存在或已删除:“课件不存在”;引用中:“课件使用中,无法删除”。引用统计join course,只计course.status<>9;无关联表时为0,关联表有但course表缺失则“课程引用数据不完整”。未来课程引用写入的并发协调仍需课程模块接入。 ### GET /manage/common/resource/list #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ResourceCommonController.java` #### 功能 提供其他页面选择课件的轻量公共数据;无分页,无内容/使用数/分享字段,不替代resource/page。 #### 输入 Query `key?:string`按名称模糊搜索;`type?:integer`精确匹配resource_type。两个参数均可省略,无默认类型;不校验type枚举,未知类型一般返回空数组。仅排除已删除,ID倒序。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":123,"name":"视频课件","resourceType":3}]} ``` 无结果data=[]。page/pageSize不是本接口参数。放在common延续旧系统公共选择接口组织方式,业务仍调用ResourceService。 #### 错误响应 均需管理X-Token;无效Token返回HTTP 200、code=-999、message=无效Token。业务错误HTTP 200、code=-1;请求类型/绑定错误及数据库异常读取实际message,不将HTTP状态当业务码。 type类型错误属于参数绑定错误。 ### POST /manage/resource/share #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ResourceController.java` #### 功能 按resource.id开启分享或改密;不额外限制资源类型。新分享首次生成10位code,重复分享和取消后再开沿用原code。 #### 输入 JSON:`id:number/Long`必填;`password:string`可选,无默认密码,缺省/null/空字符串/纯空白按无需密码访问。直接保存传入密码,不自动哈希;数据库password列最长255字符。 ```json {"id":123,"password":"example-password"} ``` #### 输出 ```json {"code":0,"message":"成功","data":{"resourceId":123,"shareStatus":1,"shareCode":"Ab12Cd34Ef","shareUrl":"https://test.zhinan.run/xhzy-live/app/courseware-share/index.html?code=Ab12Cd34Ef","sharePassword":"example-password"}} ``` 空密码字段可省略。分享没有自动到期字段;code不是密码。 #### 错误响应 均需管理X-Token;无效Token返回HTTP 200、code=-999、message=无效Token。业务错误HTTP 200、code=-1;请求类型/绑定错误及数据库异常读取实际message,不将HTTP状态当业务码。 分享地址缺失或不含{0}:“分享页面地址未配置”;缺id:“课件ID不能为空”;无有效资源:“课件不存在”。 ### POST /manage/resource/share/cancel #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/ResourceController.java` #### 功能 取消后新的匿名meta/verify请求失效;再开启同一资源恢复原链接。没有分享记录时对有效课件仍成功。已取得URL/内容不主动撤回。 #### 输入 JSON `id:number/Long`必填。 ```json {"id":123} ``` #### 输出 成功不返回业务数据,null data按NON_NULL配置省略: ```json {"code":0,"message":"成功"} ``` #### 错误响应 均需管理X-Token;无效Token返回HTTP 200、code=-999、message=无效Token。业务错误HTTP 200、code=-1;请求类型/绑定错误及数据库异常读取实际message,不将HTTP状态当业务码。 缺id:“课件ID不能为空”;无有效资源:“课件不存在”。 ## 渠道管理 除企业微信回调外,全部接口需要有效管理端 `X-Token`,且仅在 `xhzy.live.user-datasource.enabled=true` 时注册。先执行一次通讯录全量同步,后续渠道新增从本地有效企微成员中选择;渠道落库后仍在后台异步读取、创建或关联该成员的全部外部联系人,接口不等待客户同步完成。 运行环境必须配置 `XHZY_WECOM_CORP_ID`、通讯录同步 Secret `XHZY_WECOM_CONTACT_SECRET`、具备通讯录可见范围的自建应用 Secret `XHZY_WECOM_MEMBER_SECRET`,以及客户联系 Secret `XHZY_WECOM_CUSTOMER_SECRET`。通讯录变更回调另配 `XHZY_WECOM_CONTACT_CALLBACK_TOKEN`、`XHZY_WECOM_CONTACT_CALLBACK_AES_KEY`;原客户联系回调继续使用 `XHZY_WECOM_CALLBACK_TOKEN`、`XHZY_WECOM_CALLBACK_AES_KEY`。成员头像受企微授权范围限制,未返回时 `imageUrl` 缺省。客户身份独立保存在 `live_customer_identity`:按 unionID 优先复用既有微信用户或企微客户身份,unionID 缺失时按企业微信 externalUserId 复用。 `Channel` 返回字段:`id`、`name`、`wecomUserId`、`imageUrl?`、`nickname?`、`manager?: {id,name}`、`customerNum`、`remark?`、`updateTime`。 `WecomMember` 返回字段:`id`(新增渠道提交的 `wecomMemberId`)、`wecomUserId`、`nickname?`、`imageUrl?`、`position?`、`status`;成员分页只返回 `status=1` 的有效成员。 | 方法 | 路径 | 请求 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/channel/page` | Query `key?`(渠道名模糊查询)、`page?`、`pageSize?` | `Page` | | GET | `/manage/channel/list` | 无 | `Channel[]`,按渠道名和 ID 升序 | | GET | `/manage/channel/info` | Query `id` | `Channel` | | GET | `/manage/wecom/member/page` | Query `key?`(昵称或企微 ID)、`page?`、`pageSize?` | `Page` | | POST | `/manage/wecom/member/sync` | 无 | `{total}`,本次企微全量成员数 | | POST | `/manage/channel/add` | JSON `{name,wecomMemberId,imageUrl?,managerId?,remark?}` | 无业务数据 | | POST | `/manage/channel/modify` | JSON `{id,name,wecomUserId,imageUrl?,nickname?,managerId?,remark?}` | 无业务数据 | | POST | `/manage/channel/delete` | JSON `{id}` | 无业务数据 | | GET | `/manage/channel/customer/page` | Query `channelId`、`page?`、`pageSize?` | `Page<{userId,nickname,imageUrl?,mobile?,addedTime}>` | `POST /manage/wecom/member/sync` 可重复执行:按企微 UserID 幂等新增或更新,完整同步后不再存在的成员标记为不可用。企微通讯录在后台配置公开回调 `GET|POST /manage/callback/wecom/address-book`,处理 `change_contact` 的 `create_user`、`update_user`、`delete_user`;回调不使用管理 Token。新增渠道只提交成员分页返回的 `id`,服务端读取对应 UserID、昵称和头像,不接受手工输入企微账号。`managerId` 可选且同一个后台管理员最多关联一个渠道。成员头像优先,其次使用请求可选 `imageUrl`,最后使用默认头像。 编辑允许变更渠道名称、头像、昵称、关联管理员和备注;请求仍传原 `wecomUserId`,传入不同值返回“企微账号不允许修改”。删除会物理删除渠道及其客户关系,但保留直播用户、用户账户、微信身份和企微客户身份;重新添加相同渠道时只重建关系,不重复创建用户。企微删除客户后,如果该用户已通过 App 渠道链接建立归因,系统仅清除企微关系并保留 App 归因;`customerNum` 和客户分页只统计 `externalUserId` 仍有效的当前企微客户,因此删除后不再展示,重新添加后恢复展示。异步客户同步失败会写入服务日志,已创建的渠道继续保留。 常见业务错误包括“企微成员不存在或不可用”“企微成员ID不能为空”“渠道不存在”“企微账号已存在”“企微账号不允许修改”“后台用户已关联其他渠道”“企业微信配置不完整:环境变量名”和“企业微信同步失败”。分页要求 `page >= 1`、`1 <= pageSize <= 200`;无效 Token 统一返回 `code=-999`,其他业务错误返回 `code=-1`。 ## 表情包管理 本组接口对应任务7147,全部需要管理`X-Token`,并且仅在`xhzy.live.user-datasource.enabled=true`时注册。表情包是全局配置,不分页;服务端只保存图片地址和显示顺序,不增加名称、分组、启停或直播间字段。 图片采用已有COS两步式上传:前端先调用`GET /manage/common/cos/sign`取得签名并直传COS,再将最终图片URL作为`imageUrl`提交。需求没有确定图片格式、大小或固定对象路径,后端当前只校验URL非空。 `EmojiVo`字段:`id:number(Long)`、`imageUrl:string`、`sortNo:number(Long)`、`updateTime:string(yyyy-MM-dd HH:mm:ss)`。 ### GET /manage/emoji/list 按`sortNo`、`id`升序返回全部表情包,不接收分页参数。 ```json {"code":0,"message":"成功","data":[{"id":1,"imageUrl":"https://example.test/emoji/1.png","sortNo":1,"updateTime":"2026-10-10 10:00:00"}]} ``` ### POST /manage/emoji/add 新增表情包并追加到现有顺序末尾。请求只包含图片字段;成功不返回业务数据。 ```json {"imageUrl":"https://example.test/emoji/1.png"} ``` ### POST /manage/emoji/modify 修改指定表情包图片,不改变显示顺序。 ```json {"id":1,"imageUrl":"https://example.test/emoji/2.png"} ``` ### POST /manage/emoji/sort 以完整ID数组提交新的显示顺序。数组必须包含当前全部表情包ID且不能重复;空表可提交空数组。 ```json {"ids":[3,1,2]} ``` ### POST /manage/emoji/delete 删除指定表情包并将剩余顺序重新整理为从1开始的连续值。 ```json {"id":1} ``` #### 错误响应 无效Token返回HTTP 200、`code=-999`。业务失败返回HTTP 200、`code=-1`;可能的提示包括“表情包图片不能为空”“表情包ID不能为空”“表情包不存在”“表情包排序不能为空”“表情包排序ID不能重复”“表情包排序数据不完整”。 ## 视频课程 本组包含以下7个接口。全部需要管理 `X-Token`;无效Token返回HTTP 200、code=-999。业务失败code=-1;成功code=0/message=成功,空data按NON_NULL省略。路径拼接管理Base URL(dev上下文 `/xhzy-live`),不使用前台 `/app`。 **CourseVo与枚举**:id/code/计数/updateTime由服务端维护;platform嵌套id/name。openType为1小班/2大班;unlockMode为1全部/2按天/3工作日/4闯关。按天unlockTime按旧字符串契约保存,其余模式清空。subName/linkUrl/content/verifyStatus/studyOpenType可选;新增未提供时存null,不假设新页面没有的开关默认值。verifyStatus为0/1,studyOpenType为1自动/2手动。修改未提供字段保留原值,显式null按旧接口保留原值;必填字段不允许null。详情certTemplate已接入读取,当前不提供证书模板写接口;模板表缺失时明确失败。 registerNum统计`course_enrollment`中待开通或已开通的课程授权人数;studentNum统计`course_period_student`中已进入录播期次和班级的有效学员人数。coursePeriodNum统计未软删期;resourceNum由目录课件关联实时回填。分页返回完整CourseVo兼容字段,包括subName/linkUrl/content/tip/verifyStatus/studyOpenType;R7055-001列表页展示的10个字段是id/imageUrl/name/platform/openType/unlockMode/coursePeriodNum/resourceNum/registerNum/updateTime,前端不需要展示其余兼容字段。null字段可能不出现在JSON中。 ### GET /manage/course/page #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseController.java` #### 功能 按updateTime倒序分页,追加id倒序稳定排序。 #### 输入 Query key可选名称模糊查询;page/pageSize需显式传入,无默认值;page≤0归一1,pageSize=0空页,负pageSize拒绝,无200上限。 #### 输出 ```json {"code":0,"message":"成功","data":{"total":1,"current":1,"size":20,"list":[{"id":1,"name":"视频课程","subName":"副标题","code":"01","imageUrl":"https://example.test/cover.png","linkUrl":"https://example.test/course/1","content":"课程介绍","tip":"学习提示","openType":1,"unlockMode":2,"unlockTime":"09:30","verifyStatus":0,"studyOpenType":1,"studentNum":0,"resourceNum":4,"registerNum":0,"coursePeriodNum":1,"platform":{"id":1,"name":"平台一"},"updateTime":"2026-10-10 09:59:59"}]}} ``` #### 错误响应 分页参数不正确。 共同错误码见本组说明;数据库/绑定错误以实际message为准。 ### GET /manage/course/info #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseController.java` #### 功能 读取有效课程详情。 #### 输入 Query id:Long必填。 #### 输出 ```json {"code":0,"message":"成功","data":{"id":1,"name":"视频课程","code":"01","imageUrl":"https://example.test/cover.png","openType":1,"unlockMode":2,"unlockTime":"09:30","tip":"学习提示","studentNum":0,"resourceNum":0,"registerNum":0,"coursePeriodNum":1,"platform":{"id":1,"name":"平台一"},"updateTime":"2026-10-04 12:00:00"}} ``` #### 错误响应 课程ID不能为空;课程不存在。 共同错误码见本组说明;数据库/绑定错误以实际message为准。 ### POST /manage/course/add #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseController.java` #### 功能 事务内生成平台编号01–99,并初始化01期和01班;期开始日为当前日、结束日为加一月。平台行锁及唯一约束保护并发编号,软删除后不复用编号。 #### 输入 JSON:name按旧代码接受空白字符串,当前存储最多255字符,platform.id、openType、unlockMode必填;imageUrl/tip及隐藏字段可选。 ```json {"name":"视频课程","imageUrl":"https://example.test/cover.png","platform":{"id":1},"openType":1,"unlockMode":2,"unlockTime":"09:30","tip":"学习提示"} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 平台不存在;课程名称{name}已存在!;课程编号超限;课程类型/解锁方式无效;解锁时间按旧字符串契约保存。 共同错误码见本组说明;数据库/绑定错误以实际message为准。 ### POST /manage/course/modify #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseController.java` #### 功能 同步所有期的名称和解锁配置;换平台重新生成该平台编号,原平台编号不复用。 #### 输入 JSON id必填,其余字段可省略(保留原值)。 ```json {"id":1,"name":"新名称","unlockMode":1} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在及新增相同的校验错误。 共同错误码见本组说明;数据库/绑定错误以实际message为准。 ### POST /manage/course/delete #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseController.java` #### 功能 有报名、课程/期/班学生计数或有效期学员关系时禁止删除;否则课程、期、班一起软删,编号保留,名称可复用。 #### 输入 JSON id:Long必填。 ```json {"id":1} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 已有人报名,无法删除;课程不存在。 共同错误码见本组说明;数据库/绑定错误以实际message为准。 ### GET /manage/common/platform/list #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCommonController.java` #### 功能 有效平台选择,id倒序。 #### 输入 Query key可选,名称模糊查询。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":1,"name":"平台一","code":"01"}]} ``` #### 错误响应 数据库异常返回业务错误。 共同错误码见本组说明;数据库/绑定错误以实际message为准。 ### GET /manage/common/course/list #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCommonController.java` #### 功能 有效课程选择,id倒序,仅id/name。 #### 输入 Query key可选,名称模糊查询。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":1,"name":"视频课程"}]} ``` #### 错误响应 数据库异常返回业务错误。 共同错误码见本组说明;数据库/绑定错误以实际message为准。 ## 课程目录与课件 全部需要管理X-Token,成功HTTP 200/code=0/message=成功。写接口不返回业务data;无效Token code=-999,业务失败code=-1。管理Base URL追加以下路径。 **对象**:CourseCatalogVo为id/courseId/name/resourceNum/sortNo/code/resources;list返回嵌套resources,info返回目录基本字段。关联VO为id/name/sortNo/catalogId/resource。关联id用于修改、删除和排序;resource.id是原课件ID,关联name独立于课件name。嵌套resource复用管理课件内容、类型、duration、downloadStatus及shareStatus/shareCode/shareUrl,列表不展示分享密码,取消分享后shareStatus为0。空目录resources为[]。 **排序与并发**:previousId/nextId可省略或null;提供previousId时放其后,只有nextId时放其前,两者提供时须相邻;均空时移至尾部。自身、相同前后ID、无效/外课程邻居或不再相邻的位置失败,事务回滚。关联排序catalogId必填,可同课程跨目录移动,不得跨课程。源和目标目录重排连续sortNo,双方计数同步。课程行锁串行化目录写入及复制;原课件行锁与删除保护协调。重复关联沿用旧行为允许,每条关联独立计数。 **删除与复制**:有实际课件关联的目录禁止删除;关联删除不删除原课件。有效课程的关联纳入课件useNum/禁删;软删课程后其关联不再占有效引用。课程resourceNum与目录resourceNum重新统计真实关联。复制名称加“(复制)”,同平台新编号、verifyStatus=0/studentNum=0,复制内容/目录/关联并保留排序及原resource.id;生成新的课程/目录/关联ID及目录code,不复制期、班、报名。名称冲突或初始化失败整体回滚,不占编号。 ### GET /manage/course/catalog/list #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 按sortNo/id升序返回二级目录课件列表。 #### 输入 Query courseId:Long必填。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":10,"courseId":1,"name":"第一章","resourceNum":1,"sortNo":1,"code":"Ab12Cd34Ef","resources":[{"id":20,"catalogId":10,"name":"关联标题","sortNo":1,"resource":{"id":30,"name":"原课件","resourceType":1,"content":"正文","downloadStatus":0,"shareStatus":1,"shareCode":"Xy12Ab34Cd","shareUrl":"https://example.test/share?code=Xy12Ab34Cd"}}]}]} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### GET /manage/course/catalog/info #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 读取目录基本字段。 #### 输入 Query id:Long必填。 #### 输出 ```json {"code":0,"message":"成功","data":{"id":10,"courseId":1,"name":"第一章","resourceNum":1,"sortNo":1,"code":"Ab12Cd34Ef"}} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### POST /manage/course/catalog/add #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 目录追加至课程尾部;生成10字符code。 #### 输入 JSON courseId、name必填;name允许空白,当前存储最多255字符。 ```json {"courseId":1,"name":"第一章"} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### POST /manage/course/catalog/modify #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 修改目录名称,不通过此接口移动目录。 #### 输入 JSON id、name必填;可选courseId须与原值相同。 ```json {"id":10,"name":"新章名"} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### POST /manage/course/catalog/delete #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 目录有实际关联时失败“请先删除课件”;空目录物理删除。 #### 输入 JSON id必填。 ```json {"id":10} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### POST /manage/course/catalog/sort #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 课程内目录排序,规则见本组。 #### 输入 JSON id必填,previousId/nextId可空。 ```json {"id":10,"previousId":null,"nextId":11} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### POST /manage/course/catalog/resource/add #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 关联有效课件至目录尾部,不新增原课件。 #### 输入 JSON catalogId、name、resource.id必填。 ```json {"catalogId":10,"name":"关联标题","resource":{"id":30}} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### POST /manage/course/catalog/resource/modify #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 修改独立关联标题或所引用课件;移动使用sort接口。 #### 输入 JSON id、name、resource.id必填;可选catalogId须与原值相同。 ```json {"id":20,"name":"关联新标题","resource":{"id":31}} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### POST /manage/course/catalog/resource/delete #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 删除关联,重算目录及课程课件数。 #### 输入 JSON id必填,是关联ID。 ```json {"id":20} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### POST /manage/course/catalog/resource/sort #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseCatalogController.java` #### 功能 在目标目录排序;支持同课程跨目录移动。 #### 输入 JSON catalogId、id必填,previousId/nextId可空。 ```json {"catalogId":11,"id":20,"previousId":null,"nextId":21} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ### POST /manage/course/copy #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseController.java` #### 功能 复制规则与并发/回滚见本组,不生成期班。 #### 输入 JSON id必填,是源课程ID。 ```json {"id":1} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、目录不存在、课件不存在或课件关联不存在;名称允许空白,NULL及长度由存储约束处理、旧DDL边界待核验。排序位置无效、排序对象不属于目标位置、排序相邻位置已变更、不能跨课程移动课件;其它校验以实际message为准。目录删除有课件返回“请先删除课件”;复制允许重复副本名称,仍受99编号上限限制。 ## 学期与班级 本组包含10个接口,不提供班级独立CRUD、学员、导出或VOD路由。全部需要管理X-Token;HTTP 200成功code=0/message=成功,写接口无业务data;无效Token code=-999,业务失败code=-1,参数绑定错误以实际响应message为准。 **对象**:CoursePeriodVo为id/courseId/code/openDate/endDate/unlockMode/unlockTime/classNum/studentNum/maxClassCode。接口不返回name,但数据库继承课程名称和解锁配置。maxClassCode为有效班级最高编号字符串;classNum是实际有效班级数量。班级分页为id/code/studentNum/coursePeriod:{id,code}/teachers;teachers读取真实course_period_teacher并通过现有公共管理组件读取账号,字段id为教师关系ID、name/passport来自当前管理组件账号。独立教师列表中的id则为管理账号ID,供修改接口的teacherIds使用。公共选择仅返回id/code。 **新增和调整**:add接收JSON根数组,所有元素一个事务,空数组失败。courseId/code/endDate/maxClassCode必填;openDate按旧值保存;code保留字符串;maxClassCode使用Java整数解析,允许0及负值产生空班级,code最多20字符;保留调用方编号及旧字符串排序,通常使用01/02补零形式。同课程有效学期code唯一,已删code可重用。班级生成从01至maxClassCode,跳过44;例如maxClassCode=45时实际44个班级。modify的classNum必填且>0,但与旧行为一致,最终调整以maxClassCode为准,回填实际数量,不要求classNum和最大编号相等。编号增加新增班级,减少时删除高于目标编号且无报名的尾部班级记录。课程行锁串行化写操作,混合课程数组按courseId顺序锁定;失败全部回滚。 **日期和删除**:openDate按旧值保存,endDate不能为空并按旧字符串比较,endDate严格大于较小相邻code学期截止日、严格小于较大相邻code学期截止日;修改排除自身。不额外施加开始日与相邻期互不重叠规则。普通删除允许删最后一期,期和所属有效班级一起status=9;重复删除或软删课程对象失败。已有期/班学生计数禁止删除;若已有course_period_student表,也检查status<>9真实关系。缩减班级检查尾部班级计数及真实关系。未实现学员写入模块时计数不是凭空报名能力。 **分页**:page/pageSize需显式传入,无默认值;page≤0归一1,pageSize=0空页,负pageSize拒绝,无200上限。学期分页id倒序;公共学期选择code升序,班级选择sortNo升序。班级分页courseId可省略(返回空页),有效课程内按期code、班code倒序。periodIds[]与classIds[]同时传时按旧SQL的OR组合,不会越过courseId范围;外课程ID只会得到无匹配。所有读取排除软删期班及无效所属课程。 ### GET /manage/course/period/page #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 学期分页,id倒序。 #### 输入 Query courseId必填;page/pageSize可选。 #### 输出 ```json {"code":0,"message":"成功","data":{"total":1,"list":[{"id":2,"courseId":1,"code":"02","openDate":"2026-10-01","endDate":"2026-12-01","unlockMode":2,"unlockTime":"09:30","classNum":2,"studentNum":0,"maxClassCode":"02"}]}} ``` #### 错误响应 课程不存在、学期不存在、学期所属课程不一致;分页参数不正确;学期数组不能为空;学期编号{code}已存在!;截止时间不能为空;{code}学期截止时间必须大于上学期的截止时间/必须小于下学期的截止时间;班级数要大于0!;已有人报名,无法删除;{期编号}{班编号}班级已有人报名!。最大班级编号必须可解析为Java整数;具体错误以实际message为准。 ### GET /manage/course/period/info #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 读取有效学期详情。 #### 输入 Query id必填。 #### 输出 ```json {"code":0,"message":"成功","data":{"id":2,"courseId":1,"code":"02","openDate":"2026-10-01","endDate":"2026-12-01","unlockMode":2,"unlockTime":"09:30","classNum":2,"studentNum":0,"maxClassCode":"02"}} ``` #### 错误响应 课程不存在、学期不存在、学期所属课程不一致;分页参数不正确;学期数组不能为空;学期编号{code}已存在!;截止时间不能为空;{code}学期截止时间必须大于上学期的截止时间/必须小于下学期的截止时间;班级数要大于0!;已有人报名,无法删除;{期编号}{班编号}班级已有人报名!。最大班级编号必须可解析为Java整数;具体错误以实际message为准。 ### POST /manage/course/period/add #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 批量事务新增学期,继承课程名称及解锁,生成班级。 #### 输入 JSON根数组;每个元素courseId/code/endDate/maxClassCode必填;openDate按旧值保存。 ```json [{"courseId":1,"code":"02","openDate":"2026-10-01","endDate":"2026-12-01","maxClassCode":"02"}] ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、学期不存在、学期所属课程不一致;分页参数不正确;学期数组不能为空;学期编号{code}已存在!;截止时间不能为空;{code}学期截止时间必须大于上学期的截止时间/必须小于下学期的截止时间;班级数要大于0!;已有人报名,无法删除;{期编号}{班编号}班级已有人报名!。最大班级编号必须可解析为Java整数;具体错误以实际message为准。 ### POST /manage/course/period/modify #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 更新有效学期,检查相邻截止日并按最大班级编号调整班级。 #### 输入 JSON id/courseId/code/openDate/endDate/classNum/maxClassCode必填,所属课程不能改变。 ```json {"id":2,"courseId":1,"code":"02","openDate":"2026-10-01","endDate":"2026-12-02","classNum":3,"maxClassCode":"03"} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、学期不存在、学期所属课程不一致;分页参数不正确;学期数组不能为空;学期编号{code}已存在!;截止时间不能为空;{code}学期截止时间必须大于上学期的截止时间/必须小于下学期的截止时间;班级数要大于0!;已有人报名,无法删除;{期编号}{班编号}班级已有人报名!。最大班级编号必须可解析为Java整数;具体错误以实际message为准。 ### POST /manage/course/period/delete #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 无报名时软删学期及班级,可以删除最后一期。 #### 输入 JSON id必填。 ```json {"id":2} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 课程不存在、学期不存在、学期所属课程不一致;分页参数不正确;学期数组不能为空;学期编号{code}已存在!;截止时间不能为空;{code}学期截止时间必须大于上学期的截止时间/必须小于下学期的截止时间;班级数要大于0!;已有人报名,无法删除;{期编号}{班编号}班级已有人报名!。最大班级编号必须可解析为Java整数;具体错误以实际message为准。 ### GET /manage/course/class/page #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 班级分页,嵌套coursePeriod;两组筛选按OR。 #### 输入 Query courseId可选;periodIds[]、classIds[]可重复传值;page/pageSize可选。 #### 输出 ```json {"code":0,"message":"成功","data":{"total":1,"list":[{"id":3,"code":"02","studentNum":0,"coursePeriod":{"id":2,"code":"02"}}]}} ``` #### 错误响应 课程不存在、学期不存在、学期所属课程不一致;分页参数不正确;学期数组不能为空;学期编号{code}已存在!;截止时间不能为空;{code}学期截止时间必须大于上学期的截止时间/必须小于下学期的截止时间;班级数要大于0!;已有人报名,无法删除;{期编号}{班编号}班级已有人报名!。最大班级编号必须可解析为Java整数;具体错误以实际message为准。 ### GET /manage/course/class/teacher/list #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 读取指定班级当前配置的教师账号。 #### 输入 Query id必填,是班级ID。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":7,"name":"导师","passport":"teacher01"}]} ``` 本接口的教师id是管理账号ID,可直接用于教师修改接口;班级分页teachers中的id是`course_period_teacher`关系ID,两者含义不同。 #### 错误响应 班级ID不能为空、班级不存在、教师关系表尚未迁移,或教师账号{id}未接通,请核对管理账号映射。 ### POST /manage/course/class/teacher/modify #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 在同一事务中替换指定班级的全部教师关系。 #### 输入 JSON id必填,是班级ID;teacherIds是管理账号ID数组。空数组或null清空教师,重复ID自动去重。服务端会先校验全部账号,再替换原关系;校验失败不改变原配置。 ```json {"id":3,"teacherIds":[7,8]} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` #### 错误响应 班级ID不能为空、班级不存在、教师账号{id}不存在,或教师关系表尚未迁移。 ### GET /manage/common/manager/list 管理账号公共选择接口,需有效管理端 `X-Token`。Query 参数 `key` 可选,按姓名或登录账号模糊查询。仅返回未删除账号,按账号 ID 倒序排列。 ```json {"code":0,"message":"成功","data":[{"id":7,"name":"导师","passport":"teacher01"}]} ``` ### GET /manage/common/course_period/list #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 有效学期公共选择,仅id/code。 #### 输入 Query courseId必填。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":2,"code":"02"}]} ``` #### 错误响应 课程不存在、学期不存在、学期所属课程不一致;分页参数不正确;学期数组不能为空;学期编号{code}已存在!;截止时间不能为空;{code}学期截止时间必须大于上学期的截止时间/必须小于下学期的截止时间;班级数要大于0!;已有人报名,无法删除;{期编号}{班编号}班级已有人报名!。最大班级编号必须可解析为Java整数;具体错误以实际message为准。 ### GET /manage/common/class/list #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CoursePeriodController.java` #### 功能 有效班级公共选择,仅id/code。 #### 输入 Query coursePeriodId必填。 #### 输出 ```json {"code":0,"message":"成功","data":[{"id":3,"code":"02"}]} ``` #### 错误响应 课程不存在、学期不存在、学期所属课程不一致;分页参数不正确;学期数组不能为空;学期编号{code}已存在!;截止时间不能为空;{code}学期截止时间必须大于上学期的截止时间/必须小于下学期的截止时间;班级数要大于0!;已有人报名,无法删除;{期编号}{班编号}班级已有人报名!。最大班级编号必须可解析为Java整数;具体错误以实际message为准。 ## 课程学员 本组包含6个接口,其中5个读取/导出接口、1个调班写接口。全部需管理X-Token;无效Token HTTP 200/code=-999,业务失败code=-1。分页成功为ApiResponse;导出成功为真实XLSX二进制,不是JSON。管理Base URL追加下列路径。 **批量导入记录**:`course_import_record`记录课程学员批量增加或删除任务的执行结果。`type=1`为增加学员,`type=2`为删除学员;`status`沿用任务状态编码。分页返回总数、成功数、失败数和创建时间,不返回原始导入数据。增加学员通过Course Contract创建`MANUAL_IMPORT`授权并按指定期次分班;删除只撤销人工导入来源,不影响商城订单或渠道授权。提交接口为`POST /manage/course/batch_import/add`,错误数据由`GET /manage/course/batch_import/export`导出。 **StudentVo与数据来源**:id为course_period_student关系ID,identityType为1临时/2正式,studentCode/name/imageUrl/passport来自现有user;嵌套course:{id,name}、coursePeriod:{id,code}、coursePeriodClass:{id,code}和studyProgress:{totalNum,finishNum}。null字段按NON_NULL可能省略。course_period_student保存studyStatus、lastCatalogId、studyCatalogId和lockDate对应的学习状态列,但当前管理查询不返回这些字段。查询只接受所属课程、期、班一致的关系,排除软删学员、用户、课程、期班。学习开始与进度由前台课程学习接口写入真实course_study_record和user_study_record;自动报名和交易开课仍不属于本组接口。 **筛选与进度**:key为名称模糊匹配或手机号精确匹配;periodIds[]和classIds[]支持重复Query值,同时传按旧SQL的OR,始终限制courseId;外课程ID无匹配。排序期code/班code/学员关系id升序。totalNum为有效目录中有效视频课件(type=3)的关联数量,finishNum统计同user/course对应关联的finish_status=1;course_study_record.resource_id是关联ID,不是原课件ID,非视频、删除关联、无效课件及不属于该课程记录不计入。同课件重复关联独立计数;未采集的关联计未完成。 **导出列与安全**:工作表“记录”,基础列依次课程、学期、班级、名称、手机号、课程完成数;完成数文本finishNum/totalNum。班级导出再按目录和关联顺序追加每个视频关联的独立name作为列。完成记录显示100%,未完成按maxProgress/duration百分比向下取两位小数;无记录或无有效时长显示0.00%,越界进度限制0–100%。所有文本用STRING单元格保留手机号前导零,包含=、+、-、@的内容不作为公式。空结果仍有表头,班级空结果仍有课程视频列。 **下载响应**:Content-Type `application/vnd.ms-excel;charset=UTF-8`(沿用旧接口),Content-Disposition `attachment;filename = .xlsx`。文件是可解析的Office Open XML。对象/鉴权错误返回共同JSON错误,不生成伪Excel。 **一致性**:关联换resource.id时在同一事务重置该关联对应课程的观看和完成字段;仅改关联名不重置。原视频更换fileId或改非视频时重置所有关联进度;删除关联后遗留记录不参与统计,复制不复制学习记录。调班只允许在同一课程内进行,保留原学员关系ID、学号和学习状态,并在同一事务更新原班、目标班及跨学期时的学员数。支付开课不属于本组接口。 ### GET /manage/course/batch_import/page #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseBatchImportController.java` #### 功能 按课程分页读取批量导入记录,与旧后台同名分页接口的路径、方法、参数和返回字段兼容。 #### 输入 Query `courseId:Long`、`page:Integer`、`pageSize:Integer`均必填。`page<=0`归一为1,`pageSize=0`返回空列表,负数返回业务错误。 #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"id":12,"status":3,"type":1,"totalNum":10,"successNum":9,"failNum":1,"createTime":"2026-10-08 09:02:03"}],"total":1,"current":1,"size":20}} ``` 结果按记录ID倒序;`createTime`格式为`yyyy-MM-dd HH:mm:ss`。课程没有记录时返回成功空页。 #### 错误响应 无效Token返回code=-999;缺少课程ID、分页参数不正确或尚未执行`V20261008.015__course_import_records.sql`返回code=-1和对应业务提示。 ### GET /manage/course/student/page #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseStudentController.java` #### 功能 分页读取真实学员及视频完成进度。 #### 输入 Query key可选、courseId可选(省略返回空页)、periodIds[]/classIds[]可选;page/pageSize需显式传入,无默认值;page≤0归一1,pageSize=0空页,负pageSize拒绝,无200上限。 #### 输出 ```json {"code":0,"message":"成功","data":{"list":[{"id":101,"identityType":2,"studentCode":"S1","name":"张三","imageUrl":"https://example.test/avatar","passport":"0013800000000","course":{"id":1,"name":"视频课程"},"coursePeriod":{"id":2,"code":"02"},"coursePeriodClass":{"id":3,"code":"01"},"studyProgress":{"totalNum":2,"finishNum":1}}],"total":1,"current":1,"size":20}} ``` #### 错误响应 无效Token code=-999;课程不存在、班级不存在、班级ID不能为空或分页参数不正确code=-1。绑定与数据库异常读取实际message;导出失败也按共同JSON错误处理。 ### GET /manage/course/student/study_record/list #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseStudentController.java` #### 功能 按课程目录顺序读取指定学员的全部有效视频课件及学习进度。 #### 输入 Query studentId必填,是`course_period_student`关系ID。 #### 输出 ```json {"code":0,"message":"成功","data":[{"total":80,"all":120,"finish":50,"status":0,"resource":{"id":20,"name":"第一课","resource":{"id":30,"name":"视频课件","resourceType":3,"duration":120}}},{"total":70,"all":90,"finish":90,"status":1,"finishTime":"2026-10-10 08:30:00","resource":{"id":21,"name":"第二课","resource":{"id":31,"name":"视频课件二","resourceType":3,"duration":90}}}]} ``` `all`是视频总时长,`total`是累计观看时长;已完成时`finish`等于总时长,未完成时为最大播放进度。`resource.id`是课程课件关联ID,内层`resource.id`才是原课件ID。未产生学习记录的有效视频仍返回,进度字段为0。 #### 错误响应 学员ID不能为空或未找到该学员;数据库表未迁移等基础设施异常按统一系统错误返回。 ### POST /manage/course/student/move #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseStudentController.java` #### 功能 将学员调入同一课程下的另一个有效班级。 #### 输入 JSON id必填,是`course_period_student`关系ID;classId必填,是目标班级ID。 ```json {"id":101,"classId":4} ``` #### 输出 ```json {"code":0,"message":"成功"} ``` 调班保留原关系ID、studentCode和学习状态;同班提交直接成功,不重复调整计数。跨学期调班同步调整原学期和目标学期人数,课程总人数不变。 #### 错误响应 学员ID不能为空、班级ID不能为空、未找到该学员、班级不存在或不能跨课程调班。 ### GET /manage/course/student/export #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseStudentController.java` #### 功能 导出所有筛选匹配学员,六个基础列。 #### 输入 Query key/courseId/periodIds[]/classIds[]与分页相同,无分页参数。省略courseId导出仅基础表头。 #### 输出 HTTP 200返回XLSX文件,下载头、文件名、工作表与列见本组说明,不封装data。 #### 错误响应 无效Token code=-999;课程不存在、班级不存在、班级ID不能为空或分页参数不正确code=-1。绑定与数据库异常读取实际message;导出失败也按共同JSON错误处理。 ### GET /manage/course/class/student/export #### 位置 `manage-api/src/main/java/com/xhzy/live/manage/controller/CourseStudentController.java` #### 功能 导出指定有效班级学员及课程各视频关联进度列,不接受跨课程关系。 #### 输入 Query classId:Long必填,是班级ID。 #### 输出 HTTP 200返回XLSX文件,下载头、文件名、工作表与列见本组说明,不封装data。 #### 错误响应 无效Token code=-999;课程不存在、班级不存在、班级ID不能为空或分页参数不正确code=-1。绑定与数据库异常读取实际message;导出失败也按共同JSON错误处理。 ## 直播课程 本组接口由 `zhinan-live-course-spring-boot-starter` 提供,统一挂载在 `/manage/live-course`,全部需要有效管理端 `X-Token`。直播后台需启用 `zhinan.course.live.enabled=true`,依次执行 `V20261010.020__live_course.sql`、`V20261010.021__complete_live_course_links.sql`,并提供用户资料与渠道资料宿主实现。无效 Token 返回 HTTP 200、`code=-999`;业务错误返回 HTTP 200、`code=-1`。 课程状态:`0` 进行中、`1` 已结束(直播全部结束后的 3 天等待期)、`2` 回放中、`3` 已完成。目录 `liveType`:`1` 公开课、`2` 智能录播。`liveType`、`liveSessionId` 和场次开始时间均可暂不设置;存在未关联场次的有效目录时课程保持进行中,未关联目录不进入 App 可见数据。所有目录均已关联且场次结束后进入已结束状态,3 天后按 `replayDays` 开放回放;未开启回放或回放期结束后课程完成,并关闭该课程仍有效的用户关系。生命周期由定时任务自动推进,管理端查询时也会即时校正。 通用直播场次、流、供应商事件和回放媒资由 `ZhiNan_Live_Service` 管理。课程 Starter 负责课程关系、渠道总链接、客户专属链接及其失效规则;直播宿主负责企微回调验签解密、客户资料同步,并把客户增删事件交给课程 Starter。任务 7046 的渠道落地页可消费这些链接,但不再拥有课程开通关系。 ### GET /manage/live-course/page 按课程名称分页查询直播课程。Query:`key` 可选,`page` 默认 1,`pageSize` 默认 20。`studentNum` 为课程学员关系数,`openedNum` 为已执行开课动作的学员数。 ```json {"code":0,"message":"成功","data":{"list":[{"id":1,"imageUrl":"https://example.test/live-course.png","name":"直播课程","studentNum":12,"openedNum":10,"status":0,"replayEnabled":true,"replayDays":30,"advisorName":"课程顾问","advisorImageUrl":"https://example.test/advisor.png","advisorLinkUrl":"https://example.test/advisor","updateTime":"2026-10-10 12:00:00"}],"total":1,"current":1,"size":20}} ``` ### GET /manage/live-course/info Query:`id:Long` 必填。返回字段与课程分页项一致。 ### POST /manage/live-course/add 新增直播课程。`name` 必填;封面、回放和顾问信息可选。开启回放时 `replayDays` 必须大于 0。成功返回 `{"code":0,"message":"成功"}`。 ```json {"name":"直播课程","imageUrl":"https://example.test/live-course.png","replayEnabled":true,"replayDays":30,"advisorName":"课程顾问","advisorImageUrl":"https://example.test/advisor.png","advisorLinkUrl":"https://example.test/advisor"} ``` ### POST /manage/live-course/modify 编辑直播课程。请求字段与新增相同,并增加必填 `id`。 ### POST /manage/live-course/replay/modify 独立设置回放。请求 `{"id":1,"replayEnabled":true,"replayDays":30}`;开启回放时周期必填且必须大于 0。 ### POST /manage/live-course/advisor/modify 设置无课程权限时展示的顾问信息。`advisorName`、`advisorImageUrl`、`advisorLinkUrl` 均必填。 ### POST /manage/live-course/delete 软删除课程、配置、目录及渠道关系。课程仍有有效学员关系时拒绝删除,历史学员关系不物理清理。请求:`{"id":1}`。 ### POST /manage/live-course/copy 复制课程设置及目录,复制后的名称追加 `-复制`。不复制学员、课程权益、渠道关系或渠道凭证。请求:`{"id":1}`。 ### GET /manage/live-course/catalog/list Query:`courseId:Long` 必填。无分页,按 `sortNo`、`id` 排序。返回目录名称、直播类型、关联场次、场次名称、开始时间、直播状态、回放状态和回放地址。 ```json {"code":0,"message":"成功","data":[{"id":11,"courseId":1,"name":"第一讲","liveType":1,"liveSessionId":101,"liveName":"第一讲直播","startTime":"2026-10-12 19:30:00","liveStatus":0,"replayStatus":0,"sortNo":1}]} ``` ### GET /manage/live-course/catalog/info Query:`id:Long` 必填。返回字段与目录列表项一致。 ### 直播课程目录写操作 | 方法与路径 | 请求 JSON | 说明 | | --- | --- | --- | | `POST /manage/live-course/catalog/add` | `{"courseId":1,"name":"第一讲","liveType":1,"liveSessionId":101}` | 新增目录;只有名称必填,类型和场次均可省略。 | | `POST /manage/live-course/catalog/modify` | `{"id":11,"name":"第一讲(调整)","liveType":1,"liveSessionId":101}` | 修改目录。 | | `POST /manage/live-course/catalog/delete` | `{"id":11}` | 删除目录。 | | `POST /manage/live-course/catalog/sort` | `{"previousId":12,"id":11,"nextId":13}` | 按拖拽后的相邻目录排序;移到首位时 `previousId` 为空,移到末位时 `nextId` 为空。 | 目录关联的场次不存在时返回 `code=-1`。 ### GET /manage/live-course/student/page Query:`courseId:Long` 必填,`key` 可选,`page` 默认 1,`pageSize` 默认 20。`key` 匹配用户 ID、昵称或手机号。`openStatus` 只记录是否执行过开课动作;`relationStatus` 单独表示课程关系状态。 ```json {"code":0,"message":"成功","data":{"list":[{"id":21,"userId":10,"imageUrl":"https://example.test/avatar.png","nickname":"学员","mobile":"13800000000","channelId":3,"channelName":"渠道A","openStatus":1,"openTime":"2026-10-10 10:00:00","relationStatus":1}],"total":1,"current":1,"size":20}} ``` ### GET /manage/live-course/channel/page Query:`courseId:Long` 必填,`key` 可选,`page` 默认 1,`pageSize` 默认 20。`key` 匹配渠道名称或昵称。`operatorId` 是操作人 ID,`operatorName` 是操作人名称。`linkStatus=0` 表示尚未创建链接,`1` 表示已创建;创建后返回六位 `accessCode` 和渠道总 `shareUrl`,客户专属 Token 不在管理列表暴露。 ```json {"code":0,"message":"成功","data":{"list":[{"id":31,"channelId":3,"imageUrl":"https://example.test/channel.png","channelName":"渠道A","nickname":"小王","wecomUserId":"zhangsan","customerNum":20,"remark":"重点渠道","operatorId":1,"operatorName":"管理员","accessCode":"123456","linkStatus":1,"shareUrl":"https://test.zhinan.run/xhzy-live/app/live-course/link/abc123","updateTime":"2026-10-10 12:00:00"}],"total":1,"current":1,"size":20}} ``` ### 直播课程渠道写操作 | 方法与路径 | 请求 JSON | 说明 | | --- | --- | --- | | `POST /manage/live-course/channel/add` | `{"courseId":1,"channelId":3,"remark":"重点渠道"}` | 只关联已有渠道,不提前生成链接。 | | `POST /manage/live-course/channel/create-link` | `{"id":31}` | 生成渠道总链接和听课码,并为当前渠道客户创建专属链接及待开通课程关系。 | | `POST /manage/live-course/channel/reset` | `{"id":31}` | 重新生成听课码、总链接及全部客户专属 Token,旧链接立即失效,不重复创建课程关系。 | | `POST /manage/live-course/channel/delete` | `{"id":31}` | 删除关系、使全部链接失效,并撤销该渠道产生的课程权益来源。 | ## 渠道链接 本组接口由直播课程 Starter 提供,统一位于 `/manage/channel-link`。全部需要有效管理端 `X-Token`,并声明 `channel_link_index_show` 权限码;宿主开启 `zhinan.common.manage.permission-check-enabled` 时由统一拦截器校验,超级管理员的 `*` 权限同样有效。渠道账号通过 `live_channel.manager_id` 识别,只能读取自己的渠道数据,且即使手工提交其他 `channelId` 也会被拒绝;未绑定渠道的有权账号可以读取全部渠道,并可选传 `channelId` 筛选。 启用本组接口前需依次执行 `V20261010.020__live_course.sql`、`V20261010.021__complete_live_course_links.sql` 和 `V20261010.022__channel_session_links.sql`。公开链接使用 `zhinan.course.live.public-base-url` 生成。列表里的 `followSessionId` 是“直播跟进”功能的场次上下文,本任务不实现 7085 的跟进业务。 ### GET /manage/channel-link/course/page 按直播课程名称分页查询已创建链接的课程渠道关系。Query:`channelId`、`key` 可选,`page` 默认 1,`pageSize` 默认 20。`studentNum` 和 `openedNum` 分别是该课程、该渠道的学员数与已执行开课动作人数。 ```json {"code":0,"message":"成功","data":{"list":[{"id":31,"channelId":3,"channelName":"渠道A","courseId":1,"imageUrl":"https://example.test/course.png","courseName":"直播课程","studentNum":20,"openedNum":12,"accessCode":"123456","appOpenUrl":"https://example.test/app/live-course/code/123456","h5OpenUrl":"https://example.test/app/live-course/link/abc123"}],"total":1,"current":1,"size":20}} ``` ### GET /manage/channel-link/course/catalog/list Query:`courseChannelId:Long` 必填。无分页返回该渠道课程下已关联直播场次的全部目录,字段为 `id`、`name`、`liveSessionId`、`liveName`、`startTime`、`h5WatchUrl`、`followSessionId`。普通 `h5WatchUrl` 会在公开端检查当前用户课程权限,无权限时返回课程顾问信息。 ### GET /manage/channel-link/course/catalog/customer/page Query:`courseChannelId`、`catalogId` 必填,`key` 可选匹配昵称或手机号,`page` 默认 1,`pageSize` 默认 20。分页项字段为 `userId`、`imageUrl`、`nickname`、`mobile`、`channelId`、`channelName`、`h5WatchUrl`。专属链接绑定当前学员身份,不能跨渠道或跨目录复用。 ### GET /manage/channel-link/session/page 按直播名称分页查询未被有效直播课程目录占用的单场直播。Query:`channelId`、`key` 可选,`page` 默认 1,`pageSize` 默认 20。分页项字段为 `id`、渠道信息、`liveSessionId`、`imageUrl`、`liveName`、`studentNum`、`appAccessCode`、`h5WatchUrl`、`followSessionId`。首次列出渠道与场次组合时会幂等创建口令、普通链接及当前渠道客户的专属链接;单场口令和 H5 链接不检查课程权限。 ### GET /manage/channel-link/session/customer/page Query:`sessionLinkId:Long` 必填,`key` 可选匹配昵称或手机号,`page` 默认 1,`pageSize` 默认 20。响应字段与课程目录客户链接分页一致。企微客户新增或删除回调会同步创建或失效对应专属链接。 ### 企业微信客户回调 公开地址为 `GET|POST /manage/callback/wecom/contact`,由企业微信直接调用,不使用管理 Token。需配置 `XHZY_WECOM_CALLBACK_TOKEN`、`XHZY_WECOM_CALLBACK_AES_KEY` 和现有 CorpId/Secret。GET 完成 URL 验证;POST 验签、AES 解密后处理 `add_external_contact`、`edit_external_contact`、`del_external_contact`、`del_follow_user`,同步渠道客户、专属链接和课程关系。 ## App 直播课程 除链接信息外均需用户 `X-Token`,用户 ID 只从 Token 获取。 | 方法与路径 | 输入 | 输出/说明 | | --- | --- | --- | | `GET /user/course/list` | 无 | 当前用户未开课或已开课的直播课程;已关闭关系不返回。 | | `GET /user/course/info` | Query `id` | 当前用户课程详情,包含关系状态、下一场直播和回放期限;无有效关系返回“无课程访问权限”。 | | `GET /user/course/access-info` | Query `id` | 返回 `hasAccess` 及课程顾问信息,供无权限页展示。 | | `GET /user/course/catalog` | Query `courseId` | 仅已开课关系可用;返回后台配置的课程目录、已关联场次的进入地址及受课程生命周期约束的回放信息。 | | `POST /user/course/open` | `{"id":12,"code":"Ab12Cd34"}` | 兼容原 APP 录播课程开课:`id` 为学期 ID,校验开课码后开通,成功响应不含 `data`。 | | `POST /user/course/select/open` | `{"courseId":1}` | 7016 直播选课:将已有待开通关系确认为已开通,并关闭该用户其他直播课程。 | | `POST /user/course/code/open` | `{"code":"123456"}` | 通过六位听课码建立并开通直播课程关系;无既有关系时同时绑定听课码对应渠道。 | | `POST /user/course/link/open` | `{"token":"..."}` | 通过渠道总链接或本人专属链接开通;他人专属链接拒绝。 | | `GET /user/course/link/info` | Query `token`,无需 Token | 校验链接并返回课程、渠道及顾问信息;重置或删除后的旧 Token 返回“课程链接无效”。 | ### GET /user/course/list 成功响应中的课程字段如下;列表只包含 `relationStatus=0` 未开课或 `relationStatus=1` 已开课的数据。 ```json { "code": 0, "message": "成功", "data": [{ "id": 1, "imageUrl": "https://example.test/course.png", "name": "直播课程", "status": 0, "relationStatus": 1, "replayEnabled": true, "replayDays": 30, "replayAvailable": false, "statusText": "进行中", "nextLiveStartTime": "2026-10-11 19:30:00" }] } ``` `status`:`0`进行中、`1`已结束且处于3天等待期、`2`回放中、`3`已完成。`replayExpireTime`仅在回放中返回。课程完成后,系统关闭所有未开课和已开课关系,列表不再返回该课程。 ### GET /user/course/info Query:`id:Long` 必填。输出课程字段与列表一致。进行中时 `nextLiveStartTime` 是最近一期尚未开始的直播时间;回放中时 `replayExpireTime` 是回放截止时间;没有未开课或已开课关系时返回 `code=-1,message=无课程访问权限`。 ### GET /user/course/catalog Query:`courseId:Long` 必填。只有 `relationStatus=1` 的已开课用户可以访问;未开课返回 `课程尚未开通`。 ```json { "code": 0, "message": "成功", "data": [{ "id": 11, "courseId": 1, "name": "第一讲", "liveType": 1, "liveSessionId": 101, "liveName": "第一场直播", "startTime": "2026-10-11 19:30:00", "liveStatus": 0, "replayStatus": 0, "replayAvailable": false, "entryUrl": "https://test.zhinan.run/xhzy-live/app/live-session/course/11?token=example", "sortNo": 1 }] } ``` `entryUrl` 是当前用户对应渠道的场次入口。后台已创建但尚未关联直播场次的目录也会返回,此时 `liveSessionId`、`liveName`、`startTime`、`liveStatus` 和 `entryUrl` 均为 `null`。全课直播结束前,即使单场已有回放,`replayAvailable=false`,且不返回 `replayFileId`、`replayUrl`;课程进入回放期并且该场回放已发布后才返回这些字段。 ### POST /user/course/open 原 APP 录播课程开课兼容接口。请求:`{"id":12,"code":"Ab12Cd34"}`,其中 `id` 是课程学期 ID,不是课程 ID。校验学期开课日期、待开课学员关系及开课码后,将该录播课程关系改为已开课;成功响应为 `{"code":0,"message":"成功"}`。参数、校验行为和返回结构保持原系统兼容。 ### POST /user/course/select/open 7016 直播课程选课接口。请求:`{"courseId":1}`。仅允许选择当前用户已有的未开课或已开课直播课程;成功后所选课程为已开课,其他可选直播课程关系改为已关闭。 ### POST /user/course/code/open 请求:`{"code":"123456"}`。成功后当前课程关系改为已开课,并关闭该用户其他直播课程。当前用户没有该课程关系时,先把用户绑定到听课码对应渠道,再建立并开通课程关系;已有关系时不重复绑定。课程已完成或听课码不存在时统一返回:`您输入的听课码无效,如有疑问,可联系客服解决`。 ### GET /review/vod/token 与旧 APP 路径和参数兼容。Query:`fileId:String` 必填,无需 Token。前台只应使用课程目录在回放开放后返回的 `replayFileId`。 ```json { "code": 0, "message": "成功", "data": { "fileId": "vod-file-id", "appId": 1500069429, "pSign": "eyJhbGciOiJIUzI1NiJ9...", "licenseUrl": "https://example.test/player.license" } } ``` ## 渠道链接访问 本组接口位于 web-api 的 `/live/channel-link`,链接 Token 和六位口令均由后台生成,前端不能自行拼接身份信息。普通课程目录链接可选传用户 `X-Token` 以判断课程权限;其余解析接口和客户专属登录接口不要求预先登录。客户专属 Token 等同一次绑定身份的访问凭证,企微客户删除、课程渠道重置或删除后旧 Token 失效。 ### GET /live/channel-link/course/info Query:`linkToken:String`、`catalogId:Long` 必填;请求头 `X-Token` 可选。返回 `type=COURSE_CATALOG`、课程/目录/场次/渠道信息和 `hasAccess`。普通链接只有当前登录用户具备课程权限时 `hasAccess=true`;无权限时同时返回 `advisorName`、`advisorImageUrl`、`advisorLinkUrl`。客户专属链接返回其绑定的 `userId` 和 `customerLink=true`。 ### GET /live/channel-link/session/info Query:`token:String` 必填。解析普通或客户专属单场直播链接,返回 `type=SINGLE_SESSION`、场次/渠道信息。按需求不检查课程权限,成功时 `hasAccess=true`;客户专属链接额外返回绑定 `userId`。 ### GET /live/channel-link/session/code-info Query:`code:String` 必填。按六位学习工具 APP 口令解析单场直播,与普通单场 H5 链接一样不检查课程权限。无效口令返回 `code=-1,message=直播口令无效`。 ### POST /live/channel-link/customer-login 使用客户专属链接直接换取其绑定用户的 APP 登录 Token,无需先手工登录。普通分享链接会被拒绝。 课程目录请求: ```json {"type":"COURSE_CATALOG","token":"customer-token","catalogId":11} ``` 单场直播请求: ```json {"type":"SINGLE_SESSION","token":"customer-token"} ``` 成功响应的 `data.token` 是 APP 登录 Token,`data.info` 是对应的 `ChannelLinkAccessVo`。课程目录专属登录同时将该用户的待开通课程关系确认为已开通;用户不存在或已被拉黑时返回统一业务错误,不签发 Token。 ## 用户学习记录 ### GET /manage/user/study/page 按用户分页查询每日学习轨迹。Query:`userId` 必填,`page` 默认 1,`pageSize` 默认 20。按记录 ID 倒序返回,响应项与旧系统一致:`id`、`course:{id,name}`、`resource:{id,name}`、`createTime`;其中 `resource.id` 是课程课件关联 ID。 ```json {"code":0,"message":"成功","data":{"list":[{"id":301,"course":{"id":1,"name":"视频课程"},"resource":{"id":15,"name":"第一课"},"createTime":"2026-10-10 09:30:00"}],"total":1,"current":1,"size":20}} ``` 用户不存在、用户 ID 为空或分页参数不正确返回 `code=-1`。 ## 教师学习分析 本组接口需管理端 `X-Token`,并以当前管理员 ID 查询 `course_period_teacher`。课程列表按任教课程过滤,期和班级列表按实际分配关系过滤,学员分页、明细和导出只允许访问当前管理员任教班级;前端不能提交或覆盖管理员 ID。 | 方法 | 路径 | 输入 | 响应 `data` | | --- | --- | --- | --- | | GET | `/manage/teacher/common/course/list` | Query `key?` | `CourseVo[]`,仅 `{id,name}`。 | | GET | `/manage/teacher/common/course_period/list` | Query `courseId` | `CoursePeriodVo[]`,仅任教学期。 | | GET | `/manage/teacher/common/class/list` | Query `coursePeriodId` | `CoursePeriodClassVo[]`,仅任教班级。 | | GET | `/manage/teacher/course/page` | Query `key?`, `page?`, `pageSize?` | 任教课程分页,课程项含 `id,name,imageUrl`。 | | GET | `/manage/teacher/course/student/page` | Query `key?`, `courseId?`, `periodIds[]?`, `classIds[]?`, `page?`, `pageSize?` | 与管理端课程学员分页相同的 `Page`,增加教师班级数据范围。 | | GET | `/manage/teacher/course/student/study_record/list` | Query `studentId` | 与课程学员学习记录相同的 `StudyRecordVo[]`。 | | GET | `/manage/teacher/course/student/export` | Query `courseId` | 当前教师任教班级学员及视频进度 XLSX。 | 未分配的课程在列表和分页中返回空数据;直接读取未任教学员明细返回 `code=-1`,不会退化为管理员全量数据。教师筛选中 `periodIds[]` 与 `classIds[]` 同时传入时仍保持旧系统 OR 语义,但结果始终受任教班级约束。 兼容性最终状态、保留差异依据及未获批准事项见[兼容性记录](requirements/manage-api/compatibility.md);该记录覆盖并更正前述分批说明的历史描述。 用户已明确决定:COS及鉴权保持当前实现,不向旧方式调整。管理认证仅读取X-Token请求头,不新增Query Token;COS key必须以/开头,method按当前规则归一。匿名分享接口不要求管理Token。此两项不是待批准事项。 兼容说明:课件、课程、期班和学员分页需显式传入page和pageSize,不提供1/20缺省;page≤0归一为1,pageSize=0返回空页,负pageSize拒绝。无引用课件省略useNum,目录关联省略catalogId。考试content含examId/type;普通DTO整数按旧绑定处理。数据库NULL和长度边界由表结构约束。教师信息读取当前管理账号。最大班号与百分比保留安全边界。 ### 用户档案接口(BUG 12079) 以下接口使用管理端 Base URL 和 `X-Token`。新增的详情、订单、备注接口需 `member_index_show`,默认 `page=1`、`pageSize=20`,页大小范围 1–200。参数名称及嵌套字段保持老版国学 APP V2 的档案契约,外层使用统一 `ApiResponse`,分页返回 `list/total/current/size`。 | 方法 | 路径 | Query | 返回 | | --- | --- | --- | --- | | GET | `/manage/user/info` | `id` | 用户基本资料、`personalSignature`、`balancePoint`、最新 `remark`、`orderNum`、`buyMoney`、`refundMoney`、`couponNum` | | GET | `/manage/user/order/page` | `userId,page?,pageSize?` | 完整商城订单分页,包含订单商品、收货地址、支付信息等原有 `ShopOrderVo` 字段 | | GET | `/manage/user/remark/page` | `userId,page?,pageSize?` | 备注分页:`id,userId,manager:{id,name},content,createTime`,按 ID 倒序 | | GET | `/manage/user/study/page` | `userId,page?,pageSize?` | 现有录播课程模块提供的学习记录分页:`id,course:{id,name},resource:{id,name},createTime` | | POST | `/manage/user/remark/add` | JSON `{userId,content}` | 新增备注,并将它标记为该用户的最新备注 | | POST | `/manage/user/remark/modify` | JSON `{id,content}` | 修改指定备注 | | POST | `/manage/user/remark/delete` | JSON `{id}` | 删除指定备注,并重新标记最新备注 | 订单查询和档案金额汇总限定 `sysCode=xhlive`,同时按档案用户 ID 筛选。`orderNum` 统计已支付订单;`buyMoney` 为已支付订单金额减退款,`refundMoney` 为已支付订单退款总额,单位均为元。直播项目优惠券尚未启用,保留的 `couponNum` 返回 0。 备注操作人按 `managerId` 从 Common_Manage 管理数据库读取,包括已软删除管理员;名称缺失时保留管理员 ID。用户不存在或已删除时返回业务错误,时间字段格式为 `yyyy-MM-dd HH:mm:ss`。这些查询不新增操作记录。 ### 购买后须知(BUG 12077) `GET /shop/pay/order/tooltip?id={订单ID}` 读取当前用户订单的主商品购买后须知。返回 `data` 为数组,每项为 `{ "id": 商品ID, "tooltip": "后台配置的购买后须知" }`,同一商品去重,忽略空白须知和组合子商品;无须知时返回 `[]`。HTML 内容原样保留。接口沿用订单归属校验,不能读取其他用户或其他系统的订单。 支付成功后的 `/shop/pay/order/result` 也在 `data.tooltips` 返回同样内容。须知读取 `shop_product_content.tooltip` 的当前配置,与老版逻辑一致。