Commit Graph

15 Commits

Author SHA1 Message Date
becomer e7c9b06056 xsgz 提交前预检必填字段,避免含糊失败
学校请假表单由后台动态配置,可能包含本平台无法提供值的必填字段
(如"是否离校"单选、请假类型细分下拉)。原先这类情况会直接提交,
由学校端返回业务错误,用户看到的提示不明确。

新增 checkXsgzUnfilledRequired:提交前检查所有 required 字段是否已有值
(空串/null 均视为缺失),有缺失则不发请求,直接提示
「该校请假表单还有必填项无法自动填写:X、Y,请前往学校学工系统办理」,
并写入日志。请假与销假两条路径都已接入。

新增 5 个单测覆盖:有值不报、无值报字段名、空串算缺失、
无 fieldName 回退 fieldId、非必填不报。
2026-10-17 17:46:49 +08:00
becomer 07b0cfaa88 校正 sdau 提交参数序列化,严格对齐前端 toFormatFormData
ci/woodpecker/tag/build Pipeline was successful
重新核对前端源码发现两处与实现不符,已修正:

1. FormData 序列化格式
   前端 toFormatFormData(T, needCurlyBraces=false) 的规则是:
   - 嵌套/数组用**点号**展开(fields.0.value),不是方括号;
   - 值为 null 或空字符串时**跳过**该键;
   - 数组按下标逐个展开。
   原实现把 fields 整个 JSON.stringify 成一个字符串,与前端不一致。
   新增 xsgzFlatten 按上述规则摊平,并加单测覆盖(点号/跳过空值/数组下标)。

2. fields 内容必须是「字段定义 + 用户填值」
   前端提交前会 forEach 把用户输入写回各字段的 value:
     f.value.forEach(l => { l.value = t[l.fieldId] ... })
   原实现只提交了空的字段定义。
   现在先拉 form_fields_for_add,再 fillXsgzFieldValues 把请假原因等
   按字段标签(含"原因/事由")回填到 value;销假同理(含"说明/原因")。

参数契约已逐项比对(源码原文 vs 实现):
  请假:{projectId, requestType:"LEAVE", startTime, endTime,
         fields[], address, longitude, latitude}
  销假:{originalId, projectId, requestType:"CANCELLATION", fields[],
         cancellationApplyInArea, inArea, address, longitude, latitude}
2026-10-17 17:43:31 +08:00
becomer 4a064b53cc sdau 销假接口实现:与请假共用 application_submit,靠 requestType 区分
静态分析定位到请假详情页组件链(views/leave/detail/):
ActionPopup-CRBsBafz.js 中 handleType=CANCELAPPLY 的销假申请逻辑,
其提交函数 re 从 fieldsConfig-BzLAymBp.js 导入,即与请假同一个
application_submit 接口。

销假 payload(requestType=CANCELLATION):
  { originalId: <原假单ID>, projectId, requestType:"CANCELLATION",
    fields[], cancellationApplyInArea, inArea, address, longitude, latitude }

改动:
- service:新增 XsgzSubmitCancel / XsgzSubmitApplication(请假/销假/续假共用),
  XsgzSubmitPayload 增加 originalId/inArea/cancellationApplyInArea
- service:XsgzLeaveRecord 增加 projectId 字段;ToLeaveItems 把 projectId
  写入 TypeWid,供前端销假时回传
- handler:新增 executeXsgzLeaveCancel,SSE 事件流 + 落日志
- handler:ExecuteLeaveRequest 增加 projectId
- 前端:ExecuteLeaveParams 增加 projectId;销假提交时回传 typeWid

另:batch-cancel / batch-cancel-list 经确认是辅导员侧接口,学生销假不走它们。
2026-10-17 17:40:16 +08:00
becomer cb3e0f8917 sdau 提交请假接口实现:按前端源码解析的契约接入 application_submit
从主 bundle 路由表 __vite__mapDeps 定位到请假页 chunk 链
(LeaveApply → index-C4CfkeDm.js → fieldsConfig-BzLAymBp.js),
完整解析出提交契约:

  POST /api/leave/student_action/application_submit  (multipart/form-data)
  payload = { projectId, requestType:"LEAVE", startTime, endTime,
              fields[], address, longitude, latitude }

改动:
- service:新增 XsgzSubmitLeave(multipart 提交)、XsgzFetchIsRequireLocation
- service:do() 支持 JSON body;新增 XsgzSubmitPayload
- handler:新增 executeXsgzLeaveApply,SSE 事件流与辅导猫链路一致,
  并落 cp_leave_logs
