Commit d2443c67 by DaiJiezhang

docs: retain only auth integration plan

parent b1738cf8
## Context
The project already maps `as_asset_device` through `AssetDeviceEntity` and `AssetDeviceMapper`, but it has no device Controller, Service, DTOs, page, image upload path, route, or menu. The table keeps a device name, two optional image attachment fields, a logical `user_person_id`, two text status fields, and the common `delete_time` soft-delete marker.
Enterprise WeChat asset work is currently in progress in the same repository. Its files are already dirty, including the enterprise WeChat Controller, Service, DTOs, frontend module, CSS, and tests. Device management must therefore be independently buildable and defer the two shared frontend registrations until final integration.
There is no existing upload service. This change stores original image files locally and records controlled, application-relative file identifiers in `image_attachment_1` and `image_attachment_2`. The browser renders a fixed-size preview of the original image; it does not generate or store a second thumbnail file.
## Goals / Non-Goals
**Goals:**
- Provide a complete, safe device-asset CRUD workflow over existing `as_asset_device` rows.
- Support up to two JPG, PNG, or GIF images per device, with a 20 MB maximum for each image.
- Provide searchable company-person selection and readable person names in the list response.
- Keep active device names unique, apply approved dropdown values, and protect referenced devices from deletion.
- Add a dedicated Device Asset Management page, route, and menu without changing enterprise WeChat business behavior.
- Allow parallel implementation by isolating device work to new files and assigning final shared-file integration to one owner.
**Non-Goals:**
- Do not modify database schema, run migrations, or directly operate the database.
- Do not add device selection to the enterprise WeChat creation form; that is a later change.
- Do not generate physical thumbnail files, convert image formats, or add an object-storage dependency.
- Do not permanently remove original image files during normal device soft deletion.
- Do not introduce authentication or a general-purpose file-management module.
## Decisions
### Device API and data contract
Use `/api/device-assets` for list, creation, update, and deletion. `GET` accepts `page`, `size`, `deviceName`, `userPersonId`, `userUsageStatus`, and `assetRelationStatus`; it filters `delete_time = 0` and orders by descending ID. `POST` and `PUT /{id}` accept multipart form data so device fields and both optional image files are saved together. `DELETE /{id}` performs a soft deletion.
The list and detail response return `id`, device fields, original-image access URLs, `userPersonId`, `userPersonName`, and audit times, but never return `deleteTime` or the physical storage path. This follows the current asset API envelope and pagination shape.
### Validation and reference handling
`deviceName` is required and is unique among active records. Save operations reject values outside the fixed approved status lists. A selected `userPersonId` must resolve to a non-deleted company person; it can be omitted.
Before a device is soft-deleted, the service checks active `as_phone_asset`, `as_wecom_account`, `as_wechat_account`, and `as_douyin_account` rows for the device ID. If any exists, deletion fails with a readable list of reference sources. This preserves logical association integrity because the database intentionally has no foreign keys.
### Image storage and access
The server validates extension and image content for JPG, PNG, and GIF and limits each file to 20 MB. It saves each original file beneath a configurable local root, defaulting to `./uploads/device-assets`, with a generated opaque file identifier. Attachment database columns store only that identifier.
An application endpoint resolves an identifier to a file only after constraining it to the upload root; response DTOs expose the endpoint URL rather than a disk path. On update, omitted image parts preserve the current image; an explicit remove flag clears its database reference. A successful replacement saves the new file before updating the row, while failed requests clean up newly written temporary files. Soft deletion keeps files for recoverability.
Browser-sized preview was selected over server-generated thumbnail files because it adds no image-processing dependency or duplicate storage. The trade-off is that a list may download larger original files; lazy image loading will reduce initial page work.
### Frontend behavior
`DeviceAssetView` provides filters, a paged table, a create/edit dialog, remote company-person search, the two status dropdowns, two image selectors, inline browser previews, and a delete confirmation. Each slot supports retain, replace, or explicit removal during edit. The page sets `loading` and `saving` states and surfaces API errors through the existing Element Plus message pattern.
Use a dedicated `device-asset.css`, imported by the device module, rather than editing the currently modified shared stylesheet. The final integration adds the `设备资产管理` navigation item and maps `#/device-assets` to the new view.
### Parallel ownership and integration
Device work owns only newly added `DeviceAsset*` backend files, device frontend module files, device CSS, and device tests. It reuses but does not edit `AssetDeviceEntity`, `AssetDeviceMapper`, or enterprise WeChat code. Enterprise WeChat work retains ownership of all current dirty files. A single integration owner changes `App.js` and `router/index.js` after both feature branches are ready.
This ownership model avoids merge conflicts. The device branch is developed in a separate Git worktree from the current dirty enterprise-WeChat worktree; final integration runs all relevant tests after both changes are present.
## Risks / Trade-offs
- [Original files can be 20 MB] → Lazy-load list previews, display a loading state, and never generate duplicate thumbnail files.
- [Local disk is not shared across multiple application instances] → Keep the upload root configurable and document that a future multi-instance deployment must move to shared/object storage.
- [Soft deletion retains image files] → Retention is deliberate for recovery; any physical cleanup must be a separately authorized maintenance workflow.
- [Database has no foreign keys] → Service-level active-reference checks block deletion, and tests cover each referencing asset type.
- [User-provided file extension can be misleading] → Validate both allowed type and decodable image content; serve files only through opaque identifiers constrained to the upload root.
- [Shared menu and router files are collision points] → Make their two-line integration a separately owned final commit only.
## Migration Plan
1. Create the device feature branch/worktree from the agreed base without resetting or stashing the current enterprise-WeChat working tree.
2. Implement and test only device-owned new files; do not change enterprise-WeChat files, shared CSS, or shared navigation during this phase.
3. Configure the multipart request maximum to accommodate two 20 MB files plus form metadata, without changing database schema.
4. Deploy with a writable device upload directory and verify that its access endpoint can read only files under the configured root.
5. Run device API tests, frontend build, device Playwright tests, then the existing phone and enterprise-WeChat smoke tests after final integration.
6. Roll back code by reverting the device feature and route/menu integration commits. Existing rows and retained files remain recoverable; no database rollback is required.
## Open Questions
None. The approved status values, per-image 20 MB limit, browser-sized previews, local storage, deletion protection, route, menu label, and deferred enterprise-WeChat selection are all fixed for this change.
## Why
`as_asset_device` already has an Entity and Mapper, but the asset console cannot browse, create, edit, upload images for, or safely delete device assets. Completing this closed management loop is needed before later work can let enterprise WeChat assets choose a device.
## What Changes
- Add a device-asset REST API for paged listing, creation, editing, image upload and protected soft deletion against `as_asset_device`.
- Add a Device Asset Management workspace at `#/device-assets`, including filters, paged table, creation/edit dialog, two-image upload and browser-sized thumbnail preview.
- Limit each uploaded JPG, PNG, or GIF image to 20 MB. Store only original files locally; the list renders a fixed-size browser preview and does not create a second thumbnail file.
- Add fixed dropdown options for user usage status (`使用中`, `闲置`, `维修中`, `停用`) and asset relation status (`已关联`, `未关联`, `待确认`).
- Resolve `user_person_id` to a readable company-person name and prohibit deletion while an active phone, enterprise WeChat, WeChat, or Douyin asset still references the device.
- Add the `设备资产管理` menu item and the `#/device-assets` route as a final, isolated integration change so this work can proceed in parallel with enterprise WeChat asset work.
## Capabilities
### New Capabilities
- `device-asset-api`: Provides safe device-asset CRUD, filtered pagination, image attachment handling, company-person lookup, and reference-protected deletion.
- `device-asset-workspace`: Provides the Device Asset Management Vue workspace, its filters, forms, image previews, and menu/route entry.
### Modified Capabilities
- None.
## Impact
- Backend: adds device-specific Controller, Service, request/response DTOs, local file-storage support, and API tests; reuses the existing `AssetDeviceEntity`, `AssetDeviceMapper`, `CompanyPersonMapper`, and referencing asset Mappers without changing database schema.
- Frontend: adds an isolated device module, API client, dedicated CSS, and Playwright coverage. Only the final integration changes `frontend/src/App.js` and `frontend/src/router/index.js`.
- API and files: introduces `/api/device-assets` CRUD, company-person lookup, and controlled image-file access endpoints. Images are stored under a configurable local directory, defaulting to `./uploads/device-assets`; each image is limited to 20 MB.
- Database: no DDL, migration, or direct database operation is included. Existing `delete_time` semantics remain the soft-delete mechanism.
## ADDED Requirements
### Requirement: Paged active device list
The system SHALL provide `GET /api/device-assets` with validated `page` and `size` parameters and optional `deviceName`, `userPersonId`, `userUsageStatus`, and `assetRelationStatus` filters. It MUST return only rows whose `delete_time` is `0`, ordered by descending ID, in the existing `records`, `total`, `page`, and `size` pagination shape.
#### Scenario: Filtered active-device page
- **WHEN** a user requests page 1 with `deviceName` and an approved status filter
- **THEN** the response contains only matching non-deleted device records and their pagination metadata
### Requirement: Readable device response and company-person lookup
The system SHALL return each device's ID, device name, two image access URLs when present, `userPersonId`, resolved `userPersonName`, both status fields, and creation and update times. It MUST NOT return `deleteTime` or a physical file path. The system SHALL provide a read-only company-person lookup endpoint that returns active matching person IDs and names for the device form.
#### Scenario: Missing or deleted device user
- **WHEN** a device has no user person or its referenced person is not active
- **THEN** the device record retains `userPersonId` when present and returns a null user-person name without failing the page
### Requirement: Device creation and update
The system SHALL provide multipart `POST /api/device-assets` and `PUT /api/device-assets/{id}` endpoints. Creation and update MUST require a nonblank device name, reject duplicate active device names, accept only the approved status values, and require an active company person when `userPersonId` is supplied. Update MUST preserve an existing image when no replacement or explicit removal is supplied.
#### Scenario: Create a device with valid approved values
- **WHEN** a user submits a unique device name, optional active user person, and approved usage and relation statuses
- **THEN** the system creates an active device record with creation and update timestamps and returns its readable response
#### Scenario: Reject invalid status or duplicate name
- **WHEN** a user submits an unsupported status value or a device name already used by an active device
- **THEN** the system rejects the request without writing a new or changed device row
### Requirement: Device image attachment handling
The system SHALL accept at most two optional device images, one for each attachment slot. Each file MUST be JPG, PNG, or GIF, MUST be decodable as that image type, and MUST not exceed 20 MB. The system MUST store original files under a configurable local root and expose them only through an opaque application file URL; it MUST NOT return local disk paths or create separate thumbnail files.
#### Scenario: Display image by browser-sized preview
- **WHEN** a device response contains an image access URL
- **THEN** the client can load the original image through the controlled URL and render it in a fixed-size preview without requesting a separately generated thumbnail
#### Scenario: Replace or remove an image while editing
- **WHEN** a user updates one image slot with a valid replacement or an explicit remove flag
- **THEN** the system respectively records the new opaque identifier or clears that slot while leaving the other slot unchanged
### Requirement: Reference-protected device deletion
The system SHALL provide `DELETE /api/device-assets/{id}` as a soft delete. Before deletion it MUST check active phone, enterprise WeChat, WeChat, and Douyin assets for the target device ID. If any active reference exists, it MUST reject deletion and identify the referencing asset types; otherwise it MUST update `delete_time` and `update_time` without physically deleting stored image files.
#### Scenario: Reject deletion of a referenced device
- **WHEN** an active phone or account asset references the requested device ID
- **THEN** the system returns a readable failure and the device remains active
#### Scenario: Soft-delete an unreferenced device
- **WHEN** no active supported asset references the requested device ID
- **THEN** the system marks the device deleted and it no longer appears in the device list
## ADDED Requirements
### Requirement: Device Asset Management route and menu
The frontend SHALL provide a Device Asset Management workspace at `#/device-assets` and a sidebar menu item labelled `设备资产管理` that opens it. The route and menu integration MUST preserve the existing enterprise-WeChat route and page behavior.
#### Scenario: Open Device Asset Management
- **WHEN** a user selects `设备资产管理` from the sidebar
- **THEN** the application navigates to `#/device-assets` and renders the device list workspace
### Requirement: Device list filters and pagination
The workspace SHALL render device name, user person, usage status, relation status, and audit-time columns with filters for name, user person, usage status, and relation status. It MUST expose pagination and show loading, empty, and request-error states.
#### Scenario: Reset a filtered device list
- **WHEN** a user clears the filters through the reset control
- **THEN** the workspace requests the first unfiltered page and displays its returned records
### Requirement: Device create and edit form
The workspace SHALL provide create and edit dialogs with a required device name, remote company-person selector, the approved usage-status dropdown, and the approved relation-status dropdown. It MUST prevent duplicate save submissions while a request is pending and show validation or API failures to the user.
#### Scenario: Submit a valid device form
- **WHEN** a user completes a valid create or edit dialog and selects save
- **THEN** the workspace submits multipart form data, closes the dialog after success, and refreshes the list
### Requirement: Two-image browser preview
The workspace SHALL allow each device form to select up to two JPG, PNG, or GIF files, each no larger than 20 MB. It MUST render fixed-size previews of existing and newly selected original images, allow an existing slot to be retained, replaced, or marked for removal, and allow a user to open the original image preview.
#### Scenario: Reject an oversized or unsupported image before save
- **WHEN** a user selects an image larger than 20 MB or outside the supported formats
- **THEN** the workspace displays an error and does not include that file in the save request
### Requirement: Protected delete interaction
The workspace SHALL require delete confirmation and refresh the list after a successful soft delete. If the API reports active references, it MUST display the returned reason and keep the device row visible.
#### Scenario: Attempt to delete a referenced device
- **WHEN** a user confirms deletion of a device that is still referenced
- **THEN** the workspace shows the API's reference warning and does not remove the row from the table
## Context
`LoginView.js``frontend/img/black_hole.png` 导入为 CSS 自定义属性(CSS 变量,即可由页面传给样式的命名值),`app.css` 再将该图片与遮罩渐变合成为登录页背景。当前实现使用原图,因此静止画面是唯一可作为“一模一样”验收基准的版本;用 Canvas 或 SVG 重新绘制会改变纹理、光照和压缩细节。
本变更让图片的背景定位随鼠标小幅变化,并以同一图片创建只覆盖黑洞区域的遮罩动态层。该层低速旋转以模拟吸积盘自转;登录表单、遮罩与认证业务层保持固定,避免动画影响输入、点击或会话数据。
### 实施范围与文件用途注释
| 文件 | 文件用途注释(白话) | 关联逻辑 | 本次变化 |
| --- | --- | --- | --- |
| `frontend/src/modules/auth/LoginView.js` | 登录界面组件,负责把表单状态和页面交互交给 Vue。 | 指针事件 -> 坐标计算 -> CSS 变量;表单 -> `signIn` -> 路由。 | 仅新增背景偏移状态、事件处理与供样式定位局部旋转层的标记;不改变提交逻辑。 |
| `frontend/src/styles/app.css` | 登录页和后台页面共享样式表,其中 `login-` 前缀规则只作用于登录页。 | CSS 变量 -> 原图背景定位/过渡;遮罩层 -> 局部旋转;媒体查询 -> 无动画回退。 | 将背景图片层、局部旋转层与遮罩层分开,并新增视差、自转和减少动态效果规则。 |
| `frontend/tests/login-experience.spec.js` | 浏览器端登录体验回归测试,使用模拟认证接口而非真实账号。 | Playwright 指针事件/等待动画帧 -> 计算样式 -> 登录页背景。 | 新增验证背景变量、移动后偏移、离开后复位与局部旋转层的用例。 |
### 实施范围内方法的新手注释门禁
| 方法/回调 | 必须添加的注释内容 |
| --- | --- |
| `isBackgroundParallaxEnabled` | 代码作用(白话):判断当前设备是否适合鼠标背景动画。关联文件:`LoginView.js``app.css`。关联逻辑(调用链/数据流):系统动态偏好/精细指针能力 -> 指针处理是否写入 CSS 变量。 |
| `handleBackgroundPointerMove` | 代码作用(白话):把鼠标相对登录区域的位置换算成小范围背景偏移。关联文件:`LoginView.js``app.css`。关联逻辑(调用链/数据流):`pointermove` -> 坐标归一化 -> CSS 变量 -> 背景位置。 |
| `resetBackgroundParallax` | 代码作用(白话):鼠标离开时让背景回到原图的默认裁切位置。关联文件:`LoginView.js``app.css`。关联逻辑(调用链/数据流):`pointerleave`/组件卸载 -> 零偏移 CSS 变量 -> 平滑复位。 |
| 新增 Playwright 测试回调 | 代码作用(白话):验证动画仅改变背景偏移而不替换原图。关联文件:`login-experience.spec.js``LoginView.js`。关联逻辑(调用链/数据流):模拟鼠标 -> DOM 计算样式 -> 原图 URL 与偏移断言。 |
## Goals / Non-Goals
**Goals:**
- 静止状态继续使用同一张 Vite 处理的黑洞图片,保持既有构图、遮罩和登录面板位置。
- 在精细指针设备上,根据鼠标在登录区域的位置平滑改变背景定位,最大偏移受限且不会露出图片边缘。
- 以低速、无限循环的 CSS 动画(由浏览器样式表驱动的画面变化)旋转同源图片的局部遮罩层,形成仅黑洞吸积盘自转的视觉效果。
- 鼠标离开区域后复位;触摸/粗指针设备以及 `prefers-reduced-motion: reduce`(系统声明希望减少动画)保持静止。
- 覆盖可自动执行的浏览器验收,不触发真实登录或写入数据。
**Non-Goals:**
- 不用 Canvas、WebGL、SVG 或 AI 生成资源重画黑洞图;它们只能近似,不能保证与原图一致。
- 不修改登录接口、认证状态、DTO(前后端传递的数据格式)、数据库、路由或表单样式。
- 不为触摸设备加入陀螺仪、拖拽或其他背景交互。
- 不模拟真实的三维引力、粒子或光线追踪物理效果;自转是基于原图的二维视觉模拟。
## Decisions
### 1. 以原图背景定位替代代码重绘
背景继续引用 `black_hole.png`,代码只写入水平、垂直偏移的 CSS 变量;遮罩层与面板不参与移动。
- 选择原因:静止画面复用同一个图片资源,能保留视觉验收的全部像素细节;修改定位可以实现空间感而不更换内容。
- 替代方案:Canvas/WebGL 着色器(由程序计算每个像素颜色的渲染方式)。未采用,因为难以匹配原图纹理,且会增加浏览器兼容、性能与调色成本。
### 2. 使用 Pointer Events 与受限 CSS 变量
登录页根元素监听 `pointermove``pointerleave`,仅接受鼠标/精细指针;处理函数把相对坐标映射到有限像素区间,并通过 CSS 过渡平滑显示。CSS 自定义属性是浏览器原生传值机制,不需要新增依赖或全局状态。
- 选择原因:不会移动表单 DOM,也不会触碰登录请求数据;偏移上限可避免 `cover`(保持图片填满区域的裁切方式)出现空白边缘。
- 替代方案:直接 `transform` 整个 `.login-page`。未采用,因为这会让登录面板和遮罩一起移动,影响输入可用性与视觉稳定性。
### 3. 使用同源遮罩层模拟局部吸积盘自转
在背景图片上叠加一个全尺寸、同源图片图层;该图层通过径向遮罩(让图层只在黑洞附近可见、边缘渐隐的样式)限制影响范围,并围绕黑洞位置以约 30 秒一圈的速率旋转。基础背景、渐变遮罩与登录面板不参与该动画。
- 选择原因:静止时叠加层与原图像素相同;动画时只在黑洞区域产生变化,不会让整幅星空或表单旋转,也无需新增图片资源。
- 替代方案:旋转整个登录页背景。未采用,因为星空和构图会一起旋转,且视觉上不像黑洞本身自转。
### 4. 无动画优先的可访问性回退
JavaScript 在系统请求减少动态效果或设备没有精细指针时不写入偏移;CSS 同时提供 `prefers-reduced-motion` 规则取消过渡,形成双层保护。
- 选择原因:避免动画让触摸设备误触后产生不可预期的画面变化,也尊重用户的系统辅助设置。
- 替代方案:所有设备都启用动画。未采用,因为无法可靠取得触摸位置且会违背减少动态效果偏好。
## Risks / Trade-offs
- [不同屏幕比例下原图裁切不一样] → 保留当前各断点的基础背景位置,只在其周围做小幅偏移,并在桌面与移动视口测试。
- [高频鼠标事件增加样式更新] → 仅写入两个 CSS 变量,不创建网络请求或 DOM 节点;如测试发现掉帧,再使用 `requestAnimationFrame`(浏览器每帧合并更新的机制)合并更新。
- [浏览器不支持 Pointer Events] → 背景保持当前静态画面,登录功能完全不受影响。
- [遮罩边缘与不同视口下的黑洞位置不完全重合] → 复用现有响应式背景定位值,并将遮罩边缘渐隐;在桌面与小屏视口人工检查后再微调位置。
- [局部旋转造成 GPU 绘制压力] → 只创建一个伪元素/装饰层,采用低速 `transform` 动画;减少动态效果设置下关闭动画。
- [测试环境无法模拟系统动态偏好] → 自动测试验证默认鼠标流程和复位;减少动态效果作为手工 smoke 验收项记录。
## Migration Plan
1. 按范围文件逐组实现背景状态、样式和测试,保留原有图片导入与登录提交代码。
2. 执行前端单元/端到端测试与生产构建,并在桌面浏览器手工检查鼠标移动、离开复位、局部自转和登录表单。
3. 若视觉或性能不符合验收,仅撤回新增事件、变量和样式规则;原图背景与认证逻辑无需迁移或数据回滚。
## Open Questions
- 无阻塞问题;默认以当前已验收的 `black_hole.png` 为唯一视觉基准,并采用轻量、低位移的鼠标视差。
## Why
登录页现已通过 `black_hole.png` 呈现黑洞背景,但背景保持静止。若用纯 CSS 或 Canvas 重新绘制,无法保证与原图像素级一致;应保留已验收的原图,并让代码只控制它的观看角度与局部旋转,从而在不改变视觉基准的前提下提供鼠标跟随和吸积盘自转的空间感。
## What Changes
- 为登录页新增基于指针位置的背景视差效果:鼠标移动时,原有黑洞背景在受限范围内平滑偏移。
- 新增仅覆盖黑洞区域的同源图片动态层,以低速循环旋转模拟吸积盘自转;星空、遮罩、登录面板和表单保持静止。
- 保留并继续由 Vite 导入 `frontend/img/black_hole.png`,不以 Canvas、SVG 或生成图替换该资源,确保静止状态的画面与现有背景一致。
- 为离开登录区域、触摸设备及“减少动态效果”系统偏好提供静止或复位行为,避免动画影响可用性。
- 保持账号密码提交、记住账号、路由跳转和认证接口完全不变。
## Capabilities
### New Capabilities
- `login-background-parallax`: 登录黑洞背景保留原始图片视觉,并在适用的指针设备上提供可平滑复位的轻量视差动画和局部吸积盘自转模拟。
### Modified Capabilities
- 无。
## Impact
- 受影响前端文件:`frontend/src/modules/auth/LoginView.js``frontend/src/styles/app.css`
- 不新增运行时依赖;不修改后端接口、DTO(前后端传递的数据格式)、数据库或认证会话。
- 验收将覆盖桌面端鼠标移动、离开区域复位、局部吸积盘自转、小屏/触摸设备静止、减少动态效果偏好,以及既有登录主流程。
## ADDED Requirements
### Requirement: 登录黑洞背景保持原图渲染
系统 SHALL 继续通过 `frontend/img/black_hole.png` 的 Vite 解析 URL 渲染登录页背景。任何背景动画 SHALL 仅改变该图片的展示位置或裁切,不得以 Canvas、SVG、渐变或新图片替换原图。
#### Scenario: 初始登录页显示原图
- **WHEN** 未登录用户打开登录路由且尚未在页面上移动鼠标
- **THEN** `.login-page` 的背景图片变量 SHALL 包含 Vite 解析的黑洞图片 URL,背景偏移 SHALL 为默认值
### Requirement: 精细指针驱动受限背景视差
系统 SHALL 在支持精细指针且未请求减少动态效果的设备上,根据鼠标在登录区域内的相对位置更新背景偏移。偏移 SHALL 被限制在设计定义的小范围内,且不得移动登录表单、遮罩或提交按钮。
#### Scenario: 鼠标在登录区域内移动
- **WHEN** 用户将鼠标从登录区域中心移动到任一边缘方向
- **THEN** 黑洞背景的计算偏移 SHALL 向对应方向平滑变化,且登录面板的几何位置 SHALL 保持不变
#### Scenario: 鼠标离开登录区域
- **WHEN** 用户的鼠标离开登录区域
- **THEN** 背景偏移 SHALL 平滑回到默认值,且原图 URL SHALL 保持不变
### Requirement: 黑洞吸积盘 SHALL 局部自转
系统 SHALL 使用与基础背景相同的黑洞图片 URL 创建局部遮罩动画层,并以低速循环旋转模拟吸积盘自转。遮罩层 SHALL 只影响黑洞附近的视觉区域;基础星空、渐变遮罩、登录面板和表单 SHALL 保持静止。
#### Scenario: 登录页显示局部自转
- **WHEN** 支持精细指针的用户打开登录页且未启用减少动态效果
- **THEN** 黑洞局部动画层 SHALL 使用与基础背景相同的图片 URL 并处于循环旋转状态,登录面板的几何位置 SHALL 保持不变
### Requirement: 不适用设备保持静态背景
系统 SHALL 在触摸/粗指针设备或系统启用 `prefers-reduced-motion: reduce` 时保持背景静态,不因指针事件、视差过渡或吸积盘自转产生持续动画。
#### Scenario: 用户请求减少动态效果
- **WHEN** 浏览器报告 `prefers-reduced-motion: reduce`
- **THEN** 背景 SHALL 使用默认偏移并禁用视差过渡和吸积盘自转,同时登录表单 SHALL 正常可输入和提交
## 1. 登录页视差实现
- [ ] 1.1 在 `frontend/src/modules/auth/LoginView.js` 新增受限背景偏移状态、精细指针/减少动态效果判断、鼠标移动处理和离开复位;为每个新增方法及业务回调补齐“代码作用(白话)/关联文件/关联逻辑(调用链、消息链或数据流)”注释,并保持 `submit`、记住账号与认证调用不变。
- [ ] 1.2 在登录页根元素绑定指针移动和离开事件,仅将偏移 CSS 变量传递给背景层;增加供样式定位局部吸积盘层的结构标记,不让表单面板、遮罩或按钮参与移动。
- [ ] 1.3 在 `frontend/src/styles/app.css` 保留现有黑洞图片、基础裁切和响应式断点,将图片层、局部吸积盘遮罩层与遮罩层分离;为同源局部层添加约 30 秒一圈的低速旋转,且不旋转整幅星空或表单。
- [ ] 1.4 添加受限视差、平滑过渡以及 `prefers-reduced-motion: reduce` 静态回退规则,并同时禁用视差与吸积盘自转。
## 2. 自动化验收
- [ ] 2.1 在 `frontend/tests/login-experience.spec.js` 增加带完整新手注释的 Playwright 用例,断言初始背景仍指向 Vite 解析的黑洞原图,鼠标移动后仅背景偏移变化而面板位置不变,离开后偏移复位。
- [ ] 2.2 增加带完整新手注释的 Playwright 用例,断言局部吸积盘层使用同源图片并处于循环旋转状态,且登录面板不随之移动。
- [ ] 2.3 保留既有普通账号登录、Jeddy 欢迎提示、失败登录和退出失败回归用例,并确认新增测试不请求真实认证服务或写入数据。
## 3. 验证与交付
- [ ] 3.1 在 `frontend` 目录执行既有端到端测试命令,并记录通过/失败结果;失败时先保留证据并分析原因,再决定修复方案。
- [ ] 3.2 在 `frontend` 目录执行生产构建和 `git diff --check`,确认 Vite 仍输出黑洞图片资源且没有空白字符错误。
- [ ] 3.3 在本地桌面浏览器完成 smoke(冒烟检查,即用最小操作确认主流程可用):登录页初始画面、局部吸积盘自转、鼠标四方向移动、离开复位、表单输入/提交、小屏视口及减少动态效果偏好。
## Why
当前资产后台已经预留 `as_system_user` 用户表映射,但没有登录校验、账号管理或页面权限控制;任何访问者都能直接进入现有路由和接口。现在需要让开发者与超级管理员能够安全创建和维护业务账号,并让财务、人事、运营按被授予的页面读写权限使用系统。
## What Changes
- 新增账号密码登录、登录态校验与登出能力,未登录用户只能访问登录页和登录接口。
- 接入现有 `as_system_user` 作为唯一账号表:保存用户名、不可逆密码哈希、角色、启停状态和逐页权限配置;不保存明文密码。
- 定义五种固定角色:开发者、超级管理员、财务、人事、运营;唯一开发者固定账号为 `Jeddy`,且不出现在任何前端列表或下拉选项中。
- 新增账号与权限管理能力:开发者可创建所有角色并管理全部密码;超级管理员只能创建财务、人事、运营与其他超级管理员,且不能创建或修改任何密码;其他角色不能创建账号。
- 新增独立的“设置 / 账号与权限”菜单项及路由页,以图标加文字呈现;页面包含账号列表、创建/编辑账号、启停和逐页面板的“只读/编辑/无权限”勾选。
- 将菜单展示、前端路由守卫与后端接口授权统一建立在同一份服务端权限结果上,避免只隐藏前端入口而仍可调用接口。
- 所有密码创建、重置和修改均只由开发者执行;`Jeddy` 的初始密码哈希由受控数据库操作人工写入。登录成功后仅对 `Jeddy` 弹出“🎉 欢迎系统开发者-BOSS:Jeddy 上线”,其他账号不展示欢迎提示。
## Capabilities
### New Capabilities
- `system-user-authentication`: 以 `as_system_user` 为账号来源的安全登录、会话校验、登出和开发者专属登录欢迎提示能力。
- `system-user-administration`: 按创建者角色限制的账号查询、创建、编辑、启停与仅开发者可执行的密码重置能力,并隐藏开发者账号。
- `page-permission-management`: 服务端维护的逐页“无权限/只读/编辑”配置,以及设置入口、菜单过滤、路由和接口授权能力。
### Modified Capabilities
- 无现有 OpenSpec 主规格需要修改;现有资产接口的实现将增加统一授权校验,但其业务响应字段和资产数据规则不改变。
## Impact
- 后端:新增认证/账号/权限模块、Spring Security 依赖与安全配置、`as_system_user` 查询和写入逻辑、现有 `/api/**` 的授权拦截,以及相应单元和接口测试。
- 数据库:复用 `as_system_user` 的账号字段;需先核对线上表的实际列和索引,必要时通过可回滚迁移补充权限 JSON、密码更新时间和版本字段。不会存储明文密码。
- 前端:新增登录页、认证状态、请求鉴权头、路由守卫、设置菜单和独立账号与权限管理页;`Jeddy` 登录后仅在当前登录成功流程展示专属欢迎提示;现有资产页根据服务端返回的页面权限显示或禁用编辑操作。
- 运维:新增仅部署环境持有的令牌签名密钥;`Jeddy` 的初始密码哈希由受控数据库操作维护,密钥、明文密码和哈希均不提交到仓库。
## ADDED Requirements
### Requirement: 每个非开发者账号拥有逐页三档权限
系统 SHALL 为每个非开发者账号保存首期五个注册页面(总览、域名资料、企微资料、手机号资产、提醒中心)的权限值,且值 MUST 仅为 `NONE``READ``EDIT``EDIT` MUST 包含 `READ`;未配置和未知页面 MUST 按 `NONE` 处理。超级管理员对上述五页固定为 `EDIT`
#### Scenario: 管理员配置页面权限
- **WHEN** 有权管理员在独立页面权限面板为可管理账号勾选各页面的只读或编辑权限并保存
- **THEN** 系统校验页面键和值后持久化权限映射并返回最新有效权限
#### Scenario: 新页面没有被配置
- **WHEN** 系统发布了一个尚未存在于账号权限映射中的页面
- **THEN** 非开发者用户不能访问该页面,直到有权管理员明确授予权限
### Requirement: 菜单、路由和接口必须使用服务端权限结论
系统 MUST 从当前用户接口取得有效页面权限来决定菜单可见性和路由访问,并 MUST 在后端对每个页面关联接口执行同等或更严格的最低权限校验。
#### Scenario: 只读用户进入资产页
- **WHEN** 用户对手机号资产页只有 `READ` 权限
- **THEN** 菜单和路由允许查看列表,但新增、编辑和删除操作不显示或禁用,且后端拒绝对应写请求
#### Scenario: 用户绕过菜单访问无权限地址
- **WHEN** 用户手动输入没有权限的 Hash 路由或直接请求对应接口
- **THEN** 前端显示无权页且后端返回 403,不泄露业务数据
### Requirement: 设置入口仅向账户管理员显示
系统 SHALL 在主菜单的“设置”分组内以图标和文字显示“账号与权限”入口,且 MUST 只向开发者和超级管理员显示并允许访问。
#### Scenario: 超级管理员打开设置
- **WHEN** 超级管理员登录系统
- **THEN** 主菜单显示带设置图标的账号与权限入口并可跳转至独立管理路由
#### Scenario: 运营用户查看菜单
- **WHEN** 运营用户登录系统
- **THEN** 主菜单不显示账号与权限入口且直接访问该路由会被拒绝
## ADDED Requirements
### Requirement: 开发者和超级管理员按边界创建账号
系统 MUST 只允许开发者和超级管理员创建账号。开发者 SHALL 能创建所有角色并设置非开发者账号密码;超级管理员 SHALL 只能创建超级管理员、财务、人事和运营账号,且新账号 MUST 以 `DISABLED` 状态等待开发者设置密码;财务、人事和运营 MUST 被拒绝创建账号。
#### Scenario: 开发者创建任意角色
- **WHEN** 开发者提交合法的新账号、角色与密码
- **THEN** 系统创建该账号并返回不含密码的账号资料
#### Scenario: 超级管理员创建业务账号
- **WHEN** 超级管理员提交财务、人事、运营或超级管理员账号且请求不含密码
- **THEN** 系统创建 `DISABLED` 账号并等待开发者后续设置密码和启用
#### Scenario: 超级管理员尝试创建开发者
- **WHEN** 超级管理员提交角色为 `DEVELOPER` 的创建请求
- **THEN** 系统返回 403 且不创建账号
#### Scenario: 业务角色尝试创建账号
- **WHEN** 财务、人事或运营用户调用账号创建接口
- **THEN** 系统返回 403 且不写入 `as_system_user`
### Requirement: 开发者账号不得在前端出现
系统 MUST 从所有面向浏览器的账号列表、查询结果、详情、筛选项和角色选项中排除 `DEVELOPER` 账号;任何浏览器请求开发者账号 ID 的管理接口 MUST 返回不可见或无权结果。
#### Scenario: 开发者查看账号列表
- **WHEN** 开发者打开账号与权限页面
- **THEN** 列表中不出现任何开发者账号,也不提供开发者角色的创建或编辑选项
### Requirement: 管理员可维护可管理账号但密码仅开发者可改
开发者或超级管理员 SHALL 能查询、编辑、启用和禁用其可管理账号;仅开发者 SHALL 能设置或重置非开发者账号密码。系统 MUST 禁止禁用最后一个可用的开发者或超级管理员账号。
#### Scenario: 禁用业务账号
- **WHEN** 有权管理员禁用财务、人事或运营账号
- **THEN** 系统将状态改为 `DISABLED`、撤销该账号的现有登录态并使其无法再次登录
#### Scenario: 尝试禁用最后一个管理账号
- **WHEN** 操作会导致系统不存在可用开发者或超级管理员
- **THEN** 系统拒绝操作并说明需要保留至少一个可用管理账号
## ADDED Requirements
### Requirement: 用户必须通过受保护登录进入系统
系统 SHALL 提供账号密码登录、当前登录人查询和登出接口,并 SHALL 拒绝未认证用户访问任何受保护的 `/api/**` 接口。认证失败响应 MUST 不透露用户名是否存在、账号是否被禁用或密码是否错误。
#### Scenario: 有效账号成功登录
- **WHEN** 状态为 `ACTIVE` 的用户提交正确用户名和密码
- **THEN** 系统返回当前用户的安全资料与页面权限,并写入短期 HttpOnly 登录 Cookie
#### Scenario: 无效凭据无法判断原因
- **WHEN** 用户名不存在、密码错误或账号被禁用
- **THEN** 系统返回相同的认证失败响应且不创建登录 Cookie
#### Scenario: 未登录请求资产接口
- **WHEN** 浏览器未携带有效登录凭证而请求任一受保护资产接口
- **THEN** 系统返回 401 且不返回资产数据
### Requirement: 密码仅以不可逆哈希保存且仅开发者可维护
系统 MUST 使用 BCrypt 保存密码哈希,且 MUST NOT 在数据库、API 响应、日志、审计文本或前端持久化存储中保存或回显明文密码。只有 `DEVELOPER` 可以通过系统接口设置或重置非开发者账号密码;固定开发者 `Jeddy` 的密码哈希只允许通过受控数据库操作维护。系统 MUST NOT 提供任何账号的自助改密接口或页面。
#### Scenario: 开发者设置业务账号密码
- **WHEN** 开发者提交可管理非开发者账号的新密码和确认密码
- **THEN** `as_system_user.password_hash` 保存 BCrypt 哈希且任何响应均不包含提交的密码
#### Scenario: 超级管理员尝试提交密码
- **WHEN** 超级管理员在创建或编辑账号请求中提交密码字段,或调用密码重置接口
- **THEN** 系统返回 403 且不写入或修改任何密码哈希
#### Scenario: 用户尝试自助改密
- **WHEN** 任意已登录用户访问自助改密路由或调用自助改密接口
- **THEN** 系统不提供该路由或接口,并拒绝对应请求
### Requirement: 密码重置和禁用必须即时撤销旧会话
系统 MUST 在开发者设置/重置密码、受控直接更新数据库哈希或禁用账号时递增 `auth_version`,并在每个受保护请求验证令牌版本和账号状态。数据库 MUST 提供仅在 `password_hash` 实际变化时自动更新 `password_updated_at` 并递增 `auth_version` 的触发器。
#### Scenario: 已重置密码的旧浏览器继续访问
- **WHEN** 开发者重置业务账号密码后,该账号先前浏览器携带旧登录 Cookie 请求接口
- **THEN** 系统拒绝该请求并要求重新登录
#### Scenario: 手工更新 Jeddy 的密码哈希
- **WHEN** 受控数据库操作将 `Jeddy``password_hash` 更新为新的 BCrypt 哈希
- **THEN** 数据库触发器自动更新 `password_updated_at`、递增 `auth_version`,且 `Jeddy` 的旧登录 Cookie 随后被拒绝
### Requirement: 开发者登录显示专属欢迎提示
系统 SHALL 仅在固定账号 `Jeddy``DEVELOPER` 角色成功登录的当次前端登录流程中显示“🎉 欢迎系统开发者-BOSS:Jeddy 上线”。系统 MUST NOT 为其他账号显示该提示。
#### Scenario: Jeddy 登录成功
- **WHEN** `Jeddy` 使用有效开发者凭据完成登录
- **THEN** 前端在登录成功后显示一次专属欢迎提示并进入系统
#### Scenario: 其他账号登录成功
- **WHEN** 超级管理员、财务、人事或运营账号完成登录
- **THEN** 前端进入系统且不显示开发者专属欢迎提示
## Context
当前手机号资产页面的筛选栏、列表列和新增/编辑表单直接展示后端字段的旧业务名称:`cardType` 显示为“卡类型”,`realNameOwner` 显示为“实名归属”,`managementType` 显示为“管理方式”,`disposalStatus` 显示为“处置状态”,`deviceId` 显示为“关联设备 ID”。
从已配置的选项可知,`cardType` 实际保存的是运营商;“正常使用、闲置、停机、已注销”描述的是当前状态而非处置动作;`deviceId` 仅保存数值 ID,尚未验证或读取设备名称。本次仅改用户可见文本和表单帮助说明,不能改动接口键、DTO 或数据库列,以保护已有记录和调用方兼容。
## Goals / Non-Goals
**Goals:**
- 让每个字段名称对应一个独立且可理解的业务维度:运营商、实名登记、管理模式、使用状态和设备关联。
- 在列表与表单中使用同一套名称,并在表单中说明容易误解字段的录入含义。
- 维持现有请求体、响应体与表结构完全不变。
**Non-Goals:**
- 不重命名 JavaScript、接口、DTO 或数据库字段。
- 不调整下拉选项、校验规则、筛选条件、数据迁移或历史记录。
- 不接入设备选择器、设备名称回显或设备存在性校验。
## Decisions
### 1. 保持内部键不变,只替换展示名称
展示名称与接口字段分离:页面继续绑定 `cardType``realNameOwner``managementType``disposalStatus``deviceId`,但将标签分别显示为“运营商、实名主体、管理模式、使用状态、关联设备(ID)”。
- 术语(白话解释):接口字段是浏览器和后端交换数据时使用的固定键名;展示名称是页面上给人看的文字。这里要分开,是因为后端键名已被现有数据和代码使用,而人的理解需要更准确的词。若直接改接口字段,会让已有请求、测试或数据映射失效。
- 备选方案:同时改接口和数据库字段。未采用,因为本次问题是业务文案歧义,不值得引入跨前后端及数据迁移风险。
### 2. 以现有枚举值反推正确业务名称
“移动、联通、电信、广电、虚拟运营商”统一称为“运营商”;“自有、租用、代运营”统一称为“管理模式”;“正常使用、闲置、停机、已注销”统一称为“使用状态”。“实名主体”用于记录与号码实名登记一致的个人或单位名称。
- 术语(白话解释):枚举值是下拉框中预先限定的一组可选值。这里用现有选项判断字段含义,能够避免名称和实际可选值不一致;否则用户仍可能把“卡类型”理解成实体 SIM/eSIM 类型,把“处置状态”理解成审批结果。
- 备选方案:继续保留旧名称并只增加提示。未采用,因为表格中的旧列名仍会造成长期误读。
### 3. 明确设备字段当前只记录 ID
页面标签使用“关联设备(ID)”,表单帮助文字说明“填写设备资产记录的数字 ID;当前不校验是否存在”。
- 术语(白话解释):ID 是系统给一条记录的唯一编号。这里明确显示 ID,是为了避免用户误以为可以输入设备名称;不说明会导致保存后找不到关联设备的预期落差。
- 备选方案:改为设备下拉选择。未采用,因为需要设备查询接口和存在性校验,超出单纯字段释义的范围。
## Risks / Trade-offs
- [旧标签可能已经被少量用户记住] → 在表单帮助文字中保留可理解的定义,不改变已存数据和值。
- [“实名主体”可能被理解为仅企业主体] → 帮助文字明确“个人或单位名称”,并以测试覆盖标签和说明。
- [测试通过文字定位页面元素] → 更新页面测试中的可访问名称,避免真实页面文案变更后测试产生误报。
## Migration Plan
1. 更新列表列名、表单标签、占位文字与帮助说明,并同步更新对应页面测试。
2. 在本地打开 `http://localhost:5173/asset/#/phone-assets`,核对列表、新增和编辑弹窗的名称一致性。
3. 回滚时仅还原前端展示文本和测试断言;不存在数据、接口或数据库迁移。
## Open Questions
- 无。当前字段的选项和值足以确定本次仅展示层的命名修正。
## Why
手机号资产页当前的“卡类型、实名归属、管理方式、处置状态、关联设备 ID”等名称没有明确区分运营商、实名登记、资产管理和当前使用状态。录入人员容易根据字面误填,后续筛选与资产盘点也难以得到一致的数据。
现在页面、接口字段和数据表已经形成可用闭环,因此应先只校正前端的业务名称与填写提示,消除理解偏差,同时保持已上线的字段名和数据兼容。
## What Changes
-`cardType` 的展示名称从“卡类型”调整为“运营商”,并明确其值用于记录移动、联通、电信、广电或虚拟运营商。
-`realNameOwner` 的展示名称调整为“实名主体”,说明其记录与该号码实名登记一致的个人或单位名称。
-`managementType` 的展示名称调整为“管理模式”,使“自有、租用、代运营”作为同一管理维度的选项。
-`disposalStatus` 的展示名称调整为“使用状态”,使“正常使用、闲置、停机、已注销”表达资产当前状态而非处理动作。
-`deviceId` 的展示名称调整为“关联设备(ID)”,并在字段旁说明当前只保存设备记录的 ID;本次不新增设备校验或选择器。
- 在新增和编辑表单中补充简短帮助说明;列表列名与表单标签保持一致。
## Capabilities
### New Capabilities
- 无。
### Modified Capabilities
- `phone-asset-workspace`: 统一手机号资产列表和表单的业务字段名称,并为易混淆字段提供填写含义。
## Impact
- 受影响前端文件为 `frontend/src/modules/phone/PhoneAssetView.js` 与其页面测试 `frontend/tests/phone-asset.spec.js`
- 不修改 `/api/phone-assets` 请求或响应字段、DTO、数据库表、已有资产记录、路由、权限或依赖。
- 页面内部和接口继续使用现有字段名 `cardType``realNameOwner``managementType``disposalStatus``deviceId`,仅改变用户可见名称与辅助文案,因此不产生接口兼容风险。
## MODIFIED Requirements
### Requirement: Use confirmed form controls
The workspace SHALL restrict the phone input to 11 digits after trimming whitespace and removing a leading `+86`. It SHALL display `cardType` as “运营商”, `realNameOwner` as “实名主体”, `managementType` as “管理模式”, `disposalStatus` as “使用状态”, and `deviceId` as “关联设备(ID)” consistently in the list and create/edit form. The form SHALL explain that the real-name subject is the individual or organization registered to the phone number, and that the device field accepts only an asset-record ID. Management type SHALL display empty for null and disposal status is required with initial value `正常使用`.
#### Scenario: User views a record in the list
- **WHEN** the workspace renders phone-asset records
- **THEN** it shows the columns “运营商、实名主体、管理模式、使用状态、关联设备(ID)” and does not expose the prior ambiguous labels for those fields
#### Scenario: User creates or edits a record
- **WHEN** a user opens the create or edit form
- **THEN** the field labels match the list names and the form explains the real-name subject and device-ID meanings without changing the submitted property names
#### Scenario: User saves confirmed dropdown values
- **WHEN** a user creates or edits using existing dropdown values
- **THEN** the workspace submits the existing `cardType`, `managementType`, and `disposalStatus` properties unchanged while the visible labels remain “运营商、管理模式、使用状态”
## 1. 页面字段命名
- [ ] 1.1 修改 `frontend/src/modules/phone/PhoneAssetView.js`(文件用途:承载手机号资产的筛选、列表、新增与编辑页面;关联逻辑:路由进入页面 → 表单/表格渲染 → `phone-api-client.js` 请求),将列表列名、表单标签、占位文字改为“运营商、实名主体、管理模式、使用状态、关联设备(ID)”。
- [ ] 1.2 在同一表单中为“实名主体”和“关联设备(ID)”增加简短帮助说明,分别明确“手机号实名登记的个人或单位名称”和“设备资产记录的数字 ID,当前不校验是否存在”。
- [ ] 1.3 保持 `cardType``realNameOwner``managementType``disposalStatus``deviceId` 的响应式表单属性及提交数据不变;不修改 `phone-api-client.js`(文件用途:封装手机号资产 HTTP 请求;关联逻辑:View → `/api/phone-assets` → Controller)或任何后端文件。
- [ ] 1.4 注释门禁:本组不新增或调整 JavaScript 方法;检查 `PhoneAssetView.js` 内已有方法的新手注释仍覆盖代码作用、关联文件和调用链/数据流。若实现时新增或改动任何方法,必须先补齐该方法的三项新手注释。
## 2. 自动化验证
- [ ] 2.1 修改 `frontend/tests/phone-asset.spec.js`(文件用途:验证手机号资产页的新增弹窗与输入行为;关联逻辑:浏览器页面 → 前端表单 → 被拦截的手机号资产接口),将按可访问名称断言的旧标签替换为新标签,并新增列表列和表单说明的断言。
- [ ] 2.2 注释门禁:本组不新增或调整测试辅助方法;检查现有测试方法注释仍说明代码作用、关联文件和调用链/数据流。若实现时新增或改动任何测试方法,必须先补齐该方法的三项新手注释。
- [ ] 2.3 运行手机号资产 Playwright 用例,并在本地打开 `http://localhost:5173/asset/#/phone-assets`,核对筛选栏、列表、新增弹窗和编辑弹窗的文字一致,且保存请求仍使用原有字段键。
## 3. 兼容性确认
- [ ] 3.1 对比修改前后的网络请求体与列表响应,确认接口、DTO、数据库和配置均未变化;本次没有新增、重命名或删除业务文件。
## Context
The frontend identifies the current user through `GET /api/auth/me`, which reads the current database row. Protected backend writes use the role stored in the signed login token. `SystemUserAdminService.updateUser` currently changes role, status, and page permissions without increasing `auth_version`; therefore an old token can retain a lower role while the UI renders the newly promoted role.
`phone-api-client.js`, `wecom-api-client.js`, and `system-user-api-client.js` use the shared authentication request helper. `device-api-client.js` uses a separate raw `fetch`, so it does not request the CSRF cookie or send the `X-XSRF-TOKEN` header. `DeviceAssetController` also lacks server-side administrator checks, unlike phone and enterprise-WeChat controllers; frontend route metadata cannot stop a direct API call. Spring Security maps both a missing CSRF token and role denial to a generic 403 response, which conceals the cause during diagnosis.
## Goals / Non-Goals
**Goals:**
- Make a role, status, or page-permission change invalidate the account's existing login token before its new authorization state is shown as usable.
- Preserve the existing fixed roles and server-side authorization checks; a developer remains the highest-privilege role but must reauthenticate after an authorization change.
- Use one authenticated request path for device GET, multipart create/update, and delete operations, including cookies and CSRF headers for every write.
- Enforce the existing administrator-only device rule in the backend for device rows, lookups, and controlled image reads.
- Remove stale browser identity on 401 and reacquire CSRF input if its Cookie is absent.
- Return a safe, actionable distinction between expired authentication, CSRF failure, and ordinary authorization denial.
**Non-Goals:**
- Do not add a database migration, create more developer accounts, weaken CSRF, or change device asset fields, upload validation, or response DTOs.
- Do not add self-service role changes or browser-stored login tokens.
- Do not modify the enterprise-WeChat or phone asset business rules.
## Decisions
### 1. Invalidate on every authorization-state mutation
`SystemUserAdminService.updateUser` will compare the persisted and requested role, status, and normalized page permissions. If any effective authorization value changes, it will increment `auth_version` in the same database update. `AuthTokenFilter` already rejects a token whose version differs from the database value, so the next request with that old cookie returns 401 and the user must sign in again.
`auth_version`(登录票据版本号:服务端用来立即作废旧登录票据的整数) is used because the role embedded in a signed JWT cannot safely be altered in-place. Relying only on `/api/auth/me` would keep the UI and API authorization sources inconsistent; rereading the role from the database on each request would reduce this mismatch but would not reliably revoke sessions after status or page-permission changes.
### 2. Reuse the existing authenticated request helper for multipart safely
Exported `request` in `auth-api-client.js` will remain the single browser request entry point. It will preserve caller headers, request CSRF only for non-GET non-login methods, and omit `Content-Type` when the body is `FormData` so the browser can supply the multipart boundary.
`device-api-client.js` will call this helper for list, lookup, create, update, delete, and image access requests as appropriate. The alternative of disabling CSRF for device endpoints is rejected because it would permit forged writes from a third-party page that can use a user's login cookie.
### 3. Keep safe 401/403 diagnostic classes
The security error handler will retain generic authorization wording for ordinary role/page denial, return a session-expired/login-required message for an invalidated token, and return a refresh-and-retry message for missing or invalid CSRF input. It will not reveal account existence, target roles, permission maps, token values, or internal exception details.
CSRF(跨站请求伪造防护:要求浏览器把服务端发出的随机值同时放在 Cookie 和请求头中) remains mandatory for all non-login writes. Without it, another website could submit a write using the user's ambient login Cookie.
### 4. Make administrator-only device access a backend rule
`PagePermissionService` will expose one reusable administrator requirement that reads the authenticated principal and accepts only `DEVELOPER` and `SUPER_ADMIN`. `DeviceAssetController` will call it before every list, lookup, image-read, create, update, and delete endpoint. This keeps the approved device access model unchanged while making the API enforce it.
Adding a new device entry to the configurable page-permission map was considered but rejected for this change. The approved product behavior is administrator-only; adding `READ`/`EDIT` assignment for ordinary roles would be a product-scope expansion and must be proposed separately.
### 5. Expire the browser view and verify actual CSRF Cookie presence
When the shared request helper receives a 401, it will signal the authentication state to clear the in-memory user and redirect to `/login`; it will not retry a potentially non-idempotent write. The CSRF helper will treat the Cookie as the source of truth: it skips initialization only when `XSRF-TOKEN` is actually present, otherwise it requests `/api/auth/csrf` again.
### 6. Test the real failure paths before declaring the fix
Backend tests will prove that a promoted user token becomes invalid, a fresh developer token can manage users, and a non-developer token remains denied. Browser tests will verify that the first device write initializes CSRF, sends cookies and the token header, preserves multipart boundaries, and shows the distinct safe error messages.
## Risks / Trade-offs
- [An administrator changes their own authorization state] -> Their next request becomes 401 by design; the frontend redirects to login with a clear reauthentication message.
- [A change increments `auth_version` unnecessarily] -> Compare the persisted role, status, and normalized permission JSON before incrementing; no-op edits retain the current session.
- [Setting JSON content type for multipart] -> Detect `FormData` and let the browser add the boundary; otherwise uploads would reach the server as malformed data.
- [Detailed 403 messages disclose security state] -> Restrict messages to three generic client actions: login again, refresh/retry, or no permission.
- [A logged-in ordinary user bypasses the device route] -> Require an administrator in every device controller endpoint, including the opaque image endpoint.
- [The CSRF Cookie disappears while the JavaScript flag remains true] -> Test Cookie absence and reacquisition rather than trusting an in-memory readiness flag.
- [Concurrent account updates] -> Use the existing single-row update path and increment from the persisted version; the current project has no optimistic-lock column, so concurrent administrator updates remain outside this targeted fix.
## Migration Plan
1. Deploy the backend and frontend together; no data migration or schema change is required.
2. Existing sessions continue until a managed account's authorization state changes. That change invalidates its previous session at the next protected request.
3. Verify a device create, multipart device update, device image read, phone write, enterprise-WeChat write, and system-user write using a fresh developer login; verify an ordinary user cannot call any device endpoint.
4. Roll back by reverting the application changes. Existing `auth_version` values and asset data remain intact; already-invalidated sessions simply need a new login.
## Open Questions
None. Reauthentication after a role, status, or page-permission change is the selected security behavior.
## Why
An account promoted to `DEVELOPER` can receive full permissions from `/api/auth/me` while its already-issued login token still contains the prior role. The UI then identifies the user as an administrator but protected writes, such as `POST /api/system-users`, return 403. Separately, device-asset writes bypass the existing CSRF protection flow, and the device controller relies on a frontend-only administrator route so any authenticated user can call its CRUD endpoints directly.
## What Changes
- Invalidate an existing login session whenever a managed account's role, enabled status, or page permissions change. The next protected request must require a fresh login so the token role and database role cannot diverge.
- Keep the current rule that `DEVELOPER` and `SUPER_ADMIN` are administrators; do not relax role checks or allow creation of extra developer accounts.
- Route every device-asset write request through the shared authenticated request helper so it includes cookies and the CSRF header, including multipart `POST` and `PUT` requests.
- Enforce the approved administrator-only device access rule in the backend for list, lookup, image read, create, update, and delete endpoints; do not rely on hidden menus or route metadata as authorization.
- Clear local browser identity and redirect to login after a 401 session-invalid response. Reacquire CSRF input whenever its Cookie is absent instead of trusting an in-memory initialization flag.
- Make 403 verification distinguish an authorization failure from a missing/invalid CSRF token in automated tests and developer diagnostics, without exposing sensitive account information.
## Capabilities
### New Capabilities
- `authorization-session-consistency`: Keeps the role and permissions used by a protected API request consistent with the currently effective account state.
- `authenticated-device-writes`: Makes device-asset create, update, and delete requests use the same authenticated CSRF-safe request flow as the existing asset modules.
### Modified Capabilities
- None. The related authentication and device specifications are still in unarchived changes rather than the repository's main OpenSpec specification set; this change records the corrective requirements as standalone capabilities.
## Impact
- Backend: `SystemUserAdminService`, `PagePermissionService`, `DeviceAssetController`, authentication-token filtering, and focused authorization tests. No database schema change or migration is required because `auth_version` already exists.
- Frontend: the shared auth request utility and state, device API client, and browser tests. The page layout, role names, and asset data contract remain unchanged.
- Security: CSRF(跨站请求伪造防护:阻止第三方网站借用登录 Cookie 发起写操作) continues to protect every non-login write. Not preserving it would make the 403 disappear at the cost of allowing forged writes.
## ADDED Requirements
### Requirement: Device write requests use the shared authenticated CSRF flow
The browser SHALL send device-asset create, update, and delete requests through the shared authenticated request helper. It MUST include login cookies and obtain/send the CSRF header for each protected non-login write. Multipart create and update requests MUST allow the browser to set the multipart boundary and MUST NOT force a JSON content type.
#### Scenario: Create a device with an image
- **WHEN** an authenticated administrator submits a device create form with a valid image
- **THEN** the client obtains CSRF input when needed, sends the login cookie and CSRF header with multipart form data, and the backend receives a valid device create request
#### Scenario: Update a device with multipart data
- **WHEN** an authenticated administrator edits a device name, image, or remove-image flag
- **THEN** the client sends the protected multipart update without overriding the browser-generated multipart boundary
#### Scenario: Missing device CSRF input
- **WHEN** a device write is attempted without a valid CSRF token
- **THEN** the backend rejects it before asset or file mutation and the browser shows the safe refresh-and-retry message
### Requirement: Device read contract remains compatible
The device list, company-person lookup, and controlled opaque image access SHALL retain their existing request paths and response shapes. This change MUST NOT expose physical file paths, disable reference-protected deletion, or relax the existing image validation rules.
#### Scenario: Read device list after security fix
- **WHEN** an authenticated authorized user loads the device asset page
- **THEN** the client receives the existing paged list shape and renders it without a write-oriented CSRF requirement
### Requirement: Device APIs enforce administrator access on the server
The system SHALL require an authenticated `DEVELOPER` or `SUPER_ADMIN` before executing device list, company-person lookup, controlled image read, create, update, or delete operations. A non-administrator MUST receive authorization denial even when directly calling the API without using the sidebar or route.
#### Scenario: Ordinary account bypasses the device page route
- **WHEN** an authenticated non-administrator directly sends `POST`, `PUT`, or `DELETE` to `/api/device-assets`
- **THEN** the system returns authorization denial and does not write device rows or files
#### Scenario: Ordinary account reads a device attachment URL
- **WHEN** an authenticated non-administrator requests `/api/device-assets/files/{identifier}`
- **THEN** the system returns authorization denial and does not stream the image
#### Scenario: Administrator uses any device endpoint
- **WHEN** an authenticated `DEVELOPER` or `SUPER_ADMIN` calls a device list, lookup, image, or CRUD endpoint with otherwise valid input
- **THEN** the server permits the request to continue to the existing device service behavior
## ADDED Requirements
### Requirement: Authorization-state changes revoke stale login sessions
The system SHALL increment `auth_version` whenever a managed account's effective role, enabled status, or validated page-permission map changes. A token whose embedded version differs from the current account version MUST be rejected before a protected controller or service method executes.
#### Scenario: Promoted account uses an old token
- **WHEN** an active account is changed from a non-administrator role to `DEVELOPER` or `SUPER_ADMIN` while it still holds an earlier login token
- **THEN** its next protected request is rejected as requiring a fresh login, and a new login receives a token with the current role
#### Scenario: Permission map changes without a role change
- **WHEN** an administrator changes an account's validated page permissions or enabled status
- **THEN** the prior token is rejected on its next protected request and the account's next login uses the new effective permissions and status
#### Scenario: No-op account edit
- **WHEN** an administrator saves an account with the same effective role, status, and validated page permissions
- **THEN** the account's `auth_version` is unchanged and its current session remains valid
### Requirement: Protected write failures provide safe actionable categories
The system SHALL preserve generic authorization denial for a valid authenticated user without the required role or page permission. It SHALL return a login-required response for an invalidated or absent session, and a refresh-and-retry response for a missing or invalid CSRF token. Responses MUST NOT disclose token values, account existence, roles, or permission maps.
#### Scenario: Valid non-administrator creates an account
- **WHEN** a valid non-administrator token submits `POST /api/system-users`
- **THEN** the system returns a generic authorization-denied response and does not write an account row
#### Scenario: Browser omits CSRF input
- **WHEN** an authenticated browser sends a protected non-login write without a valid CSRF token
- **THEN** the system returns a safe refresh-and-retry response and does not execute the controller business method
### Requirement: Browser session state follows authentication failure
The shared browser request helper SHALL clear the in-memory authenticated user and navigate to the login route when a protected request returns 401. It MUST NOT automatically repeat the failed request. It SHALL request CSRF input again whenever the `XSRF-TOKEN` Cookie is absent for a protected non-login write.
#### Scenario: Authorization change invalidates the active browser session
- **WHEN** an already-open browser sends a protected request with a token invalidated by an authorization-state change
- **THEN** the browser clears its local identity, navigates to login, and does not retry the failed write
#### Scenario: CSRF Cookie is missing after prior initialization
- **WHEN** a protected browser write begins after the `XSRF-TOKEN` Cookie has been removed or expired
- **THEN** the client requests fresh CSRF input before sending the write
## 0. Scope, ownership, and file-purpose annotations
| File | File purpose (plain language) | Ownership / collision boundary |
|---|---|---|
| `backend/src/main/java/com/xyw/console/auth/SystemUserAdminService.java` | Changes an account and invalidates its old login only when effective authorization changes. | Authentication change; coordinate with current account-permission work. |
| `backend/src/main/java/com/xyw/console/auth/PagePermissionService.java` | Provides reusable server-side administrator verification for the protected device API. | Authentication change; preserve existing page-permission semantics. |
| `backend/src/main/java/com/xyw/console/config/SecurityConfig.java` | Maps security-layer authentication, CSRF, and authorization failures to safe browser responses. | Authentication change; do not relax CSRF or global protection. |
| `backend/src/main/java/com/xyw/console/asset/controller/DeviceAssetController.java` | Requires an administrator before list, lookup, image, and CRUD device requests reach the service. | Device integration; preserve service and DTO contracts. |
| `backend/src/test/java/com/xyw/console/auth/SystemUserAdminServiceTest.java` | Proves version changes and account-management role boundaries without a live database. | New focused test file. |
| `backend/src/test/java/com/xyw/console/config/SecurityConfigTest.java` | Proves safe response categories for expired sessions, CSRF failures, and ordinary permission denial. | New focused test file. |
| `backend/src/test/java/com/xyw/console/asset/controller/DeviceAssetControllerTest.java` | Proves administrators pass and ordinary accounts cannot bypass device API protection. | Extend existing device test file. |
| `frontend/src/modules/auth/auth-api-client.js` | Is the shared browser helper that sends cookies, fetches CSRF input, and preserves multipart requests. | Authentication change; all API clients depend on it. |
| `frontend/src/modules/auth/auth-store.js` | Clears browser identity and navigates to login after a shared request reports an invalid session. | Authentication change; avoid circular module dependencies. |
| `frontend/src/modules/device/device-api-client.js` | Sends device list, lookup, CRUD, and image requests through the shared authenticated helper. | Device integration only; do not change device view behavior. |
| `frontend/tests/auth-session-consistency.spec.js` | Exercises stale-session, CSRF, and multipart browser request behavior. | New focused browser test. |
| `frontend/tests/device-asset.spec.js` | Extends device workflow tests with real request-header and multipart-boundary assertions. | Device integration test; preserve existing scenarios. |
- [x] 0.1 Confirm the active authentication and device working-tree changes, preserve unrelated edits, and designate one integration owner for the four shared files above. Integration owner: current authorization/session change.
- [x] 0.2 Add a plain-language file-purpose annotation to each new test file. For every method, callback, or helper added or changed in the scoped files, add a beginner comment describing its purpose, related files, and request-to-security-to-response data flow.
- [x] 0.3 Do not run a database migration or modify `as_system_user` schema; verify `auth_version` is already readable and writable before implementation.
## 1. Authorization-session consistency
- [x] 1.1 Add focused helpers in `SystemUserAdminService` that compare persisted and requested role, status, and normalized page permissions, and calculate the next `auth_version` safely from the persisted value.
- [x] 1.2 Update `SystemUserAdminService.updateUser` so a true authorization-state change increments `auth_version` in the same row update, while a no-op update does not invalidate the current session.
- [x] 1.3 Keep existing developer/super-administrator creation boundaries unchanged; a fresh `DEVELOPER` token can manage accounts, a valid non-administrator token remains denied, and no additional developer account can be created.
- [x] 1.4 Update `SecurityConfig` to return safe distinct messages for invalidated/absent login sessions, missing-or-invalid CSRF input, and valid-session authorization denial, without returning account, role, permission, token, or exception details.
- [x] 1.5 Add and annotate a reusable administrator requirement in `PagePermissionService`; apply it in every `DeviceAssetController` endpoint, including company-person lookup and opaque image reads, while preserving the approved administrator-only device scope.
## 2. Shared device write authentication
- [x] 2.1 Update `auth-api-client.js` request handling to preserve caller headers, send credentials, initialize CSRF for protected writes, and omit a forced JSON `Content-Type` when the request body is `FormData`.
- [x] 2.1a Change CSRF initialization to verify the real `XSRF-TOKEN` Cookie instead of trusting an in-memory ready flag, and have a 401 signal `auth-store.js` to clear local identity and navigate to login without repeating the request.
- [x] 2.2 Refactor every `device-api-client.js` operation to use the shared authenticated request helper; retain existing URLs, query parameter behavior, and API response shape.
- [x] 2.3 Verify device multipart create and update retain the browser-generated boundary, and device delete includes the CSRF header and cookie.
- [x] 2.4 Ensure a 401 session-invalid response clears local authentication state and leads the user to sign in again; do not silently retry a write that might repeat a user action.
## 3. Focused verification
- [x] 3.1 Add `SystemUserAdminServiceTest` coverage for role promotion, status change, permission change, no-op update, developer management success, and non-administrator denial. Annotate every test method and business callback.
- [x] 3.2 Add `SecurityConfigTest` coverage for 401 expired-session handling, CSRF 403 handling, ordinary authorization 403 handling, and the absence of sensitive details. Extend `DeviceAssetControllerTest` with administrator allow and ordinary-user denial coverage for list, lookup, image, and all CRUD operations. Annotate every test method and callback.
- [x] 3.3 Add browser coverage for the first device write obtaining CSRF, Cookie-absence reacquisition, multipart request header/boundary behavior, device delete CSRF behavior, 401 login redirect, and the safe refresh-and-retry message. Annotate every test method and route callback.
- [x] 3.4 Run `mvn -q test`, `npm run build`, focused authentication/device Playwright tests, and the existing full frontend Playwright suite. Diagnose a failing check before changing code.
- [x] 3.5 Record changed files, no-database-impact confirmation, security behavior, executed verification, and any unexecuted check in the completion handoff.
## Completion handoff (2026-08-03)
### Changed files
- Backend authorization: `SystemUserAdminService`, `PagePermissionService`, `SecurityConfig`, and `DeviceAssetController`.
- Backend tests: `SystemUserAdminServiceTest`, `SecurityConfigTest`, and extended `DeviceAssetControllerTest`.
- Frontend authentication/device requests: `auth-api-client.js`, `auth-store.js`, and `device-api-client.js`.
- Frontend tests: `authenticated-test.js` and `device-asset.spec.js`.
### Security behavior delivered
- Account role, status, or normalized page-permission changes increment `auth_version`; the previous token fails on its next protected request and the browser returns to login.
- Device list, lookup, opaque image read, create, update, and delete operations require `DEVELOPER` or `SUPER_ADMIN` in the backend, not only a protected route or hidden menu.
- Device multipart writes use the shared authenticated CSRF request helper. The helper preserves browser multipart boundaries, reacquires a missing `XSRF-TOKEN` Cookie, and does not retry a rejected write.
- CSRF rejection reports safe refresh-and-retry guidance; valid-session authorization denial remains generic.
### Database and configuration impact
- No database migration, DDL, direct database write, or configuration-file change was performed. The existing `auth_version` entity field and migration definition are reused.
### Executed verification
- `mvn -q -Dtest=SystemUserAdminServiceTest,SecurityConfigTest,DeviceAssetControllerTest test` passed.
- `mvn -q test` passed.
- `npm run build` passed, retaining only existing third-party PURE-comment and bundle-size warnings.
- `npx playwright test tests/device-asset.spec.js --config=playwright.device.config.js --reporter=list --timeout=30000` passed: 6/6.
- `npx playwright test --config=playwright.device.config.js --reporter=list --timeout=30000` passed: 14/14.
- `openspec validate fix-auth-session-and-device-csrf --strict` passed.
### Unexecuted checks
- No live production write was performed. After backend restart, manually log out and log in once, then verify account creation and device create/update/delete using the developer account.
## Context
登录页面已经使用黑洞背景和玻璃面板,但最新截图显示两个问题:其一,Chrome/Edge 的自动填充在停留或重绘后仍会向原生 `<input>` 绘制浅色内部矩形;其二,现有面板仍偏左、按钮蓝色过亮,与用户最新提供的目标图不一致。该变更只能调整前端登录页面视觉,不得改变已有登录数据流。
## Goals / Non-Goals
**Goals:**
- 让账号、密码输入框在普通、获得焦点、已自动填充和页面停留后保持同样的深色透明视觉,不再出现浅灰或白色内层矩形。
- 使用实际 Chrome/Edge 验证最终自动填充效果,而不是只依赖构建结果。
- 以最新参考图为基准校准 1920px 桌面布局:面板位于约 19% 视口宽度处,卡片右边缘接近黑洞左侧发光区域但不遮挡亮环。
- 降低主按钮亮度,改为低饱和的深蓝紫渐变,并保留可辨识的主操作状态。
- 在 1200px、768px 断点保留安全边距和居中回退。
**Non-Goals:**
- 不修改登录接口、表单字段、账号记忆的存储键、Token、路由跳转、权限或后端代码。
- 不增加新的第三方依赖或独立演示页面。
## Decisions
### 使用“覆盖浏览器绘制层”而非超长过渡
`input:-webkit-autofill` 同时设置文本颜色、背景裁剪和与字段玻璃层一致的极低透明 inset shadow,并删除 `9999s` 背景过渡。长过渡只能延后浏览器浅色背景出现,不能阻止其重绘;即时覆盖才能在停留后保持稳定。
备选方案是给输入本体设置不透明深色底色。该方案可以隐藏白底,但会破坏用户要求的透明玻璃效果,因此不采用。
### 字号由输入本体统一控制
普通、聚焦和自动填充选择器必须使用同一个显式字号与行高。浏览器自动填充状态不再单独继承或覆盖字号,避免首次进入和点击后视觉跳变。
### 按最新参考图重新校准面板位置与色彩
桌面端将以参考图的约 19% 视口左距作为视觉基准,并使用 `clamp` 计算而非单一像素值;1200px 以下缩小偏移,768px 以下居中。按钮采用暗蓝紫低饱和渐变和轻微边缘高光,避免当前浅蓝按钮脱离黑洞背景。
此前的 `left: clamp(140px, 12.5vw, 240px)` 规则不再作为最终桌面视觉基准,因为最新参考图明确要求更靠右的位置。
## Risks / Trade-offs
- [Chrome 与 Edge 版本对自动填充伪类的绘制差异] → 在两个浏览器中实际触发已保存账号密码并等待重绘后截图验证;必要时采用与外层字段一致的极低透明内阴影作为兼容回退。
- [卡片右移后可能与黑洞亮环重叠] → 以 1920×1080、1440×900、1366×768 三个视口进行截图比对,按参考图微调背景定位和 `clamp` 区间。
- [按钮变暗降低可见性] → 保持白色文字、明确边框与轻微悬停提亮,不牺牲可读性。
## Why
当前登录页在 Chrome/Edge 保存账号密码后,浏览器自动填充会在输入框内注入浅色矩形;现有超长背景过渡又使该矩形在延迟重绘后暴露。与此同时,当前卡片位置和高亮蓝色按钮未达到最新参考图的深色、融入黑洞背景的效果,需要以该参考图作为视觉验收标准。
## What Changes
- 修复 Chrome/Edge 自动填充在页面停留或再次聚焦后出现的浅灰、白色输入框内层背景,并固定各状态的文字尺寸。
- 以最新参考图校准登录卡片的桌面端位置、尺寸、透明玻璃层级和黑洞背景关系;卡片接近黑洞左侧发光区域,但不遮挡最亮环带。
- 将登录按钮从高亮浅蓝改为与背景协调的低饱和深蓝紫渐变,同时保留清晰的主要操作层级。
- 保留当前登录接口、表单输入、账号记忆、Token、状态管理、跳转和权限逻辑。
## Capabilities
### New Capabilities
- `login-visual-stability`: 定义登录页面在浏览器自动填充、焦点变化和不同视口下保持稳定的输入框与视觉布局要求。
### Modified Capabilities
- 无。
## Impact
- 受影响前端文件:`frontend/src/styles/app.css`;如需补充仅用于样式状态的 class,再调整 `frontend/src/modules/auth/LoginView.js`,不改变其业务方法和接口。
- 不影响后端 API、数据库、依赖、鉴权或权限规则。
## ADDED Requirements
### Requirement: 自动填充输入框保持透明稳定
系统 SHALL 使登录页账号和密码输入框在 Chrome/Edge 的普通、聚焦、自动填充及页面停留后的重绘状态下,保持深色透明玻璃外观,不得显示浅灰、白色或浅蓝色内部矩形背景。
#### Scenario: 已保存账号被浏览器自动填充
- **WHEN** 用户打开登录页面,Chrome 或 Edge 自动填入已保存的账号和密码
- **THEN** 输入文字保持可读,输入框内部不出现浅色矩形,外层玻璃边框与背景持续可见
#### Scenario: 自动填充页面停留后重绘
- **WHEN** 自动填充后的登录页保持打开状态并发生浏览器重绘或用户再次聚焦输入框
- **THEN** 输入框外观与首次打开时一致,字号和行高不发生变化
### Requirement: 登录页面匹配黑洞视觉基准
系统 SHALL 在桌面端把登录卡片放置在黑洞左侧区域并与背景融合,卡片不得贴近页面左边缘或遮挡黑洞最亮环带;登录按钮 SHALL 使用低饱和深蓝紫主操作样式而非高亮浅蓝色。
#### Scenario: 1920×1080 桌面视口
- **WHEN** 用户以 1920×1080 打开登录页
- **THEN** 卡片左侧约位于视口宽度 19% 的区域,卡片右边缘接近但不覆盖黑洞亮环,按钮与深色背景协调且文字清晰
#### Scenario: 窄桌面与移动端回退
- **WHEN** 视口宽度低于 1200px 或低于 768px
- **THEN** 卡片分别使用收缩后的安全左边距或水平垂直居中布局,且页面不出现横向滚动条
## 1. 输入框自动填充稳定性
- [x] 1.1 在 `frontend/src/styles/app.css` 移除登录输入框自动填充的超长背景过渡,并为普通、聚焦与自动填充状态统一字号、行高、文本颜色和即时透明覆盖规则。
- [ ] 1.2 在 Chrome 与 Edge 中使用已保存账号密码打开登录页,等待页面停留并重复聚焦输入框,确认不存在浅灰、白色或浅蓝色内部矩形。
## 2. 参考图布局与视觉层级
- [x] 2.1 在 `frontend/src/styles/app.css` 将登录卡片桌面端左距校准到最新参考图约 19% 视口宽度,并以 `clamp` 和 1200px、768px 媒体查询保证小屏安全布局。
- [x] 2.2 将登录按钮调整为低饱和深蓝紫渐变、轻微边框与受控悬停高光,使其与黑洞背景协调。
- [x] 2.3 在 1920×1080、1440×900、1366×768、平板和手机视口截图核对:卡片不遮挡黑洞亮环,页面无横向滚动条。
## 3. 验证
- [x] 3.1 运行 `npm run build`,修复本次样式变更引起的构建错误。
## Context
`origin/master` at `27c7b6d` contains device, phone, and WeCom management but not the authentication module. The source chain is `1cd0087`, `9d5953f`, `3f7c655`, `52071f0`, and `7a452f3`; `52071f0` changes only a historical OpenSpec document and is intentionally excluded under the approved documentation policy.
### Scope-file purpose notes
| Files | Purpose |
|---|---|
| `backend/src/main/java/com/xyw/console/auth/**` and `config/{SecurityConfig,WebConfig}.java` | Login, token/session validation, authorization, system-user administration, and HTTP security policy. |
| `backend/src/main/resources/db/migration/V1__system_user_auth_permissions.sql` | Adds permission/session fields required by authentication. |
| `backend/src/main/java/com/xyw/console/asset/**` changed files | Applies authentication and CSRF checks to existing asset APIs. |
| `frontend/src/modules/auth/**`, `router/index.js`, `main.js`, `App.js`, `index.html` | Login UI, browser state, first-load route decision, and neutral startup display. |
| Changed device, phone, WeCom, system-user frontend files and test files | Keep protected workspaces compatible and prove regressions are avoided. |
| `openspec/changes/integrate-authentication-to-master/**` | Sole planning record for this integration. |
## Goals / Non-Goals
**Goals:**
- Use a neutral first paint while the router validates the session.
- Send unauthenticated visitors to login without showing a protected shell.
- Keep a network failure at the requested URL with a retry path.
- Preserve asset management workflows for permitted users.
**Non-Goals:**
- Do not push, merge to `master`, deploy, or run a database migration without a further user approval.
- Do not include historical source-branch OpenSpec records.
- Do not redesign unrelated asset features.
## Decisions
1. Integrate four runtime commits on a new branch based on `origin/master`; omit `52071f0` because it affects only excluded historical documentation.
2. The router owns the initial current-user decision. The API client handles later expired sessions, avoiding competing redirects.
3. `index.html` provides a neutral boot screen and `main.js` waits for router readiness, avoiding the dashboard-to-login flash.
4. Backend and frontend are a paired release. A database without production data lowers data-risk now, but the inserted administrator's password hash, role, and page permissions must match backend rules before release.
5. Every new or modified method, object method, arrow function, and business callback in scope retains a plain-language comment describing its purpose, related files, and call/data flow.
## Risks / Trade-offs
- [Administrator cannot log in] -> verify password hash, administrator role, and page permissions against the implemented backend before release.
- [Asset regression] -> retain current `master` behavior and run device, phone, WeCom, and frontend regression suites.
- [Refresh flash returns] -> preserve the neutral boot contract and run the dedicated browser checks.
- [Session/CSRF mismatch] -> test authenticated and rejected writes in backend and browser suites.
- [Future production migration] -> approve migration execution separately; application rollback must not blindly delete user/role/permission data.
## Migration Plan
1. Use the isolated integration branch and verify its test evidence.
2. Before any release, insert a verified administrator using the backend-compatible password hash, role, and permissions.
3. Review and run the database migration only after explicit approval.
4. Deploy matching backend and frontend artifacts together.
5. If authentication or core asset behavior fails, roll back application artifacts together; preserve database data for review.
## Open Questions
1. Which exact administrator username, password hash process, role, and page permissions will be inserted before the first release?
2. When the branch is reviewed, do you approve a remote push and a new MR? Those actions remain out of scope for the current execution.
## Why
Authentication is a dependent feature chain outside `master`. The refresh-only fix cannot work on its own because `master` lacks the login, session, permission, and database code it needs.
## What Changes
- Integrate the authentication foundation, session/CSRF correction, login UI, and refresh redirect fix on a branch based on current `master`.
- Retain current device, phone, and WeCom asset behavior while adding authentication and page permissions.
- Exclude historical source-branch OpenSpec records; retain only this integration plan.
- **BREAKING**: management pages require a valid authenticated session after release.
## Capabilities
### New Capabilities
- `authentication-master-integration`: safe integration, verification, and release readiness for the full authentication feature.
### Modified Capabilities
<!-- None. -->
## Impact
- Backend authentication, authorization, CSRF protection, system-user data, and the related migration.
- Frontend login, startup routing, protected asset workspaces, and refresh behavior.
- Release readiness requires a valid administrator account and matching frontend/backend artifacts.
## ADDED Requirements
### Requirement: Complete runtime authentication integration
The integration SHALL include the authentication foundation, session/CSRF correction, login experience, and refresh redirect fix as one feature branch based on current `master`. Historical source-branch OpenSpec records MUST NOT be present in the final integration diff.
#### Scenario: Integration branch is prepared
- **WHEN** maintainers inspect the integration branch
- **THEN** it contains the required runtime code and tests but excludes historical source-branch OpenSpec records
### Requirement: Safe initial routing
The frontend SHALL show an identity-neutral startup state until initial session validation completes. The router MUST own the initial current-user route decision. An unauthenticated visitor MUST NOT see a protected workspace before reaching login.
#### Scenario: Unauthenticated protected refresh
- **WHEN** an unauthenticated visitor refreshes a protected URL
- **THEN** the neutral boot state is shown before the login route, without a dashboard shell or dashboard-shaped placeholder
#### Scenario: Initial network failure
- **WHEN** initial current-user validation fails from a network error rather than a 401 response
- **THEN** the browser remains on the requested URL and provides a retry state
### Requirement: Protected asset compatibility
Permitted authenticated users SHALL retain their device, phone, WeCom, and system-user workflows. Unauthenticated or unauthorized protected writes MUST be rejected by the backend security policy.
#### Scenario: Permitted protected write
- **WHEN** a permitted signed-in user performs an allowed asset write with valid CSRF data
- **THEN** the backend accepts the operation
#### Scenario: Rejected protected write
- **WHEN** a visitor lacks a valid session or required permission for a protected write
- **THEN** the backend rejects the operation and the frontend does not report success
## 1. Integration preparation
- [x] 1.1 Confirm `origin/master` base `27c7b6d` and source commits `1cd0087`, `9d5953f`, `3f7c655`, `52071f0`, and `7a452f3`.
- [x] 1.2 Confirm that the database has no production data, no release is scheduled, and the first administrator will be inserted directly into the database before release.
- [x] 1.3 Create an isolated branch from current `master` without changing source branches or the user's dirty worktree.
- [x] 1.4 Exclude historical source-branch OpenSpec records; retain only this integration plan.
## 2. Runtime integration
- [x] 2.1 Apply `1cd0087`, `9d5953f`, `3f7c655`, and `7a452f3` in dependency order.
- [x] 2.2 Omit `52071f0` because it changes only historical documentation excluded by task 1.4.
- [x] 2.3 Confirm no code conflicts occurred with current device, phone, or WeCom changes.
- [x] 2.4 Retain the required beginner comments for new or modified backend and frontend methods/callbacks.
## 3. Verification
- [x] 3.1 Run the backend test suite.
- [x] 3.2 Run the frontend production build.
- [x] 3.3 Run the dedicated login and refresh browser tests.
- [x] 3.4 Run the complete frontend browser regression suite for device, phone, WeCom, and legacy pages.
- [x] 3.5 Review the final staged diff for accidental source documents, secrets, generated files, and omitted runtime files.
## 4. Deferred release actions
- [ ] 4.1 Verify the first inserted administrator's password hash, role, and page permissions against the backend implementation.
- [ ] 4.2 Obtain explicit approval before pushing this branch, creating an MR, merging to `master`, deploying, or running the database migration.
## Context
当前前端 Playwright 配置会启动本机 Vite 服务,资产页测试通过 `authenticated-test.js` 模拟已登录身份;该文件本身不是 `*.spec.js`,因此并不执行登录页、失败提示或退出路由的测试。黑洞背景图片位于 `frontend/img/black_hole.png`,现已由 Vue 导入以供构建输出,但尚未进入 Git 暂存区。
本变更只补充验收与版本控制,不改变登录请求、Cookie、JWT、路由守卫、数据库或后端。
| 范围文件 | 文件用途(白话) | 关联逻辑 | 风险 |
| --- | --- | --- | --- |
| `frontend/tests/login-experience.spec.js` | 实际执行登录页测试 | Playwright → 模拟 `/api/auth/*``LoginView`/路由守卫 | Mock 与真实响应结构不一致会产生假通过 |
| `frontend/tests/authenticated-test.js` | 已登录测试共用的请求模拟 | 资产测试 → `/api/auth/me` 与 CSRF 模拟 → 路由守卫 | 调整后影响既有资产测试 |
| `frontend/img/black_hole.png` | 登录页黑洞背景资源 | Vue 导入 → Vite 构建输出 → 登录页背景 | 未提交会使其他环境缺图 |
| `frontend/src/modules/auth/LoginView.js` | 被测试的现有登录页面 | 表单 → `signIn` → 欢迎提示/跳转 | 本变更不修改业务逻辑 |
## Goals / Non-Goals
**Goals:**
- 用独立可执行的 `*.spec.js` 覆盖未登录重定向、账号密码提交、失败提示、Jeddy 专属提示、退出跳转与黑洞背景资源。
- 使用与生产接口相同的 `ApiResponse` 信封格式(接口统一返回的 `code/message/data` 结构)模拟响应,避免测试绕过真实前端解析逻辑。
- 将背景图片作为代码提交的一部分。
**Non-Goals:**
- 不测试真实数据库密码、不请求真实后端、不写入账号数据。
- 不修改认证实现、视觉设计、路由行为或新增记住账号等功能。
- 不引入新的测试依赖。
## Decisions
### 1. 使用网络拦截而非真实账号登录
Playwright 将拦截 `/api/auth/login``/api/auth/me``/api/auth/logout``/api/auth/csrf`,并按真实接口形状返回数据。
- 选择原因:测试不需要真实 BCrypt 密码、JWT 密钥或数据库,运行可重复且不会污染测试数据。
- 替代方案:连接本地数据库并创建测试账号。未采用,因为会增加密码、迁移和清理失败的风险。
### 2. 登录场景放入独立 `login-experience.spec.js`
让 Playwright 默认匹配文件名并实际执行;现有 `authenticated-test.js` 仅保留为被资产测试导入的辅助模块。
- 选择原因:测试入口清晰,失败报告能直接定位登录体验。
- 替代方案:将辅助文件重命名为测试文件。未采用,因为它本身没有独立断言,重命名会制造空测试。
### 3. 资源检查采用构建产物与源码路径双重断言
测试验证登录页使用背景图的 CSS 变量,构建命令验证 Vite 产出带哈希的图片文件;Git 暂存验证确保资源随提交传播。
- 选择原因:只检查 DOM 无法证明生产包包含图片;只检查 Git 也无法证明页面引用正确。
- 替代方案:把图片转 Base64 写入 CSS。未采用,因为会扩大样式文件且违背资源独立管理要求。
## Risks / Trade-offs
- [Mock 覆盖不到后端协议变化] → 测试严格采用当前 `code/message/data` 信封,并保留后端 Maven 测试作为另一层校验。
- [登录页文案或提示库异步渲染造成脆弱断言] → 断言可见文本和路由结果,不依赖动画时间或内部 DOM 结构。
- [背景图图片体积约 1.9MB] → 不压缩用户提供的高清资源;构建检查确认其正常输出,并在后续性能工作中单独评估。
## Migration Plan
1. 添加测试并将图片加入 Git 暂存区。
2. 执行 `npm run test:e2e``npm run build`
3. 失败时只回退新增测试和资源暂存,不影响认证与后端代码。
## Open Questions
- 无;测试账号响应使用固定非敏感模拟值,Jeddy 场景仅用于前端提示条件验证。
## Why
当前登录页已经具备真实接口、Cookie 会话和视觉改造,但自动化测试只覆盖已登录的资产页:登录、失败提示、`Jeddy` 专属欢迎提示和退出回到登录页尚未被实际执行。黑洞背景图片也仍是未跟踪文件,后续提交时可能遗漏,导致其他环境缺少登录页背景。
## What Changes
- 新增登录体验端到端测试,覆盖未登录跳转、登录成功、失败提示、仅 `Jeddy` 的欢迎提示和退出返回 `/login`
- 让测试明确模拟认证、CSRF 与退出接口,不依赖真实账号或密码,也不修改生产登录逻辑。
-`frontend/img/black_hole.png` 纳入版本控制,并验证生产构建确实输出该资源。
- 为登录页补充小屏幕与背景资源加载的自动化断言。
## Capabilities
### New Capabilities
- `login-experience-verification`: 为真实登录路由、提示、会话退出和黑洞背景资源提供可重复执行的前端验收覆盖。
### Modified Capabilities
- 无。
## Impact
- 受影响文件:`frontend/tests/``frontend/playwright.config.js`(仅当测试配置确有必要时)、`frontend/img/black_hole.png` 与登录页面测试辅助文件。
- 不修改后端接口、数据库、认证状态管理、路由守卫或生产登录业务逻辑。
- 新增测试只访问本机 Vite 服务并拦截其测试请求。
## ADDED Requirements
### Requirement: 登录体验 SHALL 具有可执行端到端覆盖
系统 SHALL 提供由 Playwright 默认测试匹配规则执行的登录体验测试文件;测试 SHALL 不依赖真实数据库、真实密码或外部服务。
#### Scenario: 未登录用户访问受保护路由
- **WHEN** 测试用户直接打开任一受保护业务 Hash 路由且 `/api/auth/me` 返回未认证结果
- **THEN** 路由 SHALL 显示 `/login` 页面,且不展示后台侧栏
#### Scenario: 普通账号成功登录
- **WHEN** 登录接口返回非 Jeddy 的有效用户资料
- **THEN** 页面 SHALL 请求既有登录接口、跳转至 `/overview`,且不显示开发者专属欢迎提示
#### Scenario: Jeddy 成功登录
- **WHEN** 登录接口返回用户名为 `Jeddy` 且角色为 `DEVELOPER` 的有效用户资料
- **THEN** 页面 SHALL 跳转至 `/overview` 并显示“🎉 欢迎系统开发者-BOSS:Jeddy 上线”
#### Scenario: 登录失败
- **WHEN** 登录接口返回非成功响应
- **THEN** 页面 SHALL 保持在登录页、恢复可提交状态,并显示既有通用错误提示
### Requirement: 退出体验 SHALL 不受服务端登出失败阻塞
系统 SHALL 验证退出操作无论登出接口返回成功或失败,均回到 `/login`;该验证不改变现有生产退出业务逻辑。
#### Scenario: 服务端登出失败
- **WHEN** 已登录用户点击退出且 `/api/auth/logout` 返回错误
- **THEN** 前端 SHALL 清空当前身份并导航至 `/login`
### Requirement: 黑洞背景资源 SHALL 随项目可靠交付
登录页 SHALL 使用 `frontend/img/black_hole.png` 作为独立背景资源;生产构建 SHALL 输出对应图片资源,且该文件 SHALL 被纳入版本控制。
#### Scenario: 生产构建包含背景图片
- **WHEN** 在前端目录执行生产构建
- **THEN** 构建输出 SHALL 包含黑洞图片资源,且登录页的背景变量 SHALL 指向由 Vite 处理的图片 URL
#### Scenario: 克隆后的项目加载背景
- **WHEN** 开发者从版本库获取项目并运行前端构建
- **THEN** 不需要手工复制图片,登录页 SHALL 能解析黑洞背景资源
## 0. 实施范围与新手注释门禁
| 文件 | 文件用途注释 | 实施时必须说明的方法/回调 |
| --- | --- | --- |
| `frontend/tests/login-experience.spec.js` | 可执行的登录体验回归测试 | 每个测试、路由模拟和响应回调须说明“代码作用(白话)/关联文件/关联逻辑(调用链/消息链/数据流)” |
| `frontend/tests/authenticated-test.js` | 已登录测试共用认证模拟 | 如修改模拟方法,说明其对资产页路由守卫的影响 |
| `frontend/img/black_hole.png` | 独立黑洞背景资源 | 不适用方法注释;须核对文件路径和 Git 暂存状态 |
| `frontend/src/modules/auth/LoginView.js` | 现有登录业务页面 | 本变更默认不改;若测试可访问性需要调整,每个新方法须写完整新手注释 |
| `frontend/playwright.config.js` | 本机 Playwright 测试入口 | 如修改启动配置,说明浏览器 → Vite → Mock API 的数据流 |
## 1. 登录体验测试
- [x] 1.1 新建 `login-experience.spec.js`,以默认 Playwright 文件规则实际执行,并使用真实 `code/message/data` 响应信封模拟 `/api/auth/me``/api/auth/login``/api/auth/logout``/api/auth/csrf`
- [x] 1.2 覆盖未登录访问受保护路由跳转 `/login`,并断言后台侧栏不显示。
- [x] 1.3 覆盖普通账号成功登录跳转 `/overview` 且不显示 Jeddy 专属提示。
- [x] 1.4 覆盖 Jeddy 成功登录显示专属欢迎提示,及登录失败保持登录页、恢复按钮状态和显示通用提示。
- [x] 1.5 覆盖登出接口失败时仍然回到 `/login`,防止退出请求错误阻塞前端跳转。
## 2. 背景资源交付
- [x] 2.1 将 `frontend/img/black_hole.png` 加入 Git 暂存区,确认不是被 `.gitignore` 排除的本地临时文件。
- [x] 2.2 在测试中断言登录页存在黑洞背景 CSS 变量或已解析背景 URL,避免页面回退为纯色背景。
- [x] 2.3 执行生产构建,确认 `dist/assets/` 中存在 Vite 输出的黑洞图片资源,且不出现资源路径解析警告。
## 3. 验收
- [x] 3.1 执行 `npm run test:e2e`,输出通过数与失败数;失败时保留错误证据并先分析原因再修复。
- [x] 3.2 执行 `npm run build``git diff --check`,记录构建结果、既有警告和空白检查结果。
- [x] 3.3 执行一次本机浏览器 smoke:登录页背景、表单占位文字、密码显示按钮、加载状态与窄屏不溢出。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment