修改为免登录,定时任务数据初始化
This commit is contained in:
@@ -4,97 +4,177 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project
|
||||
|
||||
PPGo_Job — 定时任务管理系统,GoFrame v2 重构版。
|
||||
PPGo_Job — 定时任务管理系统,已从 Beego v1 重构为 **GoFrame v2**。支持本机 Shell 执行、HTTP 请求任务和 SSH 远程执行。
|
||||
|
||||
**框架:** GoFrame v2(原版基于 Beego v1)
|
||||
**数据库:** SQLite(纯 Go 驱动 `modernc.org/sqlite`,无需 CGO)
|
||||
**定时任务:** GoFrame `gcron`(原版自定义 crons/ 包)
|
||||
- **框架:** GoFrame v2
|
||||
- **数据库:** SQLite (`modernc.org/sqlite`,纯 Go,无 CGO)
|
||||
- **定时器:** GoFrame `gcron`(6 段 cron 表达式,含秒)
|
||||
- **前端:** Vue 3 + Element Plus + TypeScript(Vite 构建),旧 LayUI 兼容层已移除
|
||||
- **远程执行:** SSH (`golang.org/x/crypto/ssh`),Telnet/Agent 模式待实现
|
||||
- **通知:** 邮件 (`net/smtp`) 已实现,钉钉/微信/短信待补充
|
||||
|
||||
## Build & Run
|
||||
|
||||
```bash
|
||||
# 编译(纯 Go,不需要 gcc/MinGW)
|
||||
# 一键构建(前端 + 后端)
|
||||
build_frontend.cmd
|
||||
|
||||
# 分步构建
|
||||
cd frontend && npm install && npm run build && cd ..
|
||||
CGO_ENABLED=0 go build -o ppgo_job.exe .
|
||||
|
||||
# 运行
|
||||
./ppgo_job.exe
|
||||
|
||||
# 访问 http://localhost:8082
|
||||
# 访问 http://localhost:8082 -> 重定向到 /app/(Vue 前端)
|
||||
# 默认账号 admin / 123456
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
# 全部测试
|
||||
CGO_ENABLED=0 go test -count=1 ./...
|
||||
|
||||
# 单元测试(无需启动服务器)
|
||||
CGO_ENABLED=0 go test -count=1 ./libs/ ./consts/ ./dao/
|
||||
|
||||
# 集成测试(启动测试服务器 :18082)
|
||||
CGO_ENABLED=0 go test -count=1 -timeout=120s -run "TestLogin|TestAuth|TestHome|TestTask|TestServer|TestGroup|TestBan|TestTemplate|TestStatic|TestDatabase|TestCORS" .
|
||||
|
||||
# 测试指定包
|
||||
CGO_ENABLED=0 go test -v -count=1 ./libs/
|
||||
```
|
||||
|
||||
> 集成测试使用端口 `:18082`,不会干扰开发实例。测试前确保 18082 端口未被占用。
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
main.go # GoFrame 启动入口
|
||||
config.yml # YAML 配置
|
||||
boot/ # 启动初始化
|
||||
db.go # 数据库初始化
|
||||
router.go # 路由注册
|
||||
tplfunc.go # 自定义模板函数(urlfor/date/substr 等)
|
||||
init.go # 启动后初始化(加载任务到调度器)
|
||||
controller/ # HTTP 层
|
||||
base.go # display/ajaxMsg/ajaxList/parseFilters 辅助函数
|
||||
login.go # 登录/登出
|
||||
home.go # 首页/仪表盘/帮助
|
||||
task.go # 任务 CRUD + 服务器/分组/日志/权限管理
|
||||
service/ # 业务逻辑
|
||||
scheduler/ # gcron 调度 + 任务执行引擎
|
||||
dao/ # 数据访问层
|
||||
model/entity/ # 数据库实体(含 Cols 字段常量)
|
||||
middleware/ # 认证中间件(Cookie + RBAC 权限)
|
||||
consts/ # 常量、表名定义
|
||||
resource/
|
||||
template/ # LayUI 模板(从原 views/ 迁移)
|
||||
static/ # 静态资源(layui/css/js)
|
||||
sql/schema.sql # 建表 DDL + 初始数据
|
||||
_old/ # 原 Beego 旧代码(参考用)
|
||||
agent/ # 远程执行器(独立二进制,后续迁移)
|
||||
config.yml # 配置(server/database/jobs/site/notify/email/msg/dingtalk/wechat)
|
||||
boot/
|
||||
db.go # SQLite 数据目录初始化
|
||||
router.go # 路由注册(公开路由 / API 路由 / Agent API)
|
||||
init.go # 自动建表 + 种子数据 + 加载调度任务
|
||||
controller/ # HTTP 层(flat package,按文件组织)
|
||||
base.go # display/ajaxMsg/ajaxList/parseSort 辅助函数
|
||||
response.go # 统一 JSON 响应格式
|
||||
api.go # Vue 前端专用 API
|
||||
login.go # 登录/登出
|
||||
home.go # 仪表盘
|
||||
task.go # 任务 CRUD + 审核
|
||||
admin.go # 服务器/分组/禁用命令/通知模板
|
||||
system.go # 权限因子/角色/管理员/个人资料
|
||||
service/
|
||||
scheduler/
|
||||
scheduler.go # gcron 调度器单例(AddTask/RemoveTask/UpdateTask/RunTaskNow)
|
||||
task_runner.go # 任务执行引擎(本地 Shell / SSH 远程 / HTTP 请求)
|
||||
ssh.go # SSH 连接器(密码 + 密钥认证)
|
||||
notify.go # 邮件通知
|
||||
dao/ # 数据访问层(单例模式 var Xxx = new(xxxDao))
|
||||
common.go # InsertAndGetId/Update/GetById/buildFilters 通用方法
|
||||
model/entity/ # 数据库实体(orm 标签 + Cols 字段常量)
|
||||
middleware/auth.go # 认证中间件(Cookie + JWT 双认证 + RBAC 权限)
|
||||
libs/ # 工具函数(SHA256/JWT/加密)
|
||||
consts/ # 常量定义(任务状态/通知类型/连接类型)
|
||||
sql/schema.sql # 建表 DDL 参考
|
||||
_old/ # Beego 旧代码参考(远程执行器 agent/ 等)
|
||||
frontend/ # Vue 3 + TypeScript 前端
|
||||
src/api/ # API 调用层(axios)
|
||||
src/router/ # Vue Router 路由
|
||||
src/stores/ # Pinia 状态管理
|
||||
src/views/ # 页面组件
|
||||
src/layout/ # 布局组件
|
||||
dist/ # 构建产物(Go 静态文件服务)
|
||||
```
|
||||
|
||||
## Layer Rules
|
||||
|
||||
- **controller**:参数解析 → 调用 Service/DAO → 渲染模板或返回 JSON。不含业务逻辑。
|
||||
- **service**:业务逻辑。不含 HTTP 概念。
|
||||
- **dao**:数据访问。纯 CRUD。
|
||||
- **middleware**:认证、权限检查。
|
||||
- **controller**: 参数解析 → 调用 Service/DAO → 渲染模板或 JSON。不含业务逻辑。
|
||||
- **service**: 业务逻辑。不含 HTTP 概念。
|
||||
- **dao**: 数据访问。纯 CRUD。单例模式 `var Xxx = new(xxxDao)`。
|
||||
- **middleware**: 认证、权限检查。
|
||||
|
||||
## Key Packages
|
||||
## Route Structure
|
||||
|
||||
| Package | Purpose |
|
||||
路由定义在 `boot/router.go`,分三组:
|
||||
|
||||
| 组 | 认证 | 用途 |
|
||||
|---|---|---|
|
||||
| `/` 公开 | 无 | `/login_in` 登录、`/` 根路径重定向到 `/app/` |
|
||||
| `/api/*` | JWT (Authorization Bearer) | Vue 前端所有 API |
|
||||
| `/server/*`, `/task/*` | 无 (Agent API) | 远程 Agent 注册/状态/任务分发 |
|
||||
|
||||
中间件: `ghttp.MiddlewareCORS` 全局 CORS → `middleware.Auth` 双认证(Cookie 兼容旧模板 + JWT 新前端)。
|
||||
|
||||
Controller 响应约定:
|
||||
- `ajaxMsg(r, msg, msgno)` → `{"status": msgno, "code": msgno, "message": msg}`
|
||||
- `ajaxList(r, msg, msgno, count, data)` → `{"code": msgno, "msg": msg, "count": count, "data": data}` (LayUI table 格式)
|
||||
- controller/response.go `Resp(r).Success(data)` / `Resp(r).Error(msg)` → `{"code": 0, "msg": "success", "data": ...}` (Vue 前端统一格式)
|
||||
|
||||
## Scheduler
|
||||
|
||||
- 单例 `service/scheduler.Scheduler`,启动时 `Init()` 加载所有启用任务到 `gcron`
|
||||
- 使用 `gcron.AddSingleton` → 同一任务不会并发执行
|
||||
- 任务命名: `task_{id}`
|
||||
- 更新/删除: `RemoveTask` 后重新 `AddTask`
|
||||
- 任务类型:
|
||||
- `shell`: 本机执行(Windows: `CMD /C`, Linux: `sh -c`)或 SSH 远程
|
||||
- `http`: 从调度器发 HTTP 请求,支持自定义 Method/Headers/Body,自动检测 JSON 响应中的业务错误(code/success/status/errno)
|
||||
|
||||
## Cron 表达式格式
|
||||
|
||||
GoFrame gcron 使用 **6 段格式**(含秒),非标准 5 段:
|
||||
|
||||
```
|
||||
秒 分 时 日 月 周
|
||||
0 0 3 * * * 每天凌晨 3 点
|
||||
0 */5 * * * * 每 5 分钟
|
||||
```
|
||||
|
||||
`scheduler.go` 中的 `parseCronFields` 也兼容标准 5 段格式(自动补秒=0)和 `@every 30s` 语法。
|
||||
|
||||
## DAO 查询模式
|
||||
|
||||
DAO 层扩展了 GoFrame Model 的 `buildFilters` 机制(`dao/common.go`):
|
||||
|
||||
```go
|
||||
// 等值查询
|
||||
dao.Task.GetList(ctx, page, size, "status", 1)
|
||||
// LIKE 查询
|
||||
dao.Task.GetList(ctx, page, size, "task_name like", "%backup%")
|
||||
// 排序
|
||||
dao.Task.GetList(ctx, page, size, "@order", "id desc")
|
||||
```
|
||||
|
||||
`parseSort(r)`(controller/base.go)做字段名校验(仅字母/数字/下划线)防 SQL 注入。
|
||||
|
||||
## Key Config
|
||||
|
||||
`config.yml` 关键配置项:
|
||||
|
||||
| 路径 | 默认值 | 说明 |
|
||||
|---|---|---|
|
||||
| `server.address` | `:8082` | 监听端口 |
|
||||
| `database.default.name` | `./data/ppgo_job.db` | SQLite 数据库路径 |
|
||||
| `jobs.pool` | 1000 | 任务并发池大小 |
|
||||
| `site.name` | 定时任务管理器 | 站点标题 |
|
||||
| `notify.type` | 0 | 通知方式 (0=邮件, 1=短信, 2=钉钉, 3=微信) |
|
||||
| `email.*` | — | SMTP 邮件配置 |
|
||||
|
||||
## Task Status Constants
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `controller` | All HTTP handlers (flat package, organized by file) |
|
||||
| `dao` | Database CRUD operations, singleton pattern (`var Xxx = new(xxxDao)`) |
|
||||
| `model/entity` | DB structs with `orm` tags and Cols constants |
|
||||
| `service/scheduler` | Cron job scheduling + task execution engine |
|
||||
| `middleware` | Cookie auth + RBAC permission checking |
|
||||
| `consts` | Table names, status constants, message codes |
|
||||
| -1 | 已删除 |
|
||||
| 0 | 已暂停 |
|
||||
| 1 | 运行中 |
|
||||
| 2 | 待审核 |
|
||||
| 3 | 审核失败 |
|
||||
|
||||
## Template Convention
|
||||
## Known Limits
|
||||
|
||||
LayUI 模板使用 `{{include "MainContent" .}}` 布局模式(layout.html 中定义)。
|
||||
|
||||
- `display(r, "page.html", data)` — 渲染带布局的页面
|
||||
- `displayTpl(r, "page.html", data)` — 直接渲染模板
|
||||
- `ajaxMsg(r, msg, code)` — JSON 消息响应
|
||||
- `ajaxList(r, msg, code, count, data)` — LayUI table JSON 格式
|
||||
|
||||
## Database
|
||||
|
||||
SQLite 建表脚本:`sql/schema.sql`
|
||||
首次启动自动创建 `./data/ppgo_job.db`
|
||||
|
||||
## Migration Status
|
||||
|
||||
| Phase | Status |
|
||||
|-------|--------|
|
||||
| 1 项目骨架 | ✅ |
|
||||
| 2 数据访问层 | ✅ |
|
||||
| 3 路由+控制器框架 | ✅ |
|
||||
| 4 认证系统 | ✅ |
|
||||
| 5 模板渲染 | ✅ |
|
||||
| 6 任务调度器 | ✅ |
|
||||
| 7 服务层 | ✅ |
|
||||
| 8 清理 | ✅ |
|
||||
|
||||
待办:SSH/Telnet/Agent 远程执行器(参考 `_old/jobs/job.go`)、通知渠道(参考 `_old/notify/`)
|
||||
- **Telnet/Agent 远程执行** — SSH 已实现,其余待补充(参考 `_old/jobs/job.go`)
|
||||
- **通知渠道** — 邮件已实现,钉钉/微信/短信待补充(参考 `_old/notify/`)
|
||||
- **Agent API** — 路由已预留(`/server/api_*`, `/task/api_*`),Agent 二进制待实现
|
||||
- **Go 版本** — 要求 go 1.26+
|
||||
|
||||
+113
@@ -151,6 +151,9 @@ func autoInitDB(ctx context.Context) {
|
||||
if authCount == 0 {
|
||||
seedAuth(ctx)
|
||||
}
|
||||
|
||||
// 种子数据:业务数据(任务分组 + 定时任务)
|
||||
seedBusinessData(ctx)
|
||||
}
|
||||
|
||||
func seedAuth(ctx context.Context) {
|
||||
@@ -186,3 +189,113 @@ func seedAuth(ctx context.Context) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// seedBusinessData 种子数据:业务数据(任务分组 + 定时任务)
|
||||
func seedBusinessData(ctx context.Context) {
|
||||
now := time.Now().Unix()
|
||||
|
||||
// ---- 任务分组 ----
|
||||
var groupCount int
|
||||
_ = g.DB().GetScan(ctx, &groupCount, "SELECT COUNT(*) FROM pp_task_group")
|
||||
if groupCount == 0 {
|
||||
g.DB().Exec(ctx,
|
||||
`INSERT INTO pp_task_group(id, group_name, description, create_id, update_id, create_time, update_time, status)
|
||||
VALUES(?,?,?,?,?,?,?,?)`,
|
||||
1, "数据引擎", "Data Engine 定时同步任务", 1, 1, now, now, 1,
|
||||
)
|
||||
g.Log().Info(ctx, "已创建默认任务分组: 数据引擎")
|
||||
}
|
||||
|
||||
// ---- 定时任务 ----
|
||||
var taskCount int
|
||||
_ = g.DB().GetScan(ctx, &taskCount, "SELECT COUNT(*) FROM pp_task")
|
||||
if taskCount == 0 {
|
||||
seedTasks(ctx, now)
|
||||
}
|
||||
}
|
||||
|
||||
// seedTasks 种子定时任务
|
||||
func seedTasks(ctx context.Context, now int64) {
|
||||
type taskSeed struct {
|
||||
id int
|
||||
group_id int
|
||||
server_ids, task_name, description, cron_spec string
|
||||
concurrent int
|
||||
task_type, command, url, method, headers, body string
|
||||
timeout, status int
|
||||
}
|
||||
rows := []taskSeed{
|
||||
{46, 1, "", "数据引擎-补偿扫描", "",
|
||||
"0 */5 * * * *", 0,
|
||||
"http", "", "http://host.docker.internal:3013/sync/ctrl/compensate", "POST",
|
||||
`{"Content-Type":"application/json"}`, "",
|
||||
600, 0},
|
||||
{48, 1, "", "腾讯广告-账户列表(account_relation)", "",
|
||||
"0 0 */6 * * *", 0,
|
||||
"http", "", "http://host.docker.internal:3013/sync/ctrl/trigger", "POST",
|
||||
`{"Content-Type":"application/json"}`,
|
||||
`{"platformCode":"tencent","interfaceCode":"account_relation","fullSync":false}`,
|
||||
1800, 0},
|
||||
{49, 1, "", "腾讯广告-图片素材(image)", "",
|
||||
"0 0 * * * *", 0,
|
||||
"http", "", "http://host.docker.internal:3013/sync/ctrl/trigger", "POST",
|
||||
`{"Content-Type":"application/json"}`,
|
||||
`{"platformCode":"tencent","interfaceCode":"image","fullSync":false}`,
|
||||
3600, 0},
|
||||
{50, 1, "", "腾讯广告-视频素材(video)", "",
|
||||
"0 0 * * * *", 0,
|
||||
"http", "", "http://host.docker.internal:3013/sync/ctrl/trigger", "POST",
|
||||
`{"Content-Type":"application/json"}`,
|
||||
`{"platformCode":"tencent","interfaceCode":"video","fullSync":false}`,
|
||||
3600, 0},
|
||||
{51, 1, "", "腾讯广告-音频素材(audio)", "",
|
||||
"0 0 */6 * * *", 0,
|
||||
"http", "", "http://host.docker.internal:3013/sync/ctrl/trigger", "POST",
|
||||
`{"Content-Type":"application/json"}`,
|
||||
`{"platformCode":"tencent","interfaceCode":"audio","fullSync":false}`,
|
||||
1800, 0},
|
||||
{52, 1, "", "腾讯广告-Token刷新", "",
|
||||
"0 0 3 * * *", 0,
|
||||
"http", "", "http://host.docker.internal:3013/sync/ctrl/refreshToken", "POST",
|
||||
`{"Content-Type":"application/json"}`,
|
||||
`{"platformCode":"tencent"}`,
|
||||
0, 1},
|
||||
{53, 1, "", "CID - 图片批量送检", "自动扫描待校验图片并提交到易盾检测",
|
||||
"0/30 * * * * *", 0,
|
||||
"http", "", "http://host.docker.internal:3001/material/verify/controller/batch-verify-image", "POST",
|
||||
`{"Content-Type":"application/json"}`,
|
||||
"{limit:10}",
|
||||
120, 0},
|
||||
{54, 1, "", "CID - 视频批量送检", "自动扫描待校验视频并提交到易盾检测",
|
||||
"15/30 * * * * *", 0,
|
||||
"http", "", "http://host.docker.internal:3001/material/verify/controller/batch-verify-video", "POST",
|
||||
`{"Content-Type":"application/json"}`,
|
||||
"{limit:10}",
|
||||
120, 0},
|
||||
{55, 1, "", "CID - 检测结果轮询", "自动查询易盾检测结果并更新到素材表",
|
||||
"0 * * * * *", 0,
|
||||
"http", "", "http://host.docker.internal:3001/yidun/callback/controller/poll-all-results", "POST",
|
||||
"{}", "",
|
||||
120, 1},
|
||||
}
|
||||
|
||||
for _, r := range rows {
|
||||
_, err := g.DB().Exec(ctx,
|
||||
`INSERT INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES(?,?,?,0,?,?,?,?,?,?,?,?,?,?,?,0,0,?, 0,0,0,'', 1,1,?,?)`,
|
||||
r.id, r.group_id, r.server_ids, r.task_name, r.description, r.cron_spec,
|
||||
r.concurrent, r.task_type, r.command, r.url, r.method, r.headers, r.body,
|
||||
r.timeout, r.status,
|
||||
now, now,
|
||||
)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "种子任务插入失败 (id=%d): %v", r.id, err)
|
||||
}
|
||||
}
|
||||
g.Log().Infof(ctx, "已创建 %d 个种子定时任务", len(rows))
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
server:
|
||||
address: ":8082"
|
||||
address: ":8086"
|
||||
name: "ppgo_job"
|
||||
logPath: "resource/log/server"
|
||||
logStdout: true
|
||||
|
||||
+38
-38
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
-155
File diff suppressed because one or more lines are too long
-155
File diff suppressed because one or more lines are too long
-155
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -5,8 +5,8 @@
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>定时任务管理器</title>
|
||||
<script type="module" crossorigin src="/app/assets/index-B1r_eA9y.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/app/assets/index-BVLzGyHU.css">
|
||||
<script type="module" crossorigin src="/app/assets/index-BgU1CNJg.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/app/assets/index-D_pQJcpD.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -86,10 +86,28 @@ const router = createRouter({
|
||||
routes,
|
||||
})
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach((to, _from, next) => {
|
||||
// 路由守卫 — 未登录时自动登录(免密进入)
|
||||
router.beforeEach(async (to, _from, next) => {
|
||||
const userStore = useUserStore()
|
||||
if (to.meta.requiresAuth !== false && !userStore.isLoggedIn) {
|
||||
// 尝试自动登录(内置账号)
|
||||
try {
|
||||
const resp = await fetch('/api/login_in', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ username: 'admin', password: '123456' }),
|
||||
})
|
||||
const res = await resp.json()
|
||||
if (res.code === 0 && res.data) {
|
||||
userStore.setToken(res.data.token)
|
||||
userStore.setUser(res.data.userInfo)
|
||||
// 继续进入目标页面
|
||||
next()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// 自动登录失败,走登录页
|
||||
}
|
||||
next('/login')
|
||||
} else if (to.path === '/login' && userStore.isLoggedIn) {
|
||||
next('/dashboard')
|
||||
|
||||
@@ -2,49 +2,59 @@
|
||||
<div class="login-page">
|
||||
<div class="login-card">
|
||||
<h2 class="login-title">PPGo_Job 管理后台</h2>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
size="large"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="用户名"
|
||||
:prefix-icon="User"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
show-password
|
||||
:prefix-icon="Lock"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
style="width: 100%"
|
||||
@click="handleLogin"
|
||||
>
|
||||
登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<p v-if="error" class="login-error">{{ error }}</p>
|
||||
|
||||
<!-- 自动登录中 -->
|
||||
<div v-if="loading && !autoLoginDone" class="auto-login-hint">
|
||||
<el-icon class="is-loading" :size="32"><Loading /></el-icon>
|
||||
<p>正在自动登录...</p>
|
||||
</div>
|
||||
|
||||
<!-- 自动登录失败时显示登录表单 -->
|
||||
<template v-else-if="error || autoLoginDone">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
size="large"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="用户名"
|
||||
:prefix-icon="User"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="密码"
|
||||
show-password
|
||||
:prefix-icon="Lock"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
style="width: 100%"
|
||||
@click="handleLogin"
|
||||
>
|
||||
登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<p v-if="error" class="login-error">{{ error }}</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { User, Lock } from '@element-plus/icons-vue'
|
||||
import { User, Lock, Loading } from '@element-plus/icons-vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { login } from '@/api/login'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
@@ -54,6 +64,7 @@ const userStore = useUserStore()
|
||||
const formRef = ref<FormInstance>()
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const autoLoginDone = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
username: 'admin',
|
||||
@@ -87,8 +98,14 @@ async function handleLogin() {
|
||||
error.value = e.message || '网络错误'
|
||||
} finally {
|
||||
loading.value = false
|
||||
autoLoginDone.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载后自动登录
|
||||
onMounted(() => {
|
||||
handleLogin()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -118,4 +135,13 @@ async function handleLogin() {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.auto-login-hint {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
color: #666;
|
||||
}
|
||||
.auto-login-hint p {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -227,10 +228,15 @@ func (s *schedulerService) execHTTP(ctx context.Context, task *entity.Task) *Tas
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "HTTP请求 -> %s %s", method, task.Url)
|
||||
g.Log().Infof(ctx, "HTTP请求 -> %s %s (超时=%ds)", method, task.Url, task.Timeout)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
result.Error = "请求失败: " + err.Error()
|
||||
if isTimeoutError(err) {
|
||||
result.IsTimeout = true
|
||||
result.Error = fmt.Sprintf("请求超时(%d秒): %s", task.Timeout, task.Url)
|
||||
} else {
|
||||
result.Error = "请求失败: " + err.Error()
|
||||
}
|
||||
return result
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
@@ -387,6 +393,25 @@ func extractMsg(data map[string]interface{}, keys ...string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// isTimeoutError 判断是否为超时错误
|
||||
func isTimeoutError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
// Go http.Client 超时返回 context.DeadlineExceeded
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return true
|
||||
}
|
||||
// 也检查 net.Error.Timeout()
|
||||
var netErr interface{ Timeout() bool }
|
||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||
return true
|
||||
}
|
||||
// 兜底:关键字匹配
|
||||
msg := err.Error()
|
||||
return strings.Contains(msg, "timeout") || strings.Contains(msg, "deadline exceeded")
|
||||
}
|
||||
|
||||
// parseInt 简单字符串转整数
|
||||
func parseInt(s string) int {
|
||||
id := 0
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
-- ============================================================================
|
||||
-- PPGo_Job 数据种子文件
|
||||
-- 从 Docker 容器导出,用于新环境初始化
|
||||
-- 生成时间: 2026-07-21
|
||||
-- ============================================================================
|
||||
-- 说明:
|
||||
-- 1. 管理员和权限因子已由 boot/init.go 自动创建,此处不再重复
|
||||
-- 2. 以下数据在 pp_task_group / pp_task 表为空时才会被 seed
|
||||
-- ============================================================================
|
||||
|
||||
-- ============================================================================
|
||||
-- 任务分组
|
||||
-- ============================================================================
|
||||
INSERT OR IGNORE INTO pp_task_group(id, group_name, description, create_id, update_id, create_time, update_time, status)
|
||||
VALUES (1, '数据引擎', 'Data Engine 定时同步任务', 1, 1, unixepoch('now'), unixepoch('now'), 1);
|
||||
|
||||
-- ============================================================================
|
||||
-- 定时任务(仅导出 status >= 0 的有效任务)
|
||||
-- ============================================================================
|
||||
|
||||
-- 数据引擎 - 补偿扫描 (id=46, status=0 暂停中)
|
||||
INSERT OR IGNORE INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES (
|
||||
46, 1, '', 0, '数据引擎-补偿扫描', '',
|
||||
'0 */5 * * * *', 0, 'http', '',
|
||||
'http://host.docker.internal:3013/sync/ctrl/compensate', 'POST',
|
||||
'{"Content-Type":"application/json"}', '',
|
||||
600, 0, 0, 0,
|
||||
0, 0, 0, '',
|
||||
1, 1, unixepoch('now'), unixepoch('now')
|
||||
);
|
||||
|
||||
-- 腾讯广告 - 账户列表 (id=48, status=0 暂停中)
|
||||
INSERT OR IGNORE INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES (
|
||||
48, 1, '', 0, '腾讯广告-账户列表(account_relation)', '',
|
||||
'0 0 */6 * * *', 0, 'http', '',
|
||||
'http://host.docker.internal:3013/sync/ctrl/trigger', 'POST',
|
||||
'{"Content-Type":"application/json"}',
|
||||
'{"platformCode":"tencent","interfaceCode":"account_relation","fullSync":false}',
|
||||
1800, 0, 0, 0,
|
||||
0, 0, 0, '',
|
||||
1, 1, unixepoch('now'), unixepoch('now')
|
||||
);
|
||||
|
||||
-- 腾讯广告 - 图片素材 (id=49, status=0 暂停中)
|
||||
INSERT OR IGNORE INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES (
|
||||
49, 1, '', 0, '腾讯广告-图片素材(image)', '',
|
||||
'0 0 * * * *', 0, 'http', '',
|
||||
'http://host.docker.internal:3013/sync/ctrl/trigger', 'POST',
|
||||
'{"Content-Type":"application/json"}',
|
||||
'{"platformCode":"tencent","interfaceCode":"image","fullSync":false}',
|
||||
3600, 0, 0, 0,
|
||||
0, 0, 0, '',
|
||||
1, 1, unixepoch('now'), unixepoch('now')
|
||||
);
|
||||
|
||||
-- 腾讯广告 - 视频素材 (id=50, status=0 暂停中)
|
||||
INSERT OR IGNORE INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES (
|
||||
50, 1, '', 0, '腾讯广告-视频素材(video)', '',
|
||||
'0 0 * * * *', 0, 'http', '',
|
||||
'http://host.docker.internal:3013/sync/ctrl/trigger', 'POST',
|
||||
'{"Content-Type":"application/json"}',
|
||||
'{"platformCode":"tencent","interfaceCode":"video","fullSync":false}',
|
||||
3600, 0, 0, 0,
|
||||
0, 0, 0, '',
|
||||
1, 1, unixepoch('now'), unixepoch('now')
|
||||
);
|
||||
|
||||
-- 腾讯广告 - 音频素材 (id=51, status=0 暂停中)
|
||||
INSERT OR IGNORE INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES (
|
||||
51, 1, '', 0, '腾讯广告-音频素材(audio)', '',
|
||||
'0 0 */6 * * *', 0, 'http', '',
|
||||
'http://host.docker.internal:3013/sync/ctrl/trigger', 'POST',
|
||||
'{"Content-Type":"application/json"}',
|
||||
'{"platformCode":"tencent","interfaceCode":"audio","fullSync":false}',
|
||||
1800, 0, 0, 0,
|
||||
0, 0, 0, '',
|
||||
1, 1, unixepoch('now'), unixepoch('now')
|
||||
);
|
||||
|
||||
-- 腾讯广告 - Token刷新 (id=52, status=1 运行中)
|
||||
INSERT OR IGNORE INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES (
|
||||
52, 1, '', 0, '腾讯广告-Token刷新', '',
|
||||
'0 0 3 * * *', 0, 'http', '',
|
||||
'http://host.docker.internal:3013/sync/ctrl/refreshToken', 'POST',
|
||||
'{"Content-Type":"application/json"}',
|
||||
'{"platformCode":"tencent"}',
|
||||
0, 0, 0, 1,
|
||||
0, 0, 0, '',
|
||||
1, 1, unixepoch('now'), unixepoch('now')
|
||||
);
|
||||
|
||||
-- CID - 图片批量送检 (id=53, status=0 暂停中)
|
||||
INSERT OR IGNORE INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES (
|
||||
53, 1, '', 0, 'CID - 图片批量送检', '自动扫描待校验图片并提交到易盾检测',
|
||||
'0/30 * * * * *', 0, 'http', '',
|
||||
'http://host.docker.internal:3001/material/verify/controller/batch-verify-image', 'POST',
|
||||
'{"Content-Type":"application/json"}',
|
||||
'{limit:10}',
|
||||
120, 0, 0, 0,
|
||||
0, 0, 0, '',
|
||||
1, 1, unixepoch('now'), unixepoch('now')
|
||||
);
|
||||
|
||||
-- CID - 视频批量送检 (id=54, status=0 暂停中)
|
||||
INSERT OR IGNORE INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES (
|
||||
54, 1, '', 0, 'CID - 视频批量送检', '自动扫描待校验视频并提交到易盾检测',
|
||||
'15/30 * * * * *', 0, 'http', '',
|
||||
'http://host.docker.internal:3001/material/verify/controller/batch-verify-video', 'POST',
|
||||
'{"Content-Type":"application/json"}',
|
||||
'{limit:10}',
|
||||
120, 0, 0, 0,
|
||||
0, 0, 0, '',
|
||||
1, 1, unixepoch('now'), unixepoch('now')
|
||||
);
|
||||
|
||||
-- CID - 检测结果轮询 (id=55, status=1 运行中)
|
||||
INSERT OR IGNORE INTO pp_task(
|
||||
id, group_id, server_ids, server_type, task_name, description, cron_spec,
|
||||
concurrent, task_type, command, url, method, headers, body,
|
||||
timeout, execute_times, prev_time, status,
|
||||
is_notify, notify_type, notify_tpl_id, notify_user_ids,
|
||||
create_id, update_id, create_time, update_time
|
||||
) VALUES (
|
||||
55, 1, '', 0, 'CID - 检测结果轮询', '自动查询易盾检测结果并更新到素材表',
|
||||
'0 * * * * *', 0, 'http', '',
|
||||
'http://host.docker.internal:3001/yidun/callback/controller/poll-all-results', 'POST',
|
||||
'{}', '',
|
||||
120, 0, 0, 1,
|
||||
0, 0, 0, '',
|
||||
1, 1, unixepoch('now'), unixepoch('now')
|
||||
);
|
||||
Reference in New Issue
Block a user