Compare commits

..
Author SHA1 Message Date
QiuSW 5e5aa48cf9 docs: complete task 44 acceptance archive (#44) 2026-08-13 09:32:37 +08:00
QiuSW 21624f6c55 docs: create task 44 acceptance archive (#44) 2026-08-13 09:30:07 +08:00
QiuSW c4a799c99c test: cover Sense administrator navigation (#44) 2026-08-13 09:29:00 +08:00
QiuSW 62ed5cc1ce docs: add Sense empty navigation troubleshooting (#44) 2026-08-13 09:27:45 +08:00
QiuSW 048a6ec969 fix: restore Sense permission navigation (#44) 2026-08-13 09:25:26 +08:00
QiuSW cb3015a5b1 docs: complete task 42 acceptance archive (#42) 2026-08-13 09:21:17 +08:00
QiuSW 245d38d471 docs: create task 42 acceptance archive (#42) 2026-08-13 09:19:03 +08:00
QiuSW 88dcebc3fb docs: record length-only Sense password policy (#42) 2026-08-13 09:16:42 +08:00
QiuSW 8091fbb89c feat: simplify Sense password policy (#42) 2026-08-13 09:14:14 +08:00
QiuSW 5e6b5fa0d9 docs: complete task 40 acceptance archive (#40) 2026-08-13 09:01:23 +08:00
QiuSW 4c4b11470d docs: create task 40 acceptance archive (#40) 2026-08-13 08:58:38 +08:00
QiuSW 2d501eabae docs: record Sense six-character password policy (#40) 2026-08-13 08:57:00 +08:00
QiuSW f841ef1913 feat: lower Sense password minimum to six characters (#40) 2026-08-13 08:51:35 +08:00
QiuSW 8b3d814628 docs: complete task 37 acceptance archive (#37) 2026-08-12 18:34:36 +08:00
QiuSW 6d35fa7514 docs: create task 37 acceptance archive (#37) 2026-08-12 18:30:37 +08:00
QiuSW efb48b4030 docs: document Sense Windows package workflow (#37) 2026-08-12 18:28:22 +08:00
QiuSW e80180e2b6 build: add Sense Windows package script (#37) 2026-08-12 18:19:08 +08:00
32 changed files with 664 additions and 538 deletions
+15
View File
@@ -53,3 +53,18 @@ corepack pnpm@9.15.1 build
The frontend automatically discovers project-local route and store modules.
Feature tickets add one route module without exposing unrelated GoAdmin demo
pages.
## Windows package
With the frozen Go toolchain first in `PATH`, build the backend, frontend and
ZIP archive with one command from the repository root:
```powershell
cmd /c .\Sense\scripts\package-windows.bat
```
The ignored local artifact is `Sense\dist\sense-windows-amd64.zip`. It contains
the Windows amd64 server, compiled UI, empty example configuration, upstream
licenses, a start script and an operations note. Run `start-sense.bat demo` only
for a temporary local preview; production startup requires externally supplied
PostgreSQL and secret environment variables.
+9 -1
View File
@@ -4,4 +4,12 @@ SENSE_DATABASE_MODE=postgres
SENSE_DATABASE_URL=
SENSE_LOG_LEVEL=info
SENSE_UI_STATIC_DIR=
SENSE_IDENTITY_SIGNING_KEY=
SENSE_BOOTSTRAP_TOKEN=
SENSE_COOKIE_SECURE=true
SENSE_CREDENTIAL_KEY=
SENSE_ONVIF_DISCOVERY_IP=
SENSE_MEDIAMTX_BINARY=
SENSE_MEDIAMTX_CONFIG=
SENSE_MEDIAMTX_API=http://127.0.0.1:9997
SENSE_MEDIAMTX_WEBRTC_BASE=http://127.0.0.1:8889
+93
View File
@@ -0,0 +1,93 @@
@echo off
setlocal
for %%I in ("%~dp0..") do set "SENSE_ROOT=%%~fI"
for %%I in ("%SENSE_ROOT%\dist") do set "OUTPUT_ROOT=%%~fI"
for %%I in ("%OUTPUT_ROOT%\sense-windows-amd64") do set "PACKAGE_DIR=%%~fI"
for %%I in ("%OUTPUT_ROOT%\sense-windows-amd64.zip") do set "ZIP_PATH=%%~fI"
for %%I in ("%SENSE_ROOT%\ui\dist") do set "UI_DIST=%%~fI"
if /I not "%PACKAGE_DIR%"=="%SENSE_ROOT%\dist\sense-windows-amd64" (
echo [ERROR] Unsafe package directory: %PACKAGE_DIR%
exit /b 1
)
if /I not "%ZIP_PATH%"=="%SENSE_ROOT%\dist\sense-windows-amd64.zip" (
echo [ERROR] Unsafe ZIP path: %ZIP_PATH%
exit /b 1
)
where go >nul 2>nul || goto :missing_go
for /f "tokens=3" %%V in ('go version') do set "GO_VERSION=%%V"
if not "%GO_VERSION%"=="go1.26.5" (
echo [ERROR] Go 1.26.5 is required, found %GO_VERSION%.
exit /b 1
)
where node >nul 2>nul || goto :missing_node
for /f "delims=" %%V in ('node --version') do set "NODE_VERSION=%%V"
if not "%NODE_VERSION%"=="v22.22.1" (
echo [ERROR] Node.js 22.22.1 is required, found %NODE_VERSION%.
exit /b 1
)
where corepack >nul 2>nul || goto :missing_corepack
for /f "delims=" %%V in ('corepack pnpm@9.15.1 --version') do set "PNPM_VERSION=%%V"
if not "%PNPM_VERSION%"=="9.15.1" (
echo [ERROR] pnpm 9.15.1 is required, found %PNPM_VERSION%.
exit /b 1
)
echo [1/5] Installing frozen frontend dependencies...
pushd "%SENSE_ROOT%\ui" || goto :failed
call corepack pnpm@9.15.1 install --frozen-lockfile || goto :failed_popd
echo [2/5] Building frontend...
call corepack pnpm@9.15.1 build || goto :failed_popd
popd
if exist "%PACKAGE_DIR%" rmdir /s /q "%PACKAGE_DIR%"
if exist "%ZIP_PATH%" del /q "%ZIP_PATH%"
mkdir "%PACKAGE_DIR%\config" || goto :failed
echo [3/5] Building Windows backend...
set "CGO_ENABLED=0"
set "GOOS=windows"
set "GOARCH=amd64"
pushd "%SENSE_ROOT%\server" || goto :failed
go build -trimpath -ldflags "-s -w" -o "%PACKAGE_DIR%\sense-server.exe" . || goto :failed_popd
popd
echo [4/5] Assembling package...
robocopy "%UI_DIST%" "%PACKAGE_DIR%\ui" /E /NFL /NDL /NJH /NJS /NP >nul
if errorlevel 8 goto :failed
robocopy "%SENSE_ROOT%\LICENSES" "%PACKAGE_DIR%\LICENSES" /E /NFL /NDL /NJH /NJS /NP >nul
if errorlevel 8 goto :failed
copy /y "%SENSE_ROOT%\config\sense.env.example" "%PACKAGE_DIR%\config\sense.env.example" >nul || goto :failed
copy /y "%SENSE_ROOT%\scripts\runtime\start-sense.bat" "%PACKAGE_DIR%\start-sense.bat" >nul || goto :failed
copy /y "%SENSE_ROOT%\scripts\runtime\README-WINDOWS.md" "%PACKAGE_DIR%\README-WINDOWS.md" >nul || goto :failed
echo [5/5] Creating ZIP...
powershell -NoProfile -ExecutionPolicy Bypass -Command "Compress-Archive -Path '%PACKAGE_DIR%' -DestinationPath '%ZIP_PATH%' -Force" || goto :failed
echo.
echo Package directory: %PACKAGE_DIR%
echo ZIP archive: %ZIP_PATH%
exit /b 0
:failed_popd
popd
:failed
echo [ERROR] Packaging failed.
exit /b 1
:missing_go
echo [ERROR] Go was not found in PATH.
exit /b 1
:missing_node
echo [ERROR] Node.js was not found in PATH.
exit /b 1
:missing_corepack
echo [ERROR] Corepack was not found in PATH.
exit /b 1
+32
View File
@@ -0,0 +1,32 @@
# Sense Windows 运行包
此目录同时包含 Sense 后端和已经构建好的前端静态页面。Brain、Bell 无需启动。
## 快速查看
在命令提示符中运行:
```bat
start-sense.bat demo
```
然后访问 <http://127.0.0.1:18080>。`demo` 使用内存数据,进程退出后数据会丢失,仅用于本机查看,不能用于生产。
## 生产启动
生产环境必须先安装并准备独立 PostgreSQL,然后在仓库和运行包之外安全设置以下环境变量:
- `SENSE_DATABASE_URL`:PostgreSQL 连接地址。
- `SENSE_IDENTITY_SIGNING_KEY`:至少 32 个字符的会话签名密钥。
- `SENSE_BOOTSTRAP_TOKEN`:首次初始化使用的引导令牌。
- `SENSE_CREDENTIAL_KEY`:Base64 编码的 32 字节摄像头凭据加密密钥。
按需要设置 `SENSE_HTTP_ADDRESS`、MediaMTX 和 ONVIF 参数后运行:
```bat
start-sense.bat
```
完整变量名可参考 `config\sense.env.example`。示例文件只有空值和非秘密默认值,启动脚本不会自动读取它;请使用 Windows 环境变量或外部秘密管理工具注入真实值。
健康检查为 `GET /healthz` 和 `GET /readyz`。MediaMTX 仍是独立程序,本运行包不会安装 PostgreSQL、MediaMTX 或 Windows 服务。
+45
View File
@@ -0,0 +1,45 @@
@echo off
setlocal
cd /d "%~dp0"
set "SENSE_UI_STATIC_DIR=%~dp0ui"
if /I "%~1"=="demo" goto :demo
set "SENSE_DATABASE_MODE=postgres"
if "%SENSE_DATABASE_URL%"=="" goto :missing_database_url
if "%SENSE_IDENTITY_SIGNING_KEY%"=="" goto :missing_identity_key
if "%SENSE_BOOTSTRAP_TOKEN%"=="" goto :missing_bootstrap_token
if "%SENSE_CREDENTIAL_KEY%"=="" goto :missing_credential_key
echo Starting Sense in production mode at %SENSE_HTTP_ADDRESS%...
"%~dp0sense-server.exe"
exit /b %ERRORLEVEL%
:demo
set "SENSE_DATABASE_MODE=memory"
if "%SENSE_HTTP_ADDRESS%"=="" set "SENSE_HTTP_ADDRESS=127.0.0.1:18080"
echo Starting Sense in temporary demo mode at http://%SENSE_HTTP_ADDRESS% ...
echo Demo data is discarded when the process stops. Do not use this mode in production.
"%~dp0sense-server.exe"
exit /b %ERRORLEVEL%
:missing_database_url
echo [ERROR] SENSE_DATABASE_URL is required in production mode.
goto :configuration_help
:missing_identity_key
echo [ERROR] SENSE_IDENTITY_SIGNING_KEY is required in production mode.
goto :configuration_help
:missing_bootstrap_token
echo [ERROR] SENSE_BOOTSTRAP_TOKEN is required in production mode.
goto :configuration_help
:missing_credential_key
echo [ERROR] SENSE_CREDENTIAL_KEY is required in production mode.
:configuration_help
echo Set the required environment variables outside this directory.
echo See README-WINDOWS.md and config\sense.env.example.
exit /b 1
+3 -17
View File
@@ -2,27 +2,13 @@ package identity
import (
"fmt"
"strings"
"unicode"
"golang.org/x/crypto/bcrypt"
)
func ValidatePassword(username, password string) error {
if len([]rune(password)) < 12 {
return fmt.Errorf("密码至少需要 12 个字符")
}
var upper, lower, digit bool
for _, value := range password {
upper = upper || unicode.IsUpper(value)
lower = lower || unicode.IsLower(value)
digit = digit || unicode.IsDigit(value)
}
if !upper || !lower || !digit {
return fmt.Errorf("密码必须同时包含大写字母、小写字母和数字")
}
if username != "" && strings.Contains(strings.ToLower(password), strings.ToLower(username)) {
return fmt.Errorf("密码不能包含用户名")
func ValidatePassword(_ string, password string) error {
if len([]rune(password)) < 6 {
return fmt.Errorf("密码至少需要 6 个字符")
}
return nil
}
@@ -0,0 +1,35 @@
package identity
import (
"strings"
"testing"
)
func TestValidatePasswordLengthAndComplexity(t *testing.T) {
tests := []struct {
name string
username string
password string
want string
}{
{name: "six lowercase characters", username: "operator", password: "abcdef"},
{name: "contains username", username: "admin", password: "admin123"},
{name: "six unicode characters", username: "operator", password: "密码密码密码"},
{name: "five characters", username: "operator", password: "Ab123", want: "至少需要 6 个字符"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := ValidatePassword(test.username, test.password)
if test.want == "" {
if err != nil {
t.Fatalf("ValidatePassword() error = %v", err)
}
return
}
if err == nil || !strings.Contains(err.Error(), test.want) {
t.Fatalf("ValidatePassword() error = %v, want substring %q", err, test.want)
}
})
}
}
+2 -2
View File
@@ -10,7 +10,8 @@
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "eslint \"src/**/*.{js,vue}\""
"lint": "eslint \"src/**/*.{js,vue}\"",
"test:navigation": "node --test tests/navigation.test.cjs"
},
"dependencies": {
"@element-plus/icons-vue": "2.3.2",
@@ -50,4 +51,3 @@
"not dead"
]
}
+3 -11
View File
@@ -58,22 +58,14 @@ import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { buildNavigation } from './navigation'
const router = useRouter()
const store = useStore()
const collapsed = ref(false)
const navigation = computed(() => {
const root = router.getRoutes().find((route) => route.path === '/')
const permissions = store.getters['sense-identity/permissions'] || []
return (root?.children || [])
.filter((route) => route.meta?.title && !route.meta?.hidden)
.filter((route) => !route.meta?.permission || permissions.includes(route.meta.permission))
.map((route) => ({
path: route.path ? `/${route.path}`.replace('//', '/') : '/',
title: route.meta.title,
icon: route.meta.icon,
order: route.meta.order || 0
}))
.sort((left, right) => left.order - right.order)
return buildNavigation(router.getRoutes(), permissions)
})
async function logout() {
+18
View File
@@ -0,0 +1,18 @@
function buildNavigation(routes, permissions = []) {
const layout = routes
.filter((route) => route.path === '/' && Array.isArray(route.children) && route.children.length > 0)
.sort((left, right) => right.children.length - left.children.length)[0]
return (layout?.children || [])
.filter((route) => route.meta?.title && !route.meta?.hidden)
.filter((route) => !route.meta?.permission || permissions.includes(route.meta.permission))
.map((route) => ({
path: route.path ? `/${route.path}`.replace('//', '/') : '/',
title: route.meta.title,
icon: route.meta.icon,
order: route.meta.order || 0
}))
.sort((left, right) => left.order - right.order)
}
module.exports = { buildNavigation }
+2 -3
View File
@@ -30,7 +30,7 @@
<el-form-item label="用户名" prop="username"><el-input v-model="form.username" :disabled="Boolean(editingID)" /></el-form-item>
<el-form-item label="姓名" prop="display_name"><el-input v-model="form.display_name" /></el-form-item>
<el-form-item label="角色" prop="role"><el-select v-model="form.role" style="width: 100%"><el-option v-for="role in roles" :key="role.value" :label="role.label" :value="role.value" /></el-select></el-form-item>
<el-form-item v-if="!editingID" label="初始密码" prop="password"><el-input v-model="form.password" type="password" show-password /><div class="field-hint">至少 12 位,包含大小写字母和数字,且不含用户名。</div></el-form-item>
<el-form-item v-if="!editingID" label="初始密码" prop="password"><el-input v-model="form.password" type="password" show-password /><div class="field-hint">至少 6 位字符。</div></el-form-item>
<el-form-item v-else label="状态"><el-switch v-model="form.enabled" active-text="启用" inactive-text="停用" /></el-form-item>
</el-form>
<template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" :loading="saving" @click="save">保存</el-button></template>
@@ -64,7 +64,7 @@ const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
display_name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
password: [{ required: true, min: 12, message: '请输入至少 12 位初始密码', trigger: 'blur' }]
password: [{ required: true, min: 6, message: '请输入至少 6 位初始密码', trigger: 'blur' }]
}
const canWrite = computed(() => store.getters['sense-identity/hasPermission']?.('identity.users.write'))
const filteredUsers = computed(() => {
@@ -99,4 +99,3 @@ onMounted(load)
<style scoped>
.field-hint { color: #86909c; font-size: 12px; line-height: 1.5; }
</style>
+65
View File
@@ -0,0 +1,65 @@
const assert = require('node:assert/strict')
const test = require('node:test')
const { buildNavigation } = require('../src/layout/navigation.js')
test('selects the layout record when normalized routes contain duplicate root paths', () => {
const routes = [
{ path: '/', name: 'sense-dashboard', children: [], meta: { title: '工作台' } },
{ path: '/devices', name: 'sense-devices', children: [], meta: { title: '设备管理' } },
{
path: '/',
children: [
{ path: 'devices', meta: { title: '设备管理', permission: 'device.read', order: 10 } },
{ path: '', meta: { title: '工作台', order: 0 } },
{ path: 'users', meta: { title: '用户与权限', permission: 'identity.users.read', order: 90 } }
]
}
]
assert.deepEqual(buildNavigation(routes, ['device.read']), [
{ path: '/', title: '工作台', icon: undefined, order: 0 },
{ path: '/devices', title: '设备管理', icon: undefined, order: 10 }
])
})
test('returns no unauthorized or hidden entries', () => {
const routes = [{
path: '/',
children: [
{ path: '', meta: { title: '工作台' } },
{ path: 'users', meta: { title: '用户与权限', permission: 'identity.users.read' } },
{ path: 'internal', meta: { title: '内部页', hidden: true } }
]
}]
assert.deepEqual(buildNavigation(routes), [
{ path: '/', title: '工作台', icon: undefined, order: 0 }
])
})
test('administrator receives every Sense MVP navigation entry', () => {
const entries = [
['', '工作台', null, 0],
['devices', '设备管理', 'device.read', 10],
['admission', '视频接入', 'device.write', 20],
['media', '视频服务', 'media.read', 30],
['liveview', '实时监看', 'media.read', 40],
['areas', '区域与警戒线', 'area.read', 50],
['users', '用户与权限', 'identity.users.read', 90],
['audit', '操作记录', 'identity.audit.read', 91]
]
const routes = [{ path: '/', children: entries.map(([path, title, permission, order]) => ({
path,
meta: { title, permission, order }
})) }]
const permissions = [
'device.read', 'device.write', 'media.read', 'area.read',
'identity.users.read', 'identity.audit.read'
]
assert.deepEqual(buildNavigation(routes, permissions).map((item) => item.title), [
'工作台', '设备管理', '视频接入', '视频服务', '实时监看',
'区域与警戒线', '用户与权限', '操作记录'
])
})
+1 -1
View File
@@ -3,7 +3,7 @@ generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Project-Profile
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Project-Profile.-
wiki_revision: 8d21b036c7a4077560b44790bb55602c84caaab1
synchronized_at: 2026-08-12T10:17:39Z
synchronized_at: 2026-08-12T10:21:47Z
<!-- gitea-wiki-mirror:end -->
# 项目档案
+1 -1
View File
@@ -3,7 +3,7 @@ generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Architecture-and-Code-Map
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Architecture-and-Code-Map.-
wiki_revision: 1712a8053f365781aef9cbf33c576c97491f31d4
synchronized_at: 2026-08-12T10:17:44Z
synchronized_at: 2026-08-12T10:21:55Z
<!-- gitea-wiki-mirror:end -->
# 架构与代码地图
+3 -3
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Business-Rules-and-Glossary
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Business-Rules-and-Glossary.-
wiki_revision: 2b222ffc8c2d943fa2a27d9e28ffaf4896143950
synchronized_at: 2026-08-12T10:17:48Z
wiki_revision: c9d47108e45f66875726e3eef79eedb2a5942514
synchronized_at: 2026-08-13T01:15:01Z
<!-- gitea-wiki-mirror:end -->
# 业务规则与术语
@@ -71,7 +71,7 @@ synchronized_at: 2026-08-12T10:17:48Z
## Sense MVP 业务与安全规则
- **Sense 账户**:只登录 Sense;不得接受 Bell JWT、Cookie 或用户数据。角色为系统管理员、实施/运维、站点管理员和只读用户,后端权限是最终边界。
- **安全初始化**:系统不提供默认账户、默认密码或默认签名密钥;首次管理员由仓库外一次性令牌创建。
- **安全初始化**:系统不提供默认账户、默认密码或默认签名密钥;首次管理员由仓库外一次性令牌创建。所有模式的密码仅要求至少 6 个字符,不限制字符种类并允许包含用户名。负责人已明确接受该生产密码策略的字典猜测与凭据填充风险。
- **Device**:设备不可变逻辑 ID 是后续 Profile、媒体和区域的内部引用。非视频适配器未实现时必须显示 `adapter_not_ready`。
- **摄像机凭据**:只写不读,使用仓库外 32 字节密钥加密;不得进入 URL、日志、审计、工单或响应。
- **受控发现**:ONVIF Discovery 默认关闭,只有显式设置获准本机 IP 才能发送发现;不得扫描未授权网段。
+17 -2
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Local-Development-and-Verification
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Local-Development-and-Verification.-
wiki_revision: a9b66768b039da1d25a5e1688c020167798e9ea0
synchronized_at: 2026-08-12T10:17:51Z
wiki_revision: 3bf0987ec9d7ddf543602e465faa088aead46531
synchronized_at: 2026-08-12T10:22:02Z
<!-- gitea-wiki-mirror:end -->
# 本地开发与验证
@@ -156,3 +156,18 @@ corepack pnpm@9.15.1 build:prod
在 Sense、Brain 停止时,development/test 可使用“合成事件测试”完成 Event 入站、规则匹配、Alert 关联、ack 和结果必填 close。production 必须验证 `/api/v1/synthetic/*` 返回 404。PostgreSQL 集成测试使用空的 Bell 专用测试数据库,禁止复用 Sense 或生产数据库。
<!-- bell-runtime:end -->
<!-- sense-windows-package:start -->
## Sense Windows 打包与产物验证
将冻结的 Go 1.26.5 放在 `PATH` 首位后,从仓库根目录运行:
```powershell
cmd /c .\Sense\scripts\package-windows.bat
Get-FileHash .\Sense\dist\sense-windows-amd64.zip -Algorithm SHA256
```
脚本精确校验 Node 22.22.1 和 pnpm 9.15.1,冻结安装并构建前端,以 Windows amd64/CGO 关闭方式编译后端,然后生成被 Git 忽略的 `Sense\dist\sense-windows-amd64\` 和 ZIP。可重复运行只会覆盖这两个固定产物。
解压后运行 `start-sense.bat demo` 可做内存模式临时预览;生产运行 `start-sense.bat` 前必须从仓库和运行包外注入 PostgreSQL URL、签名密钥、引导令牌和摄像头凭据加密密钥。包内不含 PostgreSQL、MediaMTX、系统服务、客户数据或秘密。
<!-- sense-windows-package:end -->
+16 -2
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Troubleshooting
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Troubleshooting
wiki_revision: 3ee073ae07bb2ab04f02b1178b6d289f6f5a2063
synchronized_at: 2026-08-12T09:32:15Z
wiki_revision: b8e778ae4255ba3fc36a1cd6bcb735e29bd19eee
synchronized_at: 2026-08-13T01:26:22Z
<!-- gitea-wiki-mirror:end -->
# 故障排查
@@ -47,6 +47,7 @@ synchronized_at: 2026-08-12T09:32:15Z
|---|---|
| 启动提示缺少 `SENSE_DATABASE_URL` | 正式模式必须提供独立 PostgreSQL URL;只有测试/smoke 可显式使用 `memory`。 |
| 登录统一提示用户名或密码错误 | 为避免枚举账户,失败响应不区分用户不存在、密码错误或账户停用;由管理员查看审计。 |
| 管理员登录后侧栏没有模块 | 先确认 /api/v1/identity/me 返回角色与权限;若权限正常,检查前端是否从具有 children 的应用布局路由派生菜单,不得依赖重复 / 路由记录顺序。 |
| `adapter_not_ready` | 当前设备类型尚无适配器,不代表网络故障;首期完整支持 video。 |
| `discovery_unavailable` | 未设置获准的 `SENSE_ONVIF_DISCOVERY_IP`,或该 IP 不属于本机网卡。可改用手工 ONVIF 地址。 |
| `authentication_failed` | 在设备管理中重新写入凭据后再次执行接入检查;不要把凭据写进地址。 |
@@ -58,3 +59,16 @@ synchronized_at: 2026-08-12T09:32:15Z
自动测试不访问真实摄像头或未授权网络。PostgreSQL、MediaMTX、目标浏览器与实验室摄像机的联合验证必须在获准部署环境完成。
<!-- sense-mvp:end -->
<!-- sense-windows-package:start -->
## Sense Windows 打包与启动排错
| 现象 | 检查与处理 |
|---|---|
| 打包提示 Go/Node/pnpm 版本不符 | 对照根 `goadmin-baseline.json` 安装精确版本,并把 Go 1.26.5 放到 `PATH` 首位;不要修改脚本绕过版本检查。 |
| 生产启动提示缺少环境变量 | 在运行包外设置 `SENSE_DATABASE_URL`、`SENSE_IDENTITY_SIGNING_KEY`、`SENSE_BOOTSTRAP_TOKEN`、`SENSE_CREDENTIAL_KEY`;启动脚本不会读取示例文件。 |
| 本机健康检查被代理返回空响应 | localhost 可能被 `HTTP_PROXY` 接管;测试工具应对 `127.0.0.1` 使用 no-proxy,再检查 `SENSE_HTTP_ADDRESS` 端口占用。 |
| 页面可打开但视频不可用 | Windows 运行包不包含 MediaMTX;按包内说明配置独立 MediaMTX 二进制、配置和 Control API。 |
`demo` 只用于临时查看。生产数据持久性、真实设备和媒体链路不能用 demo 验证替代。
<!-- sense-windows-package:end -->
+14 -3
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Delivery-Documentation-Guide
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Delivery-Documentation-Guide.-
wiki_revision: 9847e4c065e513107f8f79617c6529a8300874cb
synchronized_at: 2026-08-12T09:32:40Z
wiki_revision: 04d9a7f0916262c3578e7934a2e98327dae55c7a
synchronized_at: 2026-08-13T01:15:15Z
<!-- gitea-wiki-mirror:end -->
# 交付文档指南
@@ -84,10 +84,21 @@ synchronized_at: 2026-08-12T09:32:40Z
Sense 面向网管、实施人员和非技术现场人员,菜单按日常任务组织:工作台 → 设备管理 → 视频接入 → 视频服务 → 实时监看 → 区域与警戒线。普通操作优先展示中文状态与下一步,不要求用户理解 ONVIF、RTSP 或 MediaMTX 内部模型。
- 系统管理员:完成一次性安全初始化,维护 Sense 用户与角色,查看操作记录。
- 系统管理员:完成一次性安全初始化,维护 Sense 用户与角色,查看操作记录。首次管理员和新用户密码仅要求至少 6 个字符,不限制字符种类并允许包含用户名;仍应由管理员选择难猜且不复用的密码。
- 实施/运维:添加设备、只写更新凭据、在获准网卡发现或手工接入、检查主辅码流、处理媒体进程与对账。
- 站点管理员:查看/维护站点设备,实时监看,绘制和发布区域版本;无权创建系统管理员。
- 只读用户:查看设备、媒体状态、实时画面和区域,不能修改。
交付验收必须使用仓库外测试账户和获准实验室设备;文档、截图、工单和日志不得包含摄像机密码、Cookie、令牌、含凭据 URI 或客户真实画面。当前自动化已覆盖内部状态和安全边界;真实 PostgreSQL、MediaMTX、摄像机兼容与目标浏览器画面仍需实施人员验证。
<!-- sense-mvp:end -->
<!-- sense-windows-package:start -->
## Sense Windows 运行包交付
交付对象为实施和运维人员。`sense-windows-amd64.zip` 包含后端程序、已构建前端、空值示例配置、上游许可证、启动脚本与包内说明;不包含 PostgreSQL、MediaMTX、Windows 服务、生产数据或秘密。
- 临时查看必须显式运行 `start-sense.bat demo`,其内存数据在进程结束后丢失,不能当作生产部署。
- 生产启动前由运维在运行包外安全注入数据库、身份签名、初始化令牌和凭据加密配置,再运行 `start-sense.bat`。
- 交付时记录 ZIP SHA-256,并至少验证 `/healthz` 与首页;真实 PostgreSQL、MediaMTX、摄像机和目标浏览器仍需在获准环境验收。
- 包内 `README-WINDOWS.md` 是现场操作入口;不得把真实连接串、密码或令牌回填进示例文件后重新分发。
<!-- sense-windows-package:end -->
@@ -1,52 +0,0 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-12-Bell独立登录RBAC与认证审计
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-12-Bell%E7%8B%AC%E7%AB%8B%E7%99%BB%E5%BD%95RBAC%E4%B8%8E%E8%AE%A4%E8%AF%81%E5%AE%A1%E8%AE%A1.-
wiki_revision: 133ad095749c9258fa2becc65d8946233fdfd56d
synchronized_at: 2026-08-12T10:19:05Z
<!-- gitea-wiki-mirror:end -->
# 12 Bell独立登录RBAC与认证审计
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/12
- Wiki 页面:Task-12-Bell独立登录RBAC与认证审计
- Wiki revision:见本地镜像头
## 背景与目标
为 Bell 建立不信任 Sense token 的独立账户、会话、角色权限与认证审计边界。
## 最终方案
使用 bcrypt 密码和只存 HMAC 摘要的服务端会话,Cookie 固定为 `bell_session`。系统无默认账户/密码/密钥,首次管理员由一次性环境变量创建。管理员、操作员、只读角色按最小权限执行,认证审计由数据库触发器保证只追加。
## 修改文件
- `Bell/server/app/auth/`、`rbac/`、`audit/`:身份、权限与审计。
- `Bell/server/migrations/2026081201_auth.sql`:账户、角色、会话、审计及不可变约束。
- `Bell/web/src/views/login/`、`system/`:登录、用户角色和审计页面。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 缺少 session secret 拒绝启动 | 通过 |
| Bell 登录/登出和过期会话闭环 | 通过 |
| Sense Cookie 不被接受 | 通过 |
| 后端 RBAC 隔离管理员接口 | 通过 |
| 审计事实不可更新/删除 | 通过 |
## 测试
- 执行:Go 单元/集成测试;登录 200→登出 204→会话 401;伪 Sense Cookie 401;管理员/操作员/只读权限矩阵;数据库不可变触发器。
- 结果:全部通过。
- **未验证部分**:生产域名下 Secure Cookie、外部身份提供商和真实用户人工验收。
## 相关提交
- `a5fc8f5` 独立认证与 RBAC(PR #35)。
@@ -1,52 +0,0 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-17-Bell不可变Event与永久幂等收据
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-17-Bell%E4%B8%8D%E5%8F%AF%E5%8F%98Event%E4%B8%8E%E6%B0%B8%E4%B9%85%E5%B9%82%E7%AD%89%E6%94%B6%E6%8D%AE.-
wiki_revision: dab364664ed4d1aa7605de1a0c1a02299df75941
synchronized_at: 2026-08-12T10:19:08Z
<!-- gitea-wiki-mirror:end -->
# 17 Bell不可变Event与永久幂等收据
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/17
- Wiki 页面:Task-17-Bell不可变Event与永久幂等收据
- Wiki revision:见本地镜像头
## 背景与目标
可靠接收外部事件,在重试、并发和重启后保持永久幂等,同时禁止改写原始 Event。
## 最终方案
以 `(producer_id, source_event_id)` 唯一键和 payload hash 建立稳定 Receipt;相同正文返回原结果,不同正文返回冲突。Event/Receipt 持久化在同一事务,数据库触发器拒绝 Event 更新或删除,并提供游标分页与详情查询。
## 修改文件
- `Bell/server/app/event/`、`receipt/`:入站服务、HTTP、查询和类型。
- `Bell/server/migrations/2026081202_event_receipt.sql`:不可变表、索引、Receipt 和触发器。
- `Bell/web/src/views/event/Events.vue`:事件查询与技术详情。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 并发重复返回同一 Event/Receipt | 通过 |
| 同键不同 payload 返回冲突 | 通过 |
| 进程/连接重建后仍幂等 | 通过 |
| Event 数据库层不可变 | 通过 |
| 下游失败时不留下半成品 | 通过 |
## 测试
- 执行:PostgreSQL 集成测试,16 个并发相同入站、冲突、连接池重建、不可变触发器、事务 hook 回滚。
- 结果:全部通过。
- **未验证部分**:正式跨项目契约、生产规模压测与生产备份恢复。
## 相关提交
- `64a1d82` 不可变 Event 与永久幂等 Receipt(PR #35)。
@@ -1,52 +0,0 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-18-Bell开发测试合成事件入口
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-18-Bell%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95%E5%90%88%E6%88%90%E4%BA%8B%E4%BB%B6%E5%85%A5%E5%8F%A3.-
wiki_revision: 2ce181d6bd9001661af20940cbcbf74beda4d7ba
synchronized_at: 2026-08-12T10:19:11Z
<!-- gitea-wiki-mirror:end -->
# 18 Bell开发测试合成事件入口
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/18
- Wiki 页面:Task-18-Bell开发测试合成事件入口
- Wiki revision:见本地镜像头
## 背景与目标
在 Sense、Brain 离线时提供安全、可重复的 Bell 纵切验收入口,同时保证生产环境不可访问。
## 最终方案
把虚构匿名 fixture 嵌入 `server/testdata/events/`;仅在 development/test 注册 synthetic 路由并要求管理员权限,所有成功注入都调用正式 Event/Receipt 服务。production 不注册路由,直接返回 404。
## 修改文件
- `Bell/server/app/synthetic/`、`server/testdata/events/`:fixture、校验和 HTTP 入口。
- `Bell/server/migrations/2026081203_synthetic_permission.sql`:测试权限。
- `Bell/web/src/views/synthetic-event/`:仅环境与权限允许时显示的测试页。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 无 Sense/Brain 可注入匿名事件 | 通过 |
| 重放相同 fixture 验证幂等 | 通过 |
| 同键不同正文验证冲突 | 通过 |
| 非法 fixture 返回 400 | 通过 |
| production 路由返回 404 | 通过 |
## 测试
- 执行:Go 单元/HTTP 测试与 dev/test/prod 手工 API 验收。
- 结果:首次 201、重复 200、冲突 409、非法 400、production 404。
- **未验证部分**:正式生产者契约与客户环境验收。
## 相关提交
- `55d54d0` 仅开发测试可用的合成事件(PR #35)。
@@ -1,52 +0,0 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-19-Bell规则匹配与Event-Alert链路
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-19-Bell%E8%A7%84%E5%88%99%E5%8C%B9%E9%85%8D%E4%B8%8EEvent-Alert%E9%93%BE%E8%B7%AF.-
wiki_revision: ca2150b47195eb44b14c4c73a2eab8fd3be14617
synchronized_at: 2026-08-12T10:19:15Z
<!-- gitea-wiki-mirror:end -->
# 19 Bell规则匹配与Event-Alert链路
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/19
- Wiki 页面:Task-19-Bell规则匹配与Event-Alert链路
- Wiki revision:见本地镜像头
## 背景与目标
把已接收 Event 通过可审计规则转换为可处置 Alert,并保留 Event/Alert 双向追溯。
## 最终方案
Event 接收事务内执行规则评估,记录规则版本、解释和实际快照。一个 Event 可匹配多个规则;相同规则与地点的多个 Event 聚合到同一未关闭 Alert。未匹配 Event 仍可查询且不冒充通知/处置成功。
## 修改文件
- `Bell/server/app/rule/`、`alert/model/`、`alert/query/`:规则、评估、Alert 和查询。
- `Bell/server/migrations/2026081204_rule_alert_base.sql`:规则版本、评估、Alert 与多对多关联。
- `Bell/web/src/views/rule/`、`alert/list/`:规则与预警列表/详情。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 一个 Event 可形成多个 Alert | 通过 |
| 多 Event 可聚合同一开放 Alert | 通过 |
| 未匹配评估可审计 | 通过 |
| 失败时 Event/Receipt/Alert 同事务回滚 | 通过 |
| 规则写权限仅管理员 | 通过 |
## 测试
- 执行:PostgreSQL 集成测试与 API 验收;覆盖 1 Event→2 Alert、2 Event→同 2 Alert、未匹配、事务回滚和 RBAC。
- 结果:全部通过;Alert 详情返回 2 个关联 Event 与 2 条匹配解释。
- **未验证部分**:大规模规则性能、正式生产者契约和通知投递。
## 相关提交
- `d550269` 规则匹配与 Event/Alert 可导航链路(PR #35)。
@@ -1,54 +0,0 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-20-Bell-Alert并发处置与审计时间线
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-20-Bell-Alert%E5%B9%B6%E5%8F%91%E5%A4%84%E7%BD%AE%E4%B8%8E%E5%AE%A1%E8%AE%A1%E6%97%B6%E9%97%B4%E7%BA%BF.-
wiki_revision: b7ed3e488bf2484b4b4690902f8e446e0e6a1b54
synchronized_at: 2026-08-12T10:19:18Z
<!-- gitea-wiki-mirror:end -->
# 20 Bell-Alert并发处置与审计时间线
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/20
- Wiki 页面:Task-20-Bell-Alert并发处置与审计时间线
- Wiki revision:见本地镜像头
## 背景与目标
实现并发安全、责任清晰、可审计的 Alert ack/close 处置状态机。
## 最终方案
PostgreSQL 条件更新选择唯一 ack 获胜者,后来者看到真实处置人。只有处置人或管理员可 close,且必须记录四种现场结果之一;同一重复 close 幂等。成功事实进入不可变生命周期时间线,失败/重复/拒绝尝试进入安全审计。
## 修改文件
- `Bell/server/app/alert/lifecycle/`:ack/close 服务、HTTP 与并发测试。
- `Bell/server/migrations/2026081205_alert_lifecycle.sql`:生命周期事实、审计和不可变约束。
- `Bell/web/src/views/alert/list/Alerts.vue`:两步处置、结果表单与时间线。
- `Bell/web/src/layout/AppLayout.vue`:对齐已确认的导航顺序。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 并发 ack 仅一个处置人 | 通过 |
| 后来者得到真实处置人且不能覆盖 | 通过 |
| close 要求结果且受所有者/管理员约束 | 通过 |
| 相同重复 close 幂等 | 通过 |
| 重启后状态与只追加时间线保留 | 通过 |
## 测试
- 执行:20 并发 ack PostgreSQL 集成测试;API ack/冲突/结果必填/close/重复 close;重启持久性;Vue lint/build。
- 结果:全部通过,时间线含 ack 与 close 两条成功事实。
- **未验证部分**:多人真实浏览器并发、生产规模压测和通知/升级流程。
## 相关提交
- `10225f4` 并发安全的 Alert 生命周期(PR #35)。
- `9ee06e3` 对齐正式页面导航顺序(PR #35)。
@@ -1,50 +0,0 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-31-Bell原型对齐GoAdmin与Element-Plus
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-31-Bell%E5%8E%9F%E5%9E%8B%E5%AF%B9%E9%BD%90GoAdmin%E4%B8%8EElement-Plus.-
wiki_revision: e3ceec7443b6ba2f01db0caa13eae5fc9b0545c2
synchronized_at: 2026-08-12T10:19:21Z
<!-- gitea-wiki-mirror:end -->
# 31 Bell原型对齐GoAdmin与Element-Plus
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/31
- Wiki 页面:Task-31-Bell原型对齐GoAdmin与Element-Plus
- Wiki revision:见本地镜像头
## 背景与目标
将 Bell HTML 原型对齐冻结 go-admin-ui 外壳与 Element Plus 交互语言,为正式 Vue 页面提供已验证设计基线。
## 最终方案
复用共享 Sidebar、Navbar、TagsView、AppMain、表单、表格、分页、Dialog、Tag、Tabs 与权限按钮,只新增 AlertWorkbench 和 AlertAuditTimeline 业务组件;未修改只读上游仓库。
## 修改文件
- `prototypes/bell/**`:Bell 原型页面、样式与交互。
- `prototypes/assets/go-admin-ui.css`、`go-admin-shell.js`:在协调范围内复用的原型外壳适配器。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| go-admin-ui/Element Plus 外壳一致 | 通过 |
| ack/close、时间线与 Dialog 键盘交互 | 通过 |
| 1440/768/375/手机横屏响应式 | 通过 |
| 无重复组件、外部资源或控制台错误 | 通过 |
## 测试
- 执行:Harness、25 项仓库测试、JavaScript 语法、重复 ID、响应式、键盘、焦点、Escape 与交互走查。
- 结果:自动化和浏览器检查通过。
- **未验证部分**:产品/客户人工验收。
## 相关提交
- `0bdd88b` 原型对齐冻结 GoAdmin UI 基线(PR #29)。
@@ -1,49 +0,0 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-33-Bell普通用户预警处理原型
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-33-Bell%E6%99%AE%E9%80%9A%E7%94%A8%E6%88%B7%E9%A2%84%E8%AD%A6%E5%A4%84%E7%90%86%E5%8E%9F%E5%9E%8B.-
wiki_revision: c829e54319af164c80be8a07048ef7e74181600b
synchronized_at: 2026-08-12T10:19:24Z
<!-- gitea-wiki-mirror:end -->
# 33 Bell普通用户预警处理原型
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/33
- Wiki 页面:Task-33-Bell普通用户预警处理原型
- Wiki revision:见本地镜像头
## 背景与目标
把领域模型导向的 Bell 原型调整为普通客户熟悉的预警后台,使用户无需理解 Event/Alert 即可完成核心处置。
## 最终方案
一级模块固定为工作台、预警管理、事件查询、规则配置。默认展示事项、地点、时间、风险和建议;“开始处理”与“记录现场结果”分成两步,关闭前必须选择确认有危险、误报、现场正常或无法确认。内部 ID、Receipt、规则快照和审计链路进入技术详情。
## 修改文件
- `prototypes/bell/index.html`、`bell.css`、`bell.js`:普通用户信息架构、处置动作与响应式交互。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 四个常见一级模块与中文业务字段 | 通过 |
| ack 与结果必填 close 语义清晰 | 通过 |
| 并发处理人反馈和技术详情可追溯 | 通过 |
| 375/768/1440/812×375 无页面溢出 | 通过 |
## 测试
- 执行:导航、筛选、分页、ack、结果必填、误报 close、响应式、键盘/焦点、Harness 与仓库测试。
- 结果:自动化与浏览器检查通过,无控制台错误或警告。
- **未验证部分**:普通用户代表无提示人工走查与产品确认。
## 相关提交
- `8d1875c` 按确认反馈调整为传统预警后台(PR #29)。
@@ -1,52 +0,0 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-36-Bell-MVP文档与待验收归档
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-36-Bell-MVP%E6%96%87%E6%A1%A3%E4%B8%8E%E5%BE%85%E9%AA%8C%E6%94%B6%E5%BD%92%E6%A1%A3.-
wiki_revision: e9880ec350e7d9fea1119b98da48ce47c871a9c6
synchronized_at: 2026-08-12T10:24:25Z
<!-- gitea-wiki-mirror:end -->
# 36 Bell-MVP文档与待验收归档
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/36
- Wiki 页面:Task-36-Bell-MVP文档与待验收归档
- Wiki revision:见本地镜像头
## 背景与目标
统一把 Bell #9、#12、#17、#18、#19、#20、#31、#33 的长期结论与待验收证据写入 Wiki,并生成只读镜像。
## 最终方案
由单一协调 agent 在 PR #34 的精确文档基线上串行更新共享页面,Bell 代码仍由 PR #35 独立审查。先更新并读取 Wiki,再生成镜像;不删除/重命名页面,不关闭待验收工单。
## 修改文件
- `wiki-docs.json`:登记九个任务归档显式映射。
- `docs/00-project-profile.md`、`docs/02-architecture-and-code-map.md`、`docs/03-business-rules-and-glossary.md`、`docs/04-local-development-and-verification.md`:Wiki 镜像。
- `docs/task/{9,12,17,18,19,20,31,33,36}-*.md`:待验收归档镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 四个稳定 Wiki 页面保留其他项目并包含 Bell 当前事实 | 通过 |
| 八个 Bell 工单归档包含真实提交、测试和未验证项 | 通过 |
| Wiki 写后读取确认 | 通过 |
| 镜像、Harness、仓库测试与 diff 检查 | 通过 |
## 测试
- 执行:`sync_wiki_docs.py --check`、`check_harness.py --strict`、仓库 unittest、`git diff --check`。
- 结果:Wiki 36 个映射全部一致;DevHarness 通过;31 项 unittest 全部通过;`git diff --check` 通过。
- **未验证部分**:用户对代码、原型与文档的人工验收。
## 相关提交
- `e2a4345` Bell 稳定文档与待验收归档镜像。
- 最终归档镜像提交与串联 PR 见工单 #36。
@@ -0,0 +1,72 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-37-Sense-Windows前后端一键打包
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-37-Sense-Windows%E5%89%8D%E5%90%8E%E7%AB%AF%E4%B8%80%E9%94%AE%E6%89%93%E5%8C%85.-
wiki_revision: 50a1b74dd33ccd52b8642599b3b579cbf4ac870e
synchronized_at: 2026-08-12T10:33:06Z
<!-- gitea-wiki-mirror:end -->
# 37 Sense Windows前后端一键打包
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/37
- Wiki 页面:Task-37-Sense-Windows前后端一键打包
- Wiki revision:见本地镜像头
## 背景与目标
客户需要在 Windows 上快速查看和交付 Sense。目标是用一个简单 BAT 从冻结工具链构建 Vue 前端和 Go 后端,组装不含秘密的 Windows amd64 运行目录及 ZIP,并提供临时预览和生产配置边界。
## 最终方案
- `Sense/scripts/package-windows.bat` 精确校验 Go 1.26.5、Node 22.22.1、pnpm 9.15.1,执行冻结安装、前端构建、CGO 关闭的 Windows amd64 后端编译、白名单文件组装和 ZIP 压缩。
- 产物固定为被 Git 忽略的 `Sense/dist/sense-windows-amd64/` 与 `Sense/dist/sense-windows-amd64.zip`;路径保护确保重建只清理这两个目标。
- 包内包含后端 EXE、前端静态资源、空值示例配置、上游许可证、`start-sense.bat` 和 `README-WINDOWS.md`。
- `start-sense.bat demo` 是明确的内存临时预览;无参数生产启动强制要求外部 PostgreSQL URL、身份签名密钥、引导令牌和凭据加密密钥。
- Wiki 增加本地打包、交付边界与排错说明;全量镜像同步同时带入其他已完成工单的最新 Wiki revision。
## 修改文件
- `Sense/scripts/package-windows.bat`:一键构建、组装与压缩。
- `Sense/scripts/runtime/start-sense.bat`:demo/生产双模式启动入口。
- `Sense/scripts/runtime/README-WINDOWS.md`:包内运行说明。
- `Sense/config/sense.env.example`:补齐身份、凭据、ONVIF 与 MediaMTX 空值示例。
- `Sense/README.md`:增加 Windows 打包入口。
- `docs/04-local-development-and-verification.md`、`docs/06-troubleshooting.md`、`docs/delivery/README.md`:Wiki-first 镜像。
- `wiki-docs.json`、`docs/task/37-Sense-Windows前后端一键打包.md`:任务归档登记和镜像。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 冻结工具链一键生成前后端 ZIP | 通过;连续执行两次均成功 |
| ZIP 含 EXE、UI、空值配置、许可证、启动脚本与说明 | 通过;共 14 个条目 |
| 包内不含秘密或生产数据 | 通过;敏感示例值为空且文本扫描未发现令牌、私钥或带密码 PostgreSQL URL |
| 包内后端加载 UI,健康检查与首页可访问 | 通过;`/healthz` 200,`/` 200 且包含 Vue 挂载点 |
| 固定路径安全重复覆盖 | 通过 |
| Brain、Bell 离线可构建和验证 | 通过 |
## 测试
- `cmd /c Sense\scripts\package-windows.bat`:连续两次通过;前端仅有既有 vendor bundle 体积建议警告。
- ZIP:7,281,398 bytes;SHA-256 `1BAEB17F1976DD925D8C01A245C951081FAE149E5151DA5523190F24E0B6AA6D`。
- 包内后端:内存模式绑定 `127.0.0.1:18082`,绕过本机代理后 `/healthz` 与 `/` 均为 200。
- `python -m unittest discover -s tests -v`:31 项通过。
- `python dev_scripts/check_harness.py --strict`:通过。
- `python dev_scripts/sync_wiki_docs.py --check`:通过。
- `git diff --check`:通过。
- **未验证部分**:未连接真实 PostgreSQL、MediaMTX 或摄像机;未用真实生产秘密运行生产模式;未由目标实施/运维人员完成解压和人工验收。
## 遗留问题
- 前端 vendor bundle 超过 Vue CLI 建议体积阈值,本工单不改变现有分包策略,不阻塞 Windows 打包。
## 相关提交
- `e80180e` build: add Sense Windows package script (#37)
- `efb48b4` docs: document Sense Windows package workflow (#37)
- `6d35fa7` docs: create task 37 acceptance archive (#37)
@@ -0,0 +1,70 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-40-Sense全模式密码最短6位
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-40-Sense%E5%85%A8%E6%A8%A1%E5%BC%8F%E5%AF%86%E7%A0%81%E6%9C%80%E7%9F%AD6%E4%BD%8D.-
wiki_revision: 89cf46b02ed85e26165cd835ccebb78a6621aa8b
synchronized_at: 2026-08-13T01:00:27Z
<!-- gitea-wiki-mirror:end -->
# 40 Sense全模式密码最短6位
- 类型:需求变更
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-13
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/40
- Wiki 页面:Task-40-Sense全模式密码最短6位
- Wiki revision:见本地镜像头
## 背景与目标
Sense 原规则要求密码至少 12 个字符。负责人在收到生产安全风险提示后,明确确认 demo 与生产全部调整为最少 6 个字符;大小写字母、数字和不得包含用户名的复杂度规则保持不变。
## 最终方案
- 后端统一密码校验最短长度由 12 改为 6,bootstrap 和管理员创建用户继续复用同一校验入口。
- 新增 6 字符通过、5 字符拒绝及大小写/数字/用户名约束的表驱动测试。
- 前端新用户表单最小长度和中文提示同步为 6。
- Wiki 记录全模式稳定规则和管理员交付说明。
- 重新生成 Windows amd64 前后端 ZIP,并用包内程序执行 6 位密码初始化和登录。
## 修改文件
- `Sense/server/app/sense/identity/password.go`:最短长度改为 6。
- `Sense/server/app/sense/identity/password_test.go`:长度和复杂度边界测试。
- `Sense/ui/src/views/sense/identity/Users.vue`:前端最小长度及提示。
- `docs/03-business-rules-and-glossary.md`、`docs/delivery/README.md`:Wiki-first 规则镜像。
- `wiki-docs.json`、`docs/task/40-Sense全模式密码最短6位.md`:任务归档。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 6 字符合规密码通过统一校验 | 通过 |
| 5 字符密码被拒绝 | 通过 |
| 大写、小写、数字和用户名限制保留 | 通过 |
| 前后端提示一致 | 通过 |
| Windows ZIP 重新生成并启动 | 通过 |
| 不生成默认账号、密码或秘密 | 通过 |
## 测试
- `go test ./app/sense/identity/...`:通过。
- `go test ./...`:Sense 全包通过。
- `corepack pnpm@9.15.1 lint`:0 error;存在既有格式 warning。
- `cmd /c Sense\scripts\package-windows.bat`:通过;前端仅有既有 vendor bundle 体积警告。
- 包内接口验证:使用 `Abc123` 初始化返回 201,登录返回 200。
- ZIP:7,281,361 bytes;SHA-256 `9A623C7B1E0F9F925DB7BBE3B5FFBE81D3A6B5BA5819F741D0AEC5681583D63C`。
- Harness:31 项测试和 strict 检查通过;Wiki 镜像检查通过。
- **未验证部分**:未在真实 PostgreSQL 环境执行首次管理员初始化,未由目标管理员人工验收;生产采用 6 字符密码的安全风险为用户明确接受的产品决策。
## 遗留问题
- 6 字符最短长度显著弱于原 12 字符规则;本工单未增加 MFA、登录限速或外部身份提供者。
## 相关提交
- `f841ef1` feat: lower Sense password minimum to six characters (#40)
- `2d501ea` docs: record Sense six-character password policy (#40)
- `4c4b114` docs: create task 40 acceptance archive (#40)
@@ -0,0 +1,71 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-42-Sense密码策略仅限制最短6位
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-42-Sense%E5%AF%86%E7%A0%81%E7%AD%96%E7%95%A5%E4%BB%85%E9%99%90%E5%88%B6%E6%9C%80%E7%9F%AD6%E4%BD%8D.-
wiki_revision: 53d52a17ae17072e1335b8bdff30fa19ad50f20f
synchronized_at: 2026-08-13T01:20:24Z
<!-- gitea-wiki-mirror:end -->
# 42 Sense密码策略仅限制最短6位
- 类型:需求变更
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-13
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/42
- Wiki 页面:Task-42-Sense密码策略仅限制最短6位
- Wiki revision:见本地镜像头
## 背景与目标
负责人确认 Sense demo 与生产密码仅要求至少 6 个字符,取消字符种类和用户名包含限制,以支持用户指定的简单管理员凭据。实际账号和密码属于运行时秘密,不进入本归档。
## 最终方案
- 后端 `ValidatePassword` 只按 Unicode 字符数检查至少 6 个字符;bootstrap 与创建用户继续共享该入口。
- 测试覆盖 6 位全小写、包含用户名和 6 个 Unicode 字符通过,以及 5 字符拒绝。
- 前端新用户表单只提示“至少 6 位字符”。
- Wiki 记录全模式规则和明确接受的风险,交付说明仍建议选择难猜且不复用的密码。
- 重新生成 Windows 包,使用运行时注入的脱敏凭据完成 demo 管理员初始化和登录,服务保持运行。
## 修改文件
- `Sense/server/app/sense/identity/password.go`
- `Sense/server/app/sense/identity/password_test.go`
- `Sense/ui/src/views/sense/identity/Users.vue`
- `docs/03-business-rules-and-glossary.md`
- `docs/delivery/README.md`
- `wiki-docs.json`
- `docs/task/42-Sense密码策略仅限制最短6位.md`
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 6 位全小写密码通过,5 位拒绝 | 通过 |
| 包含用户名密码通过 | 通过 |
| bootstrap 与创建用户共享规则 | 通过 |
| 前端只提示至少 6 位 | 通过 |
| Windows ZIP 重建并完成管理员初始化/登录 | 通过;bootstrap 201,login 200 |
| 凭据不进入仓库、Wiki、工单或日志 | 通过;工单需求已脱敏 |
## 测试
- `go test ./app/sense/identity/...`、`go test ./...`:通过。
- `corepack pnpm@9.15.1 lint`:0 error,存在既有格式 warning。
- `cmd /c Sense\scripts\package-windows.bat`:通过;前端仅有既有 vendor bundle 体积警告。
- ZIP:7,280,519 bytes;SHA-256 `7A1DC3378ECAD90DC7521F1E49C7A279E4C31B38D6EBC8CC30E123528BECDEE4`。
- 包内 demo:管理员初始化 201、登录 200;服务监听 `127.0.0.1:18080`。
- Harness:31 项测试、strict 和 Wiki 镜像检查通过。
- **未验证部分**:未在真实 PostgreSQL 环境验证;未由目标管理员人工页面登录验收。生产弱密码风险为负责人明确接受的产品决策。
## 遗留问题
- 本策略显著增加字典猜测和凭据填充风险;当前未增加 MFA、登录限速或外部身份提供者。
## 相关提交
- `8091fbb` feat: simplify Sense password policy (#42)
- `88dcebc` docs: record length-only Sense password policy (#42)
- `245d38d` docs: create task 42 acceptance archive (#42)
@@ -0,0 +1,69 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-44-Sense管理员侧栏模块为空修复
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-44-Sense%E7%AE%A1%E7%90%86%E5%91%98%E4%BE%A7%E6%A0%8F%E6%A8%A1%E5%9D%97%E4%B8%BA%E7%A9%BA%E4%BF%AE%E5%A4%8D.-
wiki_revision: 9647fffe7d8b0ede13c53746e8966fffb3555820
synchronized_at: 2026-08-13T01:31:35Z
<!-- gitea-wiki-mirror:end -->
# 44 Sense管理员侧栏模块为空修复
- 类型:缺陷
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-13
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/44
- Wiki 页面:Task-44-Sense管理员侧栏模块为空修复
- Wiki revision:见本地镜像头
## 背景与目标
最新 Windows 包中管理员可登录,但侧栏不显示任何模块。目标是在不放宽 RBAC 的前提下恢复 Sense MVP 导航,并防止 Vue Router 规范化记录顺序再次导致空菜单。
## 根因与最终方案
后端管理员会话正确返回 9 项权限。Vue Router `getRoutes()` 会同时返回父布局 `/` 和路径也为 `/` 的工作台子记录;原代码用首个 `path === '/'` 记录派生菜单,误选无 children 的工作台记录。
修复后只选择路径为 `/` 且具有 children 的布局记录,并在多个候选中优先 children 最完整者。权限过滤、隐藏入口过滤和排序逻辑保持不变,并提取为独立可测试函数。
## 修改文件
- `Sense/ui/src/layout/AppLayout.vue`:调用稳定的菜单派生函数。
- `Sense/ui/src/layout/navigation.js`:定位布局、按权限过滤并排序。
- `Sense/ui/tests/navigation.test.cjs`:重复根路径、权限/隐藏过滤和管理员八项导航测试。
- `Sense/ui/package.json`:增加 `test:navigation`。
- `docs/06-troubleshooting.md`:Wiki-first 排错说明。
- `wiki-docs.json`、`docs/task/44-Sense管理员侧栏模块为空修复.md`:任务归档。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| 管理员显示八项 Sense MVP 导航 | 自动回归通过 |
| 不依赖重复 `/` 记录顺序 | 通过 |
| 权限不足和隐藏入口仍被过滤 | 通过 |
| 前端 test/lint/build | 通过;lint 0 error,存在既有 warning |
| Windows ZIP 重建并以管理员登录 | 通过;角色 administrator,9 项权限,新前端资源已加载 |
## 测试
- `corepack pnpm@9.15.1 test:navigation`:3 项通过。
- Vue Router 实际规范化记录探针:正确派生工作台和设备管理。
- `corepack pnpm@9.15.1 lint`:0 error,既有 warning。
- Windows 打包:通过;前端仅有既有 vendor bundle 体积警告。
- 包内运行:登录角色 administrator、权限数 9,首页引用新脚本 `js/index.25411ed5.js`。
- Harness:31 项、strict 与 Wiki 镜像检查通过。
- ZIP:7,280,825 bytes;SHA-256 `0703B397D20EAAE54BCB6B5A83170EFF6816246A8C163276495CD93EEEB72B4F`。
- **未验证部分**:未通过自动化真实浏览器截图断言侧栏 DOM;已要求用户 Ctrl+F5 后人工查看。
## 遗留问题
- 浏览器可能缓存旧前端资源,更新包后需要强制刷新。
## 相关提交
- `048a6ec` fix: restore Sense permission navigation (#44)
- `62ed5cc` docs: add Sense empty navigation troubleshooting (#44)
- `c4a799c` test: cover Sense administrator navigation (#44)
- `21624f6` docs: create task 44 acceptance archive (#44)
@@ -1,51 +0,0 @@
<!-- gitea-wiki-mirror:start -->
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Task-9-Bell独立GoAdmin产品骨架
wiki_url: https://git.ilapage.cn/ila/yovision/wiki/Task-9-Bell%E7%8B%AC%E7%AB%8BGoAdmin%E4%BA%A7%E5%93%81%E9%AA%A8%E6%9E%B6.-
wiki_revision: affa26c4134ad936c277a960aa45a838db28e73c
synchronized_at: 2026-08-12T10:19:02Z
<!-- gitea-wiki-mirror:end -->
# 9 Bell独立GoAdmin产品骨架
- 类型:需求
- 所属 Epic:#7
- 所属 MVP / 版本:#8
- 状态:待验收
- 日期:2026-08-12
- Gitea 工单:https://git.ilapage.cn/ila/yovision/issues/9
- Wiki 页面:Task-9-Bell独立GoAdmin产品骨架
- Wiki revision:见本地镜像头
## 背景与目标
在 Sense、Brain 不启动时建立 Bell 可独立构建、运行和交付的 Go + Vue 3 + Element Plus + PostgreSQL 产品骨架,冻结工具链并移除无关 go-admin 演示入口。
## 最终方案
实现独立后端命令、事务迁移、健康/就绪检查、静态资源宿主和 Vue 管理外壳;保留上游 MIT 许可证。正式运行要求 Bell 专用 PostgreSQL,未提供内置账户、密码或生产配置。
## 修改文件
- `Bell/server/`:Go 入口、配置、PostgreSQL、迁移与健康检查。
- `Bell/web/`:Vue 3、Element Plus、路由、状态与应用外壳。
- `Bell/README.md`、`Bell/.env.example`、`Bell/LICENSES/`:运行说明、无秘密模板与上游来源。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| Sense/Brain 停止时独立启动 | 通过 |
| 空 PostgreSQL 迁移,health/ready 正常 | 通过 |
| 无关演示路由不可访问 | 通过 |
| 精确工具链与许可证可追溯 | 通过 |
## 测试
- 执行:`go test ./...`、`pnpm lint`、`pnpm build:prod`、空库迁移、health/ready smoke、Harness、仓库 unittest。
- 结果:全部通过;前端构建仅有 Element Plus vendor 体积警告。
- **未验证部分**:生产部署、备份恢复与人工 UI 验收。
## 相关提交
- `a5d846a` 初始化独立 Bell 产品骨架(PR #35)。
+8 -28
View File
@@ -113,40 +113,20 @@
"path": "docs/task/27-Sense区域与方向警戒线版本配置.md"
},
{
"page": "Task-9-Bell独立GoAdmin产品骨架",
"path": "docs/task/9-Bell独立GoAdmin产品骨架.md"
"page": "Task-37-Sense-Windows前后端一键打包",
"path": "docs/task/37-Sense-Windows前后端一键打包.md"
},
{
"page": "Task-12-Bell独立登录RBAC与认证审计",
"path": "docs/task/12-Bell独立登录RBAC与认证审计.md"
"page": "Task-40-Sense全模式密码最短6位",
"path": "docs/task/40-Sense全模式密码最短6位.md"
},
{
"page": "Task-17-Bell不可变Event与永久幂等收据",
"path": "docs/task/17-Bell不可变Event与永久幂等收据.md"
"page": "Task-42-Sense密码策略仅限制最短6位",
"path": "docs/task/42-Sense密码策略仅限制最短6位.md"
},
{
"page": "Task-18-Bell开发测试合成事件入口",
"path": "docs/task/18-Bell开发测试合成事件入口.md"
},
{
"page": "Task-19-Bell规则匹配与Event-Alert链路",
"path": "docs/task/19-Bell规则匹配与Event-Alert链路.md"
},
{
"page": "Task-20-Bell-Alert并发处置与审计时间线",
"path": "docs/task/20-Bell-Alert并发处置与审计时间线.md"
},
{
"page": "Task-31-Bell原型对齐GoAdmin与Element-Plus",
"path": "docs/task/31-Bell原型对齐GoAdmin与Element-Plus.md"
},
{
"page": "Task-33-Bell普通用户预警处理原型",
"path": "docs/task/33-Bell普通用户预警处理原型.md"
},
{
"page": "Task-36-Bell-MVP文档与待验收归档",
"path": "docs/task/36-Bell-MVP文档与待验收归档.md"
"page": "Task-44-Sense管理员侧栏模块为空修复",
"path": "docs/task/44-Sense管理员侧栏模块为空修复.md"
}
]
}