- LeaveType / ExecuteLeaveApplyParams 新增 projectId:xsgz 用 UUID 作类型 ID
- 前端提交时带上 projectId

顺带修正 GetPendingLeaves:batch-cancel-list 经实测是**辅导员**接口
(学生调用报「没有辅导员销假权限」),学生端改回 leave_page 按状态标记 CanReport。

注:销假写接口(batch-cancel)学生侧契约仍未确认,暂未接入。
2026-10-17 17:34:01 +08:00
becomer 3b46e45eb6 sdau 销假/请假改为真实数据,不再只做拦截提示
问题:上一版对 xsgz 学校只返回「请前往学校学工系统办理」的提示,
用户点销假按钮(/leave/pending-list)和请假类型时拿不到任何真实数据。

改动:
- 新增 CasTicketForService:用已有 CASTGC 换取指定 service 的 CAS 票据,
  不发起账密登录(handler 侧只有会话、没有明文密码)
- 新增 XsgzSession.ToLeaveItems:把 xsgz 请假记录转换为通用 LeaveItem 契约,
  status 为「休假中/逾期未销假」时 CanReport=true
- GetPendingLeaves:xsgz 学校改为真实拉取 /api/leave/record/leave_page
- GetLeaveTypes:xsgz 学校改为真实拉取 optional_project_all
- LeaveType 新增 ProjectID 字段:xsgz 用 UUID 作类型 ID,与旧契约的整数 code 并存

只读链路已实测通过:登录→CASTGC 换票→access_token→请假记录(1条) / 请假项目(1个)。
2026-10-17 17:25:21 +08:00
becomer ef03130980 请假/销假全部入口补齐独立学工系统分流,修复点销假仍报 420
ci/woodpecker/tag/build Pipeline was successful
问题:上一版只在 GetLeaveTypes 加了 xsgz/xsfw 分支,其余请假入口仍走辅导猫
旧链路。用户点销假按钮(走 /leave/pending-list)时仍拿到辅导猫的 HTTP 420
「产品已到期」,提示误导且无法自助解决。

改动:新增 rejectExternalLeaveSystem 统一守卫,接入全部 7 个请假相关入口:
- GetLeaveTypes / GetLeaveApplyApprovers / ExecuteLeaveApply(请假侧)
- GetPendingLeaves / GetLeaveApprovers / ExecuteLeave(销假侧)
- GetLeaveExtendPrefill(续假)
命中 xsgz/xsfw 时短路返回明确文案「XX大学 的请假由学校「学工系统」独立提供,
不支持在今日校园内提交,请前往学校学工系统办理」,不再让用户看到 420。

新增单测验证分流判定与文案(纯静态映射,不触发网络)。
2026-10-17 17:12:52 +08:00
becomer c47fb0a66e 修复 cookie 过期且自动续期失败时不提示重新登录
ci/woodpecker/tag/build Pipeline was successful
手机号登录(无密码)账号 cookie 过期后自动续期失败时,错误链
只带普通错误、不带 needReLogin 信号,导致前端只显示普通错误、
不弹「重新登录」引导,用户无法跳转登录页。

- 新增 ErrNeedReLogin 哨兵 + needReLoginSignal() 统一判定:
  密码错误或无法自动续期都视为需要重新登录
- refreshCpdailyCookie 无密码/MFA 无法自动续期时返回 ErrNeedReLogin
- 所有执行路径(签到/请假/销假/返校/工作日志/表单/上传/预览)
  的续期失败分支统一升级为 wrapNeedReLogin,SSE task_error 与
  HTTP 401 均按 needReLoginSignal 判定
2026-10-14 22:09:33 +08:00
becomer 5ee8e47b0f v1.2.84:扣款额度可配置(支持小数/0)+ 置顶公告横向滚动
ci/woodpecker/tag/build Pipeline was successful
- 新增系统设置 deduct_checkin/deduct_leave/deduct_report(默认 1),
  打卡/请假/销假从数据库读取扣减额度,支持小数与 0(0=不扣费);
  sign_count 由 int 改为 decimal(12,2) 以支持小数次数。
- 新增置顶公告:announcements.is_pinned 字段 + /announcements/pinned 接口,
  打卡页顶部横向无缝滚动展示,点击查看详情;后台公告管理支持置顶开关与标识。
