Compare commits

...

14 Commits

Author SHA1 Message Date
becomer f706968618 fix: force-remove temp container in cleanup step
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/build Pipeline was successful
2026-07-01 12:49:10 +08:00
becomer 69e198f745 fix: use wget instead of curl for health check
ci/woodpecker/push/build Pipeline was successful
The 'docker' CLI image does not include curl binary,
causing the health check to always fail. Switch to wget.
2026-07-01 12:47:05 +08:00
becomer 70f907d909 fix: replace shell ${VAR} with $VAR to avoid Woodpecker pre-processing
ci/woodpecker/push/build Pipeline failed
Woodpecker interprets ${...} as its own template syntax and strips
unrecognized variables. Using $VAR (no braces) passes through to bash.
2026-07-01 12:43:56 +08:00
becomer 3a3d17e9b4 feat: blue-green zero-downtime deploy via 1Panel
ci/woodpecker/push/build Pipeline failed
- Set up 1Panel website tianshen.eu.cc (HTTP 80, ready for HTTPS)
- /api/* → likedream-go with prefix stripping
- /* → likedream-web (Caddy, port 3080)
- Blue-green deployment: start on standby port → switch proxy → stop old → switch back
- Nginx reload is hot (zero connection drops)
- Detects current active port from proxy config automatically
2026-07-01 12:40:01 +08:00
becomer d560c9f678 fix: override clone remote to local Gitea URL
ci/woodpecker/push/build Pipeline is running
ci/woodpecker/tag/build Pipeline was successful
Add clone.git.settings.remote in .woodpecker/build.yml to use
fast local clone (http://10.10.10.128:3000) instead of external
Cloudflare-proxied URL (https://git.xsvb.cn) for tag builds.
2026-07-01 01:09:16 +08:00
becomer 7a9b49794c chore: retry with correct clone URL
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/build Pipeline is running
2026-07-01 00:56:01 +08:00
becomer e3f8dc8aac fix: set GOPROXY to goproxy.cn for Go module downloads
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/tag/build Pipeline is running
proxy.golang.org is not reachable from this server; use goproxy.cn mirror.
2026-07-01 00:39:04 +08:00
becomer 1228c30a75 chore: trigger ci with new forge url config
ci/woodpecker/push/build Pipeline failed
2026-07-01 00:30:25 +08:00
becomer 4762e71308 fix: replace tags filter with branch filter in when clause
ci/woodpecker/push/build Pipeline is running
ci/woodpecker/tag/build Pipeline was canceled
Woodpecker v3 does not support `tags` in the when clause; use `branch`
with glob patterns instead for matching tag events.
2026-07-01 00:02:44 +08:00
becomer 2082ee3e68 ci: support tag-triggered pipelines with versioned Docker images
- Allow triggering on v* tags in addition to pushes to main/master
- Tag Docker images with CI_COMMIT_TAG when triggered by a tag
2026-07-01 00:01:56 +08:00
becomer 10cb7d7ac6 ci: 添加 Woodpecker CI/CD 流水线
- 构建 Docker 镜像并推送
- 自动部署到本地 Docker
- 配置文件和网络管理
2026-06-30 23:51:01 +08:00
sugarBecomer 261153067c ✨ 后端支持统计返回任务ID + 列表按ID筛选
1. TaskStatisticsV2Item 新增 successTaskIds/failTaskIds/pendingTaskIds 字段
2. GetTaskStatisticsV2 返回每日对应的任务ID列表
3. CpTaskListReq 新增 ids 参数,List 方法支持按ID列表筛选
2026-04-24 12:32:54 +08:00
sugarBecomer 19a13a0ad2 ✨ 后端新增修改任务标题接口
新增 PUT /task/title/{id} 接口,支持修改今日校园任务标题
2026-04-23 10:41:40 +08:00
sugarBecomer 5d7f79a619 🐛 修改域名 2026-04-07 14:37:12 +08:00
9 changed files with 206 additions and 11 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ permissions:
on: [release]
env:
IMAGE: RecordingTheSmile/likedream-go
REGISTRY: git.likedream.xyz
REGISTRY: mycoder.cc.cd
jobs:
Build-And-Upload:
runs-on: ubuntu-latest
+130
View File
@@ -0,0 +1,130 @@
when:
- event: push
branch: [main, master]
- event: tag
branch: [v*]
clone:
git:
image: woodpeckerci/plugin-git
settings:
remote: http://10.10.10.128:3000/becomer/likedream.git
steps:
build-image:
image: docker
commands:
- docker build -t likedream-go:latest .
- docker tag likedream-go:latest likedream-go:${CI_COMMIT_TAG:-${CI_COMMIT_SHA:0:8}}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
image: docker
commands: |
# ============================================================
# 蓝绿部署:零宕机切换 likedream-go 容器
# 判断当前代理使用的端口,另一个作为备用
# ============================================================
set -e
IMAGE_TAG="$CI_COMMIT_TAG"
if [ -z "$IMAGE_TAG" ]; then IMAGE_TAG="latest"; fi
# 确保网络存在
docker network create likedream-net 2>/dev/null || true
# 读取 1Panel 当前代理端口
CURRENT_PORT=$(docker exec 1Panel-openresty-ESmn \
grep -o 'proxy_pass http://10.10.10.128:[0-9]\+' \
/www/sites/tianshen/proxy/api.conf | grep -o '[0-9]\+$')
echo "=== 当前 1Panel 反代端口: $CURRENT_PORT ==="
if [ "$CURRENT_PORT" = "8091" ]; then
PORT_ACTIVE=8091
PORT_STANDBY=8092
elif [ "$CURRENT_PORT" = "8092" ]; then
PORT_ACTIVE=8092
PORT_STANDBY=8091
else
echo "未知当前端口 $CURRENT_PORT,默认使用 8091/8092"
PORT_ACTIVE=8091
PORT_STANDBY=8092
fi
echo " 活跃: $PORT_ACTIVE | 备用: $PORT_STANDBY"
# 清理之前可能残留的临时容器
docker rm -f likedream-go-new 2>/dev/null || true
echo "=== 蓝绿部署: 启动新容器 on :$PORT_STANDBY (image: $IMAGE_TAG) ==="
docker run -d --name likedream-go-new \
--restart unless-stopped \
--network likedream-net \
-p $PORT_STANDBY:8091 \
-v /opt/likedream-go/config.toml:/app/config.toml:ro \
likedream-go:$IMAGE_TAG
# 健康检查:等待新容器就绪
HEALTHY=false
for i in $(seq 1 15); do
if wget -q --timeout=3 -O /dev/null http://10.10.10.128:$PORT_STANDBY/health 2>/dev/null; then
HEALTHY=true
break
fi
echo " 等待新容器就绪... ($i/15)"
sleep 2
done
if [ "$HEALTHY" = false ]; then
echo "ERROR: 新容器未通过健康检查,回滚"
docker stop likedream-go-new
docker rm likedream-go-new
exit 1
fi
echo " 新容器已就绪 ✓"
# 切换 1Panel 代理到备用端口
echo "=== 切换 1Panel 反代 :$PORT_ACTIVE → :$PORT_STANDBY ==="
docker exec 1Panel-openresty-ESmn \
sed -i "s|:$PORT_ACTIVE|:$PORT_STANDBY|g" \
/www/sites/tianshen/proxy/api.conf
docker exec 1Panel-openresty-ESmn nginx -s reload
echo " Nginx 热加载完成(零中断)✓"
# 停止旧容器
echo "=== 清理旧容器 ==="
docker stop likedream-go 2>/dev/null || true
docker rm likedream-go 2>/dev/null || true
# 在活跃端口启动正式容器
echo "=== 启动正式容器 on :$PORT_ACTIVE ==="
docker run -d --name likedream-go \
--restart unless-stopped \
--network likedream-net \
-p $PORT_ACTIVE:8091 \
-v /opt/likedream-go/config.toml:/app/config.toml:ro \
likedream-go:$IMAGE_TAG
# 连接到验证码服务网络
docker network connect ocr-go_default likedream-go 2>/dev/null || true
# 切回 1Panel 代理到活跃端口
echo "=== 切回 1Panel 反代 :$PORT_STANDBY → :$PORT_ACTIVE ==="
docker exec 1Panel-openresty-ESmn \
sed -i "s|:$PORT_STANDBY|:$PORT_ACTIVE|g" \
/www/sites/tianshen/proxy/api.conf
docker exec 1Panel-openresty-ESmn nginx -s reload
echo " Nginx 热加载完成(零中断)✓"
# 清理临时容器和旧镜像
echo "=== 清理 ==="
docker rm -f likedream-go-new 2>/dev/null || true
# 保留 latest 和最近 3 个 tag 镜像
docker images likedream-go --format '{{.Tag}}' | grep -v latest | head -n -3 | while read tag; do
docker rmi likedream-go:$tag 2>/dev/null || true
done
echo "=== 部署完成 ✓ ==="
volumes:
- /var/run/docker.sock:/var/run/docker.sock
+1
View File
@@ -1,5 +1,6 @@
FROM golang:1.25-alpine AS backend-builder
WORKDIR /build
ENV GOPROXY=https://goproxy.cn,direct
COPY . .
RUN go build -ldflags="-s -w" -o bin/server
+18
View File
@@ -0,0 +1,18 @@
services:
likedream-go:
image: likedream-go:latest
container_name: likedream-go
restart: unless-stopped
ports:
- "8091:8091"
volumes:
- /opt/likedream-go/config.toml:/app/config.toml:ro
networks:
- likedream-net
- ocr-go_default
networks:
likedream-net:
external: true
ocr-go_default:
external: true
+7 -4
View File
@@ -9,10 +9,13 @@ type TaskStatisticsItem struct {
// TaskStatisticsV2Item 每日任务统计项(新版:从日志表按任务id去重)
type TaskStatisticsV2Item struct {
Date string `json:"date"` // 日期 格式:2006-01-02
SuccessTask int `json:"successTask"` // 成功任务数(任务id去重)
FailTask int `json:"failTask"` // 失败任务数(任务id去重)
PendingTask int `json:"pendingTask"` // 待执行任务数(仅当天)
Date string `json:"date"` // 日期 格式:2006-01-02
SuccessTask int `json:"successTask"` // 成功任务数(任务id去重)
FailTask int `json:"failTask"` // 失败任务数(任务id去重)
PendingTask int `json:"pendingTask"` // 待执行任务数(仅当天)
SuccessTaskIds []int64 `json:"successTaskIds,omitempty"` // 成功任务ID列表
FailTaskIds []int64 `json:"failTaskIds,omitempty"` // 失败任务ID列表
PendingTaskIds []int64 `json:"pendingTaskIds,omitempty"` // 待执行任务ID列表(仅当天)
}
// DashboardSummary 首页概览数据(新版)
+7
View File
@@ -24,6 +24,7 @@ type CpTaskListReq struct {
TaskType cptask.TaskType `json:"type"`
Username string `json:"username"`
TaskStatus cptask.TaskStatus `json:"task_status"`
Ids string `json:"ids"`
Page int `json:"page" d:"1"`
Size int `json:"size" d:"10"`
}
@@ -54,6 +55,12 @@ type PostCpTaskExecuteReq struct {
ExecuteDays []int `json:"execute_days"`
}
type UpdateTaskTitleReq struct {
g.Meta `path:"/task/title/{id}" method:"put" tags:"CpTask" summary:"修改任务标题"`
Id int64 `json:"id"`
Title string `json:"title" v:"required#标题不能为空"`
}
type DeleteTaskReq struct {
g.Meta `path:"/task/{id}" method:"delete" tags:"CpTask" summary:"删除任务"`
Id int64 `json:"id"`
@@ -56,6 +56,11 @@ func (c CpTaskCtrlV1) Delete(ctx context.Context, req *v1.DeleteTaskReq) (res *u
return service.Global.CpTask.Delete(ctx, req), nil
}
func (c CpTaskCtrlV1) UpdateTaskTitle(ctx context.Context, req *v1.UpdateTaskTitleReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.CpTask.UpdateTaskTitle(ctx, req), nil
}
func (c CpTaskCtrlV1) ExecuteNow(ctx context.Context, req *v1.ExecuteNowReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.CpTask.ExecuteNow(ctx, req), nil
@@ -19,6 +19,7 @@ type ICpTaskCtrlV1 interface {
ExecuteNow(ctx context.Context, req *v1.ExecuteNowReq) (res *utils.Response[any], err error)
PauseTask(ctx context.Context, req *v1.PauseTaskReq) (res *utils.Response[any], err error)
FindLocation(ctx context.Context, req *v1.FindLocationReq) (res *utils.Response[any], err error)
UpdateTaskTitle(ctx context.Context, req *v1.UpdateTaskTitleReq) (res *utils.Response[any], err error)
GetTaskStatistics(ctx context.Context, req *v1.GetTaskStatisticsReq) (res *utils.Response[[]*v1.TaskStatisticsItem], err error)
GetTaskStatisticsV2(ctx context.Context, req *v1.GetTaskStatisticsV2Req) (res *utils.Response[[]*v1.TaskStatisticsV2Item], err error)
GetDashboardSummary(ctx context.Context, req *v1.GetDashboardSummaryReq) (res *utils.Response[*v1.DashboardSummary], err error)
+36 -6
View File
@@ -22,6 +22,8 @@ import (
"likedream-goframe/internal/framework/schedule"
"likedream-goframe/internal/service/balance"
"likedream-goframe/internal/utils"
"strconv"
"strings"
"time"
"entgo.io/ent/dialect/sql"
@@ -88,6 +90,18 @@ func (v CpTaskServiceV1) List(ctx context.Context, req *v1.CpTaskListReq) *utils
if req.TaskStatus != "" {
query.Where(cptask.TaskStatusEQ(req.TaskStatus))
}
if req.Ids != "" {
var ids []int64
for _, s := range strings.Split(req.Ids, ",") {
s = strings.TrimSpace(s)
if id, err := strconv.ParseInt(s, 10, 64); err == nil {
ids = append(ids, id)
}
}
if len(ids) > 0 {
query.Where(cptask.IDIn(ids...))
}
}
total := query.CountX(ctx)
var data []*ent.CpTask
if total > 0 {
@@ -216,6 +230,18 @@ func (v CpTaskServiceV1) PostExecute(ctx context.Context, req *v1.PostCpTaskExec
return utils.SuccessWithMsg("更新成功")
}
func (v CpTaskServiceV1) UpdateTaskTitle(ctx context.Context, req *v1.UpdateTaskTitleReq) *utils.Response[any] {
user := utils.GetUserInfoFromCtx(ctx)
cpTask := db2.Global.CpTask.Query().
Where(cptask.ID(req.Id), cptask.HasSysUserWith(sysuser.ID(user.ID))).
OnlyX(ctx)
if cpTask == nil {
utils.OkOrPanic(errors2.NewBusinessError(404, "任务不存在"))
}
db2.Global.CpTask.UpdateOne(cpTask).SetTitle(req.Title).ExecX(ctx)
return utils.SuccessWithMsg("修改成功")
}
func (v CpTaskServiceV1) Delete(ctx context.Context, req *v1.DeleteTaskReq) *utils.Response[any] {
user := utils.GetUserInfoFromCtx(ctx)
// 检测是否属于自己的cpTask
@@ -397,14 +423,16 @@ func (v CpTaskServiceV1) GetTaskStatisticsV2(ctx context.Context, _ *v1.GetTaskS
ScanX(ctx, &failIds)
item := &v1.TaskStatisticsV2Item{
Date: dateStr,
SuccessTask: len(successIds),
FailTask: len(failIds),
Date: dateStr,
SuccessTask: len(successIds),
FailTask: len(failIds),
SuccessTaskIds: successIds,
FailTaskIds: failIds,
}
// 仅当天查待执行任务数
// 仅当天查待执行任务数及ID列表
if startOfDay.Equal(today) {
item.PendingTask = db2.Global.CpTask.Query().
pendingIds := db2.Global.CpTask.Query().
Where(
cptask.HasSysUserWith(sysuser.ID(user.ID)),
cptask.TaskStatusNEQ(cptask.TaskStatusPause),
@@ -413,7 +441,9 @@ func (v CpTaskServiceV1) GetTaskStatisticsV2(ctx context.Context, _ *v1.GetTaskS
Where(func(s *sql.Selector) {
s.Where(sql.ExprP("frequency > success_count"))
}).
CountX(ctx)
IDsX(ctx)
item.PendingTask = len(pendingIds)
item.PendingTaskIds = pendingIds
}
statistics[6-i] = item