Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ede964c4c | |||
| 4b50af3ae0 | |||
| 65293c4f81 | |||
| a41341e732 | |||
| ecfa50359b | |||
| 2879336ce4 | |||
| f706968618 | |||
| 69e198f745 | |||
| 70f907d909 | |||
| 3a3d17e9b4 | |||
| d560c9f678 | |||
| 7a9b49794c | |||
| e3f8dc8aac | |||
| 1228c30a75 | |||
| 4762e71308 | |||
| 2082ee3e68 | |||
| 10cb7d7ac6 | |||
| 261153067c | |||
| 19a13a0ad2 | |||
| 5d7f79a619 | |||
| 0934994f51 | |||
| ef61d405ba |
@@ -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
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
when:
|
||||
- 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
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
FROM golang:1.24-alpine AS backend-builder
|
||||
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
|
||||
|
||||
+6
-4
@@ -20,8 +20,9 @@ key = "sk-acacac439b346036df45e90c85c177c375568ef72c469e49"
|
||||
[database]
|
||||
type = "mysql"
|
||||
dsn = "test_likedream_go:EpXckXmxawDEQGxJ@tcp(database.out:3306)/test_likedream_go?parseTime=true&loc=Asia%2FShanghai"
|
||||
dsn2 = "host=database.out user=dev password=JPmKjmpcaG3EYAJZ dbname=dev port=5432 sslmode=disable TimeZone=Asia/Shanghai"
|
||||
# host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai
|
||||
dsn2 = "host=pg.out user=dev password=JPmKjmpcaG3EYAJZ dbname=dev port=5432 sslmode=disable TimeZone=Asia/Shanghai"
|
||||
#type = "postgres"
|
||||
#dsn = "host=pg.out user=dev password=JPmKjmpcaG3EYAJZ dbname=dev port=5432 sslmode=disable TimeZone=Asia/Shanghai"
|
||||
[server]
|
||||
port = 8091
|
||||
url = "https://devocr.likedream.xyz"
|
||||
@@ -35,8 +36,9 @@ balanceMigrateUrl = "http://localhost:3200/balance-migrate/"
|
||||
mode = "dev"
|
||||
|
||||
[captcha]
|
||||
baseUrl = "http://captcha-resolver.out"
|
||||
#baseUrl = "http://localhost:8890"
|
||||
#baseUrl = "http://captcha-resolver.out"
|
||||
baseUrl = "http://likedream-go-captcha:8890"
|
||||
apiKey = "baca122..."
|
||||
[redis]
|
||||
url = "redis://:jhkdjhkjdhsIUTYURTU_pEGmFP@database.out:56379"
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 首页概览数据(新版)
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -16,13 +16,14 @@ func ApiSemiOCRVerification(bytes []byte, shape1, shape2 int64) string {
|
||||
data := Bytes2Base64(bytes)
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/semiOcr"
|
||||
apiKey := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaApiKey).String()
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"base64": data,
|
||||
"shape1": shape1,
|
||||
"shape2": shape2,
|
||||
}).Post(baseUrl)
|
||||
}).SetHeader("X-Api-Key", apiKey).Post(baseUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("SemiOCRVerification", baseUrl).
|
||||
@@ -37,11 +38,12 @@ func ApiAutoOCRVerification(bytes []byte) string {
|
||||
data := Bytes2Base64(bytes)
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/ocr"
|
||||
apiKey := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaApiKey).String()
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"base64": data,
|
||||
}).Post(baseUrl)
|
||||
}).SetHeader("X-Api-Key", apiKey).Post(baseUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("AutoOCRVerification", baseUrl).
|
||||
@@ -55,11 +57,12 @@ func ApiAutoOCRVerification(bytes []byte) string {
|
||||
func ApiAutoCalcVerification(b64 string) string {
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/calc"
|
||||
apiKey := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaApiKey).String()
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"base64": b64,
|
||||
}).Post(baseUrl)
|
||||
}).SetHeader("X-Api-Key", apiKey).Post(baseUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("AutoOCRVerification", baseUrl).
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/google/uuid"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/match"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
@@ -226,20 +225,26 @@ func (c *CpdailyAutoSignTask) QueryFormByTitle(ctx context.Context, titleGlob st
|
||||
|
||||
// 尝试从已签到的历史任务中获取历史数据用于预填充
|
||||
signedMatch := findMatchTitle(signedTasks)
|
||||
if signedMatch != nil && (signedMatch["signInstanceWid"] == nil || matchTask["signInstanceWid"] == nil || signedMatch["signInstanceWid"].String() != matchTask["signInstanceWid"].String()) {
|
||||
histResp, histErr := c.client.R().
|
||||
SetBody(map[string]*gvar.Var{
|
||||
"taskName": gvar.New(signedMatch["taskName"].String()),
|
||||
"signWid": signedMatch["signWid"],
|
||||
"signInstanceWid": signedMatch["signInstanceWid"],
|
||||
}).
|
||||
WithContext(ctx).
|
||||
Post(c.getReqUrl("detailSignInstance"))
|
||||
if histErr == nil {
|
||||
histJSON := utils.ReadJson[map[string]*gvar.Var](histResp.Body)
|
||||
histDatas := histJSON["datas"].MapStrVar()
|
||||
if len(histDatas) > 0 {
|
||||
result["historyRows"] = gvar.New(histDatas)
|
||||
if signedMatch != nil {
|
||||
if matchTask != nil && signedMatch["signInstanceWid"] != nil && matchTask["signInstanceWid"] != nil &&
|
||||
signedMatch["signInstanceWid"].String() == matchTask["signInstanceWid"].String() {
|
||||
// 任务已签到(同一个 instanceWid),自身 rows 中的 longitude/latitude/position 就是用户自己提交的数据
|
||||
result["historyRows"] = gvar.New(formDatas)
|
||||
} else {
|
||||
histResp, histErr := c.client.R().
|
||||
SetBody(map[string]*gvar.Var{
|
||||
"taskName": gvar.New(signedMatch["taskName"].String()),
|
||||
"signWid": signedMatch["signWid"],
|
||||
"signInstanceWid": signedMatch["signInstanceWid"],
|
||||
}).
|
||||
WithContext(ctx).
|
||||
Post(c.getReqUrl("detailSignInstance"))
|
||||
if histErr == nil {
|
||||
histJSON := utils.ReadJson[map[string]*gvar.Var](histResp.Body)
|
||||
histDatas := histJSON["datas"].MapStrVar()
|
||||
if len(histDatas) > 0 {
|
||||
result["historyRows"] = gvar.New(histDatas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,9 +441,11 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
|
||||
// 优先从 historyRows(已签到的历史任务详情)中提取历史值,否则从 rows 中提取
|
||||
histRows := rows
|
||||
hasHistory := false
|
||||
if hr := rawJson["historyRows"]; hr != nil {
|
||||
if hrMap := hr.MapStrVar(); len(hrMap) > 0 {
|
||||
histRows = hrMap
|
||||
hasHistory = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,47 +457,35 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
histSignPhotoUrl := ""
|
||||
histIsMalposition := ""
|
||||
|
||||
// 优先从 signPlaceSelected 提取经纬度和地址
|
||||
if sps := histRows["signPlaceSelected"]; sps != nil && sps.String() != "" && sps.String() != "[]" {
|
||||
placeArr := gjson.Parse(sps.String()).Array()
|
||||
if len(placeArr) > 0 {
|
||||
place := placeArr[0]
|
||||
lng := place.Get("longitude").String()
|
||||
lat := place.Get("latitude").String()
|
||||
addr := place.Get("address").String()
|
||||
if lng != "" && lat != "" {
|
||||
histLatAndLon = lng + "," + lat
|
||||
}
|
||||
if addr != "" {
|
||||
histAddress = addr
|
||||
}
|
||||
}
|
||||
}
|
||||
if histLatAndLon == "" {
|
||||
// 只有有真实的已签到历史数据时,才从历史数据中提取地址和经纬度
|
||||
// 否则 rows 中包含的是管理员圈定的范围配置数据,不是用户实际签到地址
|
||||
// signPlaceSelected 始终是管理员圈定范围,不应使用
|
||||
if hasHistory {
|
||||
// 先从 longitude/latitude/position/signAddress 提取
|
||||
if lon := histRows["longitude"]; lon != nil && lon.String() != "" {
|
||||
if lat := histRows["latitude"]; lat != nil && lat.String() != "" {
|
||||
histLatAndLon = lon.String() + "," + lat.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
if histAddress == "" {
|
||||
if pos := histRows["position"]; pos != nil && pos.String() != "" {
|
||||
histAddress = pos.String()
|
||||
if histAddress == "" {
|
||||
if pos := histRows["position"]; pos != nil && pos.String() != "" {
|
||||
histAddress = pos.String()
|
||||
}
|
||||
}
|
||||
if histAddress == "" {
|
||||
if addr := histRows["signAddress"]; addr != nil && addr.String() != "" {
|
||||
histAddress = addr.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
if photo := histRows["signPhotoUrl"]; photo != nil && photo.String() != "" {
|
||||
histSignPhotoUrl = photo.String()
|
||||
}
|
||||
if mal := histRows["isMalposition"]; mal != nil {
|
||||
if mal.Int() == 1 {
|
||||
histIsMalposition = "是"
|
||||
} else {
|
||||
histIsMalposition = "否"
|
||||
if photo := histRows["signPhotoUrl"]; photo != nil && photo.String() != "" {
|
||||
histSignPhotoUrl = photo.String()
|
||||
}
|
||||
if mal := histRows["isMalposition"]; mal != nil {
|
||||
if mal.Int() == 1 {
|
||||
histIsMalposition = "是"
|
||||
} else {
|
||||
histIsMalposition = "否"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +221,6 @@ func (c *CpdailySleepCheckTask) QueryFormByTitle(ctx context.Context, titleGlob
|
||||
signedTasks := datas["signedTasks"].Array()
|
||||
|
||||
var matchTask map[string]*gvar.Var
|
||||
isFromSigned := false
|
||||
|
||||
matchTask = findMatchTitle(unSignedTasks)
|
||||
if matchTask == nil {
|
||||
@@ -229,9 +228,6 @@ func (c *CpdailySleepCheckTask) QueryFormByTitle(ctx context.Context, titleGlob
|
||||
|
||||
if matchTask == nil {
|
||||
matchTask = findMatchTitle(signedTasks)
|
||||
if matchTask != nil {
|
||||
isFromSigned = true
|
||||
}
|
||||
|
||||
if matchTask == nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("没有查询到查寝任务: %s", titleGlob)), func(err error) error {
|
||||
@@ -281,11 +277,15 @@ func (c *CpdailySleepCheckTask) QueryFormByTitle(ctx context.Context, titleGlob
|
||||
result["taskInfo"] = gvar.New(matchTask)
|
||||
result["rows"] = gvar.New(datas)
|
||||
|
||||
// 如果当前任务不是已签到的,尝试从已签到的历史任务中获取历史数据用于预填充
|
||||
if !isFromSigned {
|
||||
// 先看今天有没有已签到的同名任务
|
||||
signedMatch := findMatchTitle(signedTasks)
|
||||
if signedMatch != nil {
|
||||
// 尝试从已签到的历史任务中获取历史数据用于预填充
|
||||
// 无论当前任务是否已签到,都尝试获取历史数据
|
||||
signedMatch := findMatchTitle(signedTasks)
|
||||
if signedMatch != nil {
|
||||
if matchTask != nil && signedMatch["signInstanceWid"] != nil && matchTask["signInstanceWid"] != nil &&
|
||||
signedMatch["signInstanceWid"].String() == matchTask["signInstanceWid"].String() {
|
||||
// 任务已签到(同一个 instanceWid),自身 rows 中的 longitude/latitude/position 就是用户自己提交的数据
|
||||
result["historyRows"] = gvar.New(datas)
|
||||
} else {
|
||||
histResp, histErr := c.client.R().
|
||||
SetBody(map[string]*gvar.Var{
|
||||
"taskName": gvar.New(signedMatch["taskName"].String()),
|
||||
@@ -302,63 +302,63 @@ func (c *CpdailySleepCheckTask) QueryFormByTitle(ctx context.Context, titleGlob
|
||||
}
|
||||
}
|
||||
}
|
||||
// 今天没有已签到的同名任务,从历史月份中只查 signedTasks
|
||||
if result["historyRows"] == nil {
|
||||
func() {
|
||||
defer func() { recover() }()
|
||||
now := time.Now().In(cst.TimeZoneShangHai)
|
||||
year := strconv.Itoa(now.Year())
|
||||
month := strconv.Itoa(int(now.Month()))
|
||||
if len(month) == 1 {
|
||||
month = "0" + month
|
||||
}
|
||||
resp, e := c.client.R().
|
||||
SetBody(map[string]any{
|
||||
"statisticYearMonth": fmt.Sprintf("%s-%s", year, month),
|
||||
}).
|
||||
WithContext(ctx).
|
||||
Post(c.getReqUrl("getStuSignInfosByWeekMonth"))
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
respJSON := utils.ReadJson[map[string]*gvar.Var](resp.Body)
|
||||
monthDatas := respJSON["datas"].MapStrVar()
|
||||
monthRows := monthDatas["rows"].Vars()
|
||||
}
|
||||
// 今天没有已签到的同名任务,从历史月份中只查 signedTasks
|
||||
if result["historyRows"] == nil {
|
||||
func() {
|
||||
defer func() { recover() }()
|
||||
now := time.Now().In(cst.TimeZoneShangHai)
|
||||
year := strconv.Itoa(now.Year())
|
||||
month := strconv.Itoa(int(now.Month()))
|
||||
if len(month) == 1 {
|
||||
month = "0" + month
|
||||
}
|
||||
resp, e := c.client.R().
|
||||
SetBody(map[string]any{
|
||||
"statisticYearMonth": fmt.Sprintf("%s-%s", year, month),
|
||||
}).
|
||||
WithContext(ctx).
|
||||
Post(c.getReqUrl("getStuSignInfosByWeekMonth"))
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
respJSON := utils.ReadJson[map[string]*gvar.Var](resp.Body)
|
||||
monthDatas := respJSON["datas"].MapStrVar()
|
||||
monthRows := monthDatas["rows"].Vars()
|
||||
|
||||
// 只从 signedTasks 中查找匹配的已签到任务
|
||||
for _, row := range monthRows {
|
||||
rowMap := row.MapStrVar()
|
||||
signed := rowMap["signedTasks"].Vars()
|
||||
for _, st := range signed {
|
||||
item := st.MapStrVar()
|
||||
tn, exist := item["taskName"]
|
||||
if !exist || tn.String() == "" {
|
||||
// 只从 signedTasks 中查找匹配的已签到任务
|
||||
for _, row := range monthRows {
|
||||
rowMap := row.MapStrVar()
|
||||
signed := rowMap["signedTasks"].Vars()
|
||||
for _, st := range signed {
|
||||
item := st.MapStrVar()
|
||||
tn, exist := item["taskName"]
|
||||
if !exist || tn.String() == "" {
|
||||
continue
|
||||
}
|
||||
if match.Match(tn.String(), titleGlob) {
|
||||
// 找到已签到的历史任务,获取详情
|
||||
dr, de := c.client.R().
|
||||
SetBody(map[string]*gvar.Var{
|
||||
"signInstanceWid": item["signInstanceWid"],
|
||||
"signWid": item["signWid"],
|
||||
"taskName": gvar.New(tn.String()),
|
||||
}).
|
||||
WithContext(ctx).
|
||||
Post(c.getReqUrl("detailSignInstance"))
|
||||
if de != nil {
|
||||
continue
|
||||
}
|
||||
if match.Match(tn.String(), titleGlob) {
|
||||
// 找到已签到的历史任务,获取详情
|
||||
dr, de := c.client.R().
|
||||
SetBody(map[string]*gvar.Var{
|
||||
"signInstanceWid": item["signInstanceWid"],
|
||||
"signWid": item["signWid"],
|
||||
"taskName": gvar.New(tn.String()),
|
||||
}).
|
||||
WithContext(ctx).
|
||||
Post(c.getReqUrl("detailSignInstance"))
|
||||
if de != nil {
|
||||
continue
|
||||
}
|
||||
dj := utils.ReadJson[map[string]*gvar.Var](dr.Body)
|
||||
dd := dj["datas"].MapStrVar()
|
||||
if len(dd) > 0 {
|
||||
result["historyRows"] = gvar.New(dd)
|
||||
return
|
||||
}
|
||||
dj := utils.ReadJson[map[string]*gvar.Var](dr.Body)
|
||||
dd := dj["datas"].MapStrVar()
|
||||
if len(dd) > 0 {
|
||||
result["historyRows"] = gvar.New(dd)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -518,9 +518,11 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
|
||||
// 优先从 historyRows(已签到的历史任务详情)中提取历史值,否则从 rows 中提取
|
||||
histRows := rows
|
||||
hasHistory := false
|
||||
if hr := rawJson["historyRows"]; hr != nil {
|
||||
if hrMap := hr.MapStrVar(); len(hrMap) > 0 {
|
||||
histRows = hrMap
|
||||
hasHistory = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,48 +535,35 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
histSignPhotoUrl := ""
|
||||
histIsMalposition := ""
|
||||
|
||||
// 优先从 signPlaceSelected 提取经纬度和地址
|
||||
if sps := histRows["signPlaceSelected"]; sps != nil && sps.String() != "" && sps.String() != "[]" {
|
||||
placeArr := gjson.Parse(sps.String()).Array()
|
||||
if len(placeArr) > 0 {
|
||||
place := placeArr[0]
|
||||
lng := place.Get("longitude").String()
|
||||
lat := place.Get("latitude").String()
|
||||
addr := place.Get("address").String()
|
||||
if lng != "" && lat != "" {
|
||||
histLatAndLon = lng + "," + lat
|
||||
}
|
||||
if addr != "" {
|
||||
histAddress = addr
|
||||
}
|
||||
}
|
||||
}
|
||||
// 降级:直接从 longitude/latitude/position 字段取
|
||||
if histLatAndLon == "" {
|
||||
// 只有有真实的已签到历史数据时,才从历史数据中提取地址和经纬度
|
||||
// 否则 rows 中包含的是管理员圈定的范围配置数据,不是用户实际签到地址
|
||||
// signPlaceSelected 始终是管理员圈定范围,不应使用
|
||||
if hasHistory {
|
||||
// 先从 longitude/latitude/position/signAddress 提取
|
||||
if lon := histRows["longitude"]; lon != nil && lon.String() != "" {
|
||||
if lat := histRows["latitude"]; lat != nil && lat.String() != "" {
|
||||
histLatAndLon = lon.String() + "," + lat.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
if histAddress == "" {
|
||||
if pos := histRows["position"]; pos != nil && pos.String() != "" {
|
||||
histAddress = pos.String()
|
||||
if histAddress == "" {
|
||||
if pos := histRows["position"]; pos != nil && pos.String() != "" {
|
||||
histAddress = pos.String()
|
||||
}
|
||||
}
|
||||
if histAddress == "" {
|
||||
if addr := histRows["signAddress"]; addr != nil && addr.String() != "" {
|
||||
histAddress = addr.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
if photo := histRows["signPhotoUrl"]; photo != nil && photo.String() != "" {
|
||||
histSignPhotoUrl = photo.String()
|
||||
}
|
||||
if mal := histRows["isMalposition"]; mal != nil {
|
||||
if mal.Int() == 1 {
|
||||
histIsMalposition = "是"
|
||||
} else {
|
||||
histIsMalposition = "否"
|
||||
if photo := histRows["signPhotoUrl"]; photo != nil && photo.String() != "" {
|
||||
histSignPhotoUrl = photo.String()
|
||||
}
|
||||
if mal := histRows["isMalposition"]; mal != nil {
|
||||
if mal.Int() == 1 {
|
||||
histIsMalposition = "是"
|
||||
} else {
|
||||
histIsMalposition = "否"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
ConfigAuthorizationKey = "server.authorization"
|
||||
ConfigJwtKey = "server.jwt"
|
||||
ConfigCaptchaBaseUrl = "captcha.baseUrl"
|
||||
ConfigCaptchaApiKey = "captcha.apiKey"
|
||||
ConfigS3AccessKey = "s3.accessKey"
|
||||
ConfigS3SecretKey = "s3.secretKey"
|
||||
ConfigS3BaseUrl = "s3.baseUrl"
|
||||
|
||||
@@ -33,18 +33,22 @@ func InitDB() {
|
||||
Global = client
|
||||
dbDsn2, err := g.Cfg().Get(gctx.New(), cst.ConfigDatabaseDsn2)
|
||||
utils.OkOrPanic(err)
|
||||
GlobalGorm, err = gorm.Open(postgres.Open(dbDsn2.String()), &gorm.Config{})
|
||||
if err != nil {
|
||||
logger.Logger.Fatal("连接pg数据库失败: %v", zap.Error(err))
|
||||
// PG 数据库为可选配置,如果 dsn2 为空则跳过
|
||||
if dbDsn2.String() != "" {
|
||||
GlobalGorm, err = gorm.Open(postgres.Open(dbDsn2.String()), &gorm.Config{})
|
||||
if err != nil {
|
||||
logger.Logger.Warn("连接pg数据库失败, 跳过: %v", zap.Error(err))
|
||||
} else {
|
||||
// 可选:配置连接池
|
||||
sqlDB, err := GlobalGorm.DB()
|
||||
if err != nil {
|
||||
logger.Logger.Warn("获取pg数据库实例失败, 跳过: %v", zap.Error(err))
|
||||
} else {
|
||||
// 设置连接池参数
|
||||
sqlDB.SetMaxIdleConns(10) // 设置空闲连接池中连接的最大数量
|
||||
sqlDB.SetMaxOpenConns(100) // 设置打开数据库连接的最大数量
|
||||
sqlDB.SetConnMaxLifetime(0) // 设置连接可复用的最大时间(0表示永久)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 可选:配置连接池
|
||||
sqlDB, err := GlobalGorm.DB()
|
||||
if err != nil {
|
||||
logger.Logger.Fatal("获取数据库实例失败: %v", zap.Error(err))
|
||||
}
|
||||
|
||||
// 设置连接池参数
|
||||
sqlDB.SetMaxIdleConns(10) // 设置空闲连接池中连接的最大数量
|
||||
sqlDB.SetMaxOpenConns(100) // 设置打开数据库连接的最大数量
|
||||
sqlDB.SetConnMaxLifetime(0) // 设置连接可复用的最大时间(0表示永久)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -120,6 +120,43 @@ func NormalizeConfig(cfg EmailConfig) EmailConfig {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (s *EmailServiceV1) SaveAuthorizedOutlookConfig(ctx context.Context, cfg EmailConfig) error {
|
||||
cfg = NormalizeConfig(cfg)
|
||||
|
||||
configs := map[string]string{
|
||||
cst2.ConfigEmailAuthMode: AuthModeOutlookOAuth2,
|
||||
cst2.ConfigEmailFromEmail: cfg.FromEmail,
|
||||
cst2.ConfigEmailOAuthTenant: cfg.OAuthTenant,
|
||||
cst2.ConfigEmailOAuthClientID: cfg.OAuthClientID,
|
||||
cst2.ConfigEmailOAuthClientSecret: cfg.OAuthClientSecret,
|
||||
cst2.ConfigEmailOAuthRedirectURL: cfg.OAuthRedirectURL,
|
||||
cst2.ConfigEmailOAuthRefreshToken: cfg.OAuthRefreshToken,
|
||||
cst2.ConfigEmailOAuthScope: cfg.OAuthScope,
|
||||
}
|
||||
|
||||
for key, value := range configs {
|
||||
exists, err := db.Global.SysConfig.Query().Where(sysconfig.KeyEQ(key)).Exist(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
if _, err = db.Global.SysConfig.Update().Where(sysconfig.KeyEQ(key)).SetValue(value).Save(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = db.Global.SysConfig.Create().
|
||||
SetKey(key).
|
||||
SetValue(value).
|
||||
SetDescription("邮件配置").
|
||||
Save(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EmailServiceV1) ValidateConfig(cfg EmailConfig) error {
|
||||
cfg = NormalizeConfig(cfg)
|
||||
|
||||
|
||||
@@ -106,6 +106,14 @@ func (s *EmailServiceV1) ExchangeOutlookAuthorizeCode(ctx context.Context, state
|
||||
if token.RefreshToken == "" {
|
||||
return "", session.FrontendOrigin, fmt.Errorf("no refresh token returned, please ensure offline_access scope is granted")
|
||||
}
|
||||
|
||||
session.Config.AuthMode = AuthModeOutlookOAuth2
|
||||
session.Config.OAuthRedirectURL = session.CallbackURL
|
||||
session.Config.OAuthRefreshToken = token.RefreshToken
|
||||
if err = s.SaveAuthorizedOutlookConfig(ctx, session.Config); err != nil {
|
||||
return "", session.FrontendOrigin, err
|
||||
}
|
||||
|
||||
return token.RefreshToken, session.FrontendOrigin, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -270,6 +270,9 @@ func (s *UserInfoServiceV1) RequestBalanceMigrate(ctx context.Context, req *v1.R
|
||||
}
|
||||
|
||||
// 查询旧系统数据库中是否存在该邮箱的用户
|
||||
if db.GlobalGorm == nil {
|
||||
return utils.SuccessWithMsg("如果该邮箱在旧系统中存在积分,转移链接已发送")
|
||||
}
|
||||
var oldUser AspNetUser
|
||||
err := db.GlobalGorm.Table("AspNetUsers").Where("email = ?", oldSystemEmail).First(&oldUser).Error
|
||||
if err != nil {
|
||||
@@ -343,6 +346,9 @@ func (s *UserInfoServiceV1) BalanceMigrate(ctx context.Context, req *v1.BalanceM
|
||||
}
|
||||
|
||||
// 查询旧系统用户
|
||||
if db.GlobalGorm == nil {
|
||||
return utils.FailParam("旧系统数据库未连接")
|
||||
}
|
||||
var oldUser AspNetUser
|
||||
err := db.GlobalGorm.Table("AspNetUsers").Where("email = ?", oldSystemEmail).First(&oldUser).Error
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user