- 后台系统设置新增三项扣款额度配置;用户/后台的签到次数展示改为去尾零格式。
2026-09-25 13:07:14 +08:00
becomer 21b37c6ad8 v1.2.81:请销假链路健壮性与设备码校验
ci/woodpecker/tag/build Pipeline was successful
- leavePost 统一校验 HTTP 状态码与空 body:非 2xx 报「学校服务端响应异常(HTTP xxx)」,
  空 body 报「可能触发风控或瞬时故障」,不再抛出无意义的 JSON 解析错误
- 旧套销假识别 2210990000(需附件):提示上传附件后重试
- 打卡/请假/销假执行入口补后端设备码硬校验;请假页前端入口补绑定引导弹窗
- SMS 注册密码不再使用设备码样式的随机值(避免与设备码绑定语义混淆)
2026-09-22 19:14:26 +08:00
becomer a7180eebba v1.2.80:根治 cookie 失效误报——统一识别登录态失效并自动续期
ci/woodpecker/tag/build Pipeline was successful
① 问题:用户赵佳威(芜湖职业技术大学)反馈「无法获取签到任务」。
   日志显示 whit.campusphere.net 两类查询均报
   "解析任务列表响应失败: invalid character '<' looking for beginning of value"
   —— 今日校园返回 HTML(请求被 302 到 authserver 登录页),即 cookie 已失效。
   其 MOD_AUTH_CAS 票为 09-11 签发、早已过期;而 last_login_at 显示 09-21 是
   jwt 中间件 touchDailyLogin 刷新的"当天使用"标记,并非真实重登,故造成
   "看起来刚登录过、却取不到任务"的假象。

② 根因:原 isCookieExpired 仅匹配错误文案「未登录/登录已过期」,而 HTML 响应的
   报错是 invalid character '<',匹配不到 → 既无法识别、更不会触发续期,
   用户只看到空任务列表。该问题影响所有「已登录后调用今日校园」的接口
   (任务列表、用户信息同步、请销假等)。

③ 修复:
   - cpdaily 层新增 ErrCookieExpired 与 unmarshalBody:解析响应体前先判断
     是否为 HTML,是则返回 ErrCookieExpired(42 处 body 解析统一接入;
     登录/发码/滑块等登录流程不涉及用户 cookie,保持原样不误伤);
   - FetchAllRecentTasks 改为返回 error(两类查询都失败时),供上层识别续期;
   - isCookieExpired 合并 ErrCookieExpired 判定 → 既有 25 处调用点自动受益;
   - 新增 withCookieRefresh(通用包装):检测到登录态失效 → refreshCpdailyCookie
     (长期凭证优先,其次密码重登)→ 用新 cookie 重试一次;续期失败返回
     ErrCookieExpired,上层转「登录凭证已过期,请重新登录」;
   - 应用于 GetTaskTitles、GetPendingLeaves(含新/旧套件)、syncUserProfile(用户信息)。
   - 防递归:refreshCpdailyCookie 内部同步用户信息时禁续期
     (syncUserProfileWith allowRefresh=false),避免「续期→同步→再续期」风暴。

④ MFA 学校说明:MFA 通道登录签发短期 JWT,到期即要求重新登录,本就不需自动续期;
   非 MFA 学校(如本例)的 cookie 由上述链路自动续期,用户不再被莫名踢出。

⑤ 验证(真实数据,只读):
   - 用赵佳威过期 cookie 调 FetchAllRecentTasks:由原来的 invalid character '<'
     变为 cpdaily_cookie_expired,errors.Is(err, ErrCookieExpired)=true;
   - 模拟完整链路:旧 cookie 失败 → 用其保存密码重登成功(6 个 cookie、含新 MOD_AUTH_CAS、
     无 MFA)→ 新 cookie 重试成功取到任务「查寝」;
   - go build / go vet / go test ./... 全过。
2026-09-21 16:57:21 +08:00
becomer edeabb0fc0 v1.2.75:回滚「每日签到积分」功能(保留验证码框修复)
ci/woodpecker/tag/build Pipeline was successful
v1.2.74 的签到积分功能经评估效果不佳,按需求回滚:
- 移除 User.checkin_points 字段;
- 移除 POST /api/auth/checkin 接口及路由;
- 打卡/请销假恢复为仅扣 sign_count(移除「优先消耗 1 积分」分支与 deductQuota);
- 移除个人中心「签到积分」入口、签到弹窗与 api/auth.ts 的 checkin 方法及类型字段。

保留 v1.2.74 中的「验证码框留白修复」(与签到积分无关):
- 个人中心验证码容器 w-24 → w-[120px](3:1 匹配生成图 180x60)、图片 object-cover;
- 登录页两处验证码图片 max-w-full/max-h-full object-contain → w-full h-full object-cover。

