Compare commits

..
Author SHA1 Message Date
QiuSW 6d90d54592 docs: finalize Sense task archives (#22-#27) 2026-08-12 17:47:11 +08:00
QiuSW 997900dd08 docs: register task #27 archive 2026-08-12 17:44:23 +08:00
QiuSW 9c8a322607 docs: register task #26 archive 2026-08-12 17:42:10 +08:00
QiuSW 118423a128 docs: register task #25 archive 2026-08-12 17:40:30 +08:00
QiuSW c7c380458f docs: register task #24 archive 2026-08-12 17:38:45 +08:00
QiuSW a82718ad13 docs: register task #23 archive 2026-08-12 17:36:56 +08:00
QiuSW c524a7f2c2 docs: register task #22 archive 2026-08-12 17:35:10 +08:00
QiuSW 5177861c7c docs: document Sense MVP operations (#22-#27) 2026-08-12 17:33:09 +08:00
QiuSW e2e18b0380 feat: add versioned Sense areas and directional lines (#27) 2026-08-12 17:29:09 +08:00
QiuSW 8509699fff feat: add protected single-stream Sense live view (#26) 2026-08-12 17:24:47 +08:00
QiuSW 398534aabe feat: add MediaMTX process and reconciliation control (#25) 2026-08-12 17:21:14 +08:00
QiuSW 4f17cca91f feat: add controlled ONVIF and RTSP admission (#24) 2026-08-12 17:14:34 +08:00
QiuSW 5b8f4a4c8c feat: add Sense device inventory and credential boundary (#23) 2026-08-12 17:10:13 +08:00
QiuSW b9416ebc08 feat: add Sense identity RBAC and audit (#22) 2026-08-12 17:06:26 +08:00
QiuSW 2c0e37eff7 docs: archive task #21 for acceptance 2026-08-12 16:55:45 +08:00
QiuSW 2e22252feb docs: document Sense product skeleton (#21) 2026-08-12 16:51:44 +08:00
QiuSW d2169990d3 feat: initialize independent Sense product skeleton (#21) 2026-08-12 16:49:17 +08:00
1008 changed files with 6566 additions and 81131 deletions
-12
View File
@@ -1,12 +0,0 @@
# 仓库内文本统一以 LF 存储,避免 Windows/WSL 混用产生全量行尾差异。
# 行尾差异会淹没真实改动,也会让 gitea.env 之类的配置在 bash 中带上 \r。
* text=auto eol=lf
*.py text eol=lf
*.md text eol=lf
*.json text eol=lf
*.ps1 text eol=crlf
*.png binary
*.jpg binary
*.zip binary
+15 -25
View File
@@ -1,6 +1,9 @@
## 基本信息
- 类型:需求 / 缺陷 / 重构
- 任务类型:单项目 / 协同
- 主项目:Sense / Brain / Bell / contracts / 根级
- 主 agent:
- 所属 Epic:#
- 所属 MVP / 版本:#
- 阶段:
@@ -18,8 +21,20 @@
- 仅影响的子项目 / 交付单元:
- 是否跨子项目:是 / 否
- 是否修改共享接口或契约:是 / 否;唯一事实来源:
- write_paths:
- 各子项目需要执行的验证:
## 协同接口
<!-- 单项目工单填写“不适用”;协同工单必须完整填写。 -->
- 生产者:
- 消费者:
- 契约/共享事实源:
- 兼容策略:不适用 / 向后兼容 / 发布新版本
- 被阻塞或需要适配的工单:
- 集成顺序:
## 原始需求
- 来源:用户对话 / Gitea / 其他
@@ -53,22 +68,6 @@
|---|---|---|---|
| | | | 是 / 否 |
## 设计与原型门禁
<!-- 先判断是否属于纯显示文案豁免,再选择最低成本、足以确认的设计证据。 -->
- 修改类型:纯显示文案 / 小范围 UI / 新组件 / 新页面或独立用户功能 / 重大交互或导航 / 非 UI / 恢复既有行为的 Bug
- 所需设计证据:无 / 标注截图 / 低保真图 / 已确认原型 / 架构、API、数据、状态或流程设计 / 原设计或复现证据
- 可编辑设计源、线上原型链接和访问检查:
- 审核版本、revision、复制版本或确认日期及识别方式:
- 本地 HTML 导出:未要求 / 用户明确要求 / 项目规则要求
- 本地 HTML 路径、版本和资源检查(仅显式导出时填写):
- 状态:无 / 草稿 / 已确认 / 已废弃
- 确认人、确认时间和覆盖范围:
- 无需 UI 原型或无需任何原型的原因:
<!-- 新页面、独立用户功能、重大交互或导航变化默认通过可访问且版本明确的线上原型审核;只有用户或项目规则明确要求时才导出 prototypes/<工单号>/<版本>/index.html。原型和文字需求未确认前不得编写生产代码。 -->
## 文档影响
<!-- 至少选择一项;不影响长期文档时必须写明原因。 -->
@@ -89,15 +88,6 @@
- [ ] 新增交付文档,受众与页面:
- [ ] 需要目标岗位或客户代表验证:是 / 否;验证方式:
## 任务记录与可选快照
- 单次任务事实来源:当前 Gitea 工单正文与评论
- [ ] 默认不创建任务快照
- [ ] 用户明确要求专项快照;用途和范围:
- [ ] 项目专用规则要求任务快照;规则入口:
<!-- 工单正文保存确认基线;重要变化、最终证据和验收结论通过评论追加。只有长期事实变化时才更新 Wiki 和同步镜像。 -->
## 验收标准
- [ ] <!-- 填写 -->
-1
View File
@@ -1,5 +1,4 @@
.codex/gitea.env
gitea.env
.codex/*.log
.codex/config.toml
__pycache__/
+23 -95
View File
@@ -1,25 +1,8 @@
# Agent 开发规则
本仓库采用 DevHarness 工作流:Gitea 工单是单次任务需求、变化、实现、测试、提交和验收的事实来源,Gitea Wiki 是长期开发文档的事实来源,Git 是代码与版本绑定资料的变更记录。`docs/` 默认保存核心 Wiki 的只读镜像,`docs/task/` 只保存人工明确要求的专项或历史兼容快照。人负责确认方案与验收,Agent 负责检查、实现、测试和留下证据。
本仓库采用 DevHarness 工作流:Gitea 工单是任务过程的事实来源,Gitea Wiki 是长期开发文档和任务归档的事实来源,Git 是代码与版本绑定资料的变更记录,`docs/` 只保存 Wiki 的只读镜像。人负责确认方案与验收,Agent 负责检查、实现、测试和留下证据。
开始工作前先阅读任务涉及目录中的 `AGENTS.md`。目录越深的规则越具体,但不得削弱上级安全规则。
[项目档案](docs/00-project-profile.md) 按需阅读,不作为每次任务的固定前置。出现下列情况之一时必须读:需要环境、配置或凭据来源;需要确认目录边界;需要判断子项目与交付单元划分;需要 DevHarness 来源与基线;需要项目专用验收要求。只为查命令不必打开项目档案。
## 常用命令
所有命令默认从仓库根目录执行,与项目档案保持一致。
| 用途 | 命令 |
|---|---|
| 查看工作区 | `git status --short --branch` |
| 检查模板结构 | `python dev_scripts/harness.py check --strict` |
| 运行单元测试 | `python -m unittest discover -s tests -v` |
| 导出核心 Wiki 镜像 | `python dev_scripts/harness.py sync` |
| 检查核心 Wiki 镜像 | `python dev_scripts/harness.py sync --check` |
| 创建可选任务快照 | `python dev_scripts/harness.py archive 123 "修复登录超时"` |
| 增量导出已有快照 | `python dev_scripts/harness.py export` |
| 全量导出已有快照 | `python dev_scripts/harness.py export --all` |
开始工作前先阅读 [项目档案](docs/00-project-profile.md) 和任务涉及目录中的 `AGENTS.md`。目录越深的规则越具体,但不得削弱上级安全规则。
## 1. 永久规则
@@ -35,15 +18,14 @@
新功能、缺陷修复、重构以及任何用户可感知或改变程序行为的修改,必须先建立单元任务工单。
以下小改动可以直接提交,不要求工单:
以下小改动可以直接提交,不要求工单和任务归档:
- 只改错别字、注释或文档措辞;
- 只做格式化、导入排序或不跨文件的内部变量改名;
- 补充类型标注或文档字符串且不改变行为;
- 删除已经确认无人使用的死代码。
- 只修改用户看到的界面显示文案,并且满足本文件“工单与设计证据双门禁”的全部豁免条件。
除严格符合界面显示文案豁免的修改外,只要涉及接口、数据库、状态、权限、安全、并发、用户界面,或者无法确定是否改变行为,就必须建工单。代码组件名、类名、变量、国际化键、API 字段和数据库字段不是显示文案,不适用豁免。
只要涉及接口、数据库、状态、权限、安全、并发、用户界面,或者无法确定是否改变行为,就必须建工单。
## 3. 需求到实施
@@ -55,61 +37,24 @@
6. 开始实施前检查分支和工作区,明确哪些现有改动不属于本任务。
7. 严格按工单范围实现;新发现的问题先记录,不顺手混入当前任务。
8. 执行与风险相称的测试,把关键结果和未验证部分更新到工单。实施过程中出现计划外、当前无法解除的问题时才标记“阻塞”。
9. 只有长期事实变化时才修改 Wiki、读取确认,再运行 `python dev_scripts/harness.py sync` 导出本地镜像;没有长期文档影响时在工单说明原因并跳过 Wiki 同步。不得直接编辑镜像后反向覆盖 Wiki。
工单正文保存用户确认的任务基线;根因、范围、方案、风险或阻塞发生重要变化时追加评论。完成实现后用一条评论集中记录最终差异、测试、未验证内容、提交哈希和文档影响;用户验收后再追加验收时间与结论,不重复覆盖或抄写已有证据。
9. 长期文档必须先修改 Wiki、读取确认,再运行 `python dev_scripts/sync_wiki_docs.py` 导出本地镜像;不得直接编辑 `docs/` 后反向覆盖 Wiki。
Gitea 不可用时,输出完整工单草稿并说明阻塞。未经用户明确授权,不得默认绕过建单。
### Gitea 交互与工单最小读取
- 所有 Gitea 工单和 Wiki 的查询、创建、更新、评论、状态变更及关闭操作,优先使用项目已配置的 Gitea MCP。
- MCP 不可用或不支持所需操作时才回退 Gitea API,并在当前工单记录回退原因;初始化阶段尚无工单时记录到初始化工单草稿,建单后补回。凭据只从环境或 MCP 安全配置读取。
- 首次接手任务时读取工单确认基线和完成当前判断所需的评论,不因节省 Token 跳过范围、依赖、安全、验收或重要变更。
- 同一任务、同一会话且关键前提未变化时,复用仍有效的工单事实,优先关注当前状态、最新评论和首个未完成步骤,不重复分析已经确认且仍有效的内容。
- 会话、代码、配置、依赖、凭据、远端状态或关键前提变化,任务基线不清楚,或最新评论声明历史需求、方案、范围、风险或验收发生变化时,重新读取必要历史;无法判断影响范围时读取完整工单。
- 连接器不支持评论分页或增量读取时允许读取完整工单,但不得把“已读取全文”误当成需要重新分析全部历史,也不得为规避完整读取而新增本地工单、缓存或第二事实来源。
- 正确性、安全规则和已确认范围优先于 Token 优化;读取边界存在不确定时补读必要证据。
### 新项目 Wiki 初始化门禁
- 从本模板创建新项目时,先创建 Gitea 远端仓库并启用工单和 Wiki,再配置 `wiki-docs.json`;不得把模板自带的本地 `docs/` 当作新项目 Wiki 已初始化的证据。
- 优先使用项目已配置的 Gitea MCP 查询和写入 Wiki;MCP 不可用或不支持所需写操作时,才使用 Gitea API,并在初始化工单记录回退原因。凭据只从环境或 MCP 安全配置读取。
- 先查询线上页面列表;`Home` 不存在时必须先创建 `Home`,在线回读正文并记录 revision,然后再逐页创建或更新其他核心映射页面。
- 每个核心页面写入后必须在线回读并取得 revision。页面缺失、回读失败或没有 revision 时停止初始化,不得开始产品代码。
- 产品编码前必须运行 `python dev_scripts/harness.py sync --verify`;全部成功才表示线上 Wiki 和核心镜像初始化完成。
### 工单与设计证据双门禁
- 纯界面显示文案只有在不改变业务含义、流程、权限、状态、接口、数据、法律/安全/支付/单位等高风险含义、国际化键、程序标识符、布局和可访问性,且没有任何不确定时,才免工单和原型;修改后执行最小界面检查。
- 新页面、独立用户功能、重大交互或导航变化,必须先用 Quant-UX 或其他合适工具制作可审阅原型;用户确认原型、文字需求和覆盖范围后,才能建立或放行实现工单并编写生产代码。
- 上述完整原型形成待审核版本后,默认直接通过 Quant-UX 或其他设计工具的线上链接审核,不要求每次导出本地 HTML。工单必须记录可访问链接、版本/revision 或确认日期、审核版本识别方式、确认人、确认时间和覆盖范围;线上链接无法访问或无法区分版本时停止审核,等待用户确认等效方案。
- 只有用户明确要求 `导出原型 #N`、`导出全部原型`,或项目专用规则明确要求离线交付时,才导出到 `prototypes/<工单号>/<版本>/index.html`。已确认的本地快照不得原位覆盖;版本目录内资源使用相对路径,导出后检查入口、主要交互和资源完整性,但不自动提交。
- 现有界面的小范围样式或布局调整使用标注截图、低保真图或明确复用的现有规范;新组件记录状态、错误和边界。两者只要不符合纯文案豁免就必须建单。
- 后端、接口、数据处理和定时任务不强制 UI 原型,但必须先确认架构、API、数据、状态或流程设计;恢复既有确认行为的 Bug 可以复用原设计、截图、复现步骤或已有验收证据。
- 需要设计证据的工单记录链接或路径、版本/revision 或日期、状态、确认人、确认时间和覆盖范围;没有 UI 原型时记录替代技术设计或原因。
- 页面结构、主要流程、状态、权限、异常处理或验收结果变化时,必须更新原型或文字需求并重新确认后再继续正式编码。
- 草稿原型可以用于讨论;写入 Git/Wiki、多人协作或单独实施时建立设计任务。草稿和经明确授权的隔离技术验证都不得直接作为生产实现。
- 设计工具无法生成用户明确要求的可用 HTML 时,在工单记录限制并停止该导出或离线交付,等待用户确认等效方案;线上原型可访问且版本明确时不因此阻塞线上审核。纯显示文案、小范围 UI、非 UI 需求和恢复既有行为的 Bug 不强制建立完整线上原型或导出 HTML。
### 自然语言快捷指令
快捷指令只是本工作流的自然语言别名,不得绕过方案确认、前置依赖、安全规则、工单范围、Wiki 主源、必要验证或人工验收:
- `只分析`:只读检查并给出方案;不建单、不修改,停在等待确认。
- `建工单`:根据已确认方案创建单元任务工单;建单后停止,不修改代码。
- `执行工单 #N`:检查工单和依赖,实施、测试、提交、推送并回写证据;仅在明确要求时创建任务快照;停在“待验收”。
- `执行工单 #N`:检查工单和依赖,实施、测试、提交、归档、推送并回写证据;停在“待验收”。
- `建工单并做`:依次建单和执行,`建工单,做`、`建工单,做` 含义相同;停在“待验收”。
- `继续工单 #N`:优先核对当前状态、最新评论、Git 和必要 Wiki 证据,从首个未完成步骤继续;关键前提未变化时不重复读取和分析仍有效的内容。
- `继续工单 #N`:核对工单、Git 和 Wiki 证据,从首个未完成步骤继续,不重复仍然有效的检查。
- `检查工单 #N`:只读核对范围、验收、测试和证据并输出报告;不自动修复。
- `同步文档`:读取 Wiki、导出核心 `docs/` 并检查一致性,不处理任务归档;不修改 Wiki、不自动提交。
- `导出原型 #N`:人工触发导出指定工单已确认的原型版本,按工单和版本写入 `prototypes/`;不扩展范围、不自动提交。
- `导出全部原型`:人工触发导出当前项目已明确范围内的全部已确认原型;不自动提交。
- `导出任务归档`:人工触发 `python dev_scripts/harness.py export`,只导出新增或 revision 已变化的任务归档;不删除本地文件、不自动提交。
- `导出全部任务归档`:人工触发 `python dev_scripts/harness.py export --all`,读取并导出全部线上任务归档;不删除本地文件、不自动提交。
- `#N 验收通过`:仅在用户明确验收后,在工单追加验收结论;按需更新真实变化的长期 Wiki,推送、同步父工单并关闭任务;不创建或导出任务归档。
- `同步文档`:读取 Wiki、导出 `docs/` 并检查一致性;不修改 Wiki、不自动提交。
- `#N 验收通过`:仅在用户明确验收后,更新归档、同步并提交镜像、推送、同步父工单并关闭任务。
方案未确认或前置依赖未满足时,实施类指令必须停在对应门禁;除 `#N 验收通过` 外,快捷指令不得关闭待验收工单。原型和任务快照的导出必须由用户明确提出或项目专用规则明确要求,其他指令不得隐式执行。Gitea 工单不导出全文,`docs/task/` 只是可能不完整的专项或历史兼容快照。详细语义见 [开发工作流](docs/01-workflow.md)。
方案未确认或前置依赖未满足时,实施类指令必须停在对应门禁;除 `#N 验收通过` 外,快捷指令不得关闭待验收工单。Gitea 工单不导出全文,本地只保存 Wiki 任务归档镜像。详细语义见 [开发工作流](docs/01-workflow.md)。
### 需求记录与流转
@@ -117,13 +62,13 @@ Gitea 不可用时,输出完整工单草稿并说明阻塞。未经用户明
- 工单中的目标、非目标、已确认方案、验收标准和文档影响构成确认后的正式任务需求。
- 影响范围、接口、数据、风险或验收的需求变化必须记录日期、内容、原因和用户确认;会改变已确认结果时先更新工单并等待再次确认。
- 不复制完整聊天,不保存 Agent 内部推理,不写入密码、令牌、个人数据或生产数据;包含敏感信息的原话必须删除敏感部分或改写为脱敏摘要。
- 长期有效的产品需求、业务规则和系统边界进入对应 Wiki 主题页并导出核心 `docs/`;单次任务的完成结果和验收保留在工单正文与评论。只有用户明确要求专项快照或项目专用规则要求时才创建 Wiki 任务快照并按需导出 `docs/task/`。Gitea 工单全文不导出到仓库。
- 长期有效的产品需求、业务规则和系统边界进入对应 Wiki 主题页并导出 `docs/`;完成结果进入 Wiki 任务归档并导出 `docs/task/`。Gitea 工单全文不导出到仓库。
详细记录边界见 [开发工作流](docs/01-workflow.md) 与 [业务规则和术语](docs/03-business-rules-and-glossary.md)。
### 效率与范围控制
本节只用于减少无关工作和重复检查,不得削弱安全规则、已确认方案、工单范围、必要测试、必要的长期文档同步、Git 提交和人工验收要求。
本节只用于减少无关工作和重复检查,不得削弱安全规则、已确认方案、工单范围、必要测试、Wiki 同步、Git 提交和验收归档要求。
#### 严格控制范围
@@ -175,22 +120,19 @@ Gitea 不可用时,输出完整工单草稿并说明阻塞。未经用户明
- 实现提交信息引用工单号,例如:`fix: 修复登录超时 (#123)`。
- 不为流程制造空提交。
- 优先运行项目档案中记录的格式检查、静态检查、单元测试和必要的集成测试。
- 不能验证的真机、生产、迁移或并发行为必须写入工单;存在明确要求的任务快照时再同步记录。
- Windows 环境优先使用当前已配置的 PowerShell;可选择时优先 PowerShell 7 `pwsh.exe`,不得仅为设置编码重复启动一层 PowerShell。
- 文本文件读写在命令支持时显式指定 UTF-8;文件解码和控制台输出分别处理,只有出现真实乱码或已知宿主非 UTF-8 时才设置当前进程的输出编码或 Python UTF-8 环境变量。
- 不得默认使用 `-ExecutionPolicy Bypass`;只有可信 `.ps1`确实被执行策略阻止且没有更小替代方案时,才对该次进程使用并在工单记录原因。
- 不能验证的真机、生产、迁移或并发行为必须写入工单和归档。
## 7. 完成和验收
## 7. 完成、验收和归档
1. 逐项完成验收、测试和实现提交,在工单追加最终证据评论,记录最终方案、差异、结果、提交、遗留问题和文档影响。
1. 逐项完成验收、测试和实现提交,并把最终方案、差异、结果、提交及遗留问题写回工单。
2. 工单保持“待验收”,用户没有明确验收通过前不得关闭。
3. 有长期文档影响时,读取确认 Wiki,运行 `python dev_scripts/harness.py sync --check` 检查核心镜像,并把页面 revision 和镜像提交哈希写回工单;没有长期文档影响时不运行 Wiki 同步。
4. 用户验收通过后,在工单追加验收时间和结论,关闭单元工单,并同步更新 MVP 和 Epic;没有真实变化的 Wiki 不重复更新或检查。
5. 默认不创建任务归档。只有用户明确要求专项快照或项目专用规则明确要求时,才运行 `python dev_scripts/harness.py archive <编号> "<短标题>"`;`export` 和 `export --all` 同样必须显式触发。
3. 运行 `python dev_scripts/new_task_archive.py <编号> "<短标题>"`,先创建 Wiki 归档,再登记并导出本地镜像。
4. 读取确认 Wiki,运行 `python dev_scripts/sync_wiki_docs.py --check`;归档镜像单独提交,并把页面、revision、路径和提交哈希写回工单。
5. 用户验收通过后关闭单元工单,并同步更新 MVP 和 Epic。
MVP 内所有单元任务通过后才能做 MVP 集成验收;MVP 通过后才能关闭 MVP。Epic 的全部范围完成后才能关闭 Epic。
详细证据回写、可选快照和文档同步边界见 [开发工作流](docs/01-workflow.md)。
详细归档顺序和字段见 [开发工作流](docs/01-workflow.md)。
## 8. 可维护性
@@ -211,9 +153,8 @@ MVP 内所有单元任务通过后才能做 MVP 集成验收;MVP 通过后才
- 每个单元任务必须在工单中选择“无长期文档影响并说明原因”或列出需要更新的 Wiki 页面。
- 启动、测试、部署、排错命令,模块入口、目录职责、主要调用路径,配置、API、数据结构、状态、业务规则、安全边界、日志位置发生变化时,必须更新对应 Wiki。
- 部署命令变化时,有常驻服务的项目更新自己的 `Deployment-and-Operations` 页面(由 [部署文档模板](docs/templates/deployment.md) 复制建立);没有常驻服务的项目记录为无部署文档影响,不创建空的部署页。
- 普通内部重构只有在入口、行为、配置和验证方式均未改变时,才可以记录为不影响长期文档。
- 必需核心页面及结构以 `python dev_scripts/harness.py check --strict` 和 [新项目文档初始化](docs/07-new-project-documentation-setup.md) 为准;稳定文档与可选历史快照的分工见 [开发工作流](docs/01-workflow.md)。
- 必需核心页面及结构以 `python dev_scripts/check_harness.py --strict` 和 [新项目文档初始化](docs/07-new-project-documentation-setup.md) 为准;稳定文档与任务归档的分工见 [开发工作流](docs/01-workflow.md)。
## 9. 引导提交例外
@@ -223,20 +164,11 @@ MVP 内所有单元任务通过后才能做 MVP 集成验收;MVP 通过后才
## 10. 项目专用规则
### YoVision 分支治理
- `explore` 是旧实现的只读聚合快照,不接受新功能、修复或文档演进;需要恢复旧行为时从其来源提交读取证据,不在该分支续写产品。
- `main` 是用户审核基线,禁止直接开发、直接提交或未经用户明确审核的合并。
- 新任务分支必须从当前 `dev` 创建,完成后通过 PR 合回 `dev`;不得把功能分支直接合入 `main`。
- `dev` 完成集成测试后仍不能自动进入 `main`;只有用户明确表示审核/验收通过,Agent 才能执行 `dev → main`。
- 紧急修复也必须建单并取得用户对分支与合并路径的明确授权,不默认绕过 `dev`。
<!-- 在项目初始化时填写不可违反的技术、安全和业务约束。复杂子项目请在其目录中增加 AGENTS.md。 -->
- 长期开发文档以 Gitea Wiki 为事实来源,单次任务证据以 Gitea 工单为事实来源;`docs/` 默认保存显式映射生成的核心只读镜像,`docs/task/` 是人工明确要求的专项或历史兼容快照,可能不完整或不是最新状态。
- 核心 Wiki 与镜像的固定顺序是:修改 Wiki → 读取确认 → 导出核心 `docs` → 校验差异 → 提交镜像。
- 默认不创建任务归档;`archive`、`导出任务归档` 或 `导出全部任务归档` 必须由用户明确提出或项目专用规则明确要求,且不得自动传播删除或重命名。
- 同步配置只允许写入 `docs/` 下的 Markdown;发现核心镜像有未提交修改时必须停止。
- 长期开发文档以 Gitea Wiki 为事实来源,`docs/` 是显式映射生成的只读镜像。
- Wiki 与镜像的固定顺序是:修改 Wiki → 读取确认 → 导出 `docs` → 校验差异 → 提交镜像。
- 同步配置只允许写入 `docs/` 下的 Markdown;发现镜像有未提交修改时必须停止。
- Gitea 凭据只通过进程环境或 MCP 安全配置提供,不得写入仓库。
- `dev_scripts/` 只存放 DevHarness 自身工具;业务项目的通用脚本必须使用独立目录,不得混放。
- 本仓库包含 `Sense/`、`Brain/`、`Bell/` 三个独立开发与交付单元;Sense 与 Bell 是认证、数据、部署和发布完全独立的销售产品,Brain 是无界面推理交付单元。
@@ -262,10 +194,6 @@ MVP 内所有单元任务通过后才能做 MVP 集成验收;MVP 通过后才
- `goadmin-baseline.json` 是 Sense、Bell 共用的 GoAdmin 上游与工具链版本事实来源;上游 URL 加完整 commit 是可复现标识,tag、`package.json` version、本机目录内容或本机已安装版本都不能替代 commit。
- `D:\github\goadmin\go-admin`、`go-admin-ui`、`go-admin-doc` 是当前机器的只读参考副本。Agent 可以读取和验证,不得在这些目录开发、修改或提交 yovision 产品代码。
- “基于 go-admin / go-admin-ui 二次开发”必须表现为从 `goadmin-baseline.json` 指定的两个完整上游 commit 导入或派生产品源码,并保留可核对的目录结构、框架启动链、通用认证/RBAC、菜单路由、请求封装及许可证来源;仅采用 Go、Vue、Element Plus、相似目录、页面外观或交互不算满足基线。
- 禁止用自建 `net/http`、自建认证/RBAC/会话框架或自制管理端外壳替代 go-admin/go-admin-ui 后仍声称是 GoAdmin 二次开发。确有上游能力无法满足时,必须在独立工单中说明缺口、保留上游扩展边界并等待用户确认。
- 每个 Sense/Bell 骨架、基础能力或框架相关工单开始实施前,必须读取任务相关的冻结 `go-admin-doc` 内容,并在工单中记录参考的文档主题或文件、继承的上游路径以及计划隐藏/禁用的默认模块。
- 初始化验收必须包含来源证据:上游 URL/commit、实际导入路径、保留的 MIT 文件、相对上游的裁剪/修改清单和构建测试;缺少任一项不得标记为 GoAdmin 骨架完成。
- Sense、Bell 必须从同一冻结基线分别初始化,保留独立代码、配置、构建、测试、版本和发布;不得通过共同修改参考仓库形成隐藏的共享实现。
- 复制或派生上游代码时必须保留对应 MIT 许可证和版权声明,并记录实际复制来源的 commit。
- `go-admin-doc` 只用于理解上游框架用法;yovision 的 Gitea Wiki、`AGENTS.md` 和 `goadmin-baseline.json` 仍是本项目事实来源。
-3
View File
@@ -26,9 +26,6 @@
## GoAdmin 技术基线
- 初始化或升级 Bell 前必须读取根 `goadmin-baseline.json`,按其中的 go-admin、go-admin-ui commit 和 Go/Node/pnpm 精确版本实施。
- Bell 必须由冻结 go-admin 后端与 go-admin-ui 前端源码直接派生;旧 `explore` 实现只能用于业务语义对照,禁止把其中自研认证、RBAC、会话、HTTP 平台或前端外壳复制为新基础。
- 开始每个骨架、认证权限、菜单路由、代码生成、ORM 或通用 UI 工单前,必须读取任务相关的冻结 go-admin-doc,并把参考主题/文件及采用方式写回工单。
- 骨架验收必须证明上游来源、继承路径、许可证、裁剪清单和相对上游差异;只有相似技术栈或视觉不能通过验收。
- `D:\github\goadmin` 下的仓库仅可只读参考;Bell 产品代码只能写入 `Bell/` 或工单授权路径,不得在参考仓库原地开发。
- 派生上游代码必须保留 MIT 许可证和版权声明;上游 `go-admin-doc` 只作框架用法参考,不覆盖 yovision Wiki 和本目录规则。
- 变更冻结基线必须升级为 Sense/Bell 协同工单;Bell 可以独立发布,但不得单独漂移上游或工具链版本。
-3
View File
@@ -1,3 +0,0 @@
# Safe local defaults. Do not add credentials, customer data, or production paths.
BRAIN_DEVICE=auto
BRAIN_LOG_LEVEL=INFO
-1
View File
@@ -1 +0,0 @@
3.11.15
-90
View File
@@ -1,90 +0,0 @@
# YoVision Brain
Brain 是无界面的独立推理交付单元。本骨架只提供可安装 Python 包、命令入口和运行时探测;尚不包含视频、模型、规则、事件或部署能力,也不依赖 Sense、Bell 在线。
## 冻结基线
| 项目 | 版本 / 选择 |
|---|---|
| Python | CPython `3.11.15`(`.python-version`;本机由 uv 隔离管理) |
| 环境与包管理 | Python `venv` + pip `26.2.1`;可用 uv `0.11.6` 取得固定 Python |
| 构建后端 | setuptools `80.9.0` |
| 测试 | pytest `8.4.2` |
| 数组运行时 | NumPy `2.3.3` |
| 推理运行时 | PyTorch `2.12.1` |
| CPU wheel | PyTorch 官方 `https://download.pytorch.org/whl/cpu` |
| NVIDIA wheel | PyTorch 官方 CUDA 12.6 `https://download.pytorch.org/whl/cu126` |
选择 Python 3.11 是因为 PyTorch 的 Windows 支持范围包含 Python 3.9–3.12,并且本机已有隔离的 CPython 3.11.15。选择 `torch 2.12.1 + cu126` 是因为 PyTorch 官方为 Linux/Windows 同时发布该固定组合;NVIDIA 的 CUDA 12.x 兼容表要求 Windows 驱动至少为 528.33,本机驱动 566.24 满足运行 CUDA 12.6 wheel 的驱动前提。
本机安装的 CUDA Toolkit 11.2 不参与 PyTorch wheel 构建,也不因本项目而修改。驱动满足最低版本只是兼容前提,不等于 GPU 已验证;必须以 `--smoke cuda` 的真实结果为准。
官方依据:
- [PyTorch - Start Locally](https://docs.pytorch.org/get-started/locally/)
- [PyTorch - Previous Versions](https://pytorch.org/get-started/previous-versions/)
- [NVIDIA CUDA 12.6 Release Notes](https://docs.nvidia.com/cuda/archive/12.6.0/cuda-toolkit-release-notes/index.html)
- [Python 3.11.15](https://www.python.org/downloads/release/python-31115/)
## 创建隔离环境
从仓库根目录执行。无需激活虚拟环境,也不需要更改 PowerShell 执行策略:
```powershell
uv python install 3.11.15
uv venv --python 3.11.15 Brain/.venv
Brain\.venv\Scripts\python.exe -m pip install pip==26.2.1 --index-url https://pypi.org/simple
Brain\.venv\Scripts\python.exe -m pip install -e "Brain[dev]" --index-url https://pypi.org/simple
```
若不使用 uv,也可以让已安装的 Python 3.11.15 创建环境:
```powershell
py -V:3.11 -m venv Brain/.venv
Brain\.venv\Scripts\python.exe -m pip install pip==26.2.1 --index-url https://pypi.org/simple
Brain\.venv\Scripts\python.exe -m pip install -e "Brain[dev]" --index-url https://pypi.org/simple
```
## 安装运行时
CPU 环境使用 PyTorch 官方 CPU 索引:
```powershell
Brain\.venv\Scripts\python.exe -m pip install numpy==2.3.3 --index-url https://pypi.org/simple
Brain\.venv\Scripts\python.exe -m pip install torch==2.12.1 --index-url https://download.pytorch.org/whl/cpu
```
目标 NVIDIA 环境使用官方 CUDA 12.6 wheel。该 wheel 自带所需 CUDA 用户态运行库,不要求把系统 Toolkit 改成 12.6:
```powershell
Brain\.venv\Scripts\python.exe -m pip install numpy==2.3.3 --index-url https://pypi.org/simple
Brain\.venv\Scripts\python.exe -m pip install torch==2.12.1 --index-url https://download.pytorch.org/whl/cu126
```
不要在同一环境混装 CPU 与 CUDA wheel;切换后端时重建 `.venv`。
## 运行和验证
入口的帮助与版本查询不导入 PyTorch,因此未安装运行时时也可用:
```powershell
Brain\.venv\Scripts\python.exe -m yovision_brain --help
Brain\.venv\Scripts\python.exe -m yovision_brain --version
```
安装相应运行时后执行:
```powershell
Brain\.venv\Scripts\python.exe -m pytest Brain/tests/test_package.py -q
Brain\.venv\Scripts\python.exe -m yovision_brain --runtime-info
Brain\.venv\Scripts\python.exe -m yovision_brain --smoke cpu
Brain\.venv\Scripts\python.exe -m yovision_brain --smoke cuda
```
`--runtime-info` 只输出 Python、PyTorch 和设备能力,不读取或显示环境变量值。`--smoke cuda` 在 CUDA wheel、驱动或设备不可用时以非零状态退出,不会回退 CPU 后伪称成功。
## 配置与安全边界
`.env.example` 只有无秘密默认值。Brain 不接收用户会话,不持有账户、RBAC、Alert 或通知状态。不得把 token、摄像头凭据、客户数据、内部文件路径或未经授权的人脸信息写入配置、日志或事件。
第三方许可证与来源见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。
-17
View File
@@ -1,17 +0,0 @@
# Third-party notices
本文件记录 Brain 骨架直接固定或明确依赖的第三方软件。具体安装包内许可证文本仍是最终依据。
| 组件 | 固定版本 | 许可证 | 来源 |
|---|---:|---|---|
| CPython | 3.11.15 | Python Software Foundation License | https://www.python.org/downloads/release/python-31115/ |
| pip | 26.2.1 | MIT | https://github.com/pypa/pip/tree/26.2.1 |
| uv(可选 Python 获取工具,不随 Brain 分发) | 0.11.6 | Apache-2.0 OR MIT | https://github.com/astral-sh/uv/tree/0.11.6 |
| python-build-standalone(uv 管理的 Python 分发来源,不随 Brain 分发) | 2026 系列 | MPL-2.0;分发包内另含 CPython 与组件许可证 | https://github.com/astral-sh/python-build-standalone |
| setuptools | 80.9.0 | MIT | https://github.com/pypa/setuptools/tree/v80.9.0 |
| pytest | 8.4.2 | MIT | https://github.com/pytest-dev/pytest/tree/8.4.2 |
| NumPy | 2.3.3 | BSD-3-Clause | https://github.com/numpy/numpy/tree/v2.3.3 |
| PyTorch | 2.12.1 | BSD-3-Clause | https://github.com/pytorch/pytorch/tree/v2.12.1 |
| NVIDIA CUDA runtime(随官方 PyTorch CUDA wheel 分发) | 12.6 系列 | NVIDIA CUDA Toolkit End User License Agreement | https://docs.nvidia.com/cuda/eula/index.html |
PyTorch 及后续模型可能带来额外第三方依赖与模型许可。本骨架未选择或分发任何模型;引入模型前必须另行核对商用、再分发、数据和输出限制,不能把框架许可证视为模型许可证。
-27
View File
@@ -1,27 +0,0 @@
[build-system]
requires = ["setuptools==80.9.0"]
build-backend = "setuptools.build_meta"
[project]
name = "yovision-brain"
version = "0.1.0"
description = "Headless inference delivery unit for YoVision"
readme = "README.md"
requires-python = "==3.11.*"
dependencies = []
[project.optional-dependencies]
# The wheel backend is selected by the official PyTorch index documented in
# README.md. Keeping one pinned requirement here prevents CPU/CUDA drift.
runtime = ["numpy==2.3.3", "torch==2.12.1"]
dev = ["pytest==8.4.2"]
[project.scripts]
yovision-brain = "yovision_brain.__main__:main"
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "--strict-markers"
-5
View File
@@ -1,5 +0,0 @@
"""YoVision Brain package."""
__version__ = "0.1.0"
__all__ = ["__version__"]
-112
View File
@@ -1,112 +0,0 @@
"""Command-line entry point for safe Brain runtime diagnostics."""
from __future__ import annotations
import argparse
import json
import platform
from collections.abc import Sequence
from typing import Any
from yovision_brain import __version__
def _load_torch() -> Any:
try:
import torch
except ImportError as exc:
raise RuntimeError(
"PyTorch runtime is not installed; install the pinned CPU or CUDA wheel "
"from Brain/README.md"
) from exc
return torch
def runtime_info() -> dict[str, object]:
"""Return non-sensitive interpreter and compute-runtime facts."""
info: dict[str, object] = {
"python": platform.python_version(),
"torch_installed": False,
"torch_version": None,
"cuda_build": None,
"cuda_available": False,
"cuda_device_count": 0,
}
try:
torch = _load_torch()
except RuntimeError:
return info
cuda_available = bool(torch.cuda.is_available())
info.update(
{
"torch_installed": True,
"torch_version": torch.__version__,
"cuda_build": torch.version.cuda,
"cuda_available": cuda_available,
"cuda_device_count": torch.cuda.device_count() if cuda_available else 0,
}
)
return info
def smoke(device: str) -> dict[str, object]:
"""Run a deterministic tensor operation on exactly the requested device."""
torch = _load_torch()
if device == "cuda" and not torch.cuda.is_available():
raise RuntimeError("CUDA was requested but PyTorch reports no available CUDA device")
tensor = torch.tensor([[1.0, 2.0], [3.0, 4.0]], device=device)
result = tensor @ tensor
expected = torch.tensor([[7.0, 10.0], [15.0, 22.0]], device=device)
if not torch.equal(result, expected):
raise RuntimeError("tensor smoke result did not match the expected value")
return {
"status": "ok",
"device": device,
"torch_version": torch.__version__,
"cuda_build": torch.version.cuda,
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="yovision-brain",
description="YoVision Brain runtime diagnostics",
)
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
action = parser.add_mutually_exclusive_group()
action.add_argument(
"--runtime-info",
action="store_true",
help="print non-sensitive Python/PyTorch/CUDA capability information",
)
action.add_argument(
"--smoke",
choices=("cpu", "cuda"),
help="run a tensor smoke test on exactly the selected device",
)
return parser
def main(argv: Sequence[str] | None = None) -> int:
args = build_parser().parse_args(argv)
try:
if args.runtime_info:
payload = runtime_info()
elif args.smoke:
payload = smoke(args.smoke)
else:
build_parser().print_help()
return 0
except RuntimeError as exc:
print(json.dumps({"status": "error", "message": str(exc)}, ensure_ascii=False))
return 2
print(json.dumps(payload, ensure_ascii=False, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
-46
View File
@@ -1,46 +0,0 @@
from __future__ import annotations
import json
import subprocess
import sys
import pytest
from yovision_brain import __version__
from yovision_brain.__main__ import main, runtime_info, smoke
def test_package_version() -> None:
assert __version__ == "0.1.0"
def test_module_help_runs_without_other_products() -> None:
result = subprocess.run(
[sys.executable, "-m", "yovision_brain", "--help"],
check=False,
capture_output=True,
text=True,
)
assert result.returncode == 0
assert "YoVision Brain runtime diagnostics" in result.stdout
def test_runtime_info_is_non_sensitive(capsys: pytest.CaptureFixture[str]) -> None:
assert main(["--runtime-info"]) == 0
payload = json.loads(capsys.readouterr().out)
assert set(payload) == {
"cuda_available",
"cuda_build",
"cuda_device_count",
"python",
"torch_installed",
"torch_version",
}
assert payload == runtime_info()
def test_cpu_tensor_smoke() -> None:
pytest.importorskip("torch")
result = smoke("cpu")
assert result["status"] == "ok"
assert result["device"] == "cpu"
+1 -63
View File
@@ -8,66 +8,4 @@ YoVision 是一个单仓多项目的智能视频事件平台,包含三个可
Sense 与 Bell 是账户、数据、部署和发布边界完全独立的两个销售产品;Brain 是无界面的独立推理交付单元,默认随 Sense 部署。跨项目协作只通过 `contracts/` 中的版本化契约。
开始工作前阅读 [AGENTS.md](AGENTS.md) 和 [文档中心](docs/README.md)。本仓库采用 DevHarness:Gitea 工单是单次任务唯一事实来源,Gitea Wiki 保存长期文档,`docs/` 默认保存核心 Wiki 的只读镜像。
## 工作闭环
```text
讨论需求或缺陷
-> 阅读代码并提出方案
-> 人工确认方案
-> 创建 Epic / MVP / 单元任务工单
-> Agent 实现并测试
-> 提交代码并集中回写工单证据
-> 人工验收后记录结论并关闭工单
-> 长期事实变化时才同步 Wiki
```
默认不创建任务归档;`docs/task/` 只保存人工明确要求的专项或历史兼容快照。
## 首次初始化顺序
1. 创建 Gitea 远端仓库并推送当前引导提交,启用工单和 Wiki。
2. 配置 `wiki-docs.json` 和安全访问方式;优先使用已配置的 Gitea MCP,MCP 不可用时才使用 Gitea API并记录原因。
3. 查询线上 Wiki;`Home` 不存在时先创建并回读 `Home`,取得 revision 后再创建其他核心页面。
4. 本地 `docs/` 的存在不能证明线上 Wiki 已初始化。
5. 开始产品编码前运行:
```powershell
python dev_scripts/harness.py sync --verify
```
YoVision 已完成远端与 Wiki 初始化,上述步骤用于维护者理解门禁和以后建立新仓库。
## 常用命令
```powershell
python dev_scripts/harness.py check --strict
python dev_scripts/harness.py sync
python dev_scripts/harness.py sync --check
python -m unittest discover -s tests -v
git diff --check
```
只有明确要求时才运行 `python dev_scripts/harness.py archive <编号> "<标题>"`、`export` 或 `export --all`。
## 分支模型
- `explore`:2026-08-14 前实现的只读聚合快照,用于迁移对照和回退。
- `main`:用户审核通过的基线,禁止直接开发。
- `dev`:集成开发与测试分支;任务分支从 `dev` 创建并通过 PR 返回 `dev`。
- 只有用户明确审核通过,才能将 `dev` 合入 `main`。
Sense 与 Bell 的新实现必须从根 `goadmin-baseline.json` 冻结的 go-admin 和 go-admin-ui 完整提交派生,开发时必须核对对应 commit 的 go-admin-doc。仅参考 GoAdmin 的技术栈、布局或视觉不属于本项目认可的二次开发。
## Harness 目录
```text
AGENTS.md Agent 通用规则与 YoVision 专用门禁
CLAUDE.md Claude Code 入口和三项目角色路由
.gitea/issue_template/ Epic、MVP、单元任务模板
docs/ 核心 Wiki 只读镜像及可选历史快照
wiki-docs.json 核心 Wiki 页面显式映射
dev_scripts/harness.py check / sync / archive / export 单一入口
dev_scripts/wiki_docs.py Gitea Wiki 客户端与镜像生成库
```
开始工作前阅读 [AGENTS.md](AGENTS.md) 和 [文档中心](docs/README.md)。本仓库采用 DevHarness:Gitea 工单记录任务过程,Gitea Wiki 保存长期文档,`docs/` 是 Wiki 的只读镜像。
-10
View File
@@ -1,10 +0,0 @@
server/temp/
server/*.exe
server/*.db
ui/node_modules/
ui/dist/
dist/
!scripts/build/
!scripts/build/**
.env
*.local.yml
-3
View File
@@ -26,9 +26,6 @@
## GoAdmin 技术基线
- 初始化或升级 Sense 前必须读取根 `goadmin-baseline.json`,按其中的 go-admin、go-admin-ui commit 和 Go/Node/pnpm 精确版本实施。
- Sense 必须由冻结 go-admin 后端与 go-admin-ui 前端源码直接派生;旧 `explore` 实现只能用于需求和行为对照,禁止把其中自研 HTTP、认证、RBAC、会话或前端外壳复制为新基础。
- 开始每个骨架、认证权限、菜单路由、代码生成、ORM 或通用 UI 工单前,必须读取任务相关的冻结 go-admin-doc,并把参考主题/文件及采用方式写回工单。
- 骨架验收必须证明上游来源、继承路径、许可证、裁剪清单和相对上游差异;只有相似技术栈或视觉不能通过验收。
- `D:\github\goadmin` 下的仓库仅可只读参考;Sense 产品代码只能写入 `Sense/` 或工单授权路径,不得在参考仓库原地开发。
- 派生上游代码必须保留 MIT 许可证和版权声明;上游 `go-admin-doc` 只作框架用法参考,不覆盖 yovision Wiki 和本目录规则。
- 变更冻结基线必须升级为 Sense/Bell 协同工单;Sense 可以独立发布,但不得单独漂移上游或工具链版本。
+11 -9
View File
@@ -1,13 +1,15 @@
# Sense 上游来源
# Upstream sources
Sense 的初始产品骨架从项目根目录 `goadmin-baseline.json` 冻结的上游源码派生,来源如下:
Sense is an independent product derived from the frozen GoAdmin baseline in
the repository root `goadmin-baseline.json`.
| 用途 | 上游仓库 | 完整 commit | 本地目录 | 许可证 |
| --- | --- | --- | --- | --- |
| 后端基线 | https://github.com/go-admin-team/go-admin.git | `f06540883b41d03782bb6b2c4150f298f328c6b6` | `Sense/server/` | MIT,见 `Sense/server/LICENSE.md` |
| 前端基线 | https://github.com/go-admin-team/go-admin-ui.git | `67d393d713877572fab0b897296a4c1d525fc81d` | `Sense/ui/` | MIT,见 `Sense/ui/LICENSE` |
| 使用参考 | https://github.com/go-admin-team/go-admin-doc.git | `424855aacf6905f3fde860c3331385cb25529a0d` | 未复制产品代码 | MIT |
- go-admin: `https://github.com/go-admin-team/go-admin`, commit
`f06540883b41d03782bb6b2c4150f298f328c6b6`, MIT.
- go-admin-ui: `https://github.com/go-admin-team/go-admin-ui`, commit
`67d393d713877572fab0b897296a4c1d525fc81d`, MIT.
- go-admin-doc: documentation reference only, commit
`424855aacf6905f3fde860c3331385cb25529a0d`, MIT.
导入时保留上游受版本控制的源文件与许可证,排除 `.git/`、上游 CI 配置、后端示例数据库以及本机生成的 `node_modules/`、`dist/`。随后仅在 `Sense/` 内完成产品命名、安全默认值、无关路由关闭和 PostgreSQL 默认配置等派生修改。
The local copies under `D:\github\goadmin` are read-only references and are
not a distributable or reproducible source identifier.
`D:\github\goadmin` 下的三个仓库仅为只读参考副本,不是 Sense 的开发目录或依赖事实来源。
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -19,3 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-157
View File
@@ -1,157 +0,0 @@
# Sense Windows 安装与运行
本说明适用于 `sense-windows-amd64` 交付包。Sense 后端和管理网页来自冻结的 GoAdmin/go-admin-ui 基线;启动仍使用 GoAdmin Cobra 的 `migrate` 与 `server` 命令。Brain、Bell 不需要启动。
## 1. 准备环境
- Windows 10/11 或 Windows Server 2019 及以上,amd64。
- PostgreSQL 17;先由数据库管理员创建独立的 Sense 数据库和最小权限账号。
- 已审核版本与许可证的 Windows amd64 `mediamtx.exe`,放到 `bin\mediamtx.exe`。
- 备份、恢复时还需要 PostgreSQL 客户端的 `pg_dump.exe`、`pg_restore.exe`;可把目录加入 PATH,或配置 `SENSE_POSTGRES_BIN`。
交付包不包含 PostgreSQL、数据库数据、管理员默认密码、摄像头密码或客户配置。不要把包解压到所有用户都可写的共享目录。
## 2. 配置 production
编辑 `config\sense.env`。脚本只按 `NAME=value` 读取白名单字段,不会执行文件内容。值中可以包含 `#`、`&`、`;`、空格或 `=`;如首尾使用成对单/双引号,外层引号会被移除。
至少填写:
```dotenv
SENSE_DATABASE_URL=host=127.0.0.1 port=5432 user=sense password=请替换 dbname=sense sslmode=disable
SENSE_JWT_SECRET=请替换为至少32字符的随机值
SENSE_MEDIAMTX_MODE=managed
SENSE_MEDIAMTX_BINARY=bin\mediamtx.exe
SENSE_MEDIAMTX_CONFIG=config\mediamtx.yml
```
用 PowerShell 生成随机值,不要把输出写入工单、Wiki 或 Git:
```powershell
$bytes = New-Object byte[] 48
[Security.Cryptography.RandomNumberGenerator]::Fill($bytes)
[Convert]::ToBase64String($bytes)
```
同名的非空进程环境变量优先于 `sense.env`。这便于由服务管理器或秘密管理工具注入值;空进程变量不会覆盖文件值。脚本不会打印数据库连接串、JWT secret、Bootstrap token 或摄像头密钥。
运行启动前检查:
```bat
check-sense.bat
```
它会检查配置格式、HTTP 端口、PostgreSQL TCP 连接、数据库名、JWT 长度、网页文件和 MediaMTX 模式。`managed` 模式要求二进制与配置文件存在;`external` 模式要求本机 Control API 已可连接。production 不允许 `disabled`。
## 3. 启动、迁移与停止
首次及日常启动:
```bat
start-sense.bat
```
脚本先执行 `sense.exe migrate -c data\runtime\settings.yml`,成功后再执行 `sense.exe server -c ...`。迁移失败时不会启动 HTTP 服务。迁移会检查 PostgreSQL 数据库是否存在;脚本不会自动创建生产数据库。
`config\db.sql` 与 `config\pg.sql` 是冻结 GoAdmin 首次初始化所需的无秘密基线数据,必须和 `sense.exe` 同版本保留;删除它们会导致空库首次迁移失败。
浏览器访问 `http://127.0.0.1:18080/`。当前窗口按 `Ctrl+C` 可让 Sense 优雅停止,并请求停止由它启动的 MediaMTX。也可在另一管理员终端运行:
```bat
stop-sense.bat
```
停止脚本只会强制停止监听配置端口、且可执行文件确实位于当前交付包的 Sense 进程树;端口属于其他程序时会拒绝操作。日常维护优先在启动窗口按 `Ctrl+C` 完成优雅停止,窗口丢失或进程失去响应时再使用停止脚本。
只执行迁移或禁用启动时自动迁移:
```bat
migrate-sense.bat
start-sense.bat -SkipMigration
```
只有已完成备份并明确掌握版本状态时才使用 `-SkipMigration`。也可把 `SENSE_AUTO_MIGRATE=false` 放到外部进程环境中。
## 4. 创建首个管理员与修改密码
Sense 不提供生产默认管理员。首次初始化:
1. 生成至少 32 字符的一次性随机值,临时填入 `SENSE_BOOTSTRAP_TOKEN`。
2. 启动 Sense。
3. 在另一个终端运行 `initialize-admin.bat -Username admin`,按隐藏提示输入至少 6 位密码。
4. 成功后立即清空 `SENSE_BOOTSTRAP_TOKEN` 并重启 Sense。
Bootstrap 只允许在用户表为空时执行一次,token 通过请求头传递,不放在 JSON 或命令行中。不要把密码作为 bat 参数。
管理员登录后,在右上角头像进入“个人中心 → 修改密码”。密码至少 6 个字符;修改成功后重新登录。其他管理员的密码重置只能由授权管理员通过 GoAdmin 用户管理入口完成并形成审计记录。
## 5. 备份与恢复
创建 PostgreSQL custom-format 备份:
```bat
backup-sense.bat
backup-sense.bat -OutputDirectory D:\SenseBackups
```
默认写入包外可单独保护的 `backups` 目录。脚本从连接串移除密码后再构造 `pg_dump` 命令,密码只通过子进程环境传递。
恢复会清理并替换目标库中的对象,必须先停止 Sense、备份当前库,并两次确认数据库名:
```bat
restore-sense.bat -BackupFile D:\SenseBackups\sense-sense-20260815-120000.dump -ConfirmDatabaseName sense
migrate-sense.bat
```
恢复脚本还会交互要求输入 `RESTORE-数据库名`;名称不完全一致时拒绝执行。不要对来源不明或版本不匹配的备份执行恢复。
## 6. Demo 隔离
Demo 使用独立的 `config\sense.demo.env` 和 `SENSE_DEMO_DATABASE_URL`:
```bat
start-sense.bat demo
```
数据库名必须包含 `demo` 或 `test`,且不会回退到 production 的 `SENSE_DATABASE_URL`。默认 HTTP 端口为 18081、MediaMTX 为 disabled。Demo 数据不属于生产数据,不得迁入生产库或用于客户交付。
Demo 启动窗口按 `Ctrl+C` 停止;窗口不可用时执行 `stop-sense.bat -Mode demo`。
## 7. 日志与排错
- Sense 文件日志:`logs\`
- 运行时生成的 GoAdmin YAML:`data\runtime\settings.yml`(包含秘密,不得复制到工单或发送给无权限人员)
- MediaMTX 日志:由 Sense 启动窗口和 MediaMTX 自身输出提供
- 包完整性:`MANIFEST.sha256`
常见错误:
- `SENSE_DATABASE_URL is required`:编辑当前包的 `config\sense.env`,或设置非空进程变量。
- `PostgreSQL is unreachable`:确认服务、地址、端口和防火墙;数据库不存在会在迁移阶段明确失败。
- `port ... already in use`:先运行 `stop-sense.bat`,或确认占用者后修改 `SENSE_PORT`。
- `Managed MediaMTX binary not found`:把已审核的 `mediamtx.exe` 放入 `bin`,不要只复制配置文件。
- `External MediaMTX Control API is unreachable`:启动外部实例并确认 API 只监听回环地址。
- `migration failed`:不要跳过;先备份,保留错误输出,核对数据库账号权限和版本。
- 网页返回 404:检查 `web\index.html` 与 `SENSE_WEB_ROOT=web`,不要把源码目录或 `node_modules` 放进包。
- 网页返回 200 但白屏:在浏览器开发者工具检查 JS/CSS 是否 404;正式包的构建审计会逐项核对 `web\index.html` 引用的本地资源,缺失时拒绝生成交付包。
## 8. 构建交付包
开发机在仓库根目录执行:
```bat
Sense\scripts\build\build-windows.bat
```
若要把已审核的 MediaMTX 一并放入包:
```powershell
Sense\scripts\build\build-windows.ps1 -MediaMTXPath D:\approved\mediamtx.exe
```
构建严格检查 Go 1.26.5、Node 22.22.1 和 pnpm 9.15.1,生成:
- `Sense\dist\sense-windows-amd64\`
- `Sense\dist\sense-windows-amd64.zip`
构建末尾会审计包内容:逐项核对 `web\index.html` 引用的本地 JS/CSS,并拒绝 `node_modules`、嵌套 `dist`、Git/缓存目录、数据库/备份文件、非空秘密字段、常见默认密码和私钥标记。`dist` 为可重建产物,不提交 Git。
+38 -41
View File
@@ -1,58 +1,55 @@
# Sense
Sense 是从项目冻结的 GoAdmin 后端与 go-admin-ui 前端源码独立派生的视频接入管理产品。后端保留 Cobra、Gin、GORM、Casbin、JWT 和迁移框架,前端保留 Vue、Router、Store、API、Layout、权限路由和通用管理组件。
Sense is the independently deployable intelligent NVR and video-sensing
management product in YoVision. It uses Go 1.26.5, PostgreSQL, Vue 3 and
Element Plus. Brain and Bell are not required for startup or smoke tests.
## 工具链
## Toolchain
Use the exact versions from the repository root `goadmin-baseline.json`:
- Go 1.26.5
- Node.js 22.22.1
- pnpm 9.15.1(建议通过 `corepack pnpm@9.15.1` 调用)
- PostgreSQL
- pnpm 9.15.1
完整上游来源见 `LICENSES/SOURCES.md`,版本事实来源为仓库根目录 `goadmin-baseline.json`。
## 本地验证入口
已使用 Windows 打包脚本生成 `Sense\dist\sense-windows-amd64` 后,可从 Sense 项目目录直接启动交付包:
```bat
start_sense.bat
start_sense.bat demo
```
该入口只负责定位并调用包内 `start-sense.bat`;生产配置、迁移和 MediaMTX 编排仍由交付包处理。交付包不存在时,入口会提示先运行 `scripts\build\build-windows.bat`,不会自动构建或启动开发服务。
后端:
The repositories under `D:\github\goadmin` are read-only references. Do not
develop Sense inside them.
```powershell
cd Sense/server
go test ./...
go vet ./...
go build ./...
$env:PATH = 'C:\Users\ila20\AppData\Local\Temp\yovision-tools\go1.26.5\go\bin;' + $env:PATH
powershell -ExecutionPolicy Bypass -File .\Sense\scripts\bootstrap\check-toolchain.ps1
```
前端:
## Backend
Production/default startup requires an independent PostgreSQL URL. No default
password or signing secret is provided.
```powershell
cd Sense/ui
cd Sense\server
$env:SENSE_DATABASE_MODE = 'postgres'
$env:SENSE_DATABASE_URL = '<provided outside the repository>'
go run .
```
For an isolated smoke test only:
```powershell
powershell -ExecutionPolicy Bypass -File .\Sense\scripts\bootstrap\smoke.ps1
```
Health endpoints are `GET /healthz` and `GET /readyz`. Upstream demo and code
generation routes are deliberately not registered.
## Frontend
```powershell
cd Sense\ui
corepack pnpm@9.15.1 install --frozen-lockfile
corepack pnpm@9.15.1 lint
corepack pnpm@9.15.1 test:unit
corepack pnpm@9.15.1 build:prod
corepack pnpm@9.15.1 build
```
## 安全配置
`server/config/settings.yml` 仅是无凭据模板。启动生产服务前,在仓库外复制该文件并填写 PostgreSQL 连接串和至少 32 个字符的随机 JWT 密钥,然后显式传入:
```powershell
go run . server -c C:\secure-path\sense-settings.yml
```
设备台账本身可以在不配置摄像头凭据的情况下使用。创建或更新 ONVIF/RTSP 凭据前,还必须在启动进程环境中设置 `SENSE_CREDENTIAL_KEY`:该值是随机 32 字节密钥的 Base64 编码,仅保存在仓库外。变量名模板见 `server/config/credential.env.example`;不要把真实值写入配置、脚本、日志或工单。密钥缺失或格式不正确时,Sense 会拒绝凭据写入,不会降级为明文存储。
使用 ONVIF 发现或手工接入前,还必须设置 `SENSE_ONVIF_DISCOVERY_IP` 和 `SENSE_ONVIF_ALLOWED_CIDRS`。前者只能是获准用于 WS-Discovery 的本机网卡地址;后者是获准访问的摄像头网段(多个 CIDR 用逗号分隔)。未配置时系统会给出可行动提示且不会扫描任意网卡;手工地址、Media XAddr 和 Stream URI 同样受该网段限制,并拒绝重定向或 URL 内凭据。
MediaMTX 保持独立二进制。配置 `SENSE_MEDIAMTX_BINARY`、`SENSE_MEDIAMTX_CONFIG` 和只允许回环地址的 `SENSE_MEDIAMTX_API`。Sense 只生成无摄像头凭据的基础配置;路径和凭据在运行时通过回环 Control API 下发。模板见 `server/config/mediamtx/mediamtx.yml.example`。
仓库不提供默认账号、默认密码或可用密钥。首位管理员通过受仓库外 `SENSE_BOOTSTRAP_TOKEN` 保护的一次性初始化接口创建,详细步骤以项目 Wiki 的本地开发与验证页为准。
The frontend automatically discovers project-local route and store modules.
Feature tickets add one route module without exposing unrelated GoAdmin demo
pages.
-5
View File
@@ -1,5 +0,0 @@
logLevel: info
api: true
apiAddress: 127.0.0.1:9997
metrics: false
paths: {}
-18
View File
@@ -1,18 +0,0 @@
# Demo mode must use a disposable PostgreSQL database whose name contains
# "demo" or "test". It never falls back to SENSE_DATABASE_URL.
SENSE_MODE=demo
SENSE_HOST=127.0.0.1
SENSE_PORT=18081
SENSE_DEMO_DATABASE_URL=
SENSE_JWT_SECRET=
SENSE_BOOTSTRAP_TOKEN=
SENSE_CREDENTIAL_KEY=
SENSE_ONVIF_DISCOVERY_IP=
SENSE_ONVIF_ALLOWED_CIDRS=
SENSE_MEDIAMTX_MODE=disabled
SENSE_MEDIAMTX_BINARY=
SENSE_MEDIAMTX_CONFIG=
SENSE_MEDIAMTX_API=http://127.0.0.1:9997
SENSE_WEB_ROOT=web
SENSE_AUTO_MIGRATE=true
SENSE_POSTGRES_BIN=
+6 -17
View File
@@ -1,18 +1,7 @@
# Sense production configuration. Copy this file as config\sense.env.
# Values are parsed as data; this file is never executed as a script.
SENSE_MODE=production
SENSE_HOST=127.0.0.1
SENSE_PORT=18080
# Copy values into a repository-external environment store. Do not commit secrets.
SENSE_HTTP_ADDRESS=127.0.0.1:18080
SENSE_DATABASE_MODE=postgres
SENSE_DATABASE_URL=
SENSE_JWT_SECRET=
SENSE_BOOTSTRAP_TOKEN=
SENSE_CREDENTIAL_KEY=
SENSE_ONVIF_DISCOVERY_IP=
SENSE_ONVIF_ALLOWED_CIDRS=
SENSE_MEDIAMTX_MODE=managed
SENSE_MEDIAMTX_BINARY=bin\mediamtx.exe
SENSE_MEDIAMTX_CONFIG=config\mediamtx.yml
SENSE_MEDIAMTX_API=http://127.0.0.1:9997
SENSE_WEB_ROOT=web
SENSE_AUTO_MIGRATE=true
SENSE_POSTGRES_BIN=
SENSE_LOG_LEVEL=info
SENSE_UI_STATIC_DIR=
-5
View File
@@ -1,5 +0,0 @@
Place the approved Windows amd64 mediamtx.exe in this package's bin directory,
or pass -MediaMTXPath to scripts\build\build-windows.ps1.
Sense does not redistribute MediaMTX automatically. Verify its version,
license, checksum, and customer approval before delivery.
@@ -0,0 +1,20 @@
$ErrorActionPreference = 'Stop'
$expectedGo = 'go version go1.26.5 windows/amd64'
$actualGo = (& go version).Trim()
if ($actualGo -ne $expectedGo) {
throw "Go toolchain mismatch. Expected '$expectedGo', got '$actualGo'."
}
$actualNode = (& node --version).Trim()
if ($actualNode -ne 'v22.22.1') {
throw "Node.js mismatch. Expected 'v22.22.1', got '$actualNode'."
}
$actualPnpm = (& corepack pnpm@9.15.1 --version).Trim()
if ($actualPnpm -ne '9.15.1') {
throw "pnpm mismatch. Expected '9.15.1', got '$actualPnpm'."
}
Write-Output 'Sense toolchain matches goadmin-baseline.json.'
+38
View File
@@ -0,0 +1,38 @@
$ErrorActionPreference = 'Stop'
$senseRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
$serverRoot = Join-Path $senseRoot 'server'
$env:SENSE_DATABASE_MODE = 'memory'
$env:SENSE_HTTP_ADDRESS = '127.0.0.1:18080'
$process = Start-Process -FilePath 'go' -ArgumentList @('run', '.') -WorkingDirectory $serverRoot -PassThru -WindowStyle Hidden
try {
$ready = $false
for ($attempt = 0; $attempt -lt 30; $attempt++) {
try {
$health = Invoke-RestMethod -Uri 'http://127.0.0.1:18080/healthz' -TimeoutSec 1
$readiness = Invoke-RestMethod -Uri 'http://127.0.0.1:18080/readyz' -TimeoutSec 1
if ($health.status -eq 'ok' -and $readiness.status -eq 'ready') {
$ready = $true
break
}
} catch {
Start-Sleep -Milliseconds 250
}
}
if (-not $ready) {
throw 'Sense smoke server did not become ready.'
}
try {
Invoke-WebRequest -Uri 'http://127.0.0.1:18080/api/v1/demo' -TimeoutSec 2 | Out-Null
throw 'Disabled demo route unexpectedly returned success.'
} catch {
if ($_.Exception.Response.StatusCode.value__ -ne 404) { throw }
}
Write-Output 'Sense independent smoke test passed.'
} finally {
if (-not $process.HasExited) { Stop-Process -Id $process.Id }
$process.WaitForExit()
}
-37
View File
@@ -1,37 +0,0 @@
param([Parameter(Mandatory = $true)][string]$WebRoot)
Set-StrictMode -Version 3.0
$ErrorActionPreference = 'Stop'
$root = [IO.Path]::GetFullPath($WebRoot)
$indexPath = Join-Path $root 'index.html'
if (-not (Test-Path -LiteralPath $indexPath -PathType Leaf)) {
throw "Web index not found: $indexPath"
}
$rootPrefix = $root.TrimEnd('\') + '\'
$html = Get-Content -LiteralPath $indexPath -Raw
$references = [regex]::Matches($html, '(?i)(?:src|href)\s*=\s*["''](?<path>[^"'']+)["'']')
$checked = 0
foreach ($match in $references) {
$assetReference = $match.Groups['path'].Value.Trim()
if (-not $assetReference -or $assetReference.StartsWith('//') -or $assetReference -match '^[a-z][a-z0-9+.-]*:') {
continue
}
$assetPath = ($assetReference -split '[?#]', 2)[0]
if ([IO.Path]::GetExtension($assetPath).ToLowerInvariant() -notin @('.js', '.css')) {
continue
}
$relative = [Uri]::UnescapeDataString($assetPath).TrimStart('/').Replace('/', '\')
if (-not $relative) { throw "Web index contains an empty local asset path: $assetReference" }
$resolved = [IO.Path]::GetFullPath((Join-Path $root $relative))
if (-not $resolved.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)) {
throw "Web index asset escapes the web root: $assetReference"
}
if (-not (Test-Path -LiteralPath $resolved -PathType Leaf)) {
throw "Web index references missing local asset: $assetReference"
}
$checked++
}
if ($checked -eq 0) { throw 'Web index does not reference any local JavaScript or CSS assets.' }
Write-Host "Sense web asset audit passed: $checked local references."
-3
View File
@@ -1,3 +0,0 @@
@echo off
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0build-windows.ps1" %*
exit /b %errorlevel%
-117
View File
@@ -1,117 +0,0 @@
param([string]$MediaMTXPath = '')
Set-StrictMode -Version 3.0
$ErrorActionPreference = 'Stop'
$senseRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..'))
$serverRoot = Join-Path $senseRoot 'server'
$uiRoot = Join-Path $senseRoot 'ui'
$distRoot = Join-Path $senseRoot 'dist'
$target = Join-Path $distRoot 'sense-windows-amd64'
$archive = Join-Path $distRoot 'sense-windows-amd64.zip'
$staging = Join-Path $distRoot ('.sense-windows-amd64.staging-' + $PID)
function Assert-ChildPath([string]$Parent, [string]$Child) {
$parentPath = [IO.Path]::GetFullPath($Parent).TrimEnd('\') + '\'
$childPath = [IO.Path]::GetFullPath($Child)
if (-not $childPath.StartsWith($parentPath, [StringComparison]::OrdinalIgnoreCase)) {
throw "Unsafe build path outside $Parent`: $Child"
}
}
function Get-SenseFileSha256([string]$Path) {
$sha256 = [Security.Cryptography.SHA256]::Create()
$stream = [IO.File]::OpenRead($Path)
try {
return ([BitConverter]::ToString($sha256.ComputeHash($stream))).Replace('-', '')
} finally {
$stream.Dispose()
$sha256.Dispose()
}
}
Assert-ChildPath $senseRoot $distRoot
Assert-ChildPath $distRoot $target
Assert-ChildPath $distRoot $archive
Assert-ChildPath $distRoot $staging
$goVersion = ''
Push-Location $serverRoot
try { $goVersion = (& go env GOVERSION).Trim() } finally { Pop-Location }
$nodeVersion = (& node --version).Trim().TrimStart('v')
$pnpmVersion = (& corepack pnpm@9.15.1 --version).Trim()
if ($goVersion -ne 'go1.26.5') { throw "Go 1.26.5 is required; module toolchain reported $goVersion." }
if ($nodeVersion -ne '22.22.1') { throw "Node 22.22.1 is required; found $nodeVersion." }
if ($pnpmVersion -ne '9.15.1') { throw "pnpm 9.15.1 is required; corepack reported $pnpmVersion." }
$hadNodeModules = Test-Path -LiteralPath (Join-Path $uiRoot 'node_modules')
$hadUIDist = Test-Path -LiteralPath (Join-Path $uiRoot 'dist')
try {
New-Item -ItemType Directory -Force -Path $distRoot | Out-Null
if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }
New-Item -ItemType Directory -Path $staging | Out-Null
Push-Location $uiRoot
try {
& corepack pnpm@9.15.1 install --frozen-lockfile
if ($LASTEXITCODE -ne 0) { throw 'pnpm install failed.' }
& corepack pnpm@9.15.1 run build:prod
if ($LASTEXITCODE -ne 0) { throw 'Sense UI production build failed.' }
} finally { Pop-Location }
$oldGOOS, $oldGOARCH, $oldCGO = $env:GOOS, $env:GOARCH, $env:CGO_ENABLED
try {
$env:GOOS = 'windows'; $env:GOARCH = 'amd64'; $env:CGO_ENABLED = '0'
Push-Location $serverRoot
try {
& go build -trimpath -ldflags '-s -w' -o (Join-Path $staging 'sense.exe') .
if ($LASTEXITCODE -ne 0) { throw 'Sense server Windows build failed.' }
} finally { Pop-Location }
} finally {
$env:GOOS, $env:GOARCH, $env:CGO_ENABLED = $oldGOOS, $oldGOARCH, $oldCGO
}
Copy-Item -LiteralPath (Join-Path $uiRoot 'dist') -Destination (Join-Path $staging 'web') -Recurse
New-Item -ItemType Directory -Path (Join-Path $staging 'scripts\runtime'), (Join-Path $staging 'config'), (Join-Path $staging 'bin') | Out-Null
Copy-Item -Path (Join-Path $senseRoot 'scripts\runtime\*.ps1') -Destination (Join-Path $staging 'scripts\runtime')
foreach ($name in @('start-sense', 'stop-sense', 'check-sense', 'migrate-sense', 'backup-sense', 'restore-sense', 'initialize-admin')) {
Copy-Item -LiteralPath (Join-Path $senseRoot "scripts\runtime\$name.bat") -Destination (Join-Path $staging "$name.bat")
}
Copy-Item -LiteralPath (Join-Path $senseRoot 'config\sense.env.example') -Destination (Join-Path $staging 'config\sense.env.example')
Copy-Item -LiteralPath (Join-Path $senseRoot 'config\sense.env.example') -Destination (Join-Path $staging 'config\sense.env')
Copy-Item -LiteralPath (Join-Path $senseRoot 'config\sense.demo.env.example') -Destination (Join-Path $staging 'config\sense.demo.env.example')
Copy-Item -LiteralPath (Join-Path $senseRoot 'config\sense.demo.env.example') -Destination (Join-Path $staging 'config\sense.demo.env')
Copy-Item -LiteralPath (Join-Path $senseRoot 'config\mediamtx.yml') -Destination (Join-Path $staging 'config\mediamtx.yml')
Copy-Item -LiteralPath (Join-Path $serverRoot 'config\db.sql') -Destination (Join-Path $staging 'config\db.sql')
Copy-Item -LiteralPath (Join-Path $serverRoot 'config\pg.sql') -Destination (Join-Path $staging 'config\pg.sql')
Copy-Item -LiteralPath (Join-Path $senseRoot 'README-WINDOWS.md') -Destination (Join-Path $staging 'README-WINDOWS.md')
Copy-Item -LiteralPath (Join-Path $senseRoot 'package\README-MEDIAMTX.txt') -Destination (Join-Path $staging 'bin\README-MEDIAMTX.txt')
Copy-Item -LiteralPath (Join-Path $senseRoot 'LICENSES') -Destination (Join-Path $staging 'LICENSES') -Recurse
if (-not [string]::IsNullOrWhiteSpace($MediaMTXPath)) {
$mediaSource = [IO.Path]::GetFullPath($MediaMTXPath)
if (-not (Test-Path -LiteralPath $mediaSource -PathType Leaf)) { throw "MediaMTX binary not found: $mediaSource" }
Copy-Item -LiteralPath $mediaSource -Destination (Join-Path $staging 'bin\mediamtx.exe')
}
$commit = (& git -C (Split-Path $senseRoot -Parent) rev-parse HEAD).Trim()
[IO.File]::WriteAllLines((Join-Path $staging 'VERSION.txt'), @(
"source_commit=$commit", 'go=1.26.5', 'node=22.22.1', 'pnpm=9.15.1'
), (New-Object Text.UTF8Encoding($false)))
& (Join-Path $PSScriptRoot 'test-package.ps1') -PackageRoot $staging
if ($LASTEXITCODE -ne 0) { throw 'Sense package audit failed.' }
$manifest = foreach ($file in Get-ChildItem -LiteralPath $staging -Recurse -File | Sort-Object FullName) {
$relative = $file.FullName.Substring($staging.Length + 1).Replace('\', '/')
"$(Get-SenseFileSha256 -Path $file.FullName) $relative"
}
[IO.File]::WriteAllLines((Join-Path $staging 'MANIFEST.sha256'), $manifest, (New-Object Text.UTF8Encoding($false)))
if (Test-Path -LiteralPath $target) { Remove-Item -LiteralPath $target -Recurse -Force }
Move-Item -LiteralPath $staging -Destination $target
if (Test-Path -LiteralPath $archive) { Remove-Item -LiteralPath $archive -Force }
Compress-Archive -LiteralPath $target -DestinationPath $archive -CompressionLevel Optimal
Write-Host "Sense Windows package: $target"
Write-Host "Sense Windows archive: $archive"
} finally {
if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }
if (-not $hadUIDist -and (Test-Path -LiteralPath (Join-Path $uiRoot 'dist'))) { Remove-Item -LiteralPath (Join-Path $uiRoot 'dist') -Recurse -Force }
if (-not $hadNodeModules -and (Test-Path -LiteralPath (Join-Path $uiRoot 'node_modules'))) { Remove-Item -LiteralPath (Join-Path $uiRoot 'node_modules') -Recurse -Force }
}
-37
View File
@@ -1,37 +0,0 @@
param([Parameter(Mandatory = $true)][string]$PackageRoot)
Set-StrictMode -Version 3.0
$ErrorActionPreference = 'Stop'
$root = [IO.Path]::GetFullPath($PackageRoot)
if (-not (Test-Path -LiteralPath $root -PathType Container)) { throw "Package directory not found: $root" }
$required = @(
'sense.exe', 'start-sense.bat', 'stop-sense.bat', 'check-sense.bat',
'migrate-sense.bat', 'backup-sense.bat', 'restore-sense.bat',
'initialize-admin.bat', 'README-WINDOWS.md', 'config\sense.env',
'config\sense.env.example', 'config\sense.demo.env',
'config\mediamtx.yml', 'config\db.sql', 'config\pg.sql',
'web\index.html', 'scripts\runtime\sense-common.ps1'
)
foreach ($relative in $required) {
if (-not (Test-Path -LiteralPath (Join-Path $root $relative))) { throw "Package is missing required path: $relative" }
}
& (Join-Path $PSScriptRoot 'assert-web-assets.ps1') -WebRoot (Join-Path $root 'web')
$forbiddenDirectories = Get-ChildItem -LiteralPath $root -Recurse -Directory | Where-Object { $_.Name -in @('node_modules', '.git', 'dist', '.cache') }
if ($forbiddenDirectories) { throw "Package contains forbidden build directory: $($forbiddenDirectories[0].FullName)" }
$forbiddenFiles = Get-ChildItem -LiteralPath $root -Recurse -File | Where-Object { $_.Extension -in @('.db', '.sqlite', '.sqlite3', '.dump', '.bak') }
if ($forbiddenFiles) { throw "Package contains database or backup data: $($forbiddenFiles[0].FullName)" }
$configFiles = @((Join-Path $root 'config\sense.env'), (Join-Path $root 'config\sense.demo.env'))
foreach ($configFile in $configFiles) {
$content = Get-Content -LiteralPath $configFile -Raw
foreach ($secret in @('SENSE_DATABASE_URL', 'SENSE_DEMO_DATABASE_URL', 'SENSE_JWT_SECRET', 'SENSE_BOOTSTRAP_TOKEN', 'SENSE_CREDENTIAL_KEY')) {
if ($content -match "(?m)^$secret[ \t]*=[ \t]*[^ \t\r\n]") { throw "Package contains a non-empty secret field: $secret" }
}
}
$textExtensions = @('.md', '.txt', '.env', '.example', '.ps1', '.bat', '.yml', '.yaml', '.json', '.html', '.js', '.css', '.sql')
foreach ($file in Get-ChildItem -LiteralPath $root -Recurse -File | Where-Object { $textExtensions -contains $_.Extension.ToLowerInvariant() }) {
$content = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction SilentlyContinue
if ($content -match '(?i)(admin123|password123|BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY)') {
throw "Package contains a forbidden default credential or private key marker: $($file.FullName)"
}
}
Write-Host "Sense package audit passed: $root"
-3
View File
@@ -1,3 +0,0 @@
@echo off
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\runtime\backup-sense.ps1" %*
exit /b %errorlevel%
-22
View File
@@ -1,22 +0,0 @@
param(
[ValidateSet('production', 'demo')][string]$Mode = 'production',
[string]$OutputDirectory = ''
)
. (Join-Path $PSScriptRoot 'sense-common.ps1')
try {
$root = Get-SensePackageRoot
$state = Initialize-SenseRuntime -PackageRoot $root -Mode $Mode -AllowOccupiedPort
$pgDump = Get-SensePostgresTool -Name 'pg_dump'
if ([string]::IsNullOrWhiteSpace($OutputDirectory)) { $OutputDirectory = Join-Path $root 'backups' }
$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
New-Item -ItemType Directory -Force -Path $OutputDirectory | Out-Null
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$output = Join-Path $OutputDirectory "sense-$($state.Database.Database)-$stamp.dump"
Invoke-SensePostgresTool -Tool $pgDump -Database $state.Database -Arguments @('--dbname', $state.Database.Sanitized, '--format=custom', '--no-owner', '--file', $output)
Write-Host "Sense backup created: $output"
exit 0
} catch {
Write-Error $_.Exception.Message
exit 1
}
-3
View File
@@ -1,3 +0,0 @@
@echo off
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\runtime\check-sense.ps1" %*
exit /b %errorlevel%
-19
View File
@@ -1,19 +0,0 @@
param(
[ValidateSet('production', 'demo')][string]$Mode = 'production',
[switch]$Running
)
. (Join-Path $PSScriptRoot 'sense-common.ps1')
try {
$root = Get-SensePackageRoot
$state = Initialize-SenseRuntime -PackageRoot $root -Mode $Mode -AllowOccupiedPort:$Running
if ($Running -and -not (Test-SenseTcpEndpoint -HostName $state.Host -Port $state.Port)) {
throw "Sense is not accepting TCP connections at $($state.Host):$($state.Port)."
}
Write-Host "Sense $Mode configuration check passed."
Write-Host "PostgreSQL endpoint: reachable; MediaMTX mode: $($state.MediaMode); HTTP port: $($state.Port)."
exit 0
} catch {
Write-Error $_.Exception.Message
exit 1
}
@@ -1,3 +0,0 @@
@echo off
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\runtime\initialize-admin.ps1" %*
exit /b %errorlevel%
@@ -1,29 +0,0 @@
param(
[string]$Username = '',
[ValidateSet('production', 'demo')][string]$Mode = 'production'
)
. (Join-Path $PSScriptRoot 'sense-common.ps1')
$passwordPointer = [IntPtr]::Zero
try {
$root = Get-SensePackageRoot
$state = Initialize-SenseRuntime -PackageRoot $root -Mode $Mode -AllowOccupiedPort
$token = Get-SenseEnvironmentValue -Name 'SENSE_BOOTSTRAP_TOKEN'
if ($token.Length -lt 32) { throw 'Set a temporary random SENSE_BOOTSTRAP_TOKEN of at least 32 characters, then restart Sense.' }
if ([string]::IsNullOrWhiteSpace($Username)) { $Username = Read-Host 'Administrator username' }
$securePassword = Read-Host 'Administrator password (at least 6 characters)' -AsSecureString
$passwordPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
$password = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($passwordPointer)
$body = @{ username = $Username; password = $password; nickName = $Username } | ConvertTo-Json -Compress
$headers = @{ 'X-Sense-Bootstrap-Token' = $token }
$uri = "http://$($state.Host):$($state.Port)/api/v1/public/bootstrap"
Invoke-RestMethod -Method Post -Uri $uri -Headers $headers -ContentType 'application/json; charset=utf-8' -Body $body | Out-Null
Write-Host 'Sense administrator created. Remove SENSE_BOOTSTRAP_TOKEN from config/sense.env and restart Sense now.'
exit 0
} catch {
Write-Error $_.Exception.Message
exit 1
} finally {
if ($passwordPointer -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($passwordPointer) }
Remove-Variable password -ErrorAction SilentlyContinue
}
-3
View File
@@ -1,3 +0,0 @@
@echo off
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\runtime\migrate-sense.ps1" %*
exit /b %errorlevel%
-19
View File
@@ -1,19 +0,0 @@
param([ValidateSet('production', 'demo')][string]$Mode = 'production')
. (Join-Path $PSScriptRoot 'sense-common.ps1')
try {
$root = Get-SensePackageRoot
$state = Initialize-SenseRuntime -PackageRoot $root -Mode $Mode -AllowOccupiedPort
$sense = Join-Path $root 'sense.exe'
Write-Host 'Applying pending Sense database migrations...'
Push-Location $root
try {
& $sense migrate -c $state.SettingsPath
if ($LASTEXITCODE -ne 0) { throw 'Sense database migration failed.' }
} finally { Pop-Location }
Write-Host 'Sense database migration completed.'
exit 0
} catch {
Write-Error $_.Exception.Message
exit 1
}
-3
View File
@@ -1,3 +0,0 @@
@echo off
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\runtime\restore-sense.ps1" %*
exit /b %errorlevel%
-27
View File
@@ -1,27 +0,0 @@
param(
[Parameter(Mandatory = $true)][string]$BackupFile,
[Parameter(Mandatory = $true)][string]$ConfirmDatabaseName,
[ValidateSet('production', 'demo')][string]$Mode = 'production',
[string]$Confirmation = ''
)
. (Join-Path $PSScriptRoot 'sense-common.ps1')
try {
$root = Get-SensePackageRoot
$state = Initialize-SenseRuntime -PackageRoot $root -Mode $Mode -AllowOccupiedPort
$backup = [IO.Path]::GetFullPath($BackupFile)
if (-not (Test-Path -LiteralPath $backup -PathType Leaf)) { throw "Backup file not found: $backup" }
if ($ConfirmDatabaseName -cne $state.Database.Database) {
throw 'Restore confirmation does not exactly match the configured database name.'
}
$pgRestore = Get-SensePostgresTool -Name 'pg_restore'
Write-Warning "Restoring will replace objects in database '$ConfirmDatabaseName'. Stop Sense before continuing."
$answer = if ([string]::IsNullOrWhiteSpace($Confirmation)) { Read-Host "Type RESTORE-$ConfirmDatabaseName to continue" } else { $Confirmation }
if ($answer -cne "RESTORE-$ConfirmDatabaseName") { throw 'Restore cancelled.' }
Invoke-SensePostgresTool -Tool $pgRestore -Database $state.Database -Arguments @('--dbname', $state.Database.Sanitized, '--clean', '--if-exists', '--no-owner', '--exit-on-error', $backup)
Write-Host 'Sense database restore completed. Run migrate-sense.bat before starting Sense.'
exit 0
} catch {
Write-Error $_.Exception.Message
exit 1
}
-281
View File
@@ -1,281 +0,0 @@
Set-StrictMode -Version 3.0
$ErrorActionPreference = 'Stop'
$script:SenseAllowedEnvironment = @(
'SENSE_MODE', 'SENSE_HOST', 'SENSE_PORT', 'SENSE_DATABASE_URL',
'SENSE_DEMO_DATABASE_URL', 'SENSE_JWT_SECRET', 'SENSE_BOOTSTRAP_TOKEN',
'SENSE_CREDENTIAL_KEY', 'SENSE_ONVIF_DISCOVERY_IP',
'SENSE_ONVIF_ALLOWED_CIDRS', 'SENSE_MEDIAMTX_MODE',
'SENSE_MEDIAMTX_BINARY', 'SENSE_MEDIAMTX_CONFIG',
'SENSE_MEDIAMTX_API', 'SENSE_WEB_ROOT', 'SENSE_AUTO_MIGRATE',
'SENSE_POSTGRES_BIN'
)
function Get-SensePackageRoot {
param([string]$ScriptDirectory = $PSScriptRoot)
return [System.IO.Path]::GetFullPath((Join-Path $ScriptDirectory '..\..'))
}
function Import-SenseEnvironment {
param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
throw "Sense configuration file not found: $Path"
}
$lineNumber = 0
foreach ($rawLine in Get-Content -LiteralPath $Path -Encoding UTF8) {
$lineNumber++
$line = $rawLine.Trim()
if ($line.Length -eq 0 -or $line.StartsWith('#')) { continue }
$separator = $line.IndexOf('=')
if ($separator -lt 1) {
throw "Invalid Sense configuration at line $lineNumber. Expected NAME=value."
}
$name = $line.Substring(0, $separator).Trim()
if ($script:SenseAllowedEnvironment -notcontains $name) {
throw "Unsupported Sense configuration key at line ${lineNumber}: $name"
}
$value = $line.Substring($separator + 1)
if ($value.Length -ge 2) {
$first, $last = $value[0], $value[$value.Length - 1]
if (($first -eq '"' -and $last -eq '"') -or ($first -eq "'" -and $last -eq "'")) {
$value = $value.Substring(1, $value.Length - 2)
}
}
$existing = [Environment]::GetEnvironmentVariable($name, 'Process')
if ([string]::IsNullOrWhiteSpace($existing)) {
[Environment]::SetEnvironmentVariable($name, $value, 'Process')
}
}
}
function Get-SenseEnvironmentValue {
param([Parameter(Mandatory = $true)][string]$Name, [string]$Default = '')
$value = [Environment]::GetEnvironmentVariable($Name, 'Process')
if ([string]::IsNullOrWhiteSpace($value)) { return $Default }
return $value
}
function ConvertTo-SenseYamlString {
param([AllowEmptyString()][string]$Value)
return ($Value | ConvertTo-Json -Compress)
}
function Resolve-SenseConfiguredPath {
param([Parameter(Mandatory = $true)][string]$PackageRoot, [AllowEmptyString()][string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) { return '' }
if ([System.IO.Path]::IsPathRooted($Value)) {
return [System.IO.Path]::GetFullPath($Value)
}
return [System.IO.Path]::GetFullPath((Join-Path $PackageRoot $Value))
}
function Get-SenseDatabaseInfo {
param([Parameter(Mandatory = $true)][string]$Connection)
$result = @{ Host = '127.0.0.1'; Port = 5432; Database = ''; Sanitized = $Connection; Password = '' }
if ($Connection -match '^postgres(?:ql)?://') {
$uri = [Uri]$Connection
$result.Host = $uri.Host
if (-not $uri.IsDefaultPort) { $result.Port = $uri.Port }
$result.Database = $uri.AbsolutePath.TrimStart('/')
if ($uri.UserInfo) {
$parts = $uri.UserInfo.Split(':', 2)
$user = [Uri]::UnescapeDataString($parts[0])
if ($parts.Count -eq 2) { $result.Password = [Uri]::UnescapeDataString($parts[1]) }
$builder = [UriBuilder]$uri
$builder.UserName = $user
$builder.Password = ''
$result.Sanitized = $builder.Uri.AbsoluteUri
}
return $result
}
$matches = [regex]::Matches($Connection, '(?:^|\s)(?<key>[A-Za-z_][A-Za-z0-9_]*)=(?<value>''(?:[^'']|'''')*''|"(?:[^"]|"")*"|[^\s]+)')
$sanitized = New-Object System.Collections.Generic.List[string]
foreach ($match in $matches) {
$key = $match.Groups['key'].Value
$raw = $match.Groups['value'].Value
$value = $raw
if ($raw.Length -ge 2 -and (($raw[0] -eq "'" -and $raw[$raw.Length - 1] -eq "'") -or ($raw[0] -eq '"' -and $raw[$raw.Length - 1] -eq '"'))) {
$value = $raw.Substring(1, $raw.Length - 2)
}
if ($key.ToLowerInvariant() -eq 'password') {
$result.Password = $value
continue
}
switch ($key.ToLowerInvariant()) {
'host' { $result.Host = $value }
'port' { $result.Port = [int]$value }
'dbname' { $result.Database = $value }
}
$sanitized.Add("$key=$raw")
}
if ($matches.Count -eq 0) { throw 'SENSE_DATABASE_URL must be a PostgreSQL URI or keyword connection string.' }
$result.Sanitized = $sanitized -join ' '
return $result
}
function Test-SenseTcpEndpoint {
param([Parameter(Mandatory = $true)][string]$HostName, [Parameter(Mandatory = $true)][int]$Port, [int]$TimeoutMilliseconds = 2000)
$client = New-Object System.Net.Sockets.TcpClient
try {
$task = $client.ConnectAsync($HostName, $Port)
if (-not $task.Wait($TimeoutMilliseconds)) { return $false }
return $client.Connected
} catch {
return $false
} finally {
$client.Dispose()
}
}
function Test-SenseListenPortAvailable {
param([Parameter(Mandatory = $true)][string]$HostName, [Parameter(Mandatory = $true)][int]$Port)
$ip = if ($HostName -eq '0.0.0.0') { [Net.IPAddress]::Any } elseif ($HostName -eq 'localhost') { [Net.IPAddress]::Loopback } else { [Net.IPAddress]::Parse($HostName) }
$listener = New-Object Net.Sockets.TcpListener($ip, $Port)
try { $listener.Start(); return $true } catch { return $false } finally { try { $listener.Stop() } catch {} }
}
function Initialize-SenseRuntime {
param(
[Parameter(Mandatory = $true)][string]$PackageRoot,
[ValidateSet('production', 'demo')][string]$Mode = 'production',
[switch]$AllowOccupiedPort
)
$configName = if ($Mode -eq 'demo') { 'sense.demo.env' } else { 'sense.env' }
Import-SenseEnvironment -Path (Join-Path $PackageRoot "config\$configName")
$hostName = Get-SenseEnvironmentValue -Name 'SENSE_HOST' -Default '127.0.0.1'
$portText = Get-SenseEnvironmentValue -Name 'SENSE_PORT' -Default '18080'
$port = 0
if (-not [int]::TryParse($portText, [ref]$port) -or $port -lt 1 -or $port -gt 65535) {
throw 'SENSE_PORT must be an integer between 1 and 65535.'
}
if ($hostName -notin @('127.0.0.1', '0.0.0.0', 'localhost')) {
throw 'SENSE_HOST must be 127.0.0.1, localhost, or 0.0.0.0.'
}
if (-not $AllowOccupiedPort -and -not (Test-SenseListenPortAvailable -HostName $hostName -Port $port)) {
throw "Sense HTTP port $hostName`:$port is already in use. Stop the existing process or change SENSE_PORT."
}
$databaseVariable = if ($Mode -eq 'demo') { 'SENSE_DEMO_DATABASE_URL' } else { 'SENSE_DATABASE_URL' }
$databaseURL = Get-SenseEnvironmentValue -Name $databaseVariable
if ([string]::IsNullOrWhiteSpace($databaseURL)) { throw "$databaseVariable is required." }
$database = Get-SenseDatabaseInfo -Connection $databaseURL
if ([string]::IsNullOrWhiteSpace($database.Database)) { throw "$databaseVariable must name a database." }
if ($Mode -eq 'demo' -and $database.Database -notmatch '(?i)demo|test') {
throw 'Demo mode requires a database name containing demo or test; production data must never be reused as demo data.'
}
if (-not (Test-SenseTcpEndpoint -HostName $database.Host -Port $database.Port)) {
throw "PostgreSQL is unreachable at $($database.Host):$($database.Port). Start PostgreSQL and verify the database connection."
}
$jwtSecret = Get-SenseEnvironmentValue -Name 'SENSE_JWT_SECRET'
if ($Mode -eq 'production' -and $jwtSecret.Trim().Length -lt 32) {
throw 'SENSE_JWT_SECRET must contain at least 32 characters in production.'
}
if ([string]::IsNullOrWhiteSpace($jwtSecret)) { throw 'SENSE_JWT_SECRET is required.' }
$mediaMode = (Get-SenseEnvironmentValue -Name 'SENSE_MEDIAMTX_MODE' -Default $(if ($Mode -eq 'demo') { 'disabled' } else { 'managed' })).ToLowerInvariant()
if ($mediaMode -notin @('managed', 'external', 'disabled')) { throw 'SENSE_MEDIAMTX_MODE must be managed, external, or disabled.' }
if ($Mode -eq 'production' -and $mediaMode -eq 'disabled') { throw 'MediaMTX cannot be disabled in production.' }
$mediaAPI = Get-SenseEnvironmentValue -Name 'SENSE_MEDIAMTX_API' -Default 'http://127.0.0.1:9997'
$apiUri = [Uri]$mediaAPI
if ($apiUri.Scheme -ne 'http' -or $apiUri.Host -notin @('127.0.0.1', 'localhost', '::1')) {
throw 'SENSE_MEDIAMTX_API must be an HTTP loopback URL.'
}
$mediaBinary = Resolve-SenseConfiguredPath -PackageRoot $PackageRoot -Value (Get-SenseEnvironmentValue -Name 'SENSE_MEDIAMTX_BINARY')
$mediaConfig = Resolve-SenseConfiguredPath -PackageRoot $PackageRoot -Value (Get-SenseEnvironmentValue -Name 'SENSE_MEDIAMTX_CONFIG')
if ($mediaMode -eq 'managed') {
if (-not (Test-Path -LiteralPath $mediaBinary -PathType Leaf)) { throw 'Managed MediaMTX binary not found. Set SENSE_MEDIAMTX_BINARY to mediamtx.exe.' }
if (-not (Test-Path -LiteralPath $mediaConfig -PathType Leaf)) { throw 'Managed MediaMTX configuration not found. Set SENSE_MEDIAMTX_CONFIG.' }
}
if ($mediaMode -eq 'external' -and -not (Test-SenseTcpEndpoint -HostName $apiUri.Host -Port $apiUri.Port)) {
throw "External MediaMTX Control API is unreachable at $($apiUri.Host):$($apiUri.Port)."
}
$webRoot = Resolve-SenseConfiguredPath -PackageRoot $PackageRoot -Value (Get-SenseEnvironmentValue -Name 'SENSE_WEB_ROOT' -Default 'web')
if (-not (Test-Path -LiteralPath (Join-Path $webRoot 'index.html') -PathType Leaf)) { throw 'Sense web assets are missing. Rebuild or replace the delivery package.' }
[Environment]::SetEnvironmentVariable('SENSE_WEB_ROOT', $webRoot, 'Process')
[Environment]::SetEnvironmentVariable('SENSE_MEDIAMTX_MODE', $mediaMode, 'Process')
if ($mediaMode -eq 'managed') {
[Environment]::SetEnvironmentVariable('SENSE_MEDIAMTX_BINARY', $mediaBinary, 'Process')
[Environment]::SetEnvironmentVariable('SENSE_MEDIAMTX_CONFIG', $mediaConfig, 'Process')
} else {
[Environment]::SetEnvironmentVariable('SENSE_MEDIAMTX_BINARY', '', 'Process')
[Environment]::SetEnvironmentVariable('SENSE_MEDIAMTX_CONFIG', '', 'Process')
}
[Environment]::SetEnvironmentVariable('SENSE_MEDIAMTX_API', $mediaAPI, 'Process')
$runtimeDir = Join-Path $PackageRoot 'data\runtime'
$logDir = Join-Path $PackageRoot 'logs'
New-Item -ItemType Directory -Force -Path $runtimeDir, $logDir | Out-Null
$settingsPath = Join-Path $runtimeDir 'settings.yml'
$applicationMode = if ($Mode -eq 'production') { 'prod' } else { 'test' }
$lines = @(
'settings:',
' application:',
" mode: $applicationMode",
" host: $(ConvertTo-SenseYamlString $hostName)",
' name: sense',
" port: $port",
' readtimeout: 10',
' writertimeout: 20',
' enabledp: false',
' logger:',
" path: $(ConvertTo-SenseYamlString $logDir)",
" stdout: ''",
' level: info',
' enableddb: false',
' jwt:',
" secret: $(ConvertTo-SenseYamlString $jwtSecret)",
' timeout: 2592000',
' database:',
' driver: postgres',
" source: $(ConvertTo-SenseYamlString $databaseURL)",
' gen:',
" dbname: $(ConvertTo-SenseYamlString $database.Database)",
" frontpath: ''",
' extend:',
' demo:',
' name: data',
' cache:',
" memory: ''",
' queue:',
' memory:',
' poolSize: 100',
' locker:',
' redis:'
)
[IO.File]::WriteAllLines($settingsPath, $lines, (New-Object Text.UTF8Encoding($false)))
return @{ PackageRoot = $PackageRoot; SettingsPath = $settingsPath; Host = $hostName; Port = $port; Database = $database; Mode = $Mode; MediaMode = $mediaMode }
}
function Get-SensePostgresTool {
param([Parameter(Mandatory = $true)][string]$Name)
$configured = Get-SenseEnvironmentValue -Name 'SENSE_POSTGRES_BIN'
if (-not [string]::IsNullOrWhiteSpace($configured)) {
$candidate = Join-Path $configured "$Name.exe"
if (Test-Path -LiteralPath $candidate -PathType Leaf) { return $candidate }
}
$command = Get-Command "$Name.exe" -ErrorAction SilentlyContinue
if ($command) { return $command.Source }
throw "$Name.exe was not found. Install PostgreSQL client tools or set SENSE_POSTGRES_BIN."
}
function Invoke-SensePostgresTool {
param(
[Parameter(Mandatory = $true)][string]$Tool,
[Parameter(Mandatory = $true)][hashtable]$Database,
[Parameter(Mandatory = $true)][string[]]$Arguments
)
$oldPassword = [Environment]::GetEnvironmentVariable('PGPASSWORD', 'Process')
try {
if (-not [string]::IsNullOrEmpty($Database.Password)) {
[Environment]::SetEnvironmentVariable('PGPASSWORD', $Database.Password, 'Process')
}
& $Tool @Arguments
if ($LASTEXITCODE -ne 0) { throw "PostgreSQL tool failed with exit code $LASTEXITCODE." }
} finally {
[Environment]::SetEnvironmentVariable('PGPASSWORD', $oldPassword, 'Process')
}
}
-3
View File
@@ -1,3 +0,0 @@
@echo off
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\runtime\start-sense.ps1" %*
exit /b %errorlevel%
-31
View File
@@ -1,31 +0,0 @@
param(
[ValidateSet('production', 'demo')][string]$Mode = 'production',
[switch]$SkipMigration
)
. (Join-Path $PSScriptRoot 'sense-common.ps1')
try {
$root = Get-SensePackageRoot
$state = Initialize-SenseRuntime -PackageRoot $root -Mode $Mode
$sense = Join-Path $root 'sense.exe'
if (-not (Test-Path -LiteralPath $sense -PathType Leaf)) { throw "Sense executable not found: $sense" }
$autoMigrate = (Get-SenseEnvironmentValue -Name 'SENSE_AUTO_MIGRATE' -Default 'true').ToLowerInvariant()
Push-Location $root
try {
if (-not $SkipMigration -and $autoMigrate -notin @('false', '0', 'no')) {
Write-Host 'Applying pending Sense database migrations...'
& $sense migrate -c $state.SettingsPath
if ($LASTEXITCODE -ne 0) { throw 'Sense database migration failed. Review the error above and the PostgreSQL connection.' }
}
if ($Mode -eq 'demo') {
Write-Warning 'Sense is running in isolated demo mode. Demo data must not be used as production data.'
}
Write-Host "Starting Sense at http://$($state.Host):$($state.Port)/ ..."
Write-Host 'Press Ctrl+C in this window to stop Sense and its managed MediaMTX process.'
& $sense server -c $state.SettingsPath
exit $LASTEXITCODE
} finally { Pop-Location }
} catch {
Write-Error $_.Exception.Message
exit 1
}
-3
View File
@@ -1,3 +0,0 @@
@echo off
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\runtime\stop-sense.ps1" %*
exit /b %errorlevel%
-24
View File
@@ -1,24 +0,0 @@
param([ValidateSet('production', 'demo')][string]$Mode = 'production')
. (Join-Path $PSScriptRoot 'sense-common.ps1')
try {
$root = Get-SensePackageRoot
$configName = if ($Mode -eq 'demo') { 'sense.demo.env' } else { 'sense.env' }
$config = Join-Path $root "config\$configName"
Import-SenseEnvironment -Path $config
$port = [int](Get-SenseEnvironmentValue -Name 'SENSE_PORT' -Default '18080')
$connection = Get-NetTCPConnection -State Listen -LocalPort $port -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $connection) { Write-Host "Sense is not listening on port $port."; exit 0 }
$process = Get-CimInstance Win32_Process -Filter "ProcessId = $($connection.OwningProcess)"
$expected = [IO.Path]::GetFullPath((Join-Path $root 'sense.exe'))
if (-not $process -or [IO.Path]::GetFullPath($process.ExecutablePath) -ne $expected) {
throw "Port $port belongs to another process; it was not stopped."
}
& taskkill.exe /PID $process.ProcessId /T /F | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'Failed to stop the Sense process tree.' }
Write-Host 'Sense and its managed child processes were stopped.'
exit 0
} catch {
Write-Error $_.Exception.Message
exit 1
}
-25
View File
@@ -1,25 +0,0 @@
.idea
.vscode
*/.DS_Store
static/uploadfile
main.exe
*.exe
go-admin
go-admin.exe
temp/
!temp
vendor
config/settings.dev.yml
config/settings.dev.*.yml
config/settings.dev.*.yml.log
temp/logs
config/settings.dev.yml.log
config/settings.b.dev.yml
cmd/migrate/migration/version-local/*
!cmd/migrate/migration/version-local/doc.go
config/settings.deva.yml
go-admin-server
CLAUDE.md
.claude/
config/settings.local.dev.yml
-18
View File
@@ -1,18 +0,0 @@
FROM alpine
# ENV GOPROXY https://goproxy.cn/
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk update --no-cache
RUN apk add --update gcc g++ libc6-compat
RUN apk add --no-cache ca-certificates
RUN apk add --no-cache tzdata
ENV TZ Asia/Shanghai
COPY ./main /main
COPY ./config/settings.demo.yml /config/settings.yml
COPY ./go-admin-db.db /go-admin-db.db
EXPOSE 8000
RUN chmod +x /main
CMD ["/main","server","-c", "/config/settings.yml"]
-28
View File
@@ -1,28 +0,0 @@
FROM golang:alpine as builder
MAINTAINER lwnmengjing
ENV GOPROXY https://goproxy.cn/
WORKDIR /go/release
#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk update && apk add tzdata
COPY go.mod ./go.mod
RUN go mod tidy
COPY . .
RUN pwd && ls
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -a -installsuffix cgo -o go-admin .
FROM alpine
COPY --from=builder /go/release/go-admin /
COPY --from=builder /go/release/config/settings.yml /config/settings.yml
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
EXPOSE 8000
CMD ["/go-admin","server","-c", "/config/settings.yml"]
-54
View File
@@ -1,54 +0,0 @@
PROJECT:=go-admin
.PHONY: build
build:
CGO_ENABLED=0 go build -ldflags="-w -s" -a -installsuffix "" -o go-admin .
# make build-linux
build-linux:
@docker build -t go-admin:latest .
@echo "build successful"
build-sqlite:
go build -tags sqlite3 -ldflags="-w -s" -a -installsuffix -o go-admin .
# make run
run:
# delete go-admin-api container
@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi
# 启动方法一 run go-admin-api container docker-compose 启动方式
# 进入到项目根目录 执行 make run 命令
@docker-compose up -d
# 启动方式二 docker run 这里注意-v挂载的宿主机的地址改为部署时的实际决对路径
#@docker run --name=go-admin -p 8000:8000 -v /home/code/go/src/go-admin/go-admin/config:/go-admin-api/config -v /home/code/go/src/go-admin/go-admin-api/static:/go-admin/static -v /home/code/go/src/go-admin/go-admin/temp:/go-admin-api/temp -d --restart=always go-admin:latest
@echo "go-admin service is running..."
# delete Tag=<none> 的镜像
@docker image prune -f
@docker ps -a | grep "go-admin"
stop:
# delete go-admin-api container
@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker-compose down; fi
#@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi
#@echo "go-admin stop success"
#.PHONY: test
#test:
# go test -v ./... -cover
#.PHONY: docker
#docker:
# docker build . -t go-admin:latest
# make deploy
deploy:
#@git checkout master
#@git pull origin master
make build-linux
make run
-355
View File
@@ -1,355 +0,0 @@
# go-admin
<img align="right" width="320" src="https://doc-image.zhangwj.com/img/go-admin.svg">
[![Build Status](https://github.com/wenjianzhang/go-admin/workflows/build/badge.svg)](https://github.com/go-admin-team/go-admin)
[![Release](https://img.shields.io/github/release/go-admin-team/go-admin.svg?style=flat-square)](https://github.com/go-admin-team/go-admin/releases)
[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/go-admin-team/go-admin)
[English](https://github.com/go-admin-team/go-admin/blob/master/README.md) | 简体中文
基于Gin + Vue + Element UI OR Arco Design OR Ant Design的前后端分离权限管理系统,系统初始化极度简单,只需要配置文件中,修改数据库连接,系统支持多指令操作,迁移指令可以让初始化数据库信息变得更简单,服务指令可以很简单的启动api服务
[在线文档](https://www.go-admin.pro)
[前端项目](https://github.com/go-admin-team/go-admin-ui)
[视频教程](https://space.bilibili.com/565616721/channel/detail?cid=125737)
## 🎬 在线体验
Element UI vue体验:[https://vue2.go-admin.dev](https://vue2.go-admin.dev/#/login)
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
Arco Design vue3 demo:[https://vue3.go-admin.dev](https://vue3.go-admin.dev/#/login)
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
antd体验:[https://antd.go-admin.pro](https://antd.go-admin.pro/)
> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456
## ✨ 特性
- 遵循 RESTful API 设计规范
- 基于 GIN WEB API 框架,提供了丰富的中间件支持(用户认证、跨域、访问日志、追踪ID等)
- 基于Casbin的 RBAC 访问控制模型
- JWT 认证
- 支持 Swagger 文档(基于swaggo)
- 基于 GORM 的数据库存储,可扩展多种类型数据库
- 配置文件简单的模型映射,快速能够得到想要的配置
- 代码生成工具
- 表单构建工具
- 多指令模式
- 多租户的支持
- TODO: 单元测试
## 🎁 内置
1. 多租户:系统默认支持多租户,按库分离,一个库一个租户。
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识,接口权限等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
9. 登录日志:系统登录日志记录查询包含登录异常。
1. 接口文档:根据业务代码自动生成相关的api接口文档。
1. 代码生成:根据数据表结构生成对应的增删改查相对应业务,全程可视化操作,让基本业务可以零代码实现。
1. 表单构建:自定义页面样式,拖拉拽实现页面布局。
1. 服务监控:查看一些服务器的基本信息。
1. 内容管理:demo功能,下设分类管理、内容管理。可以参考使用方便快速入门。
1. 定时任务:自动化任务,目前支持接口调用和函数调用。
## 准备工作
你需要在本地安装 [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
同时配套了系列教程包含视频和文档,如何从下载完成到熟练使用,强烈建议大家先看完这些教程再来实践本项目!!!
### 轻松实现go-admin写出第一个应用 - 文档教程
[步骤一 - 基础内容介绍](https://doc.zhangwj.com/guide/intro/tutorial01.html)
[步骤二 - 实际应用 - 编写增删改查](https://doc.zhangwj.com/guide/intro/tutorial02.html)
### 手把手教你从入门到放弃 - 视频教程
[如何启动go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
[使用生成工具轻松实现业务](https://www.bilibili.com/video/BV1Dg4y1i79D)
[v1.1.0版本代码生成工具-释放双手](https://www.bilibili.com/video/BV1N54y1i71P) [进阶]
[多命令启动方式讲解以及IDE配置](https://www.bilibili.com/video/BV1Fg4y1q7ph)
[go-admin菜单的配置说明](https://www.bilibili.com/video/BV1Wp4y1D715) [必看]
[如何配置菜单信息以及接口信息](https://www.bilibili.com/video/BV1zv411B7nG) [必看]
[go-admin权限配置使用说明](https://www.bilibili.com/video/BV1rt4y197d3) [必看]
[go-admin数据权限使用说明](https://www.bilibili.com/video/BV1LK4y1s71e) [必看]
**如有问题请先看上述使用文档和文章,若不能满足,欢迎 issue 和 pr ,视频教程和文档持续更新中**
## 📦 本地开发
### 环境要求
go 1.26.5
node版本: v22+(推荐 v24 LTS)
包管理器: pnpm v9+(UI 项目使用 pnpm)
### 开发目录创建
```bash
# 创建开发目录
mkdir goadmin
cd goadmin
```
### 获取代码
> 重点注意:两个项目必须放在同一文件夹下;
```bash
# 获取后端代码
git clone https://github.com/go-admin-team/go-admin.git
# 获取前端代码
git clone https://github.com/go-admin-team/go-admin-ui.git
```
### 启动说明
#### 服务端启动说明
```bash
# 进入 go-admin 后端项目
cd ./go-admin
# 更新整理依赖
go mod tidy
# 编译项目
go build
# 修改配置
# 文件路径 go-admin/config/settings.yml
vi ./config/settings.yml
# 1. 配置文件中修改数据库信息
# 注意: settings.database 下对应的配置数据
# 2. 确认log路径
```
⚠️注意 在windows环境如果没有安装中CGO,会出现这个问题;
```bash
E:\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
```
or
```bash
D:\Code\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
```
[解决cgo问题进入](https://doc.go-admin.dev/zh-CN/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
#### 初始化数据库,以及服务启动
``` bash
# 首次配置需要初始化数据库资源信息
# macOS or linux 下使用
$ ./go-admin migrate -c config/settings.dev.yml
# ⚠️注意:windows 下使用
$ go-admin.exe migrate -c config/settings.dev.yml
# 启动项目,也可以用IDE进行调试
# macOS or linux 下使用
$ ./go-admin server -c config/settings.yml
# ⚠️注意:windows 下使用
$ go-admin.exe server -c config/settings.yml
```
#### sys_api 表的数据如何添加
在项目启动时,使用`-a true` 系统会自动添加缺少的接口数据
```bash
./go-admin server -c config/settings.yml -a true
```
#### 使用docker 编译启动
```shell
# 编译镜像
docker build -t go-admin .
# 启动容器,第一个go-admin是容器名字,第二个go-admin是镜像名称
# -v 映射配置文件 本地路径:容器路径
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
```
#### 文档生成
```bash
go generate
```
#### 交叉编译
```bash
# windows
env GOOS=windows GOARCH=amd64 go build main.go
# or
# linux
env GOOS=linux GOARCH=amd64 go build main.go
```
### UI交互端启动说明
```bash
# 安装 pnpm(若未安装)
npm install -g pnpm
# 安装依赖
pnpm install
# 国内网络可指定镜像源加速
pnpm install --registry=https://registry.npmmirror.com
# 启动服务
pnpm dev
```
## 📨 互动
<table>
<tr>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
</tr>
<tr>
<td>微信</td>
<td>公众号🔥🔥🔥</td>
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
<td>哔哩哔哩🔥🔥🔥</td>
</tr>
</table>
## 💎 贡献者
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
## JetBrains 开源证书支持
`go-admin` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
## 🤝 特别感谢
1. [ant-design](https://github.com/ant-design/ant-design)
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
2. [arco-design](https://github.com/arco-design/arco-design)
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
4. [gin](https://github.com/gin-gonic/gin)
5. [casbin](https://github.com/casbin/casbin)
6. [spf13/viper](https://github.com/spf13/viper)
7. [gorm](https://github.com/jinzhu/gorm)
8. [gin-swagger](https://github.com/swaggo/gin-swagger)
9. [jwt-go](https://github.com/dgrijalva/jwt-go)
10. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
11. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
12. [form-generator](https://github.com/JakHuang/form-generator)
## 🤟 打赏
> 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink:
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
## 🤝 链接
[Go开发者成长线路图](http://www.golangroadmap.com/)
## 🔑 License
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
Copyright (c) 2024 wenjianzhang
-345
View File
@@ -1,345 +0,0 @@
# go-admin
<img align="right" width="320" src="https://raw.githubusercontent.com/wenjianzhang/image/203c5930b9ed08d5cf2fcb4516b85e412f8e0e60/img/go-admin.svg">
[![Build Status](https://github.com/wenjianzhang/go-admin/workflows/build/badge.svg)](https://github.com/go-admin-team/go-admin)
[![Release](https://img.shields.io/github/release/go-admin-team/go-admin.svg?style=flat-square)](https://github.com/go-admin-team/go-admin/releases)
[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/go-admin-team/go-admin)
English | [简体中文](https://github.com/go-admin-team/go-admin/blob/master/README.Zh-cn.md)
The front-end and back-end separation authority management system based on Gin + Vue + Element UI OR Arco Design is extremely simple to initialize the system. You only need to modify the database connection in the configuration file. The system supports multi-instruction operations. Migration instructions can make it easier to initialize database information. Service instructions It's easy to start the api service.
[documentation](https://www.go-admin.dev)
[Front-end project](https://github.com/go-admin-team/go-admin-ui)
[Video tutorial](https://space.bilibili.com/565616721/channel/detail?cid=125737)
## 🎬 Online Demo
Element UI vue demo:[https://vue2.go-admin.dev](https://vue2.go-admin.dev/#/login)
> 账号 / 密码: admin / 123456
Arco Design vue3 demo:[https://vue3.go-admin.dev](https://vue3.go-admin.dev/#/login)
> 账号 / 密码: admin / 123456
antd demo:[https://antd.go-admin.pro](https://antd.go-admin.pro/)
> 账号 / 密码: admin / 123456
>
## ✨ Feature
- Follow RESTful API design specifications
- Based on the GIN WEB API framework, it provides rich middleware support (user authentication, cross-domain, access log, tracking ID, etc.)
- RBAC access control model based on Casbin
- JWT authentication
- Support Swagger documents (based on swaggo)
- Database storage based on GORM, which can expand multiple types of databases
- Simple model mapping of configuration files to quickly get the desired configuration
- Code generation tool
- Form builder
- Multi-command mode
- TODO: unit test
## 🎁 Internal
1. User management: The user is the system operator, this function mainly completes the system user configuration.
2. Department management: configure the system organization (company, department, group), and display the tree structure to support data permissions.
3. Position management: configure the positions of system users.
4. Menu management: configure the system menu, operation authority, button authority identification, interface authority, etc.
5. Role management: Role menu permission assignment and role setting are divided into data scope permissions by organization.
6. Dictionary management: Maintain some relatively fixed data frequently used in the system.
7. Parameter management: dynamically configure common parameters for the system.
8. Operation log: system normal operation log record and query; system abnormal information log record and query.
9. Login log: The system login log record query contains login exceptions.
1. Interface documentation: Automatically generate related api interface documents according to the business code.
1. Code generation: According to the data table structure, generate the corresponding addition, deletion, modification, and check corresponding business, and the whole process of visual operation, so that the basic business can be implemented with zero code.
1. Form construction: Customize the page style, drag and drop to realize the page layout.
1. Service monitoring: View the basic information of some servers.
1. Content management: demo function, including classification management and content management. You can refer to the easy to use quick start.
## Ready to work
You need to install locally [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/)
At the same time, a series of tutorials including videos and documents are provided. How to complete the downloading to the proficient use, it is strongly recommended that you read these tutorials before you practice this project! ! !
### Easily implement go-admin to write the first application-documentation tutorial
[Step 1 - basic content introduction](https://doc.zhangwj.com/guide/intro/tutorial01.html)
[Step 2 - Practical application - writing database operations](https://doc.zhangwj.com/guide/intro/tutorial02.html)
### Teach you from getting started to giving up-video tutorial
[How to start go-admin](https://www.bilibili.com/video/BV1z5411x7JG)
[Easily implement business using build tools](https://www.bilibili.com/video/BV1Dg4y1i79D)
[v1.1.0 version code generation tool-free your hands](https://www.bilibili.com/video/BV1N54y1i71P) [Advanced]
[Explanation of multi-command startup mode and IDE configuration](https://www.bilibili.com/video/BV1Fg4y1q7ph)
[Configuration instructions for go-admin menu](https://www.bilibili.com/video/BV1Wp4y1D715) [Must see]
[How to configure menu information and interface information](https://www.bilibili.com/video/BV1zv411B7nG) [Must see]
[go-admin permission configuration instructions](https://www.bilibili.com/video/BV1rt4y197d3) [Must see]
[Instructions for use of go-admin data permissions](https://www.bilibili.com/video/BV1LK4y1s71e) [Must see]
**If you have any questions, please read the above-mentioned usage documents and articles first. If you are not satisfied, welcome to issue and pr. Video tutorials and documents are being updated continuously.**
## 📦 Local development
### Environmental requirements
go 1.26.5
nodejs: v22+ (v24 LTS recommended)
package manager: pnpm v9+ (the UI project uses pnpm)
### Development directory creation
```bash
# Create a development directory
mkdir goadmin
cd goadmin
```
### Get the code
> Important note: the two projects must be placed in the same folder;
```bash
# Get backend code
git clone https://github.com/go-admin-team/go-admin.git
# Get the front-end code
git clone https://github.com/go-admin-team/go-admin-ui.git
```
### Startup instructions
#### Server startup instructions
```bash
# Enter the go-admin backend project
cd ./go-admin
# Update dependencies
go mod tidy
# Compile the project
go build
# Change setting
# File path go-admin/config/settings.yml
vi ./config/settings.yml
# 1. Modify the database information in the configuration file
# Note: The corresponding configuration data under settings.database
# 2. Confirm the log path
```
:::tip ⚠️Note that this problem will occur if CGO is not installed in the windows10+ environment;
```bash
E:\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec /missing-cc: exec: "/missing-cc": file does not exist
```
or
```bash
D:\Code\go-admin>go build
# github.com/mattn/go-sqlite3
cgo: exec gcc: exec: "gcc": executable file not found in %PATH%
```
[Solve the cgo problem and enter](https://doc.go-admin.dev/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98)
:::
#### Initialize the database, and start the service
``` bash
# The first configuration needs to initialize the database resource information
# Use under macOS or linux
$ ./go-admin migrate -c config/settings.dev.yml
# ⚠️Note: Use under windows
$ go-admin.exe migrate -c config/settings.dev.yml
# Start the project, you can also use the IDE for debugging
# Use under macOS or linux
$ ./go-admin server -c config/settings.yml
# ⚠️Note: Use under windows
$ go-admin.exe server -c config/settings.yml
```
#### Use docker to compile and start
```shell
# Compile the image
docker build -t go-admin .
# Start the container, the first go-admin is the container name, and the second go-admin is the image name
# -v Mapping configuration file Local path: container path
docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server
```
#### Generation Document
```bash
go generate
```
#### Cross compile
```bash
# windows
env GOOS=windows GOARCH=amd64 go build main.go
# or
# linux
env GOOS=linux GOARCH=amd64 go build main.go
```
### UI interactive terminal startup instructions
```bash
# Install pnpm if you don't have it
npm install -g pnpm
# Installation dependencies
pnpm install
# Start service
pnpm dev
```
## 📨 Interactive
<table>
<tr>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/wx.png" width="180px"></td>
<td><img src="https://doc-image.zhangwj.com/img/qrcode_for_gh_b798dc7db30c_258.jpg" width="180px"></td>
<td><img src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/qq2.png" width="200px"></td>
<td><a href="https://space.bilibili.com/565616721">wenjianzhang</a></td>
</tr>
<tr>
<td>Wechat</td>
<td>Wechat公众号🔥🔥🔥</td>
<td><a target="_blank" href="https://shang.qq.com/wpa/qunwpa?idkey=0f2bf59f5f2edec6a4550c364242c0641f870aa328e468c4ee4b7dbfb392627b"><img border="0" src="https://pub.idqqimg.com/wpa/images/group.png" alt="go-admin技术交流乙号" title="go-admin技术交流乙号"></a></td>
<td>bilibili🔥🔥🔥</td>
</tr>
</table>
## 💎 Contributors
<span style="margin: 0 5px;" ><a href="https://github.com/wenjianzhang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3890175?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/G-Akiraka" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45746659?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/lwnmengjing" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/12806223?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bing127" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31166183?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/chengxiao" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1379545?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NightFire0307" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19854086?v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/appleboy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/21979?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/ninstein" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/580303?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/kikiyou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17959053?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/horizonzy" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/22524871?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Cassuis" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/48005724?s=64&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/hqcchina" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/5179057?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/nodece" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16235121?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stephenzhang0713" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/18169290?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhouxixi-dev" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/100399679?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Jalins" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/31172582?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wkf928592" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6063351?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxxiong6" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/6983441?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Silicon-He" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52478309?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/GizmoOAO" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20385106?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/bestgopher" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/36840497?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/wxb1207" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/20775558?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/misakichan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16569274?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zhuxuyang" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/19301024?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/mss-boot" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/109259065?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/AuroraV" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/37330199?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Vingurzhou" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/57127283?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/haimait" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/40926384?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/zyd" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/3446278?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/infnan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/38274826?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/d1y" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45585937?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/qlijin" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/515900?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/logtous
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/88697234?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/stepway
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/9927079?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/NaturalGao
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/43291304?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/DemoLiang
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/23476007?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/jfcg
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/1410597?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
<span style="margin: 0 5px;" ><a href="https://github.com/Nicole0724
" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/10487328?s=60&v=4&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
## JetBrains open source certificate support
The `go-admin` project has always been developed in the GoLand integrated development environment under JetBrains, based on the **free JetBrains Open Source license(s)** genuine free license. I would like to express my gratitude.
<a href="https://www.jetbrains.com/?from=kubeadm-ha" target="_blank"><img src="https://raw.githubusercontent.com/panjf2000/illustrations/master/jetbrains/jetbrains-variant-4.png" width="250" align="middle"/></a>
## 🤝 Thanks
1. [ant-design](https://github.com/ant-design/ant-design)
2. [ant-design-pro](https://github.com/ant-design/ant-design-pro)
2. [arco-design](https://github.com/arco-design/arco-design)
2. [arco-design-pro](https://github.com/arco-design/arco-design-pro)
2. [gin](https://github.com/gin-gonic/gin)
2. [casbin](https://github.com/casbin/casbin)
2. [spf13/viper](https://github.com/spf13/viper)
2. [gorm](https://github.com/jinzhu/gorm)
2. [gin-swagger](https://github.com/swaggo/gin-swagger)
2. [jwt-go](https://github.com/dgrijalva/jwt-go)
2. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
2. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue)
2. [form-generator](https://github.com/JakHuang/form-generator)
## 🤟 Sponsor Us
> If you think this project helped you, you can buy a glass of juice for the author to show encouragement :tropical_drink:
<img class="no-margin" src="https://raw.githubusercontent.com/wenjianzhang/image/master/img/pay.png" height="200px" >
## 🤝 Link
- [Go developer growth roadmap](http://www.golangroadmap.com/)
- [mss-boot-io](https://docs.mss-boot-io.top/)
## 🔑 License
[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md)
Copyright (c) 2022 wenjianzhang
-1
View File
@@ -1 +0,0 @@
theme: jekyll-theme-cayman
-36
View File
@@ -1,36 +0,0 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg/captcha"
)
type System struct {
api.Api
}
// GenerateCaptchaHandler 获取验证码
// @Summary 获取验证码
// @Description 获取验证码
// @Tags 登陆
// @Success 200 {object} response.Response{data=string,id=string,msg=string} "{"code": 200, "data": [...]}"
// @Router /api/v1/captcha [get]
func (e System) GenerateCaptchaHandler(c *gin.Context) {
if err := e.MakeContext(c).Errors; err != nil {
e.Error(500, err, "服务初始化失败!")
return
}
id, b64s, _, err := captcha.DriverDigitFunc()
if err != nil {
e.Logger.Errorf("DriverDigitFunc error, %s", err.Error())
e.Error(500, err, "验证码获取失败")
return
}
e.Custom(gin.H{
"code": 200,
"data": b64s,
"id": id,
"msg": "success",
})
}
-39
View File
@@ -1,39 +0,0 @@
package apis
import (
"github.com/gin-gonic/gin"
)
const INDEX = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>GO-ADMIN欢迎您</title>
<style>
body{
margin:0;
padding:0;
overflow-y:hidden
}
</style>
<script src="https://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
<script type="text/javascript">
window.onerror=function(){return true;}
$(function(){
headerH = 0;
var h=$(window).height();
$("#iframe").height((h-headerH)+"px");
});
</script>
</head>
<body>
<iframe id="iframe" frameborder="0" src="https://www.go-admin.pro" style="width:100%;"></iframe>
</body>
</html>
`
func GoAdmin(c *gin.Context) {
c.Header("Content-Type", "text/html; charset=utf-8")
c.String(200, INDEX)
}
@@ -1,94 +0,0 @@
package apis
import (
"crypto/subtle"
"errors"
"net/http"
"os"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware/handler"
)
const bootstrapTokenHeader = "X-Sense-Bootstrap-Token"
type bootstrapRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
NickName string `json:"nickName"`
}
// Bootstrap creates the first Sense administrator. The one-time authorization
// token is supplied out of band and never accepted in the JSON request body.
func (e System) Bootstrap(c *gin.Context) {
if err := e.MakeContext(c).MakeOrm().Errors; err != nil {
e.Error(http.StatusInternalServerError, err, "安全初始化失败")
return
}
db := e.Orm
var req bootstrapRequest
if err := c.ShouldBindJSON(&req); err != nil {
e.Custom(gin.H{"code": http.StatusBadRequest, "msg": "安全初始化不可用或输入不符合要求"})
return
}
req.Username = strings.TrimSpace(req.Username)
if req.NickName == "" {
req.NickName = req.Username
}
if err := validateBootstrapInput(req); err != nil || !validBootstrapToken(c.GetHeader(bootstrapTokenHeader)) {
handler.WriteIdentityAudit(c, "1", "安全初始化被拒绝", req.Username)
e.Custom(gin.H{"code": http.StatusForbidden, "msg": "安全初始化不可用或输入不符合要求"})
return
}
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Exec("LOCK TABLE sys_user IN EXCLUSIVE MODE").Error; err != nil {
return err
}
var count int64
if err := tx.Model(&models.SysUser{}).Count(&count).Error; err != nil {
return err
}
if count != 0 {
return errors.New("identity already initialized")
}
var role models.SysRole
if err := tx.Where("role_key = ? AND status = ?", "admin", "2").First(&role).Error; err != nil {
return err
}
return tx.Create(&models.SysUser{
Username: req.Username,
Password: req.Password,
NickName: req.NickName,
RoleId: role.RoleId,
Status: "2",
}).Error
})
if err != nil {
handler.WriteIdentityAudit(c, "1", "安全初始化被拒绝", req.Username)
e.Custom(gin.H{"code": http.StatusForbidden, "msg": "安全初始化不可用或输入不符合要求"})
return
}
handler.WriteIdentityAudit(c, "2", "首个管理员创建成功", req.Username)
e.Custom(gin.H{"code": http.StatusOK, "msg": "管理员创建成功"})
}
func validateBootstrapInput(req bootstrapRequest) error {
if len(req.Username) < 3 || len(req.Username) > 64 || strings.ContainsAny(req.Username, " \t\r\n") {
return errors.New("invalid username")
}
return service.ValidatePassword(req.Password)
}
func validBootstrapToken(candidate string) bool {
expected := os.Getenv("SENSE_BOOTSTRAP_TOKEN")
if len(expected) < 32 || len(candidate) != len(expected) {
return false
}
return subtle.ConstantTimeCompare([]byte(candidate), []byte(expected)) == 1
}
@@ -1,33 +0,0 @@
package apis
import "testing"
func TestValidateBootstrapInput(t *testing.T) {
if err := validateBootstrapInput(bootstrapRequest{Username: "admin", Password: "abcdef"}); err != nil {
t.Fatalf("expected valid bootstrap input: %v", err)
}
for _, req := range []bootstrapRequest{
{Username: "ab", Password: "abcdef"},
{Username: "bad name", Password: "abcdef"},
{Username: "admin", Password: "abcde"},
} {
if err := validateBootstrapInput(req); err == nil {
t.Fatalf("expected invalid bootstrap input: %+v", req)
}
}
}
func TestValidBootstrapToken(t *testing.T) {
const token = "0123456789abcdef0123456789abcdef"
t.Setenv("SENSE_BOOTSTRAP_TOKEN", token)
if !validBootstrapToken(token) {
t.Fatal("expected configured token to be accepted")
}
if validBootstrapToken("wrong") {
t.Fatal("expected wrong token to be rejected")
}
t.Setenv("SENSE_BOOTSTRAP_TOKEN", "short")
if validBootstrapToken("short") {
t.Fatal("expected short configured token to be rejected")
}
}
-148
View File
@@ -1,148 +0,0 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
"git.ilapage.cn/ila/yovision/Sense/server/common/actions"
)
type SysApi struct {
api.Api
}
// GetPage 获取接口管理列表
// @Summary 获取接口管理列表
// @Description 获取接口管理列表
// @Tags 接口管理
// @Param name query string false "名称"
// @Param title query string false "标题"
// @Param path query string false "地址"
// @Param action query string false "类型"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-api [get]
// @Security Bearer
func (e SysApi) GetPage(c *gin.Context) {
s := service.SysApi{}
req := dto.SysApiGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
//数据权限检查
p := actions.GetPermissionFromContext(c)
list := make([]models.SysApi, 0)
var count int64
err = s.GetPage(&req, p, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 获取接口管理
// @Summary 获取接口管理
// @Description 获取接口管理
// @Tags 接口管理
// @Param id path string false "id"
// @Success 200 {object} response.Response{data=models.SysApi} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-api/{id} [get]
// @Security Bearer
func (e SysApi) Get(c *gin.Context) {
req := dto.SysApiGetReq{}
s := service.SysApi{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
p := actions.GetPermissionFromContext(c)
var object models.SysApi
err = s.Get(&req, p, &object).Error
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Update 修改接口管理
// @Summary 修改接口管理
// @Description 修改接口管理
// @Tags 接口管理
// @Accept application/json
// @Product application/json
// @Param data body dto.SysApiUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/sys-api/{id} [put]
// @Security Bearer
func (e SysApi) Update(c *gin.Context) {
req := dto.SysApiUpdateReq{}
s := service.SysApi{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
return
}
req.SetUpdateBy(user.GetUserId(c))
p := actions.GetPermissionFromContext(c)
err = s.Update(&req, p)
if err != nil {
e.Error(500, err, "更新失败")
return
}
e.OK(req.GetId(), "更新成功")
}
// DeleteSysApi 删除接口管理
// @Summary 删除接口管理
// @Description 删除接口管理
// @Tags 接口管理
// @Param data body dto.SysApiDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
// @Router /api/v1/sys-api [delete]
// @Security Bearer
func (e SysApi) DeleteSysApi(c *gin.Context) {
req := dto.SysApiDeleteReq{}
s := service.SysApi{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
return
}
p := actions.GetPermissionFromContext(c)
err = s.Remove(&req, p)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
-313
View File
@@ -1,313 +0,0 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
)
type SysConfig struct {
api.Api
}
// GetPage 获取配置管理列表
// @Summary 获取配置管理列表
// @Description 获取配置管理列表
// @Tags 配置管理
// @Param configName query string false "名称"
// @Param configKey query string false "key"
// @Param configType query string false "类型"
// @Param isFrontend query int false "是否前端"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-config [get]
// @Security Bearer
func (e SysConfig) GetPage(c *gin.Context) {
s := service.SysConfig{}
req := dto.SysConfigGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
return
}
list := make([]models.SysConfig, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 获取配置管理
// @Summary 获取配置管理
// @Description 获取配置管理
// @Tags 配置管理
// @Param id path string false "id"
// @Success 200 {object} response.Response{data=models.SysConfig} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-config/{id} [get]
// @Security Bearer
func (e SysConfig) Get(c *gin.Context) {
req := dto.SysConfigGetReq{}
s := service.SysConfig{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysConfig
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(object, "查询成功")
}
// Insert 创建配置管理
// @Summary 创建配置管理
// @Description 创建配置管理
// @Tags 配置管理
// @Accept application/json
// @Product application/json
// @Param data body dto.SysConfigControl true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "创建成功"}"
// @Router /api/v1/sys-config [post]
// @Security Bearer
func (e SysConfig) Insert(c *gin.Context) {
s := service.SysConfig{}
req := dto.SysConfigControl{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Error(500, err, "创建失败")
return
}
e.OK(req.GetId(), "创建成功")
}
// Update 修改配置管理
// @Summary 修改配置管理
// @Description 修改配置管理
// @Tags 配置管理
// @Accept application/json
// @Product application/json
// @Param data body dto.SysConfigControl true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/sys-config/{id} [put]
// @Security Bearer
func (e SysConfig) Update(c *gin.Context) {
s := service.SysConfig{}
req := dto.SysConfigControl{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Error(500, err, "更新失败")
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete 删除配置管理
// @Summary 删除配置管理
// @Description 删除配置管理
// @Tags 配置管理
// @Param ids body []int false "ids"
// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
// @Router /api/v1/sys-config [delete]
// @Security Bearer
func (e SysConfig) Delete(c *gin.Context) {
s := service.SysConfig{}
req := dto.SysConfigDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Remove(&req)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
// Get2SysApp 获取系统配置信息
// @Summary 获取系统前台配置信息,主要注意这里不在验证权限
// @Description 获取系统配置信息,主要注意这里不在验证权限
// @Tags 配置管理
// @Success 200 {object} response.Response{data=map[string]string} "{"code": 200, "data": [...]}"
// @Router /api/v1/app-config [get]
func (e SysConfig) Get2SysApp(c *gin.Context) {
req := dto.SysConfigGetToSysAppReq{}
s := service.SysConfig{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
return
}
// 控制只读前台的数据
req.IsFrontend = "1"
list := make([]models.SysConfig, 0)
err = s.GetWithKeyList(&req, &list)
if err != nil {
e.Error(500, err, "查询失败")
return
}
mp := make(map[string]string)
for i := 0; i < len(list); i++ {
key := list[i].ConfigKey
if key != "" {
mp[key] = list[i].ConfigValue
}
}
e.OK(mp, "查询成功")
}
// Get2Set 获取配置
// @Summary 获取配置
// @Description 界面操作设置配置值的获取
// @Tags 配置管理
// @Accept application/json
// @Product application/json
// @Success 200 {object} response.Response{data=map[string]interface{}} "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/set-config [get]
// @Security Bearer
func (e SysConfig) Get2Set(c *gin.Context) {
s := service.SysConfig{}
req := make([]dto.GetSetSysConfigReq, 0)
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.GetForSet(&req)
if err != nil {
e.Error(500, err, "查询失败")
return
}
m := make(map[string]interface{}, 0)
for _, v := range req {
m[v.ConfigKey] = v.ConfigValue
}
e.OK(m, "查询成功")
}
// Update2Set 设置配置
// @Summary 设置配置
// @Description 界面操作设置配置值
// @Tags 配置管理
// @Accept application/json
// @Product application/json
// @Param data body []dto.GetSetSysConfigReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/set-config [put]
// @Security Bearer
func (e SysConfig) Update2Set(c *gin.Context) {
s := service.SysConfig{}
req := make([]dto.GetSetSysConfigReq, 0)
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.UpdateForSet(&req)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK("", "更新成功")
}
// GetSysConfigByKEYForService 根据Key获取SysConfig的Service
// @Summary 根据Key获取SysConfig的Service
// @Description 根据Key获取SysConfig的Service
// @Tags 配置管理
// @Param configKey path string false "configKey"
// @Success 200 {object} response.Response{data=dto.SysConfigByKeyReq} "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-config/{id} [get]
// @Security Bearer
func (e SysConfig) GetSysConfigByKEYForService(c *gin.Context) {
var s = new(service.SysConfig)
var req = new(dto.SysConfigByKeyReq)
var resp = new(dto.GetSysConfigByKEYForServiceResp)
err := e.MakeContext(c).
MakeOrm().
Bind(req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.GetWithKey(req, resp)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(resp, s.Msg)
}
-238
View File
@@ -1,238 +0,0 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
)
type SysDept struct {
api.Api
}
// GetPage
// @Summary 分页部门列表数据
// @Description 分页列表
// @Tags 部门
// @Param deptName query string false "deptName"
// @Param deptId query string false "deptId"
// @Param position query string false "position"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dept [get]
// @Security Bearer
func (e SysDept) GetPage(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDept, 0)
list, err = s.SetDeptPage(&req)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(list, "查询成功")
}
// Get
// @Summary 获取部门数据
// @Description 获取JSON
// @Tags 部门
// @Param deptId path string false "deptId"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dept/{deptId} [get]
// @Security Bearer
func (e SysDept) Get(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysDept
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert 添加部门
// @Summary 添加部门
// @Description 获取JSON
// @Tags 部门
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDeptInsertReq true "data"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dept [post]
// @Security Bearer
func (e SysDept) Insert(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 设置创建人
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Error(500, err, "创建失败")
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改部门
// @Description 获取JSON
// @Tags 部门
// @Accept application/json
// @Product application/json
// @Param id path int true "id"
// @Param data body dto.SysDeptUpdateReq true "body"
// @Success 200 {string} string "{"code": 200, "message": "添加成功"}"
// @Success 200 {string} string "{"code": -1, "message": "添加失败"}"
// @Router /api/v1/dept/{deptId} [put]
// @Security Bearer
func (e SysDept) Update(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除部门
// @Description 删除数据
// @Tags 部门
// @Param data body dto.SysDeptDeleteReq true "body"
// @Success 200 {string} string "{"code": 200, "message": "删除成功"}"
// @Success 200 {string} string "{"code": -1, "message": "删除失败"}"
// @Router /api/v1/dept [delete]
// @Security Bearer
func (e SysDept) Delete(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.Remove(&req)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
// Get2Tree 用户管理 左侧部门树
func (e SysDept) Get2Tree(c *gin.Context) {
s := service.SysDept{}
req := dto.SysDeptGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]dto.DeptLabel, 0)
list, err = s.SetDeptTree(&req)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(list, "")
}
// GetDeptTreeRoleSelect TODO: 此接口需要调整不应该将list和选中放在一起
func (e SysDept) GetDeptTreeRoleSelect(c *gin.Context) {
s := service.SysDept{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
id, err := pkg.StringToInt(c.Param("roleId"))
result, err := s.SetDeptLabel()
if err != nil {
e.Error(500, err, err.Error())
return
}
menuIds := make([]int, 0)
if id != 0 {
menuIds, err = s.GetWithRoleId(id)
if err != nil {
e.Error(500, err, err.Error())
return
}
}
e.OK(gin.H{
"depts": result,
"checkedKeys": menuIds,
}, "")
}
@@ -1,220 +0,0 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
)
type SysDictData struct {
api.Api
}
// GetPage
// @Summary 字典数据列表
// @Description 获取JSON
// @Tags 字典数据
// @Param status query string false "status"
// @Param dictCode query string false "dictCode"
// @Param dictType query string false "dictType"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/data [get]
// @Security Bearer
func (e SysDictData) GetPage(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDictData, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get
// @Summary 通过编码获取字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Param dictCode path int true "字典编码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/data/{dictCode} [get]
// @Security Bearer
func (e SysDictData) Get(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysDictData
err = s.Get(&req, &object)
if err != nil {
e.Logger.Warnf("Get error: %s", err.Error())
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert
// @Summary 添加字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDictDataInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}"
// @Router /api/v1/dict/data [post]
// @Security Bearer
func (e SysDictData) Insert(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Error(500, err, "创建失败")
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改字典数据
// @Description 获取JSON
// @Tags 字典数据
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDictDataUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}"
// @Router /api/v1/dict/data/{dictCode} [put]
// @Security Bearer
func (e SysDictData) Update(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Error(500, err, "更新失败")
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除字典数据
// @Description 删除数据
// @Tags 字典数据
// @Param dictCode body dto.SysDictDataDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}"
// @Router /api/v1/dict/data [delete]
// @Security Bearer
func (e SysDictData) Delete(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Remove(&req)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
// GetAll 数据字典根据key获取 业务页面使用
// @Summary 数据字典根据key获取
// @Description 数据字典根据key获取
// @Tags 字典数据
// @Param dictType query int true "dictType"
// @Success 200 {object} response.Response{data=[]dto.SysDictDataGetAllResp} "{"code": 200, "data": [...]}"
// @Router /api/v1/dict-data/option-select [get]
// @Security Bearer
func (e SysDictData) GetAll(c *gin.Context) {
s := service.SysDictData{}
req := dto.SysDictDataGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDictData, 0)
err = s.GetAll(&req, &list)
if err != nil {
e.Error(500, err, "查询失败")
return
}
l := make([]dto.SysDictDataGetAllResp, 0)
for _, i := range list {
d := dto.SysDictDataGetAllResp{}
e.Translate(i, &d)
l = append(l, d)
}
e.OK(l,"查询成功")
}
@@ -1,210 +0,0 @@
package apis
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
)
type SysDictType struct {
api.Api
}
// GetPage 字典类型列表数据
// @Summary 字典类型列表数据
// @Description 获取JSON
// @Tags 字典类型
// @Param dictName query string false "dictName"
// @Param dictId query string false "dictId"
// @Param dictType query string false "dictType"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type [get]
// @Security Bearer
func (e SysDictType) GetPage(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDictType, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 字典类型通过字典id获取
// @Summary 字典类型通过字典id获取
// @Description 获取JSON
// @Tags 字典类型
// @Param dictId path int true "字典类型编码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type/{dictId} [get]
// @Security Bearer
func (e SysDictType) Get(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysDictType
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
//Insert 字典类型创建
// @Summary 添加字典类型
// @Description 获取JSON
// @Tags 字典类型
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDictTypeInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type [post]
// @Security Bearer
func (e SysDictType) Insert(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Logger.Error(err)
e.Error(500, err,fmt.Sprintf(" 创建字典类型失败,详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改字典类型
// @Description 获取JSON
// @Tags 字典类型
// @Accept application/json
// @Product application/json
// @Param data body dto.SysDictTypeUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type/{dictId} [put]
// @Security Bearer
func (e SysDictType) Update(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Error(500, err, err.Error())
e.Logger.Error(err)
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除字典类型
// @Description 删除数据
// @Tags 字典类型
// @Param dictCode body dto.SysDictTypeDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type [delete]
// @Security Bearer
func (e SysDictType) Delete(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Remove(&req)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(req.GetId(), "删除成功")
}
// GetAll
// @Summary 字典类型全部数据 代码生成使用接口
// @Description 获取JSON
// @Tags 字典类型
// @Param dictName query string false "dictName"
// @Param dictId query string false "dictId"
// @Param dictType query string false "dictType"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/dict/type-option-select [get]
// @Security Bearer
func (e SysDictType) GetAll(c *gin.Context) {
s := service.SysDictType{}
req :=dto.SysDictTypeGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysDictType, 0)
err = s.GetAll(&req, &list)
if err != nil {
e.Error(500, err, err.Error())
return
}
e.OK(list, "查询成功")
}
@@ -1,110 +0,0 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk/api"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
)
type SysLoginLog struct {
api.Api
}
// GetPage 登录日志列表
// @Summary 登录日志列表
// @Description 获取JSON
// @Tags 登录日志
// @Param username query string false "用户名"
// @Param ipaddr query string false "ip地址"
// @Param loginLocation query string false "归属地"
// @Param status query string false "状态"
// @Param beginTime query string false "开始时间"
// @Param endTime query string false "结束时间"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-login-log [get]
// @Security Bearer
func (e SysLoginLog) GetPage(c *gin.Context) {
s := service.SysLoginLog{}
req :=dto.SysLoginLogGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysLoginLog, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 登录日志通过id获取
// @Summary 登录日志通过id获取
// @Description 获取JSON
// @Tags 登录日志
// @Param id path string false "id"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-login-log/{id} [get]
// @Security Bearer
func (e SysLoginLog) Get(c *gin.Context) {
s := service.SysLoginLog{}
req :=dto.SysLoginLogGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysLoginLog
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Delete 登录日志删除
// @Summary 登录日志删除
// @Description 登录日志删除
// @Tags 登录日志
// @Param data body dto.SysLoginLogDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-login-log [delete]
// @Security Bearer
func (e SysLoginLog) Delete(c *gin.Context) {
s := service.SysLoginLog{}
req :=dto.SysLoginLogDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.Remove(&req)
if err != nil {
e.Error(500, err, "删除失败")
return
}
e.OK(req.GetId(), "删除成功")
}
-249
View File
@@ -1,249 +0,0 @@
package apis
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
)
type SysMenu struct {
api.Api
}
// GetPage Menu列表数据
// @Summary Menu列表数据
// @Description 获取JSON
// @Tags 菜单
// @Param menuName query string false "menuName"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu [get]
// @Security Bearer
func (e SysMenu) GetPage(c *gin.Context) {
s := service.SysMenu{}
req := dto.SysMenuGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var list = make([]models.SysMenu, 0)
err = s.GetPage(&req, &list).Error
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(list, "查询成功")
}
// Get 获取菜单详情
// @Summary Menu详情数据
// @Description 获取JSON
// @Tags 菜单
// @Param id path string false "id"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu/{id} [get]
// @Security Bearer
func (e SysMenu) Get(c *gin.Context) {
req := dto.SysMenuGetReq{}
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object = models.SysMenu{}
err = s.Get(&req, &object).Error
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert 创建菜单
// @Summary 创建菜单
// @Description 获取JSON
// @Tags 菜单
// @Accept application/json
// @Product application/json
// @Param data body dto.SysMenuInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu [post]
// @Security Bearer
func (e SysMenu) Insert(c *gin.Context) {
req := dto.SysMenuInsertReq{}
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 设置创建人
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req).Error
if err != nil {
e.Error(500, err, "创建失败")
return
}
e.OK(req.GetId(), "创建成功")
}
// Update 修改菜单
// @Summary 修改菜单
// @Description 获取JSON
// @Tags 菜单
// @Accept application/json
// @Product application/json
// @Param id path int true "id"
// @Param data body dto.SysMenuUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu/{id} [put]
// @Security Bearer
func (e SysMenu) Update(c *gin.Context) {
req := dto.SysMenuUpdateReq{}
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req).Error
if err != nil {
e.Error(500, err, "更新失败")
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete 删除菜单
// @Summary 删除菜单
// @Description 删除数据
// @Tags 菜单
// @Param data body dto.SysMenuDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menu [delete]
// @Security Bearer
func (e SysMenu) Delete(c *gin.Context) {
control := new(dto.SysMenuDeleteReq)
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
Bind(control, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.Remove(control).Error
if err != nil {
e.Logger.Errorf("RemoveSysMenu error, %s", err)
e.Error(500, err, "删除失败")
return
}
e.OK(control.GetId(), "删除成功")
}
// GetMenuRole 根据登录角色名称获取菜单列表数据(左菜单使用)
// @Summary 根据登录角色名称获取菜单列表数据(左菜单使用)
// @Description 获取JSON
// @Tags 菜单
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menurole [get]
// @Security Bearer
func (e SysMenu) GetMenuRole(c *gin.Context) {
s := new(service.SysMenu)
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
result, err := s.SetMenuRole(user.GetRoleName(c))
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(result, "")
}
// GetMenuTreeSelect 根据角色ID查询菜单下拉树结构
// @Summary 角色修改使用的菜单列表
// @Description 获取JSON
// @Tags 菜单
// @Accept application/json
// @Product application/json
// @Param roleId path int true "roleId"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/menuTreeselect/{roleId} [get]
// @Security Bearer
func (e SysMenu) GetMenuTreeSelect(c *gin.Context) {
m := service.SysMenu{}
r := service.SysRole{}
req := dto.SelectRole{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&m.Service).
MakeService(&r.Service).
Bind(&req, nil).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
result, err := m.SetLabel()
if err != nil {
e.Error(500, err, "查询失败")
return
}
menuIds := make([]int, 0)
if req.RoleId != 0 {
menuIds, err = r.GetRoleMenuId(req.RoleId)
if err != nil {
e.Error(500, err, "")
return
}
}
e.OK(gin.H{
"menus": result,
"checkedKeys": menuIds,
}, "获取成功")
}
@@ -1,118 +0,0 @@
package apis
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk/api"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
)
type SysOperaLog struct {
api.Api
}
// GetPage 操作日志列表
// @Summary 操作日志列表
// @Description 获取JSON
// @Tags 操作日志
// @Param title query string false "title"
// @Param method query string false "method"
// @Param requestMethod query string false "requestMethod"
// @Param operUrl query string false "operUrl"
// @Param operIp query string false "operIp"
// @Param status query string false "status"
// @Param beginTime query string false "beginTime"
// @Param endTime query string false "endTime"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-opera-log [get]
// @Security Bearer
func (e SysOperaLog) GetPage(c *gin.Context) {
s := service.SysOperaLog{}
req := new(dto.SysOperaLogGetPageReq)
err := e.MakeContext(c).
MakeOrm().
Bind(req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysOperaLog, 0)
var count int64
err = s.GetPage(req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get 操作日志通过id获取
// @Summary 操作日志通过id获取
// @Description 获取JSON
// @Tags 操作日志
// @Param id path string false "id"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-opera-log/{id} [get]
// @Security Bearer
func (e SysOperaLog) Get(c *gin.Context) {
s := new(service.SysOperaLog)
req :=dto.SysOperaLogGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysOperaLog
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Delete 操作日志删除
// DeleteSysMenu 操作日志删除
// @Summary 删除操作日志
// @Description 删除数据
// @Tags 操作日志
// @Param data body dto.SysOperaLogDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-opera-log [delete]
// @Security Bearer
func (e SysOperaLog) Delete(c *gin.Context) {
s := new(service.SysOperaLog)
req :=dto.SysOperaLogDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
err = s.Remove(&req)
if err != nil {
e.Logger.Error(err)
e.Error(500,err, fmt.Sprintf("删除失败!错误详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "删除成功")
}
-184
View File
@@ -1,184 +0,0 @@
package apis
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
)
type SysPost struct {
api.Api
}
// GetPage
// @Summary 岗位列表数据
// @Description 获取JSON
// @Tags 岗位
// @Param postName query string false "postName"
// @Param postCode query string false "postCode"
// @Param postId query string false "postId"
// @Param status query string false "status"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post [get]
// @Security Bearer
func (e SysPost) GetPage(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysPost, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get
// @Summary 获取岗位信息
// @Description 获取JSON
// @Tags 岗位
// @Param id path int true "编码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post/{postId} [get]
// @Security Bearer
func (e SysPost) Get(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysPost
err = s.Get(&req, &object)
if err != nil {
e.Error(500, err, fmt.Sprintf("岗位信息获取失败!错误详情:%s", err.Error()))
return
}
e.OK(object, "查询成功")
}
// Insert
// @Summary 添加岗位
// @Description 获取JSON
// @Tags 岗位
// @Accept application/json
// @Product application/json
// @Param data body dto.SysPostInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post [post]
// @Security Bearer
func (e SysPost) Insert(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Error(500, err, fmt.Sprintf("新建岗位失败!错误详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改岗位
// @Description 获取JSON
// @Tags 岗位
// @Accept application/json
// @Product application/json
// @Param data body dto.SysPostUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post/{id} [put]
// @Security Bearer
func (e SysPost) Update(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req)
if err != nil {
e.Error(500, err, fmt.Sprintf("岗位更新失败!错误详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除岗位
// @Description 删除数据
// @Tags 岗位
// @Param id body dto.SysPostDeleteReq true "请求参数"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/post [delete]
// @Security Bearer
func (e SysPost) Delete(c *gin.Context) {
s := service.SysPost{}
req :=dto.SysPostDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.Remove(&req)
if err != nil {
e.Error(500, err, fmt.Sprintf("岗位删除失败!错误详情:%s", err.Error()))
return
}
e.OK(req.GetId(), "删除成功")
}
-284
View File
@@ -1,284 +0,0 @@
package apis
import (
"fmt"
"git.ilapage.cn/ila/yovision/Sense/server/common/global"
"net/http"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/sdk"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
)
type SysRole struct {
api.Api
}
// GetPage
// @Summary 角色列表数据
// @Description Get JSON
// @Tags 角色/Role
// @Param roleName query string false "roleName"
// @Param status query string false "status"
// @Param roleKey query string false "roleKey"
// @Param pageSize query int false "页条数"
// @Param pageIndex query int false "页码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role [get]
// @Security Bearer
func (e SysRole) GetPage(c *gin.Context) {
s := service.SysRole{}
req := dto.SysRoleGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.Form).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
list := make([]models.SysRole, 0)
var count int64
err = s.GetPage(&req, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get
// @Summary 获取Role数据
// @Description 获取JSON
// @Tags 角色/Role
// @Param roleId path string false "roleId"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role/{id} [get]
// @Security Bearer
func (e SysRole) Get(c *gin.Context) {
s := service.SysRole{}
req := dto.SysRoleGetReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, fmt.Sprintf(" %s ", err.Error()))
return
}
var object models.SysRole
err = s.Get(&req, &object)
if err != nil {
e.Error(http.StatusUnprocessableEntity, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert
// @Summary 创建角色
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body dto.SysRoleInsertReq true "data"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role [post]
// @Security Bearer
func (e SysRole) Insert(c *gin.Context) {
s := service.SysRole{}
req := dto.SysRoleInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 设置创建人
req.CreateBy = user.GetUserId(c)
if req.Status == "" {
req.Status = "2"
}
cb := sdk.Runtime.GetCasbinKey(c.Request.Host)
err = s.Insert(&req, cb)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, "创建失败,"+err.Error())
return
}
_, err = global.LoadPolicy(c)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, "创建失败,"+err.Error())
return
}
e.OK(req.GetId(), "创建成功")
}
// Update 修改用户角色
// @Summary 修改用户角色
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body dto.SysRoleUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role/{id} [put]
// @Security Bearer
func (e SysRole) Update(c *gin.Context) {
s := service.SysRole{}
req := dto.SysRoleUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
cb := sdk.Runtime.GetCasbinKey(c.Request.Host)
req.SetUpdateBy(user.GetUserId(c))
err = s.Update(&req, cb)
if err != nil {
e.Logger.Error(err)
return
}
_, err = global.LoadPolicy(c)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, "更新失败,"+err.Error())
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除用户角色
// @Description 删除数据
// @Tags 角色/Role
// @Param data body dto.SysRoleDeleteReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role [delete]
// @Security Bearer
func (e SysRole) Delete(c *gin.Context) {
s := new(service.SysRole)
req := dto.SysRoleDeleteReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, fmt.Sprintf("删除角色 %v 失败,\r\n失败信息 %s", req.Ids, err.Error()))
return
}
cb := sdk.Runtime.GetCasbinKey(c.Request.Host)
err = s.Remove(&req, cb)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, "")
return
}
e.OK(req.GetId(), fmt.Sprintf("删除角色角色 %v 状态成功!", req.GetId()))
}
// Update2Status 修改用户角色状态
// @Summary 修改用户角色
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body dto.UpdateStatusReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role-status/{id} [put]
// @Security Bearer
func (e SysRole) Update2Status(c *gin.Context) {
s := service.SysRole{}
req := dto.UpdateStatusReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error()))
return
}
req.SetUpdateBy(user.GetUserId(c))
err = s.UpdateStatus(&req)
if err != nil {
e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error()))
return
}
e.OK(req.GetId(), fmt.Sprintf("更新角色 %v 状态成功!", req.GetId()))
}
// Update2DataScope 更新角色数据权限
// @Summary 更新角色数据权限
// @Description 获取JSON
// @Tags 角色/Role
// @Accept application/json
// @Product application/json
// @Param data body dto.RoleDataScopeReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/role-status/{id} [put]
// @Security Bearer
func (e SysRole) Update2DataScope(c *gin.Context) {
s := service.SysRole{}
req := dto.RoleDataScopeReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
data := &models.SysRole{
RoleId: req.RoleId,
DataScope: req.DataScope,
DeptIds: req.DeptIds,
}
data.UpdateBy = user.GetUserId(c)
err = s.UpdateDataScope(&req).Error
if err != nil {
e.Error(500, err, fmt.Sprintf("更新角色数据权限失败!错误详情:%s", err.Error()))
return
}
e.OK(nil, "操作成功")
}
-473
View File
@@ -1,473 +0,0 @@
package apis
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/models"
"github.com/gin-gonic/gin/binding"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
_ "github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"github.com/google/uuid"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto"
"git.ilapage.cn/ila/yovision/Sense/server/common/actions"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware/handler"
)
type SysUser struct {
api.Api
}
// GetPage
// @Summary 列表用户信息数据
// @Description 获取JSON
// @Tags 用户
// @Param username query string false "username"
// @Success 200 {string} {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user [get]
// @Security Bearer
func (e SysUser) GetPage(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserGetPageReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
//数据权限检查
p := actions.GetPermissionFromContext(c)
list := make([]models.SysUser, 0)
var count int64
err = s.GetPage(&req, p, &list, &count)
if err != nil {
e.Error(500, err, "查询失败")
return
}
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
}
// Get
// @Summary 获取用户
// @Description 获取JSON
// @Tags 用户
// @Param userId path int true "用户编码"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user/{userId} [get]
// @Security Bearer
func (e SysUser) Get(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserById{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
var object models.SysUser
//数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.Get(&req, p, &object)
if err != nil {
e.Error(http.StatusUnprocessableEntity, err, "查询失败")
return
}
e.OK(object, "查询成功")
}
// Insert
// @Summary 创建用户
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.SysUserInsertReq true "用户数据"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user [post]
// @Security Bearer
func (e SysUser) Insert(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserInsertReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
if err = service.ValidatePassword(req.Password); err != nil {
e.Error(http.StatusBadRequest, err, err.Error())
return
}
// 设置创建人
req.SetCreateBy(user.GetUserId(c))
err = s.Insert(&req)
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
e.OK(req.GetId(), "创建成功")
}
// Update
// @Summary 修改用户数据
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.SysUserUpdateReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user/{userId} [put]
// @Security Bearer
func (e SysUser) Update(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserUpdateReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
//数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.Update(&req, p)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(req.GetId(), "更新成功")
}
// Delete
// @Summary 删除用户数据
// @Description 删除数据
// @Tags 用户
// @Param userId path int true "userId"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/sys-user/{userId} [delete]
// @Security Bearer
func (e SysUser) Delete(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserById{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 设置编辑人
req.SetUpdateBy(user.GetUserId(c))
// 数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.Remove(&req, p)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(req.GetId(), "删除成功")
}
// InsetAvatar
// @Summary 修改头像
// @Description 获取JSON
// @Tags 个人中心
// @Accept multipart/form-data
// @Param file formData file true "file"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/avatar [post]
// @Security Bearer
func (e SysUser) InsetAvatar(c *gin.Context) {
s := service.SysUser{}
req := dto.UpdateSysUserAvatarReq{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 数据权限检查
p := actions.GetPermissionFromContext(c)
form, _ := c.MultipartForm()
files := form.File["upload[]"]
guid := uuid.New().String()
filPath := "static/uploadfile/" + guid + ".jpg"
for _, file := range files {
e.Logger.Debugf("upload avatar file: %s", file.Filename)
// 上传文件至指定目录
err = c.SaveUploadedFile(file, filPath)
if err != nil {
e.Logger.Errorf("save file error, %s", err.Error())
e.Error(500, err, "")
return
}
}
req.UserId = p.UserId
req.Avatar = "/" + filPath
err = s.UpdateAvatar(&req, p)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(filPath, "修改成功")
}
// UpdateStatus 修改用户状态
// @Summary 修改用户状态
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.UpdateSysUserStatusReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/status [put]
// @Security Bearer
func (e SysUser) UpdateStatus(c *gin.Context) {
s := service.SysUser{}
req := dto.UpdateSysUserStatusReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON, nil).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
//数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.UpdateStatus(&req, p)
if err != nil {
e.Logger.Error(err)
return
}
e.OK(req.GetId(), "更新成功")
}
// ResetPwd 重置用户密码
// @Summary 重置用户密码
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.ResetSysUserPwdReq true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/pwd/reset [put]
// @Security Bearer
func (e SysUser) ResetPwd(c *gin.Context) {
s := service.SysUser{}
req := dto.ResetSysUserPwdReq{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req, binding.JSON).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
if err = service.ValidatePassword(req.Password); err != nil {
handler.WriteIdentityAudit(c, "1", "管理员重置密码失败", user.GetUserName(c))
e.Error(http.StatusBadRequest, err, err.Error())
return
}
req.SetUpdateBy(user.GetUserId(c))
//数据权限检查
p := actions.GetPermissionFromContext(c)
err = s.ResetPwd(&req, p)
if err != nil {
handler.WriteIdentityAudit(c, "1", "管理员重置密码失败", user.GetUserName(c))
e.Logger.Error(err)
return
}
handler.WriteIdentityAudit(c, "2", "管理员重置密码成功", user.GetUserName(c))
e.OK(req.GetId(), "更新成功")
}
// UpdatePwd
// @Summary 修改密码
// @Description 获取JSON
// @Tags 用户
// @Accept application/json
// @Product application/json
// @Param data body dto.PassWord true "body"
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/pwd/set [put]
// @Security Bearer
func (e SysUser) UpdatePwd(c *gin.Context) {
s := service.SysUser{}
req := dto.PassWord{}
err := e.MakeContext(c).
MakeOrm().
Bind(&req).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
// 数据权限检查
p := actions.GetPermissionFromContext(c)
if err = service.ValidatePassword(req.NewPassword); err != nil {
handler.WriteIdentityAudit(c, "1", "修改密码失败", user.GetUserName(c))
e.Error(http.StatusBadRequest, err, err.Error())
return
}
err = s.UpdatePwd(user.GetUserId(c), req.OldPassword, req.NewPassword, p)
if err != nil {
handler.WriteIdentityAudit(c, "1", "修改密码失败", user.GetUserName(c))
e.Logger.Error(err)
e.Error(http.StatusForbidden, err, "密码修改失败")
return
}
handler.WriteIdentityAudit(c, "2", "修改密码成功", user.GetUserName(c))
e.OK(nil, "密码修改成功")
}
// GetProfile
// @Summary 获取个人中心用户
// @Description 获取JSON
// @Tags 个人中心
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/user/profile [get]
// @Security Bearer
func (e SysUser) GetProfile(c *gin.Context) {
s := service.SysUser{}
req := dto.SysUserById{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
req.Id = user.GetUserId(c)
sysUser := models.SysUser{}
roles := make([]models.SysRole, 0)
posts := make([]models.SysPost, 0)
err = s.GetProfile(&req, &sysUser, &roles, &posts)
if err != nil {
e.Logger.Errorf("get user profile error, %s", err.Error())
e.Error(500, err, "获取用户信息失败")
return
}
e.OK(gin.H{
"user": sysUser,
"roles": roles,
"posts": posts,
}, "查询成功")
}
// GetInfo
// @Summary 获取个人信息
// @Description 获取JSON
// @Tags 个人中心
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
// @Router /api/v1/getinfo [get]
// @Security Bearer
func (e SysUser) GetInfo(c *gin.Context) {
req := dto.SysUserById{}
s := service.SysUser{}
r := service.SysRole{}
err := e.MakeContext(c).
MakeOrm().
MakeService(&r.Service).
MakeService(&s.Service).
Errors
if err != nil {
e.Logger.Error(err)
e.Error(500, err, err.Error())
return
}
p := actions.GetPermissionFromContext(c)
var roles = make([]string, 1)
roles[0] = user.GetRoleName(c)
var permissions = make([]string, 1)
permissions[0] = "*:*:*"
var buttons = make([]string, 1)
buttons[0] = "*:*:*"
var mp = make(map[string]interface{})
mp["roles"] = roles
if user.GetRoleName(c) == "admin" || user.GetRoleName(c) == "系统管理员" {
mp["permissions"] = permissions
mp["buttons"] = buttons
} else {
list, _ := r.GetById(user.GetRoleId(c))
mp["permissions"] = list
mp["buttons"] = list
}
sysUser := models.SysUser{}
req.Id = user.GetUserId(c)
err = s.Get(&req, p, &sysUser)
if err != nil {
e.Error(http.StatusUnauthorized, err, "登录失败")
return
}
mp["introduction"] = " am a super administrator"
mp["avatar"] = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"
if sysUser.Avatar != "" {
mp["avatar"] = sysUser.Avatar
}
mp["userName"] = sysUser.Username
mp["userId"] = sysUser.UserId
mp["deptId"] = sysUser.DeptId
mp["name"] = sysUser.NickName
mp["code"] = 200
e.OK(mp, "")
}
@@ -1,16 +0,0 @@
package models
type CasbinRule struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Ptype string `gorm:"size:512;uniqueIndex:unique_index"`
V0 string `gorm:"size:512;uniqueIndex:unique_index"`
V1 string `gorm:"size:512;uniqueIndex:unique_index"`
V2 string `gorm:"size:512;uniqueIndex:unique_index"`
V3 string `gorm:"size:512;uniqueIndex:unique_index"`
V4 string `gorm:"size:512;uniqueIndex:unique_index"`
V5 string `gorm:"size:512;uniqueIndex:unique_index"`
}
func (CasbinRule) TableName() string {
return "sys_casbin_rule"
}
@@ -1,81 +0,0 @@
package models
import (
"errors"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"gorm.io/gorm"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/config"
)
type DataPermission struct {
DataScope string
UserId int
DeptId int
RoleId int
}
func (e *DataPermission) GetDataScope(tableName string, db *gorm.DB) (*gorm.DB, error) {
if !config.ApplicationConfig.EnableDP {
usageStr := `数据权限已经为您` + pkg.Green(`关闭`) + `,如需开启请参考配置文件字段说明`
log.Debug("%s\n", usageStr)
return db, nil
}
user := new(SysUser)
role := new(SysRole)
err := db.Find(user, e.UserId).Error
if err != nil {
return nil, errors.New("获取用户数据出错 msg:" + err.Error())
}
err = db.Find(role, user.RoleId).Error
if err != nil {
return nil, errors.New("获取用户数据出错 msg:" + err.Error())
}
if role.DataScope == "2" {
db = db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", user.RoleId)
}
if role.DataScope == "3" {
db = db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", user.DeptId)
}
if role.DataScope == "4" {
db = db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))", "%"+pkg.IntToString(user.DeptId)+"%")
}
if role.DataScope == "5" || role.DataScope == "" {
db = db.Where(tableName+".create_by = ?", e.UserId)
}
return db, nil
}
//func DataScopes(tableName string, userId int) func(db *gorm.DB) *gorm.DB {
// return func(db *gorm.DB) *gorm.DB {
// user := new(SysUser)
// role := new(SysRole)
// user.UserId = userId
// err := db.Find(user, userId).Error
// if err != nil {
// db.Error = errors.New("获取用户数据出错 msg:" + err.Error())
// return db
// }
// err = db.Find(role, user.RoleId).Error
// if err != nil {
// db.Error = errors.New("获取用户数据出错 msg:" + err.Error())
// return db
// }
// if role.DataScope == "2" {
// return db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", user.RoleId)
// }
// if role.DataScope == "3" {
// return db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", user.DeptId)
// }
// if role.DataScope == "4" {
// return db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))", "%"+pkg.IntToString(user.DeptId)+"%")
// }
// if role.DataScope == "5" || role.DataScope == "" {
// return db.Where(tableName+".create_by = ?", userId)
// }
// return db
// }
//}
-55
View File
@@ -1,55 +0,0 @@
package models
import (
"fmt"
"git.ilapage.cn/ila/yovision/Sense/server/common/global"
"gorm.io/gorm"
"io/ioutil"
"log"
"strings"
)
func InitDb(db *gorm.DB) (err error) {
filePath := "config/db.sql"
err = ExecSql(db, filePath)
if global.Driver == "postgres" {
filePath = "config/pg.sql"
err = ExecSql(db, filePath)
}
return err
}
func ExecSql(db *gorm.DB, filePath string) error {
sql, err := Ioutil(filePath)
if err != nil {
fmt.Println("数据库基础数据初始化脚本读取失败!原因:", err.Error())
return err
}
sqlList := strings.Split(sql, ";")
for i := 0; i < len(sqlList)-1; i++ {
if strings.Contains(sqlList[i], "--") {
fmt.Println(sqlList[i])
continue
}
sql := strings.Replace(sqlList[i]+";", "\n", "", -1)
sql = strings.TrimSpace(sql)
if err = db.Exec(sql).Error; err != nil {
log.Printf("error sql: %s", sql)
if !strings.Contains(err.Error(), "Query was empty") {
return err
}
}
}
return nil
}
func Ioutil(filePath string) (string, error) {
if contents, err := ioutil.ReadFile(filePath); err == nil {
//因为contents是[]byte类型,直接转换成string类型后会多一行空格,需要使用strings.Replace替换换行符
result := strings.Replace(string(contents), "\n", "", 1)
fmt.Println("Use ioutil.ReadFile to read a file:", result)
return result, nil
} else {
return "", err
}
}
-91
View File
@@ -1,91 +0,0 @@
package models
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"regexp"
"strings"
"github.com/bitly/go-simplejson"
"github.com/go-admin-team/go-admin-core/sdk"
"github.com/go-admin-team/go-admin-core/sdk/runtime"
"github.com/go-admin-team/go-admin-core/storage"
"git.ilapage.cn/ila/yovision/Sense/server/common/models"
)
type SysApi struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
Handle string `json:"handle" gorm:"size:128;comment:handle"`
Title string `json:"title" gorm:"size:128;comment:标题"`
Path string `json:"path" gorm:"size:128;comment:地址"`
Action string `json:"action" gorm:"size:16;comment:请求类型"`
Type string `json:"type" gorm:"size:16;comment:接口类型"`
models.ModelTime
models.ControlBy
}
func (*SysApi) TableName() string {
return "sys_api"
}
func (e *SysApi) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysApi) GetId() interface{} {
return e.Id
}
func SaveSysApi(message storage.Messager) (err error) {
var rb []byte
rb, err = json.Marshal(message.GetValues())
if err != nil {
err = fmt.Errorf("json Marshal error, %v", err.Error())
return err
}
var l runtime.Routers
err = json.Unmarshal(rb, &l)
if err != nil {
err = fmt.Errorf("json Unmarshal error, %s", err.Error())
return err
}
dbList := sdk.Runtime.GetDb()
for _, d := range dbList {
for _, v := range l.List {
if v.HttpMethod != "HEAD" ||
strings.Contains(v.RelativePath, "/swagger/") ||
strings.Contains(v.RelativePath, "/static/") ||
strings.Contains(v.RelativePath, "/form-generator/") ||
strings.Contains(v.RelativePath, "/sys/tables") {
// 根据接口方法注释里的@Summary填充接口名称,适用于代码生成器
// 可在此处增加配置路径前缀的if判断,只对代码生成的自建应用进行定向的接口名称填充
jsonFile, _ := ioutil.ReadFile("docs/swagger.json")
jsonData, _ := simplejson.NewFromReader(bytes.NewReader(jsonFile))
urlPath := v.RelativePath
idPatten := "(.*)/:(\\w+)" // 正则替换,把:id换成{id}
reg, _ := regexp.Compile(idPatten)
if reg.MatchString(urlPath) {
urlPath = reg.ReplaceAllString(v.RelativePath, "${1}/{${2}}") // 把:id换成{id}
}
apiTitle, _ := jsonData.Get("paths").Get(urlPath).Get(strings.ToLower(v.HttpMethod)).Get("summary").String()
err := d.Debug().Where(SysApi{Path: v.RelativePath, Action: v.HttpMethod}).
Attrs(SysApi{Handle: v.Handler, Title: apiTitle}).
FirstOrCreate(&SysApi{}).
//Update("handle", v.Handler).
Error
if err != nil {
err := fmt.Errorf("Models SaveSysApi error: %s \r\n ", err.Error())
return err
}
}
}
}
return nil
}
@@ -1,30 +0,0 @@
package models
import (
"git.ilapage.cn/ila/yovision/Sense/server/common/models"
)
type SysConfig struct {
models.Model
ConfigName string `json:"configName" gorm:"size:128;comment:ConfigName"` //
ConfigKey string `json:"configKey" gorm:"size:128;comment:ConfigKey"` //
ConfigValue string `json:"configValue" gorm:"size:255;comment:ConfigValue"` //
ConfigType string `json:"configType" gorm:"size:64;comment:ConfigType"`
IsFrontend string `json:"isFrontend" gorm:"size:64;comment:是否前台"` //
Remark string `json:"remark" gorm:"size:128;comment:Remark"` //
models.ControlBy
models.ModelTime
}
func (*SysConfig) TableName() string {
return "sys_config"
}
func (e *SysConfig) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysConfig) GetId() interface{} {
return e.Id
}
-33
View File
@@ -1,33 +0,0 @@
package models
import "git.ilapage.cn/ila/yovision/Sense/server/common/models"
type SysDept struct {
DeptId int `json:"deptId" gorm:"primaryKey;autoIncrement;"` //部门编码
ParentId int `json:"parentId" gorm:""` //上级部门
DeptPath string `json:"deptPath" gorm:"size:255;"` //
DeptName string `json:"deptName" gorm:"size:128;"` //部门名称
Sort int `json:"sort" gorm:"size:4;"` //排序
Leader string `json:"leader" gorm:"size:128;"` //负责人
Phone string `json:"phone" gorm:"size:11;"` //手机
Email string `json:"email" gorm:"size:64;"` //邮箱
Status int `json:"status" gorm:"size:4;"` //状态
models.ControlBy
models.ModelTime
DataScope string `json:"dataScope" gorm:"-"`
Params string `json:"params" gorm:"-"`
Children []SysDept `json:"children" gorm:"-"`
}
func (*SysDept) TableName() string {
return "sys_dept"
}
func (e *SysDept) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysDept) GetId() interface{} {
return e.DeptId
}
@@ -1,34 +0,0 @@
package models
import (
"git.ilapage.cn/ila/yovision/Sense/server/common/models"
)
type SysDictData struct {
DictCode int `json:"dictCode" gorm:"primaryKey;column:dict_code;autoIncrement;comment:主键编码"`
DictSort int `json:"dictSort" gorm:"size:20;comment:DictSort"`
DictLabel string `json:"dictLabel" gorm:"size:128;comment:DictLabel"`
DictValue string `json:"dictValue" gorm:"size:255;comment:DictValue"`
DictType string `json:"dictType" gorm:"size:64;comment:DictType"`
CssClass string `json:"cssClass" gorm:"size:128;comment:CssClass"`
ListClass string `json:"listClass" gorm:"size:128;comment:ListClass"`
IsDefault string `json:"isDefault" gorm:"size:8;comment:IsDefault"`
Status int `json:"status" gorm:"size:4;comment:Status"`
Default string `json:"default" gorm:"size:8;comment:Default"`
Remark string `json:"remark" gorm:"size:255;comment:Remark"`
models.ControlBy
models.ModelTime
}
func (*SysDictData) TableName() string {
return "sys_dict_data"
}
func (e *SysDictData) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysDictData) GetId() interface{} {
return e.DictCode
}
@@ -1,28 +0,0 @@
package models
import (
"git.ilapage.cn/ila/yovision/Sense/server/common/models"
)
type SysDictType struct {
ID int `json:"id" gorm:"primaryKey;column:dict_id;autoIncrement;comment:主键编码"`
DictName string `json:"dictName" gorm:"size:128;comment:DictName"`
DictType string `json:"dictType" gorm:"size:128;comment:DictType"`
Status int `json:"status" gorm:"size:4;comment:Status"`
Remark string `json:"remark" gorm:"size:255;comment:Remark"`
models.ControlBy
models.ModelTime
}
func (*SysDictType) TableName() string {
return "sys_dict_type"
}
func (e *SysDictType) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysDictType) GetId() interface{} {
return e.ID
}
@@ -1,72 +0,0 @@
package models
import (
"encoding/json"
"errors"
"time"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk"
"github.com/go-admin-team/go-admin-core/storage"
"git.ilapage.cn/ila/yovision/Sense/server/common/models"
)
type SysLoginLog struct {
models.Model
Username string `json:"username" gorm:"size:128;comment:用户名"`
Status string `json:"status" gorm:"size:4;comment:状态"`
Ipaddr string `json:"ipaddr" gorm:"size:255;comment:ip地址"`
LoginLocation string `json:"loginLocation" gorm:"size:255;comment:归属地"`
Browser string `json:"browser" gorm:"size:255;comment:浏览器"`
Os string `json:"os" gorm:"size:255;comment:系统"`
Platform string `json:"platform" gorm:"size:255;comment:固件"`
LoginTime time.Time `json:"loginTime" gorm:"comment:登录时间"`
Remark string `json:"remark" gorm:"size:255;comment:备注"`
Msg string `json:"msg" gorm:"size:255;comment:信息"`
CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"`
UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"`
models.ControlBy
}
func (*SysLoginLog) TableName() string {
return "sys_login_log"
}
func (e *SysLoginLog) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysLoginLog) GetId() interface{} {
return e.Id
}
// SaveLoginLog 从队列中获取登录日志
func SaveLoginLog(message storage.Messager) (err error) {
//准备db
db := sdk.Runtime.GetDbByKey(message.GetPrefix())
if db == nil {
err = errors.New("db not exist")
log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error())
return err
}
var rb []byte
rb, err = json.Marshal(message.GetValues())
if err != nil {
log.Errorf("json Marshal error, %s", err.Error())
return err
}
var l SysLoginLog
err = json.Unmarshal(rb, &l)
if err != nil {
log.Errorf("json Unmarshal error, %s", err.Error())
return err
}
err = db.Create(&l).Error
if err != nil {
log.Errorf("db create error, %s", err.Error())
return err
}
return nil
}
-50
View File
@@ -1,50 +0,0 @@
package models
import "git.ilapage.cn/ila/yovision/Sense/server/common/models"
type SysMenu struct {
MenuId int `json:"menuId" gorm:"primaryKey;autoIncrement"`
MenuName string `json:"menuName" gorm:"size:128;"`
Title string `json:"title" gorm:"size:128;"`
Icon string `json:"icon" gorm:"size:128;"`
Path string `json:"path" gorm:"size:128;"`
Paths string `json:"paths" gorm:"size:128;"`
MenuType string `json:"menuType" gorm:"size:1;"`
Action string `json:"action" gorm:"size:16;"`
Permission string `json:"permission" gorm:"size:255;"`
ParentId int `json:"parentId" gorm:"size:11;"`
NoCache bool `json:"noCache" gorm:"size:8;"`
Breadcrumb string `json:"breadcrumb" gorm:"size:255;"`
Component string `json:"component" gorm:"size:255;"`
Sort int `json:"sort" gorm:"size:4;"`
Visible string `json:"visible" gorm:"size:1;"`
IsFrame string `json:"isFrame" gorm:"size:1;DEFAULT:0;"`
SysApi []SysApi `json:"sysApi" gorm:"many2many:sys_menu_api_rule"`
Apis []int `json:"apis" gorm:"-"`
DataScope string `json:"dataScope" gorm:"-"`
Params string `json:"params" gorm:"-"`
RoleId int `gorm:"-"`
Children []SysMenu `json:"children,omitempty" gorm:"-"`
IsSelect bool `json:"is_select" gorm:"-"`
models.ControlBy
models.ModelTime
}
type SysMenuSlice []SysMenu
func (x SysMenuSlice) Len() int { return len(x) }
func (x SysMenuSlice) Less(i, j int) bool { return x[i].Sort < x[j].Sort }
func (x SysMenuSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (*SysMenu) TableName() string {
return "sys_menu"
}
func (e *SysMenu) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysMenu) GetId() interface{} {
return e.MenuId
}
@@ -1,88 +0,0 @@
package models
import (
"encoding/json"
"errors"
"time"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk"
"github.com/go-admin-team/go-admin-core/storage"
"git.ilapage.cn/ila/yovision/Sense/server/common/models"
)
type SysOperaLog struct {
models.Model
Title string `json:"title" gorm:"size:255;comment:操作模块"`
BusinessType string `json:"businessType" gorm:"size:128;comment:操作类型"`
BusinessTypes string `json:"businessTypes" gorm:"size:128;comment:BusinessTypes"`
Method string `json:"method" gorm:"size:128;comment:函数"`
RequestMethod string `json:"requestMethod" gorm:"size:128;comment:请求方式 GET POST PUT DELETE"`
OperatorType string `json:"operatorType" gorm:"size:128;comment:操作类型"`
OperName string `json:"operName" gorm:"size:128;comment:操作者"`
DeptName string `json:"deptName" gorm:"size:128;comment:部门名称"`
OperUrl string `json:"operUrl" gorm:"size:255;comment:访问地址"`
OperIp string `json:"operIp" gorm:"size:128;comment:客户端ip"`
OperLocation string `json:"operLocation" gorm:"size:128;comment:访问位置"`
OperParam string `json:"operParam" gorm:"text;comment:请求参数"`
Status string `json:"status" gorm:"size:4;comment:操作状态 1:正常 2:关闭"`
OperTime time.Time `json:"operTime" gorm:"comment:操作时间"`
JsonResult string `json:"jsonResult" gorm:"size:255;comment:返回数据"`
Remark string `json:"remark" gorm:"size:255;comment:备注"`
LatencyTime string `json:"latencyTime" gorm:"size:128;comment:耗时"`
UserAgent string `json:"userAgent" gorm:"size:255;comment:ua"`
CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"`
UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"`
models.ControlBy
}
func (*SysOperaLog) TableName() string {
return "sys_opera_log"
}
func (e *SysOperaLog) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysOperaLog) GetId() interface{} {
return e.Id
}
// SaveOperaLog 从队列中获取操作日志
func SaveOperaLog(message storage.Messager) (err error) {
//准备db
db := sdk.Runtime.GetDbByKey(message.GetPrefix())
if db == nil {
err = errors.New("db not exist")
log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error())
// Log writing to the database ignores error
return nil
}
var rb []byte
rb, err = json.Marshal(message.GetValues())
if err != nil {
log.Errorf("json Marshal error, %s", err.Error())
// Log writing to the database ignores error
return nil
}
var l SysOperaLog
err = json.Unmarshal(rb, &l)
if err != nil {
log.Errorf("json Unmarshal error, %s", err.Error())
// Log writing to the database ignores error
return nil
}
// 超出100个字符返回值截断
if len(l.JsonResult) > 100 {
l.JsonResult = l.JsonResult[:100]
}
err = db.Create(&l).Error
if err != nil {
log.Errorf("db create error, %s", err.Error())
// Log writing to the database ignores error
return nil
}
return nil
}
-30
View File
@@ -1,30 +0,0 @@
package models
import "git.ilapage.cn/ila/yovision/Sense/server/common/models"
type SysPost struct {
PostId int `gorm:"primaryKey;autoIncrement" json:"postId"` //岗位编号
PostName string `gorm:"size:128;" json:"postName"` //岗位名称
PostCode string `gorm:"size:128;" json:"postCode"` //岗位代码
Sort int `gorm:"size:4;" json:"sort"` //岗位排序
Status int `gorm:"size:4;" json:"status"` //状态
Remark string `gorm:"size:255;" json:"remark"` //描述
models.ControlBy
models.ModelTime
DataScope string `gorm:"-" json:"dataScope"`
Params string `gorm:"-" json:"params"`
}
func (*SysPost) TableName() string {
return "sys_post"
}
func (e *SysPost) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysPost) GetId() interface{} {
return e.PostId
}
-35
View File
@@ -1,35 +0,0 @@
package models
import "git.ilapage.cn/ila/yovision/Sense/server/common/models"
type SysRole struct {
RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码
RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称
Status string `json:"status" gorm:"size:4;"` // 状态 1禁用 2正常
RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码
RoleSort int `json:"roleSort" gorm:""` //角色排序
Flag string `json:"flag" gorm:"size:128;"` //
Remark string `json:"remark" gorm:"size:255;"` //备注
Admin bool `json:"admin" gorm:"size:4;"`
DataScope string `json:"dataScope" gorm:"size:128;"`
Params string `json:"params" gorm:"-"`
MenuIds []int `json:"menuIds" gorm:"-"`
DeptIds []int `json:"deptIds" gorm:"-"`
SysDept []SysDept `json:"sysDept" gorm:"many2many:sys_role_dept;foreignKey:RoleId;joinForeignKey:role_id;references:DeptId;joinReferences:dept_id;"`
SysMenu *[]SysMenu `json:"sysMenu" gorm:"many2many:sys_role_menu;foreignKey:RoleId;joinForeignKey:role_id;references:MenuId;joinReferences:menu_id;"`
models.ControlBy
models.ModelTime
}
func (*SysRole) TableName() string {
return "sys_role"
}
func (e *SysRole) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysRole) GetId() interface{} {
return e.RoleId
}
-77
View File
@@ -1,77 +0,0 @@
package models
import (
"git.ilapage.cn/ila/yovision/Sense/server/common/models"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
type SysUser struct {
UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"`
Username string `json:"username" gorm:"size:64;comment:用户名"`
Password string `json:"-" gorm:"size:128;comment:密码"`
NickName string `json:"nickName" gorm:"size:128;comment:昵称"`
Phone string `json:"phone" gorm:"size:11;comment:手机号"`
RoleId int `json:"roleId" gorm:"size:20;comment:角色ID"`
Salt string `json:"-" gorm:"size:255;comment:加盐"`
Avatar string `json:"avatar" gorm:"size:255;comment:头像"`
Sex string `json:"sex" gorm:"size:255;comment:性别"`
Email string `json:"email" gorm:"size:128;comment:邮箱"`
DeptId int `json:"deptId" gorm:"size:20;comment:部门"`
PostId int `json:"postId" gorm:"size:20;comment:岗位"`
Remark string `json:"remark" gorm:"size:255;comment:备注"`
Status string `json:"status" gorm:"size:4;comment:状态"`
DeptIds []int `json:"deptIds" gorm:"-"`
PostIds []int `json:"postIds" gorm:"-"`
RoleIds []int `json:"roleIds" gorm:"-"`
Dept *SysDept `json:"dept"`
models.ControlBy
models.ModelTime
}
func (*SysUser) TableName() string {
return "sys_user"
}
func (e *SysUser) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysUser) GetId() interface{} {
return e.UserId
}
// Encrypt 加密
func (e *SysUser) Encrypt() (err error) {
if e.Password == "" {
return
}
var hash []byte
if hash, err = bcrypt.GenerateFromPassword([]byte(e.Password), bcrypt.DefaultCost); err != nil {
return
} else {
e.Password = string(hash)
return
}
}
func (e *SysUser) BeforeCreate(_ *gorm.DB) error {
return e.Encrypt()
}
func (e *SysUser) BeforeUpdate(_ *gorm.DB) error {
var err error
if e.Password != "" {
err = e.Encrypt()
}
return err
}
func (e *SysUser) AfterFind(_ *gorm.DB) error {
e.DeptIds = []int{e.DeptId}
e.PostIds = []int{e.PostId}
e.RoleIds = []int{e.RoleId}
return nil
}
@@ -1,40 +0,0 @@
package router
import (
"os"
common "git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk"
)
// InitRouter 路由初始化,不要怀疑,这里用到了
func InitRouter() {
var r *gin.Engine
h := sdk.Runtime.GetEngine()
if h == nil {
log.Fatal("not found engine...")
os.Exit(-1)
}
switch h.(type) {
case *gin.Engine:
r = h.(*gin.Engine)
default:
log.Fatal("not support other engine")
os.Exit(-1)
}
// the jwt middleware
authMiddleware, err := common.AuthInit()
if err != nil {
log.Fatalf("JWT Init Error, %s", err.Error())
}
// 注册系统路由
InitSysRouter(r, authMiddleware)
// 注册业务路由
// TODO: 这里可存放业务路由,里边并无实际路由只有演示代码
InitExamplesRouter(r, authMiddleware)
}
-41
View File
@@ -1,41 +0,0 @@
package router
import (
"github.com/gin-gonic/gin"
_ "github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
var (
routerNoCheckRole = make([]func(*gin.RouterGroup), 0)
routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0)
)
func InitExamplesRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine {
// 无需认证的路由
examplesNoCheckRoleRouter(r)
// 需要认证的路由
examplesCheckRoleRouter(r, authMiddleware)
return r
}
// 无需认证的路由示例
func examplesNoCheckRoleRouter(r *gin.Engine) {
// 可根据业务需求来设置接口版本
v1 := r.Group("/api/v1")
for _, f := range routerNoCheckRole {
f(v1)
}
}
// 需要认证的路由示例
func examplesCheckRoleRouter(r *gin.Engine, authMiddleware *jwtauth.GinJWTMiddleware) {
// 可根据业务需求来设置接口版本
v1 := r.Group("/api/v1")
for _, f := range routerCheckRole {
f(v1, authMiddleware)
}
}
@@ -1,18 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/sense/admission"
"git.ilapage.cn/ila/yovision/Sense/server/common/actions"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() { routerCheckRole = append(routerCheckRole, registerSenseAdmissionRouter) }
func registerSenseAdmissionRouter(v1 *gin.RouterGroup, auth *jwt.GinJWTMiddleware) {
api := &admission.API{}
r := v1.Group("/admission").Use(auth.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
r.GET("/discover", api.Discover)
r.GET("/devices/:id", api.Get)
r.POST("/devices/:id/probe", api.Probe)
}
@@ -1,20 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/sense/area"
"git.ilapage.cn/ila/yovision/Sense/server/common/actions"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() { routerCheckRole = append(routerCheckRole, registerSenseAreaRouter) }
func registerSenseAreaRouter(v1 *gin.RouterGroup, auth *jwt.GinJWTMiddleware) {
api := &area.API{}
r := v1.Group("/area").Use(auth.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
r.GET("/configurations", api.List)
r.POST("/configurations", api.Create)
r.PUT("/configurations/:id", api.Update)
r.GET("/configurations/:id/versions", api.Versions)
}
@@ -1,27 +0,0 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
senseapis "git.ilapage.cn/ila/yovision/Sense/server/app/sense/device/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/actions"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSenseDeviceRouter)
}
func registerSenseDeviceRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := senseapis.Device{}
r := v1.Group("/devices").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.PUT("/:id/disable", api.Disable)
r.PUT("/:id/credentials", api.UpdateCredentials)
}
}
@@ -1,27 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/sense/liveview"
"git.ilapage.cn/ila/yovision/Sense/server/common/actions"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSenseLiveviewRouter)
routerNoCheckRole = append(routerNoCheckRole, registerSenseLiveviewPlayerRouter)
}
func registerSenseLiveviewRouter(v1 *gin.RouterGroup, auth *jwt.GinJWTMiddleware) {
api := &liveview.API{}
r := v1.Group("/liveview").Use(auth.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
r.GET("/routes", api.List)
r.POST("/sessions", api.Create)
r.GET("/sessions/:id", api.Get)
}
func registerSenseLiveviewPlayerRouter(v1 *gin.RouterGroup) {
api := &liveview.API{}
v1.GET("/liveview/player/:id", api.Player)
}
@@ -1,21 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/sense/media"
"git.ilapage.cn/ila/yovision/Sense/server/common/actions"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() { routerCheckRole = append(routerCheckRole, registerSenseMediaRouter) }
func registerSenseMediaRouter(v1 *gin.RouterGroup, auth *jwt.GinJWTMiddleware) {
api := &media.API{}
r := v1.Group("/media").Use(auth.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction())
r.GET("/routes", api.List)
r.GET("/process", api.Process)
r.POST("/reconcile", api.ReconcileAll)
r.POST("/routes/:id/reconcile", api.Reconcile)
r.POST("/routes/:id/stop", api.Stop)
}
-23
View File
@@ -1,23 +0,0 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
)
func init() {
}
// registerSysApiRouter
func registerSysApiRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysApi{}
r := v1.Group("/sys-api").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.PUT("/:id", api.Update)
}
}
@@ -1,42 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() {
}
// 需认证的路由代码
func registerSysConfigRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysConfig{}
r := v1.Group("/config").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
r1 := v1.Group("/configKey").Use(authMiddleware.MiddlewareFunc())
{
r1.GET("/:configKey", api.GetSysConfigByKEYForService)
}
r2 := v1.Group("/app-config")
{
r2.GET("", api.Get2SysApp)
}
r3 := v1.Group("/set-config").Use(authMiddleware.MiddlewareFunc())
{
r3.PUT("", api.Update2Set)
r3.GET("", api.Get2Set)
}
}
-32
View File
@@ -1,32 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysDeptRouter)
}
// 需认证的路由代码
func registerSysDeptRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysDept{}
r := v1.Group("/dept").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r1.GET("/deptTree", api.Get2Tree)
}
}
-37
View File
@@ -1,37 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() {
routerCheckRole = append(routerCheckRole, registerDictRouter)
}
func registerDictRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
dictApi := apis.SysDictType{}
dataApi := apis.SysDictData{}
dicts := v1.Group("/dict").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
dicts.GET("/data", dataApi.GetPage)
dicts.GET("/data/:dictCode", dataApi.Get)
dicts.POST("/data", dataApi.Insert)
dicts.PUT("/data/:dictCode", dataApi.Update)
dicts.DELETE("/data", dataApi.Delete)
dicts.GET("/type-option-select", dictApi.GetAll)
dicts.GET("/type", dictApi.GetPage)
dicts.GET("/type/:id", dictApi.Get)
dicts.POST("/type", dictApi.Insert)
dicts.PUT("/type/:id", dictApi.Update)
dicts.DELETE("/type", dictApi.Delete)
}
opSelect := v1.Group("/dict-data").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
opSelect.GET("/option-select", dataApi.GetAll)
}
}
@@ -1,24 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysLoginLogRouter)
}
// 需认证的路由代码
func registerSysLoginLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysLoginLog{}
r := v1.Group("/sys-login-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.DELETE("", api.Delete)
}
}
-33
View File
@@ -1,33 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysMenuRouter)
}
// 需认证的路由代码
func registerSysMenuRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysMenu{}
r := v1.Group("/menu").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc())
{
r1.GET("/menurole", api.GetMenuRole)
//r1.GET("/menuids", api.GetMenuIDS)
}
}
@@ -1,23 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysOperaLogRouter)
}
// 需认证的路由代码
func registerSysOperaLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysOperaLog{}
r := v1.Group("/sys-opera-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.DELETE("", api.Delete)
}
}
-25
View File
@@ -1,25 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSyPostRouter)
}
// 需认证的路由代码
func registerSyPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysPost{}
r := v1.Group("/post").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
}
-31
View File
@@ -1,31 +0,0 @@
package router
import (
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
)
func init() {
routerCheckRole = append(routerCheckRole, registerSysRoleRouter)
}
// 需认证的路由代码
func registerSysRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysRole{}
r := v1.Group("/role").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r.GET("", api.GetPage)
r.GET("/:id", api.Get)
r.POST("", api.Insert)
r.PUT("/:id", api.Update)
r.DELETE("", api.Delete)
}
r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
r1.PUT("/role-status", api.Update2Status)
r1.PUT("/roledatascope", api.Update2DataScope)
}
}
@@ -1,94 +0,0 @@
package router
import (
"git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis"
"mime"
"github.com/go-admin-team/go-admin-core/sdk/config"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
"github.com/go-admin-team/go-admin-core/sdk/pkg/ws"
ginSwagger "github.com/swaggo/gin-swagger"
swaggerfiles "github.com/swaggo/files"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware"
"git.ilapage.cn/ila/yovision/Sense/server/common/middleware/handler"
_ "git.ilapage.cn/ila/yovision/Sense/server/docs/admin"
)
func InitSysRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.RouterGroup {
g := r.Group("")
sysBaseRouter(g)
// 静态文件
sysStaticFileRouter(g)
// swagger;注意:生产环境可以注释掉
if config.ApplicationConfig.Mode != "prod" {
sysSwaggerRouter(g)
}
// 需要认证
sysCheckRoleRouterInit(g, authMiddleware)
return g
}
func sysBaseRouter(r *gin.RouterGroup) {
go ws.WebsocketManager.Start()
go ws.WebsocketManager.SendService()
go ws.WebsocketManager.SendAllService()
if config.ApplicationConfig.Mode != "prod" {
r.GET("/", apis.GoAdmin)
}
r.GET("/info", handler.Ping)
}
func sysStaticFileRouter(r *gin.RouterGroup) {
err := mime.AddExtensionType(".js", "application/javascript")
if err != nil {
return
}
r.Static("/static", "./static")
if config.ApplicationConfig.Mode != "prod" {
r.Static("/form-generator", "./static/form-generator")
}
}
func sysSwaggerRouter(r *gin.RouterGroup) {
r.GET("/swagger/admin/*any", ginSwagger.WrapHandler(swaggerfiles.NewHandler(), ginSwagger.InstanceName("admin")))
}
func sysCheckRoleRouterInit(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
wss := r.Group("").Use(authMiddleware.MiddlewareFunc())
{
wss.GET("/ws/:id/:channel", ws.WebsocketManager.WsClient)
wss.GET("/wslogout/:id/:channel", ws.WebsocketManager.UnWsClient)
}
v1 := r.Group("/api/v1")
{
v1.GET("/captcha", apis.System{}.GenerateCaptchaHandler)
v1.POST("/bootstrap", apis.System{}.Bootstrap)
v1.POST("/login", authMiddleware.LoginHandler)
// Refresh time can be longer than token timeout
v1.GET("/refresh_token", authMiddleware.RefreshHandler)
}
registerBaseRouter(v1, authMiddleware)
}
func registerBaseRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) {
api := apis.SysMenu{}
api2 := apis.SysDept{}
configAPI := apis.SysConfig{}
// The GoAdmin login shell reads frontend-only branding before a user is
// authenticated. Keep this single read route public without registering the
// disabled system-configuration CRUD and write routes.
v1.GET("/app-config", configAPI.Get2SysApp)
v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
v1auth.GET("/roleMenuTreeselect/:roleId", api.GetMenuTreeSelect)
v1auth.GET("/roleDeptTreeselect/:roleId", api2.GetDeptTreeRoleSelect)
}
v1.Group("").Use(authMiddleware.MiddlewareFunc()).POST("/logout", handler.LogOut)
}
@@ -1,37 +0,0 @@
package router
import (
"testing"
"github.com/gin-gonic/gin"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
)
func TestRegisterBaseRouterExposesOnlyFrontendAppConfig(t *testing.T) {
gin.SetMode(gin.TestMode)
engine := gin.New()
registerBaseRouter(engine.Group("/api/v1"), &jwt.GinJWTMiddleware{})
routes := make(map[string]struct{})
for _, route := range engine.Routes() {
routes[route.Method+" "+route.Path] = struct{}{}
}
if _, ok := routes["GET /api/v1/app-config"]; !ok {
t.Fatal("anonymous frontend app-config route is not registered")
}
for _, disabled := range []string{
"GET /api/v1/config",
"POST /api/v1/config",
"GET /api/v1/config/:id",
"PUT /api/v1/config/:id",
"DELETE /api/v1/config",
"GET /api/v1/configKey/:configKey",
"GET /api/v1/set-config",
"PUT /api/v1/set-config",
} {
if _, ok := routes[disabled]; ok {
t.Fatalf("disabled system-configuration route was registered: %s", disabled)
}
}
}

Some files were not shown because too many files have changed in this diff Show More