优化代码
This commit is contained in:
1 parent
8ba15d87b3
commit
7b1c94b5ff
5 files changed
+261
-2
No files matched your search
@@ -0,0 +1,51 @@
|
||||
# 修复登录无限循环(登录成功后弹回登录页)
|
||||
|
||||
## Context(问题与证据链)
|
||||
|
||||
用户反馈:登录成功后又回到登录界面,无限循环。排查已确认(含用户亲证):
|
||||
|
||||
**用户确认的现象**
|
||||
- 登录页地址栏为 `api.yunzer.cn`(远程线上认证中心,非本地 9000)
|
||||
- 登录后见过「选择要进入的企业」界面(hero920103 绑定 2 家企业),**选完企业后又弹回登录页**
|
||||
|
||||
**数据库证据(go-platform 库)**
|
||||
- hero920103 = identity_id 2(李志强),绑定两家企业(tid=1、tid=2)→ 登录必走"待选企业"分支
|
||||
- `yz_auth_session`:18:09~18:12 连续 4 条会话被踢(revoked=1, revoke_reason='kicked');最新会话 id=386(18:18:29,tid=2,client_id 为空)
|
||||
- 18:24:42 登录日志(id=218,user_id=1)与 hero920103 实际 identity_id=2 不符 → **该日志由远程旧版代码写入**(字段语义不同),且 IP=127.0.0.1/::1 符合"远程 nginx 反代回环"特征
|
||||
|
||||
**代码机制(go/services/auth/session.go L61-134)**
|
||||
- `CreateSession` 按 identity_id **全局**统计活跃会话,超 MaxSession 即踢最旧会话(kicked),不区分 tid/client/服务实例
|
||||
- 登录 → 远程建会话 → 企业选择 → 新会话又创建 → 互踢 → authorize 发现会话已吊销 → 送回登录页 → 再登录 → **无限循环**
|
||||
- 远程为旧版代码(字段错位可证),其企业选择后的会话处理存在缺陷,无法本地修复
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 第一步:本地开发认证切换到本地 9000 实例(核心修复)
|
||||
|
||||
修改 `backend/.env.development`:
|
||||
|
||||
```
|
||||
VITE_AUTH_BASE='http://127.0.0.1:9000/auth'
|
||||
```
|
||||
|
||||
本地 9000 已注册全套认证端点(/auth/login、/auth/authorize、/auth/token、/auth/userinfo、/auth/switch-tenant,见 `go/routers/auth/auth.go`),且为最新代码(企业选择、cookie、PKCE 逻辑完整)。
|
||||
|
||||
### 第二步:配套校验与清理
|
||||
|
||||
1. 核对 `yz_auth_client` 表 yz-backend 的 redirect_uris 白名单包含 `http://127.0.0.1:4401/auth-callback.html`(不匹配则用 MySQL MCP 补登)
|
||||
2. 清理脏数据:将 hero920103(identity_id=2)名下 revoked=0 的旧会话标记 revoked=1(reason='cleanup'),避免旧会话占用互踢名额
|
||||
3. 重启 Vite dev server 使 env 生效
|
||||
4. 验证本地登录页 HTML 表单嵌入 client_id(curl 抓取确认,防止空 client 会话再现)
|
||||
|
||||
### 第三步:端到端验证
|
||||
|
||||
1. 无痕窗口(避免残留 localStorage token/userInfo)访问 `http://127.0.0.1:4401`
|
||||
2. 完整走:跳本地登录页 → 输入 hero920103 → 企业选择(展示 2 家)→ 选 tid=2 → 回跳 auth-callback → 进入工作台,无循环
|
||||
3. MySQL MCP 复查 `yz_auth_session` 无异常互踢
|
||||
4. 登录恢复后,验证原任务功能:apps/oa/schedule 日历按 hero920103 排班显示上班(绿)/休息(橙)背景(后端 `/backend/oa/scheduling/my` 已就绪)
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `backend/.env.development`(唯一代码修改:VITE_AUTH_BASE 指向本地)
|
||||
- 数据库:`yz_auth_client`(白名单核对)、`yz_auth_session`(脏数据清理)
|
||||
- 不改 Go 代码(互踢规则是合理安全设计;根因是开发环境认证指向了旧版远程中心)
|
||||
@@ -34,6 +34,11 @@ export function getScheduleBoard(params) {
|
||||
return request({ url: '/backend/oa/scheduling/board', method: 'get', params })
|
||||
}
|
||||
|
||||
// 当前登录用户的排班(按账号匹配员工档案),供日程日历显示上班/休息背景
|
||||
export function getMyScheduleBoard(params) {
|
||||
return request({ url: '/backend/oa/scheduling/my', method: 'get', params })
|
||||
}
|
||||
|
||||
// 批量按格子编辑排班:cells 为排班板多选的「员工 × 日期」任意组合
|
||||
// shift_id>0 设为该班次(有排班=调班,无排班=新增);shift_id=0 清除(休息/请假)
|
||||
export function saveScheduleEntryCells(data) {
|
||||
|
||||
@@ -78,6 +78,12 @@
|
||||
>
|
||||
<el-button :icon="ArrowRight" circle @click="changeMonth(1)" />
|
||||
<el-button @click="goToday">今天</el-button>
|
||||
<span
|
||||
v-if="mySchedLoaded && mySchedCount === 0"
|
||||
class="my-sched-tip"
|
||||
title="当前登录账号未匹配到排班(账号需要有对应员工档案且已排班),因此不渲染上班/休息背景"
|
||||
>当前账号暂无排班</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="calendar-week-header">
|
||||
@@ -94,7 +100,9 @@
|
||||
selected: cell.date === selectedDate,
|
||||
'has-items': cell.items.length > 0,
|
||||
'is-off': cell.tag === 'off',
|
||||
'is-work': cell.tag === 'work'
|
||||
'is-work': cell.tag === 'work',
|
||||
'is-my-work': cell.myTag === 'work',
|
||||
'is-my-rest': cell.myTag === 'rest'
|
||||
}"
|
||||
@click="selectedDate = cell.date"
|
||||
@dblclick="openCreate(cell.date)"
|
||||
@@ -109,6 +117,13 @@
|
||||
<span v-else-if="cell.day === 1" class="month-tag"
|
||||
>{{ cell.month }}月</span
|
||||
>
|
||||
<span
|
||||
v-if="cell.myEntry"
|
||||
class="my-shift-chip"
|
||||
:style="{ background: cell.myEntry.color }"
|
||||
:title="`${cell.myEntry.shift_name} ${cell.myEntry.work_start}-${cell.myEntry.work_end}`"
|
||||
>{{ cell.myEntry.short_name || cell.myEntry.shift_name }}</span
|
||||
>
|
||||
</div>
|
||||
<div v-if="cell.holidayName" class="cell-holiday" :title="cell.holidayName">
|
||||
{{ cell.holidayName }}
|
||||
@@ -164,6 +179,28 @@
|
||||
<span v-else-if="selectedIsRestDay" class="day-title-holiday"
|
||||
>周末</span
|
||||
>
|
||||
<span
|
||||
v-if="selectedMyEntry"
|
||||
class="day-title-holiday my-shift-title"
|
||||
>
|
||||
<span
|
||||
class="holiday-tag"
|
||||
:style="{
|
||||
background: selectedMyEntry.color,
|
||||
color: '#fff',
|
||||
borderColor: selectedMyEntry.color
|
||||
}"
|
||||
>{{ selectedMyEntry.short_name || selectedMyEntry.shift_name }}</span
|
||||
>
|
||||
{{ selectedMyEntry.work_start }}-{{
|
||||
selectedMyEntry.work_end
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="mySchedCount > 0"
|
||||
class="day-title-holiday my-rest-title"
|
||||
>休息</span
|
||||
>
|
||||
</div>
|
||||
<div class="day-sub">
|
||||
{{ selectedItems.length }} 项日程<template v-if="pendingCount > 0"
|
||||
@@ -445,6 +482,7 @@ import {
|
||||
getScheduleList,
|
||||
getScheduleStats
|
||||
} from "@/api/oaSchedule";
|
||||
import { getMyScheduleBoard } from "@/api/oaScheduling";
|
||||
|
||||
const viewMode = ref("calendar");
|
||||
const stats = ref({});
|
||||
@@ -516,6 +554,39 @@ const calendarMonth = ref(now.getMonth() + 1);
|
||||
const calendarItems = ref([]);
|
||||
const selectedDate = ref(todayStr());
|
||||
|
||||
// ---------- 我的排班(上班 / 休息背景) ----------
|
||||
// mySchedMap: { 'YYYY-MM-DD': 排班记录 };mySchedCount 为区间内排班天数。
|
||||
// 只有区间内存在排班时才渲染背景,未排班的月份不显示,避免误导。
|
||||
const mySchedMap = ref({});
|
||||
const mySchedCount = ref(0);
|
||||
// 是否已完成加载:用于区分“还没查完”与“查了但没有”,后者在日历标题旁提示
|
||||
const mySchedLoaded = ref(false);
|
||||
|
||||
async function loadMySchedule(startStr, endStr) {
|
||||
try {
|
||||
const res = await getMyScheduleBoard({
|
||||
start_date: startStr,
|
||||
end_date: endStr
|
||||
});
|
||||
const list = res?.data?.list || [];
|
||||
const map = {};
|
||||
for (const item of list) {
|
||||
map[normDate(item.date)] = item;
|
||||
}
|
||||
mySchedMap.value = map;
|
||||
mySchedCount.value = list.length;
|
||||
mySchedLoaded.value = true;
|
||||
} catch {
|
||||
// 排班背景属于辅助信息,失败时静默降级为无背景
|
||||
mySchedMap.value = {};
|
||||
mySchedCount.value = 0;
|
||||
mySchedLoaded.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 选中日期的排班,供侧栏标题展示班次
|
||||
const selectedMyEntry = computed(() => mySchedMap.value[selectedDate.value] || null);
|
||||
|
||||
// ---------- 中国法定节假日 / 调休 ----------
|
||||
// 后端返回 { 'YYYY-MM-DD': {name, type, wage} },type: 0 调休上班 / 1 法定放假。
|
||||
// 普通周末不在此表内,由 isWeekend 按星期判定,避免与节假日数据重复。
|
||||
@@ -643,6 +714,7 @@ const calendarCells = computed(() => {
|
||||
start.getDate() + i
|
||||
);
|
||||
const date = fmtDate(d);
|
||||
const my = mySchedMap.value[date];
|
||||
cells.push({
|
||||
date,
|
||||
day: d.getDate(),
|
||||
@@ -652,7 +724,10 @@ const calendarCells = computed(() => {
|
||||
items: map[date] || [],
|
||||
// 节假日相关:tag 用于格内角标与整格底色,holidayName 用于节日名文案
|
||||
tag: dayTag(date),
|
||||
holidayName: holidayName(date)
|
||||
holidayName: holidayName(date),
|
||||
// 我的排班:work=上班(有排班记录)/ rest=休息(区间内有排班但当天无记录)
|
||||
myEntry: my || null,
|
||||
myTag: mySchedCount.value === 0 ? "" : my ? "work" : "rest"
|
||||
});
|
||||
}
|
||||
return cells;
|
||||
@@ -732,6 +807,8 @@ async function loadCalendarData() {
|
||||
);
|
||||
const startStr = fmtDate(start);
|
||||
const endStr = fmtDate(end);
|
||||
// 我的排班与节假日一样按可视区间加载,用于日历格子的上班/休息背景
|
||||
loadMySchedule(startStr, endStr);
|
||||
// 节假日不在这里加载:由上方 watch([calendarYear, calendarMonth]) 统一驱动,
|
||||
// 保证"翻月即加载对应月份所需年份",且首次挂载、二次进入都能自动补齐。
|
||||
const res = await getScheduleList({
|
||||
@@ -1265,6 +1342,16 @@ onMounted(() => {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* “当前账号暂无排班”提示:说明为什么没有上班/休息背景 */
|
||||
.my-sched-tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 10px;
|
||||
padding: 2px 10px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.calendar-week-header {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
@@ -1381,6 +1468,40 @@ onMounted(() => {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 我的排班背景:上班浅绿 / 休息浅橙。排班是实际作息,覆盖节假日底色,
|
||||
但保留休/班角标与节日名;today / selected 在后面定义,优先级仍最高 */
|
||||
.calendar-cell.is-my-work {
|
||||
background: #f0f9eb;
|
||||
}
|
||||
|
||||
.calendar-cell.is-my-work:hover {
|
||||
background: #e6f5db;
|
||||
}
|
||||
|
||||
.calendar-cell.is-my-rest {
|
||||
background: #fdf6ec;
|
||||
}
|
||||
|
||||
.calendar-cell.is-my-rest:hover {
|
||||
background: #faecd8;
|
||||
}
|
||||
|
||||
/* 格内班次角标:用班次定义色做实底小色块 */
|
||||
.my-shift-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
max-width: 56px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 今天:整格实蓝底,最醒目。放在 is-off / is-work 之后,
|
||||
保证今天即使逢节假日也保留蓝色标识。
|
||||
实底较深,格内文字统一转白(或白底反白)以保证对比度。 */
|
||||
@@ -1753,6 +1874,20 @@ html.dark & {
|
||||
.calendar-cell.is-work {
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
.calendar-cell.is-my-work {
|
||||
background: rgba(103, 194, 58, 0.1);
|
||||
|
||||
&:hover {
|
||||
background: rgba(103, 194, 58, 0.16);
|
||||
}
|
||||
}
|
||||
.calendar-cell.is-my-rest {
|
||||
background: rgba(230, 162, 60, 0.1);
|
||||
|
||||
&:hover {
|
||||
background: rgba(230, 162, 60, 0.16);
|
||||
}
|
||||
}
|
||||
/* 今天:整格主色实底,与浅色主题保持一致 */
|
||||
.calendar-cell.today {
|
||||
background: var(--el-color-primary);
|
||||
@@ -1795,6 +1930,12 @@ html.dark & {
|
||||
.day-title-holiday .holiday-tag.is-work {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.day-title-holiday.my-shift-title {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.day-title-holiday.my-rest-title {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
.cell-item {
|
||||
background: var(--el-fill-color-light);
|
||||
|
||||
|
||||
@@ -778,6 +778,67 @@ func (c *BackendOaSchedulingController) ClearEntries() {
|
||||
c.scOK(map[string]interface{}{"cleared": len(entries)})
|
||||
}
|
||||
|
||||
// MyBoard 当前登录用户的排班:按登录账号匹配员工档案,返回区间内每天的排班,
|
||||
// 供日程日历渲染上班/休息背景。
|
||||
// GET /backend/oa/scheduling/my?start_date=&end_date=
|
||||
func (c *BackendOaSchedulingController) MyBoard() {
|
||||
tid, _, username, ok := c.scOperator()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
startDate := strings.TrimSpace(c.GetString("start_date"))
|
||||
endDate := strings.TrimSpace(c.GetString("end_date"))
|
||||
if startDate == "" || endDate == "" {
|
||||
now := time.Now()
|
||||
startDate = now.Format("2006-01-02")
|
||||
endDate = startDate
|
||||
}
|
||||
dates, msg := scDateSpan(startDate, endDate)
|
||||
if msg != "" {
|
||||
c.scFail(400, 400, msg)
|
||||
return
|
||||
}
|
||||
if len(dates) > scBoardMaxDays {
|
||||
c.scFail(400, 400, fmt.Sprintf("一次最多查询 %d 天", scBoardMaxDays))
|
||||
return
|
||||
}
|
||||
|
||||
// 账号未关联员工档案时返回空列表,前端静默不渲染排班背景
|
||||
var employee models.BackendEmployee
|
||||
if err := c.scBaseEmployeeQuery(tid).Filter("account", username).One(&employee); err != nil {
|
||||
c.scOK(map[string]interface{}{"employee": nil, "list": []map[string]interface{}{}})
|
||||
return
|
||||
}
|
||||
|
||||
var entries []models.OaScheduleEntry
|
||||
_, _ = c.scEntryQuery(tid).
|
||||
Filter("employee_id", employee.ID).
|
||||
Filter("schedule_date__gte", startDate).
|
||||
Filter("schedule_date__lte", endDate).
|
||||
OrderBy("schedule_date").
|
||||
All(&entries)
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(entries))
|
||||
for i := range entries {
|
||||
e := entries[i]
|
||||
list = append(list, map[string]interface{}{
|
||||
"date": e.ScheduleDate,
|
||||
"shift_id": e.ShiftID,
|
||||
"shift_name": e.ShiftName,
|
||||
"short_name": e.ShortName,
|
||||
"color": e.Color,
|
||||
"work_start": e.WorkStart,
|
||||
"work_end": e.WorkEnd,
|
||||
"note": e.Note,
|
||||
})
|
||||
}
|
||||
c.scOK(map[string]interface{}{
|
||||
"employee": map[string]interface{}{"id": employee.ID, "name": employee.Name},
|
||||
"list": list,
|
||||
})
|
||||
}
|
||||
|
||||
// ChangeList 排班变更记录(最近 100 条),用于追溯临时调配。
|
||||
// GET /backend/oa/scheduling/changes?start_date=&end_date=&employee_id=
|
||||
func (c *BackendOaSchedulingController) ChangeList() {
|
||||
|
||||
@@ -347,6 +347,7 @@ func RegisterAuthRoutes() {
|
||||
beego.Router("/backend/oa/scheduling/shifts/:id", &controllers.BackendOaSchedulingController{}, "post:UpdateShift;delete:DeleteShift")
|
||||
beego.Router("/backend/oa/scheduling/employees", &controllers.BackendOaSchedulingController{}, "get:ScheduleEmployeeOptions")
|
||||
beego.Router("/backend/oa/scheduling/board", &controllers.BackendOaSchedulingController{}, "get:Board")
|
||||
beego.Router("/backend/oa/scheduling/my", &controllers.BackendOaSchedulingController{}, "get:MyBoard")
|
||||
beego.Router("/backend/oa/scheduling/plans", &controllers.BackendOaSchedulingController{}, "get:PlanList")
|
||||
beego.Router("/backend/oa/scheduling/templates", &controllers.BackendOaSchedulingController{}, "get:TemplateList")
|
||||
beego.Router("/backend/oa/scheduling/templates/apply", &controllers.BackendOaSchedulingController{}, "post:TemplateApply")
|
||||
|
||||
Reference in new issue
Block a user