验证:grep 确认无 checkin 残留;go build / go vet 通过;vue-tsc --noEmit 通过。
2026-09-19 11:41:42 +08:00
becomer 559b146408 v1.2.74:新增每日签到积分(个人中心)+ 打卡/请销假优先消耗积分;修复验证码框空隙
ci/woodpecker/tag/build Pipeline was successful
① 签到积分体系:
   - User 新增 checkin_points(decimal(10,4),0~1),AutoMigrate 自动加列;
   - 新增 POST /api/auth/checkin:校验已有图片验证码后,随机发放 0.01~0.30
     积分(保留 4 位小数),封顶 1.0;达 1 后不可再签到(原子条件更新防并发超发);
   - 个人中心新增「签到积分」入口:显示 当前积分/1 与签到按钮(达上限置灰「已达上限」),
     弹窗内含积分规则、当前积分与图片验证码。

② 打卡 / 请销假改为优先消耗签到积分:
   - 额度检查与扣减逻辑由「仅 signCount」改为「积分≥1 优先扣 1 积分,否则扣 1 次数」;
   - 覆盖 3 处执行点:任务执行(task.go)、销假新套与旧套(leave.go),
     抽公共函数 deductQuota 复用;均用条件更新原子扣减;VIP 仍免扣。

③ 修复验证码框留白:
   - 生成图为 180x60(3:1,代码强制下限),而个人中心验证码容器原为 w-24 h-10(2.4:1),
     图片按宽度缩放后上下留空。统一容器宽度为 w-[120px](3:1),
     并把图片改为 w-full h-full object-cover 填满(登录页两处同改)。

④ 验证:go build / go vet 通过;vue-tsc --noEmit、npm run build 通过。
2026-09-19 11:15:25 +08:00
becomer 53cbedeb95 v1.2.51:泰州学院(tzxy)等新套学校请假/销假适配——新增 wec-counselor-leave-new-apps 全链路 + 按学校应用自动分流
ci/woodpecker/tag/build Pipeline was successful
① 根因与定位:tzxy 的旧前缀 wec-counselor-leave-apps 被网关整体拦截返回 HTTP 420「产品已到期」,
   但该校实际使用的是新一代「请销假」应用 wec-counselor-leave-new-apps/stu/mobile/index.html
   (由辅导猫首页 getAppInfos 应用列表确认;实际接口实测全部正常)。
   新旧两套是完全不同的 API:新套明文 JSON + CpdailyClientType、类型用 typeWid、
   审批流/抄送独立接口;旧套 bodyString 加密 + Cpdaily-Extension 签名、类型用 code。

② 自动分流:新增 handler/leave_flavor.go——辅导猫 getAppInfos 探测本校请假应用 openUrl
   判定 new/old,探测失败用新套 ping 兜底,结果按学校缓存 30 分钟;
   所有请假链路(类型/审批人/待销假列表/销假审批人/销假执行/请假执行/上传策略/预览)按 flavor 分发。

③ 新套实现(cpdaily/leave_new.go):queryLeaveType(分页拉全)、getLeaveTypeDetail(权威开关)、
   getApproveFlowList(审批人+flowData)、getCcSearchList/getLastCcObjects(抄送人)、
   /stu/leave/apply(明文 JSON 提交)、/stu/leave/list、reportList(规定销假位置)、
   getReportApprovers(销假确认人)、queryLeaveDetail、/stu/leave/report(销假提交)、
   OBS getUploadPolicy/previewAttachment(COS 风格,obsKey 从 URL 推导与官方前端一致)。

④ 契约翻译:新套数据在 handler 层翻译为现有前端契约(LeaveType/LeaveItem/LeaveApprover),
   前端零结构改动;新增 statusText 权威状态文案(新套 1审批中/2即将休假/3休假中/4销假逾期/
   5销假确认中/6销假已驳回/7已完成/8已驳回/9已撤销/10退回重填),前端优先展示、旧套回退本地映射;
   pending-list 仅对 status 3/4 补拉详情判断 canReport(reportMode 1/2),附图/审批人开关。

⑤ 服务端复核:新套请假执行重新拉类型详情校验(原因必填/字数/证明材料)、审批流候选
   (userWid 匹配完整对象、无候选不阻止)、抄送人完整对象解析、离校语义(isLeaveOut 强制时以类型为准);
   销假执行复核 canReport 状态与确认人。

⑥ 测试:新增 leave_new_test.go 7 个测试(obsKey 推导/canReport/状态文案/销假表单/请假表单
   离校与不离校两分支/响应码解析);go build/go vet/go test 全过;前端 vue-tsc + npm run build 通过;
   端到端只读实测 tzxy:flavor=new、5 类型、审批人张文烨、2 销假位置、销假确认人、详情与 OBS 策略全部正常。
2026-09-15 13:34:05 +08:00
becomer 7639912d7c v1.2.45:请假申请功能落地(后端链路 + 前端请假弹窗),前端 tab 改「请/销假」、请假/销假按钮并排平分——① 功能:新增请假接口组(GET /leave/types 类型列表含权威开关、POST /leave/apply-approvers 审批人、POST /leave/apply SSE 同步执行),cpdaily/leave_apply.go 封装 checkOnceLeave + queryLeaveTypes + queryLeaveTypeCfg + obtainApproveFlow + apply 提交全链路;前端 LeaveView 新增请假申请弹窗(类型选择/起止时间/离校开关与三字段/请假原因含字数校验/一键定位/图片附件/审批人选择),日志区同时展示请假与销假记录并以徽标区分;② 关键差异(对比老版本 + 只读实测三校样本):queryLeaveTypes 返回的 needApproval/enableAttach 等开关全为 null,权威开关只在 queryLeaveTypeCfg(老版本逐类型再查一次,本实现按同样方式合并后返回,单类型 cfg 失败不阻塞列表);/leave/apply 为明文 JSON body + 仅 Cpdaily-Extension 头(与销假的 bodyString+sign 加密链路不同,实测确认);mustOut=1 时强制离校且开关不可改;审批人接口时间必须精确到秒(yyyy-MM-dd HH:mm:ss,datetime-local 值自动补秒);③ 服务端复核:execute 时重新查 cfg 并校验 disableTime/原因字数下限/needApproval 必选审批人,再做 checkOnceLeave 资格检查,全部失败均落日志;日志表新增 action 字段(apply/report)延续同表记录;限流扣费与销假同规则(2m×3/1h×6/24h×10,VIP 免扣非 VIP 成功扣 sign_count);④ 全程只读实测(checkOnceLeave/queryLeaveTypes/queryLeaveTypeCfg,未调用 /leave/apply 提交),新增 4 个单测覆盖类型合并/资格检查/表单构造(离校与不离校两分支)/提交,go test ./...、go vet、前端 build、vue-tsc 全过
ci/woodpecker/tag/build Pipeline was successful
2026-09-14 12:13:47 +08:00
becomer 2f20eebccd v1.2.44:销假功能完整落地(后端 SSE 同步执行 + 前端销假页重写),并按真实接口实测修正位置/审批人语义——① 功能:新增 /leave 路由组(pending-list 待销假列表 / approvers 审批人 / execute SSE 执行 / logs 日志 CRUD),cpdaily/leave.go 封装 listOther+detailOther+obtainApproveFlow+report 全链路,cp_leave_logs 落库成败日志,用户级限流(2m×3/1h×6/24h×10),VIP 免扣非 VIP 成功扣 sign_count,图片经 OBS 浏览器直传(taskType=leave);前端 api/leave.ts + LeaveView.vue 重写(销假按钮/日志区/待销假弹窗/销假表单弹窗);② 规定销假位置修正:老版本取 leaveExtends[0](续假位置,实测近乎全空)或注释的 detail.applyLocation(请假提交时定位),均非学校规定位置;实测真实返回 reportPlaces[](address/经纬度/radius 米/设置人),已改为解析并前端列表选择+预填经纬度,用户可在规定位置间切换;③ 多校差异适配:实测 ynu/whit/lzufe 三校样本确认 leaveType.reportApplyFlow 控制销假是否需选审批人(ynu=1,whit/lzufe=0)、enableAttach 控制图片上传(ynu=1,其余=0),前端按开关自适应渲染审批人选择器与图片组件,提交时按需携带 approverWid;④ 解析 bug 修复:leaveType.code 真实为数字(3/12/2),原声明 string 导致 detailOther 全部解析失败(即"规定位置拿不到"的根因),改为 interface{} 承接并同时产出文本/数字双形式;obtainApproveFlow 要求时间精确到秒(yyyy-MM-dd HH:mm:ss),已自动补秒;⑤ 全程只读实测(listOther/detailOther/obtainApproveFlow,未调用任何 /report 提交),新增/重写 4 个单测覆盖开关解析/规定位置/审批人/提交表单,go test ./...、go vet、前端 build、vue-tsc 全过
ci/woodpecker/tag/build Pipeline was successful
2026-09-14 11:44:37 +08:00