Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca20be8b7e | |||
| ce98ce4afe | |||
| c65318c545 | |||
| 63b91629fa | |||
| 658a9c2b3e | |||
| 60789ded9d | |||
| 7d8998d83f | |||
| 8974bd26a5 | |||
| 6f25652183 | |||
| a8f4a2afa6 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:alpine AS backend-builder
|
||||
FROM golang:1.24-alpine AS backend-builder
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
|
||||
|
||||
+5
-1
@@ -1,13 +1,17 @@
|
||||
[ocr]
|
||||
url = "https://devocr.likedream.xyz"
|
||||
|
||||
[quesbank]
|
||||
url = "http://tk.swk.tw/api/search.php"
|
||||
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
|
||||
[server]
|
||||
port = 8090
|
||||
port = 8091
|
||||
url = "https://devocr.likedream.xyz"
|
||||
openapiPath= "/api.json"
|
||||
swaggerPath= "/swagger"
|
||||
|
||||
@@ -31,7 +31,7 @@ require (
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/net v0.45.0
|
||||
golang.org/x/net v0.46.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
resty.dev/v3 v3.0.0-beta.3
|
||||
@@ -87,15 +87,21 @@ require (
|
||||
github.com/oklog/ulid/v2 v2.1.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/samber/lo v1.51.0 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tiendc/go-deepcopy v1.7.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
github.com/xuri/excelize/v2 v2.10.0 // indirect
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zclconf/go-cty v1.14.4 // indirect
|
||||
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
|
||||
|
||||
@@ -163,6 +163,11 @@ github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@@ -203,6 +208,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
|
||||
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -215,6 +222,12 @@ github.com/wenlng/go-captcha/v2 v2.0.4 h1:5cSUF36ZyA03qeDMjKmeXGpbYJMXEexZIYK3Vg
|
||||
github.com/wenlng/go-captcha/v2 v2.0.4/go.mod h1:5hac1em3uXoyC5ipZ0xFv9umNM/waQvYAQdr0cx/h34=
|
||||
github.com/wneessen/go-mail v0.6.2 h1:c6V7c8D2mz868z9WJ+8zDKtUyLfZ1++uAZmo2GRFji8=
|
||||
github.com/wneessen/go-mail v0.6.2/go.mod h1:L/PYjPK3/2ZlNb2/FjEBIn9n1rUWjW+Toy531oVmeb4=
|
||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
|
||||
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
@@ -270,6 +283,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"likedream-goframe/internal/ent/cptask"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
type UploadImageReq struct {
|
||||
g.Meta `path:"/image/upload" method:"post" tags:"Image" summary:"上传图片"`
|
||||
File *ghttp.UploadFile `json:"file" v:"required#请上传图片"`
|
||||
g.Meta `path:"/image/upload" method:"post" tags:"Image" summary:"上传图片"`
|
||||
File *ghttp.UploadFile `json:"file" v:"required#请上传图片"`
|
||||
CpUserId int64 `json:"cpUserId" v:"required#请指定今日校园用户"`
|
||||
TaskType cptask.TaskType `json:"taskType" v:"required#请指定任务类型"`
|
||||
}
|
||||
|
||||
type PreviewImageReq struct {
|
||||
g.Meta `path:"/image/preview" method:"get" tags:"Image" summary:"预览图片"`
|
||||
Key string `json:"key" v:"required#请输入图片key"`
|
||||
Key string `json:"key" v:"required#请指定图片key" in:"query"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package v1
|
||||
|
||||
type UploadImageRes struct {
|
||||
FileKey string `json:"key"`
|
||||
FileSize *int64 `json:"size"`
|
||||
PreviewUrl string `json:"previewUrl"`
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ type PostCpTaskExecuteReq struct {
|
||||
NotificationType cptask.NotificationType `json:"notification_type"`
|
||||
ExecuteHour int `json:"execute_hour"`
|
||||
ExecuteMinute int `json:"execute_minute"`
|
||||
ExecuteDays []int `json:"execute_days"`
|
||||
}
|
||||
|
||||
type DeleteTaskReq struct {
|
||||
|
||||
@@ -85,7 +85,9 @@ type CpUserAuthorizeConfirmReq struct {
|
||||
DataKey string `json:"dataKey"`
|
||||
}
|
||||
|
||||
type CpUserAuthorizeRandomReq struct {
|
||||
g.Meta `path:"/user/authorizeRandom" method:"post" tags:"CpUser" summary:"随机设备码"`
|
||||
Id int64 `json:"id"`
|
||||
type CpUserCustomDeviceReq struct {
|
||||
g.Meta `path:"/user/customDevice" method:"post" tags:"CpUser" summary:"自定义设备码"`
|
||||
Id int64 `json:"id" v:"required#请指定用户"`
|
||||
MobileCode string `json:"mobileCode" v:"required#请输入设备码"`
|
||||
MobileType string `json:"mobileType" v:"required|in:random,manual#请指定设备码类型|设备码类型无效"`
|
||||
}
|
||||
|
||||
@@ -51,6 +51,24 @@ func ApiAutoOCRVerification(bytes []byte) string {
|
||||
result := post.String()
|
||||
return gjson.Get(result, "data").String()
|
||||
}
|
||||
|
||||
func ApiAutoCalcVerification(b64 string) string {
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/calc"
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"base64": b64,
|
||||
}).Post(baseUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("AutoOCRVerification", baseUrl).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
result := post.String()
|
||||
return gjson.Get(result, "data").String()
|
||||
}
|
||||
func Bytes2Base64(data []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
@@ -48,22 +48,8 @@ func (p *PersistCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
delete(globalCookies, cookie.Name)
|
||||
}
|
||||
|
||||
// 查找是否存在相同 name, path 和 domain 的 cookie
|
||||
existingCookies := p.hostMap[host][cookie.Name]
|
||||
found := false
|
||||
for i, existing := range existingCookies {
|
||||
if existing.Path == cookie.Path && existing.Domain == cookie.Domain {
|
||||
// 找到完全匹配的 cookie,替换它
|
||||
existingCookies[i] = cookie
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
// 没有找到匹配的,添加新的 cookie
|
||||
p.hostMap[host][cookie.Name] = append(p.hostMap[host][cookie.Name], cookie)
|
||||
}
|
||||
// 同一 host 下同名 cookie 直接覆盖,避免多个同名 cookie 导致旧值被选中
|
||||
p.hostMap[host][cookie.Name] = []*http.Cookie{cookie}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ func GetCpdailyLoginBySchoolInfo(ctx context.Context, info *school.SchoolDetailI
|
||||
// 河北石油职业技术大学
|
||||
case "01e270d1-00e2-4ce9-b0af-5d5fbd462b8a":
|
||||
return NewIap(ctx)
|
||||
case "676c9395-a29c-4068-97a8-b48e05dae499":
|
||||
return NewHnuu(ctx)
|
||||
default:
|
||||
if info.IsIap() {
|
||||
return NewIap(ctx)
|
||||
|
||||
@@ -4,13 +4,16 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/ent/cpuser"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"likedream-goframe/internal/utils"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/oops"
|
||||
"go.uber.org/zap"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
@@ -97,12 +100,71 @@ func CookieEnabled(ctx context.Context, host string, client *resty.Client, login
|
||||
|
||||
utils.OkOrPanic(err)
|
||||
needLogin := sessionResponse.StatusCode() == 302
|
||||
if needLogin && loginType == cpuser.LoginTypePhone {
|
||||
utils.OkOrPanic(errors.NewBusinessError(500, "登录信息失效,请重新通过短信验证码登录"))
|
||||
}
|
||||
return !needLogin
|
||||
}
|
||||
|
||||
// RefreshCookieParams 刷新 cookie 所需的用户参数
|
||||
type RefreshCookieParams struct {
|
||||
ID int64
|
||||
Cookie string
|
||||
Tgc string
|
||||
LoginType cpuser.LoginType
|
||||
Username string
|
||||
Password string
|
||||
SchoolID string
|
||||
}
|
||||
|
||||
// RefreshCookieResult 刷新 cookie 的结果
|
||||
type RefreshCookieResult struct {
|
||||
Client *resty.Client
|
||||
Cookie string
|
||||
Tgc string
|
||||
}
|
||||
|
||||
// EnsureCookieValid 检测 cookie 是否过期,过期则自动刷新。
|
||||
// phone 类型用 TGC 刷新,account 类型用账号密码重新登录。
|
||||
func EnsureCookieValid(ctx context.Context, params *RefreshCookieParams, schoolDetail *school.SchoolDetailInfo) *RefreshCookieResult {
|
||||
client := GetClient(ctx, params.Cookie)
|
||||
if CookieEnabled(ctx, schoolDetail.WecloudUrl, client, params.LoginType) {
|
||||
logger.Logger.Info("[TGC] cookie有效,无需刷新",
|
||||
zap.Int64("userId", params.ID),
|
||||
zap.String("loginType", string(params.LoginType)),
|
||||
)
|
||||
return &RefreshCookieResult{Client: client, Cookie: params.Cookie, Tgc: params.Tgc}
|
||||
}
|
||||
|
||||
logger.Logger.Info("[TGC] cookie已失效,开始刷新",
|
||||
zap.Int64("userId", params.ID),
|
||||
zap.String("loginType", string(params.LoginType)),
|
||||
zap.Bool("tgcEmpty", params.Tgc == ""),
|
||||
)
|
||||
|
||||
var newCookie, newTgc string
|
||||
|
||||
if params.LoginType == cpuser.LoginTypePhone {
|
||||
if params.Tgc == "" {
|
||||
utils.OkOrPanic(errors.NewBusinessError(500, "TGC为空,请重新通过短信验证码登录"))
|
||||
}
|
||||
smsHandler := NewSmsWithoutDeviceID()
|
||||
newCookie, newTgc = smsHandler.RefreshByTgc(ctx, params.Tgc, params.SchoolID)
|
||||
logger.Logger.Info("[TGC] RefreshByTgc完成",
|
||||
zap.Int64("userId", params.ID),
|
||||
zap.Bool("cookieChanged", newCookie != params.Cookie),
|
||||
)
|
||||
} else {
|
||||
loginExecutor := GetCpdailyLoginBySchoolInfo(ctx, schoolDetail)
|
||||
loginExecutor.Login(ctx, schoolDetail, params.Username, params.Password)
|
||||
newCookie = loginExecutor.GetCookieString()
|
||||
newTgc = params.Tgc
|
||||
}
|
||||
|
||||
return &RefreshCookieResult{
|
||||
Client: GetClient(ctx, newCookie),
|
||||
Cookie: newCookie,
|
||||
Tgc: newTgc,
|
||||
}
|
||||
}
|
||||
|
||||
func GetClient(ctx context.Context, cookie string) *resty.Client {
|
||||
cookieJar := cookiejar.NewPersistCookieJar()
|
||||
cookieJar.Deserialize(cookie)
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"likedream-goframe/internal/application/cookiejar"
|
||||
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/samber/oops"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
var RSA_KEY_MAP = map[string]string{
|
||||
"public_exponent": "010001",
|
||||
"private_exponent": "413798867d69babed22e0dd3d4031c635f3e9dbca0fa50a32974a0e230787b7f7ba78caefbee828a051c690357a8cc31dba8efc738b4db22e887571ef1ec5a5a55b6d866f6a67527f6a7d78a127c9f687008bb540228b50aa2d1ca5a4ff71107234f936b611ac46432a26da9c302eaa7180820df70593353b3f8c0247fe97a45",
|
||||
"modulus": "00b5eeb166e069920e80bebd1fea4829d3d1f3216f2aabe79b6c47a3c18dcee5fd22c2e7ac519cab59198ece036dcf289ea8201e2a0b9ded307f8fb704136eaeb670286f5ad44e691005ba9ea5af04ada5367cd724b5a26fdb5120cc95b6431604bd219c6b7d83a6f8f24b43918ea988a76f93c333aa5a20991493d4eb1117e7b1",
|
||||
}
|
||||
|
||||
// 淮南联合大学登录实现 RSA加密 & 验证码必出(加法验证码)
|
||||
type CpdailyHnuuLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func (c CpdailyHnuuLogin) Login(ctx context.Context, schoolDetail *school.SchoolDetailInfo, username, password string) {
|
||||
jar := cookiejar.NewPersistCookieJar()
|
||||
|
||||
c.client.SetCookieJar(jar).SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
// 获取合法的登录URL
|
||||
loginUrl := getCasValidLoginUrl(schoolDetail)
|
||||
|
||||
// 请求登录页面并获取真正的登录URL
|
||||
loginPageResponse, err := c.client.R().
|
||||
WithContext(ctx).
|
||||
Get(loginUrl)
|
||||
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
defer loginPageResponse.Body.Close()
|
||||
loginPageBytes, err := io.ReadAll(loginPageResponse.Body)
|
||||
utils.OkOrPanic(err)
|
||||
loginResponseUrl := loginPageResponse.RawResponse.Request.URL
|
||||
// 解析登录页面
|
||||
loginPage, err := goquery.NewDocumentFromReader(bytes.NewReader(loginPageBytes))
|
||||
utils.OkOrPanic(err)
|
||||
// 判断登录类型
|
||||
var (
|
||||
allInputs = map[string]string{}
|
||||
formNode = loginPage.Find(".ant-form").First()
|
||||
)
|
||||
inputs := formNode.Find("input")
|
||||
for _, input := range inputs.EachIter() {
|
||||
name, exists := input.Attr("name")
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
value, _ := input.Attr("value")
|
||||
allInputs[name] = value
|
||||
}
|
||||
|
||||
allInputs["username"] = username
|
||||
pair := cryptoutil.NewNutRSAKeyPair(RSA_KEY_MAP["public_exponent"], "", RSA_KEY_MAP["modulus"])
|
||||
// 处理密码
|
||||
allInputs["password"] = pair.EncryptedString(password)
|
||||
|
||||
// 解决验证码
|
||||
// 解决常规图片验证码
|
||||
captchaUrl := getHnuuCaptchaUrl(loginResponseUrl)
|
||||
code, uid := ResolveCalcCaptcha(ctx, captchaUrl, c.client)
|
||||
|
||||
allInputs["code"] = code
|
||||
allInputs["id"] = uid
|
||||
jar = cookiejar.NewPersistCookieJar()
|
||||
c.client.SetCookieJar(jar)
|
||||
// 登录提交
|
||||
loginPostResponse, err := c.client.R().
|
||||
SetFormData(allInputs).
|
||||
WithContext(ctx).
|
||||
Post(getHnuuLoginPostUrl(loginResponseUrl))
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
Wrap(err)
|
||||
})
|
||||
defer loginPostResponse.Body.Close()
|
||||
|
||||
type LoginResponse struct {
|
||||
Meta struct {
|
||||
Success bool `json:"success"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
Message string `json:"message"`
|
||||
} `json:"meta"`
|
||||
Data struct {
|
||||
Code string `json:"code"`
|
||||
} `json:"data"`
|
||||
Tgt string `json:"tgt"`
|
||||
Ticket string `json:"ticket"`
|
||||
}
|
||||
json := utils.ReadJson[LoginResponse](loginPostResponse.Body)
|
||||
if json.Tgt != "" {
|
||||
handleHnuuLoginSuccess(ctx, c.client, loginResponseUrl.Query().Get("service"), json.Ticket, json.Tgt, schoolDetail.WecloudUrl)
|
||||
} else {
|
||||
errMsg := ""
|
||||
if json.Data.Code == "CODEFALSE" {
|
||||
errMsg = "验证码错误"
|
||||
} else if json.Data.Code == "FALSE" {
|
||||
errMsg = "请检查账号密码是否正确"
|
||||
} else if json.Data.Code != "PASSERROR" {
|
||||
errMsg = "密码错误"
|
||||
} else if json.Data.Code != "NOUSER" {
|
||||
errMsg = "账号不存在"
|
||||
} else if json.Data.Code != "USERLOCK" {
|
||||
errMsg = "账号锁定"
|
||||
} else {
|
||||
errMsg = "未知错误" + json.Data.Code
|
||||
}
|
||||
utils.OkOrPanic(errors.NewBusinessError(500, errMsg))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleHnuuLoginSuccess(ctx context.Context, client *resty.Client, url, ticket string, tgt string, host string) {
|
||||
redirectUrl := fmt.Sprintf("%s&ticket=%s", url, ticket)
|
||||
client.R().SetHeader("Referer", "https://cas.hnuu.edu.cn/").Get(redirectUrl)
|
||||
response, err2 := client.R().SetHeader("Referer", "https://cas.hnuu.edu.cn/").Get("https://hnuu.campusphere.net/iap/login")
|
||||
utils.OkOrPanic(err2)
|
||||
newUrl := response.RawResponse.Request.URL
|
||||
ticketUrl := fmt.Sprintf("https://cas.hnuu.edu.cn/lyuapServer/v1/tickets/%s", tgt)
|
||||
get, err := client.R().SetFormData(map[string]string{
|
||||
"service": url,
|
||||
"loginToken": "loginToken",
|
||||
}).Post(ticketUrl)
|
||||
utils.OkOrPanic(err)
|
||||
defer get.Body.Close()
|
||||
ticket = utils.ReadString(get.Body)
|
||||
redirectUrl = fmt.Sprintf("%s&ticket=%s", newUrl.Query().Get("service"), ticket)
|
||||
client.R().SetHeader("Referer", "https://cas.hnuu.edu.cn/").Get(redirectUrl)
|
||||
ActivateSession(ctx, host, client)
|
||||
}
|
||||
|
||||
func getHnuuLoginPostUrl(url *url.URL) string {
|
||||
// https://cas.hnuu.edu.cn/lyuapServer/v1/tickets
|
||||
return fmt.Sprintf("%s://%s/lyuapServer/v1/tickets", url.Scheme, url.Host)
|
||||
}
|
||||
|
||||
func getHnuuCaptchaUrl(url *url.URL) string {
|
||||
// https://cas.hnuu.edu.cn/lyuapServer/kaptcha?_t=1766803822&uid=
|
||||
return fmt.Sprintf("%s://%s/lyuapServer/kaptcha?_t=%d&uid=", url.Scheme, url.Host, utils.GetCurrentTimeMillis())
|
||||
}
|
||||
|
||||
func (c CpdailyHnuuLogin) GetCookieJar() http.CookieJar {
|
||||
return c.client.CookieJar()
|
||||
}
|
||||
|
||||
func (c CpdailyHnuuLogin) GetCookieString() string {
|
||||
cookieJar := c.GetCookieJar()
|
||||
serializableJar := cookieJar.(*cookiejar.PersistCookieJar)
|
||||
return serializableJar.Serialize()
|
||||
}
|
||||
|
||||
func (c CpdailyHnuuLogin) GetClient() *resty.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func NewHnuu(ctx context.Context) CpdailyLogin {
|
||||
return &CpdailyHnuuLogin{
|
||||
client: httpclient.NewCpdailyClient(),
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,32 @@ func ResolveOcrCaptcha(ctx context.Context, url string, client *resty.Client) st
|
||||
return captcha.ApiAutoOCRVerification(byteData)
|
||||
}
|
||||
|
||||
func ResolveCalcCaptcha(ctx context.Context, url string, client *resty.Client) (string, string) {
|
||||
b64, uid := DownBase64(ctx, url, client)
|
||||
return captcha.ApiAutoCalcVerification(b64), uid
|
||||
}
|
||||
|
||||
func DownBase64(ctx context.Context, url string, client *resty.Client) (string, string) {
|
||||
res, err := client.R().
|
||||
WithContext(ctx).
|
||||
Get(url)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("captchaUrl", url).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
defer res.Body.Close()
|
||||
type CaptchResponse struct {
|
||||
KaptchaType string `json:"kaptchaType"`
|
||||
UID string `json:"uid"`
|
||||
Content string `json:"content"`
|
||||
Timeout int `json:"timeout"`
|
||||
}
|
||||
json := utils.ReadJson[CaptchResponse](res.Body)
|
||||
return json.Content, json.UID
|
||||
}
|
||||
|
||||
func DownCode2Byte(ctx context.Context, url string, client *resty.Client) []byte {
|
||||
res, err := client.R().
|
||||
WithContext(ctx).
|
||||
|
||||
@@ -11,12 +11,15 @@ import (
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/samber/oops"
|
||||
"go.uber.org/zap"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
@@ -199,6 +202,7 @@ func (s *SmsLoginHandler) SetLoginCookie(ctx context.Context, jar http.CookieJar
|
||||
jar.SetCookies(targetSchoolUrl, []*http.Cookie{cookie})
|
||||
jar.SetCookies(targetApiUrl, []*http.Cookie{cookie})
|
||||
}
|
||||
|
||||
s.client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(50))
|
||||
req := s.client.R().
|
||||
SetHeader("AmpCookies", ampToken).
|
||||
@@ -278,3 +282,93 @@ func (s *SmsLoginHandler) GetCookieString() string {
|
||||
serializableJar := cookieJar.(*cookiejar.PersistCookieJar)
|
||||
return serializableJar.Serialize()
|
||||
}
|
||||
|
||||
// RefreshByTgc 使用保存的完整 cookie(包含 CASTGC 等长期凭证)重新去学校换取 CAS cookie。
|
||||
// tgc 参数是 SetLoginCookie 之后序列化的完整 cookie jar,包含 CASTGC(有效期10年)、
|
||||
// sessionToken 等认证信息。流程:先用 sessionToken 调用 getImUserToken 换取新的 CAS cookie,
|
||||
// 再激活 session。
|
||||
func (s *SmsLoginHandler) RefreshByTgc(ctx context.Context, tgc string, schoolId string) (cookie string, newTgc string) {
|
||||
jar := cookiejar.NewPersistCookieJar()
|
||||
jar.Deserialize(tgc)
|
||||
// 合并所有 host 的 cookie 到 global,消除因多 host 存储导致的重复 cookie
|
||||
jar.RemoveCookieInfo()
|
||||
s.client = httpclient.NewCpdailyClient()
|
||||
s.client.SetCookieJar(jar)
|
||||
|
||||
detail := school.GetSchoolDetail(ctx, schoolId)
|
||||
|
||||
logger.Logger.Info("[TGC] RefreshByTgc 开始用TGC换取CAS cookie",
|
||||
zap.String("schoolId", schoolId),
|
||||
zap.String("wecloudUrl", detail.WecloudUrl),
|
||||
zap.Int("tgcLen", len(tgc)),
|
||||
)
|
||||
|
||||
// 从 cookie jar 中提取 sessionToken,用于换取新的 CAS cookie
|
||||
schoolUrl, _ := url.Parse(detail.WecloudUrl)
|
||||
sessionToken := ""
|
||||
for _, c := range jar.Cookies(schoolUrl) {
|
||||
if c.Name == "sessionToken" {
|
||||
sessionToken = c.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if sessionToken != "" {
|
||||
// 换取新的 CAS cookie(与 SetLoginCookie 相同的流程)
|
||||
getCookieUrl := fmt.Sprintf("%s%s", detail.WecloudUrl, "/wec-im-group/group/imUserToken/getImUserToken")
|
||||
|
||||
ampCookieMap := map[string]any{
|
||||
"AMP2": []map[string]any{
|
||||
{"path": "/", "value": sessionToken, "domain": "", "name": "sessionToken"},
|
||||
},
|
||||
"AMP1": []map[string]any{
|
||||
{"path": "/", "value": sessionToken, "domain": "", "name": "sessionToken"},
|
||||
},
|
||||
}
|
||||
|
||||
ampTokenString, e := sonic.Marshal(ampCookieMap)
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)
|
||||
|
||||
ampTokenEncrypted := cryptoutil.DesEncrypt(ampTokenString, cryptoutil.Normal)
|
||||
ampToken := base64.StdEncoding.EncodeToString(ampTokenEncrypted)
|
||||
|
||||
ctx = utils.NewCpdailySmsContextBuilder().
|
||||
WithDeviceID(s.deviceID).
|
||||
WithTenantID(detail.Id).
|
||||
WithSessionToken(sessionToken).
|
||||
Build(ctx)
|
||||
|
||||
// 使用不复制 Cookie 头的重定向策略,避免旧 Cookie 覆盖 jar 中更新的值
|
||||
s.client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||
if len(via) >= 50 {
|
||||
return fmt.Errorf("resty: stopped after 50 redirects")
|
||||
}
|
||||
// 只复制非 Cookie 头,让 jar 自动管理 Cookie
|
||||
for key, val := range via[0].Header {
|
||||
if !strings.EqualFold(key, "Cookie") {
|
||||
req.Header[key] = val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
req := s.client.R().
|
||||
SetHeader("AmpCookies", ampToken).
|
||||
SetContext(ctx)
|
||||
|
||||
setSmsRequestHeaders(ctx, req)
|
||||
|
||||
resp, e := req.Get(getCookieUrl)
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)
|
||||
|
||||
respJson := utils.ReadJson[cpdailySmsCommonResponse[any]](resp.Body)
|
||||
respJson.EnsureSuccess()
|
||||
|
||||
logger.Logger.Info("[TGC] RefreshByTgc 换取CAS cookie完成")
|
||||
} else {
|
||||
logger.Logger.Warn("[TGC] RefreshByTgc 未找到sessionToken,跳过换取CAS cookie步骤")
|
||||
}
|
||||
|
||||
newTgc = tgc // TGC 保持不变(CASTGC 有效期10年)
|
||||
cookie = s.GetCookieString()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@ import (
|
||||
"fmt"
|
||||
"likedream-goframe/internal/application/cpdaily"
|
||||
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/cpdaily/imageuploader"
|
||||
"likedream-goframe/internal/application/cpdaily/task/formtype"
|
||||
"likedream-goframe/internal/application/cpdaily/taskhelper"
|
||||
"likedream-goframe/internal/application/cpdaily/types"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"maps"
|
||||
@@ -454,31 +452,26 @@ func (c *CpdailyCollectionTask) FillCustomForm(ctx context.Context, form map[str
|
||||
newRowObj["value"] = gvar.New(strings.Join(values, ","))
|
||||
}
|
||||
} else if c.isPicType(fieldType) {
|
||||
// 图片类型
|
||||
// 图片类型 - content 中存储的是今日校园的 previewUrl,逗号分隔
|
||||
if param.Content == "" {
|
||||
utils.OkOrPanic(errors.NewBusinessError(400, "查寝需要图片,但是当前图片为空"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
imageUuidSeq := strings.Split(param.Content, ",")
|
||||
var imageUuids []string
|
||||
for _, imageUuid := range imageUuidSeq {
|
||||
imageUuid = strings.TrimSpace(imageUuid)
|
||||
if imageUuid == "" {
|
||||
continue
|
||||
previewUrls := strings.Split(param.Content, ",")
|
||||
var validUrls []string
|
||||
for _, url := range previewUrls {
|
||||
url = strings.TrimSpace(url)
|
||||
if url != "" {
|
||||
validUrls = append(validUrls, url)
|
||||
}
|
||||
imageUuids = append(imageUuids, imageUuid)
|
||||
}
|
||||
|
||||
if len(imageUuids) == 0 {
|
||||
if len(validUrls) == 0 {
|
||||
utils.OkOrPanic(errors.NewBusinessError(400, "查寝照片未上传"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
// 随机选择一张照片
|
||||
imageUuid := imageUuids[rand.IntN(len(imageUuids))]
|
||||
imageId, e := uuid.Parse(imageUuid)
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)
|
||||
|
||||
previewUrl := imageuploader.UploadImageFromStorage(ctx, c.host, imageId, types.TaskTypeCollection, c.client)
|
||||
previewUrl := validUrls[rand.IntN(len(validUrls))]
|
||||
newRowObj["value"] = gvar.New(previewUrl)
|
||||
} else {
|
||||
utils.OkOrPanic(errors.NewBusinessError(500, "出现未知类型表单,请反馈表单类型:"+fieldType), oops.WithContext(ctx).Wrap)
|
||||
|
||||
@@ -5,14 +5,15 @@ import (
|
||||
"fmt"
|
||||
"likedream-goframe/internal/application/cpdaily"
|
||||
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/cpdaily/imageuploader"
|
||||
"likedream-goframe/internal/application/cpdaily/submitcaptcha"
|
||||
"likedream-goframe/internal/application/cpdaily/task/formtype"
|
||||
"likedream-goframe/internal/application/cpdaily/taskhelper"
|
||||
"likedream-goframe/internal/application/cpdaily/types"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -20,6 +21,7 @@ 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"
|
||||
)
|
||||
@@ -221,6 +223,82 @@ func (c *CpdailyAutoSignTask) QueryFormByTitle(ctx context.Context, titleGlob st
|
||||
result["realTitle"] = gvar.New(realTitle)
|
||||
result["taskInfo"] = gvar.New(matchTask)
|
||||
result["rows"] = gvar.New(formDatas)
|
||||
|
||||
// 尝试从已签到的历史任务中获取历史数据用于预填充
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 今天没有已签到的同名任务,从历史月份中只查 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()
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -356,30 +434,75 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "任务表单为空"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
// 优先从 historyRows(已签到的历史任务详情)中提取历史值,否则从 rows 中提取
|
||||
histRows := rows
|
||||
if hr := rawJson["historyRows"]; hr != nil {
|
||||
if hrMap := hr.MapStrVar(); len(hrMap) > 0 {
|
||||
histRows = hrMap
|
||||
}
|
||||
}
|
||||
|
||||
var list []*formtype.CpTaskFormItem
|
||||
|
||||
// 经纬度
|
||||
//dto := &formtype.CpTaskFormItem{
|
||||
// Title: "经度",
|
||||
// ColName: "lon",
|
||||
// Type: formtype.ItemTypeInput,
|
||||
// Required: true,
|
||||
//}
|
||||
//list = append(list, dto)
|
||||
//
|
||||
//dto = &formtype.CpTaskFormItem{
|
||||
// Title: "纬度",
|
||||
// ColName: "lat",
|
||||
// Type: formtype.ItemTypeInput,
|
||||
// Required: true,
|
||||
//}
|
||||
//list = append(list, dto)
|
||||
// 从任务详情中提取历史值用于预填充
|
||||
histLatAndLon := ""
|
||||
histAddress := ""
|
||||
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 == "" {
|
||||
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 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 = "否"
|
||||
}
|
||||
}
|
||||
|
||||
dto := &formtype.CpTaskFormItem{
|
||||
Title: "经纬度",
|
||||
ColName: "latAndLon",
|
||||
Type: formtype.ItemTypeLatAndLon,
|
||||
Required: true,
|
||||
Tip: "您可以通过地址搜索签到经纬度,如果觉得不够准确可以自行修改",
|
||||
Title: "经纬度",
|
||||
ColName: "latAndLon",
|
||||
Type: formtype.ItemTypeLatAndLon,
|
||||
Required: true,
|
||||
Content: histLatAndLon,
|
||||
Default: histLatAndLon,
|
||||
OtherValue: histAddress,
|
||||
Tip: "您可以通过地址搜索签到经纬度,如果觉得不够准确可以自行修改",
|
||||
}
|
||||
list = append(list, dto)
|
||||
|
||||
@@ -388,6 +511,8 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
ColName: "address",
|
||||
Type: formtype.ItemTypeInput,
|
||||
Required: true,
|
||||
Content: histAddress,
|
||||
Default: histAddress,
|
||||
Tip: "这个就是指签到完毕后查看时最底部那个最详细的地址信息哦",
|
||||
}
|
||||
list = append(list, dto)
|
||||
@@ -398,6 +523,8 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
ColName: "signImg",
|
||||
Type: formtype.ItemTypeImage,
|
||||
Required: true,
|
||||
Content: histSignPhotoUrl,
|
||||
Default: histSignPhotoUrl,
|
||||
Tip: "您可以上传多张图片,会顺序轮换更新的哦~",
|
||||
}
|
||||
list = append(list, dto)
|
||||
@@ -409,6 +536,8 @@ func (c *CpdailyAutoSignTask) ParseFormJson(ctx context.Context, rawJson map[str
|
||||
ColName: "isMalposition",
|
||||
Type: formtype.ItemTypeSingle,
|
||||
Required: true,
|
||||
Content: histIsMalposition,
|
||||
Default: histIsMalposition,
|
||||
Options: []formtype.CpTaskFormItemOption{
|
||||
{
|
||||
Content: "是",
|
||||
@@ -527,12 +656,8 @@ func (c *CpdailyAutoSignTask) FillCustomForm(ctx context.Context, form map[strin
|
||||
utils.OkOrPanic(errors.NewBusinessError(400, "签到照片未上传"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
imageId, e := uuid.Parse(imgId)
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)
|
||||
|
||||
previewUrl := imageuploader.UploadImageFromStorage(ctx, c.host, imageId, types.TaskTypeSign, c.client)
|
||||
|
||||
resultForm["signPhotoUrl"] = gvar.New(previewUrl)
|
||||
// imgId 现在直接就是今日校园的 previewUrl,无需再上传
|
||||
resultForm["signPhotoUrl"] = gvar.New(imgId)
|
||||
}
|
||||
|
||||
// 处理验证码
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"likedream-goframe/internal/application/cpdaily"
|
||||
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||
"likedream-goframe/internal/application/cpdaily/imageuploader"
|
||||
"likedream-goframe/internal/application/cpdaily/submitcaptcha"
|
||||
"likedream-goframe/internal/application/cpdaily/task/formtype"
|
||||
"likedream-goframe/internal/application/cpdaily/taskhelper"
|
||||
@@ -222,6 +221,7 @@ 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,6 +229,9 @@ 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 {
|
||||
@@ -277,6 +280,87 @@ func (c *CpdailySleepCheckTask) QueryFormByTitle(ctx context.Context, titleGlob
|
||||
result["realTitle"] = gvar.New(realTitle)
|
||||
result["taskInfo"] = gvar.New(matchTask)
|
||||
result["rows"] = gvar.New(datas)
|
||||
|
||||
// 如果当前任务不是已签到的,尝试从已签到的历史任务中获取历史数据用于预填充
|
||||
if !isFromSigned {
|
||||
// 先看今天有没有已签到的同名任务
|
||||
signedMatch := findMatchTitle(signedTasks)
|
||||
if signedMatch != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 今天没有已签到的同名任务,从历史月份中只查 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() == "" {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -432,30 +516,77 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "任务表单为空"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
// 优先从 historyRows(已签到的历史任务详情)中提取历史值,否则从 rows 中提取
|
||||
histRows := rows
|
||||
if hr := rawJson["historyRows"]; hr != nil {
|
||||
if hrMap := hr.MapStrVar(); len(hrMap) > 0 {
|
||||
histRows = hrMap
|
||||
}
|
||||
}
|
||||
|
||||
var list []*formtype.CpTaskFormItem
|
||||
|
||||
// 经纬度
|
||||
//dto := &formtype.CpTaskFormItem{
|
||||
// Title: "经度",
|
||||
// ColName: "lon",
|
||||
// Type: formtype.ItemTypeInput,
|
||||
// Required: true,
|
||||
//}
|
||||
//list = append(list, dto)
|
||||
//
|
||||
//dto = &formtype.CpTaskFormItem{
|
||||
// Title: "纬度",
|
||||
// ColName: "lat",
|
||||
// Type: formtype.ItemTypeInput,
|
||||
// Required: true,
|
||||
//}
|
||||
//list = append(list, dto)
|
||||
// 从任务详情中提取历史值用于预填充
|
||||
// signPlaceSelected 包含签到地点信息(经纬度、地址)
|
||||
histLatAndLon := ""
|
||||
histAddress := ""
|
||||
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 == "" {
|
||||
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 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 = "否"
|
||||
}
|
||||
}
|
||||
|
||||
dto := &formtype.CpTaskFormItem{
|
||||
Title: "经纬度",
|
||||
ColName: "latAndLon",
|
||||
Type: formtype.ItemTypeLatAndLon,
|
||||
Required: true,
|
||||
Tip: "您可以通过地址搜索签到经纬度,如果觉得不够准确可以自行修改",
|
||||
Title: "经纬度",
|
||||
ColName: "latAndLon",
|
||||
Type: formtype.ItemTypeLatAndLon,
|
||||
Required: true,
|
||||
Content: histLatAndLon,
|
||||
Default: histLatAndLon,
|
||||
OtherValue: histAddress,
|
||||
Tip: "您可以通过地址搜索签到经纬度,如果觉得不够准确可以自行修改",
|
||||
}
|
||||
list = append(list, dto)
|
||||
dto = &formtype.CpTaskFormItem{
|
||||
@@ -463,6 +594,8 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
ColName: "address",
|
||||
Type: formtype.ItemTypeInput,
|
||||
Required: true,
|
||||
Content: histAddress,
|
||||
Default: histAddress,
|
||||
Tip: "这个就是指签到完毕后查看时最底部那个最详细的地址信息哦",
|
||||
}
|
||||
list = append(list, dto)
|
||||
@@ -473,6 +606,8 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
ColName: "signImg",
|
||||
Type: formtype.ItemTypeImage,
|
||||
Required: true,
|
||||
Content: histSignPhotoUrl,
|
||||
Default: histSignPhotoUrl,
|
||||
Tip: "您可以上传多张图片,会顺序轮换更新的哦~",
|
||||
}
|
||||
list = append(list, dto)
|
||||
@@ -485,6 +620,8 @@ func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[s
|
||||
ColName: "isMalposition",
|
||||
Type: formtype.ItemTypeSingle,
|
||||
Required: true,
|
||||
Content: histIsMalposition,
|
||||
Default: histIsMalposition,
|
||||
Options: []formtype.CpTaskFormItemOption{
|
||||
{
|
||||
Content: "是",
|
||||
@@ -577,12 +714,8 @@ func (c *CpdailySleepCheckTask) FillCustomForm(ctx context.Context, form map[str
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "未找到查寝照片参数,请上传照片"), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
imageId, e := uuid.Parse(imgId)
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)
|
||||
|
||||
previewUrl := imageuploader.UploadImageFromStorage(ctx, c.host, imageId, types.TaskTypeSleepCheck, c.client)
|
||||
|
||||
resultForm["signPhotoUrl"] = gvar.New(previewUrl)
|
||||
// imgId 现在直接就是今日校园的 previewUrl,无需再上传
|
||||
resultForm["signPhotoUrl"] = gvar.New(imgId)
|
||||
}
|
||||
|
||||
// 判断是否存在二维码查寝
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"resty.dev/v3"
|
||||
"likedream-goframe/internal/utils"
|
||||
"time"
|
||||
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
var Common = resty.New().
|
||||
@@ -10,3 +12,11 @@ var Common = resty.New().
|
||||
SetRetryCount(5).
|
||||
SetTimeout(30*time.Second).
|
||||
SetHeader("User-Agent", "likedream")
|
||||
|
||||
func init() {
|
||||
if !utils.IsDebug() {
|
||||
Common.SetDebug(false)
|
||||
} else {
|
||||
Common.SetProxy("http://127.0.0.1:9000")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package music
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_GetMusic(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
result := MusicDownloader.Search(ctx, "天外来物")
|
||||
t.Log(result)
|
||||
}
|
||||
@@ -11,6 +11,7 @@ const (
|
||||
PointTypeWork PointType = "insertwork"
|
||||
PointTypeHyperlink PointType = "insertlink"
|
||||
PointTypeLive PointType = "insertlive"
|
||||
PointTypeExam PointType = "insertexam" // 考试任务点
|
||||
)
|
||||
|
||||
// PointVideoDto 视频任务点
|
||||
@@ -85,6 +86,25 @@ type PointHyperlinkDto struct {
|
||||
IsJob bool // 是否是任务点
|
||||
}
|
||||
|
||||
// PointExamDto 考试任务点
|
||||
type PointExamDto struct {
|
||||
CardIndex int
|
||||
CourseID string
|
||||
ClassID string
|
||||
KnowledgeID int
|
||||
Cpi string
|
||||
ExamID string // 考试ID
|
||||
Title string // 考试标题
|
||||
JobID string // 任务ID
|
||||
Enc string // 加密参数
|
||||
PaperId string // 试卷ID
|
||||
ExamRelationId string // 考试关联ID
|
||||
TotalScore float64 // 总分
|
||||
Duration int // 考试时长(分钟)
|
||||
IsPassed bool // 是否已完成
|
||||
IsJob bool // 是否是任务点
|
||||
}
|
||||
|
||||
// PointLiveDto 直播任务点
|
||||
type PointLiveDto struct {
|
||||
CardIndex int
|
||||
@@ -187,6 +207,7 @@ const (
|
||||
ChapterPointTypeWork ChapterPointType = 3 // 作业
|
||||
ChapterPointTypeHyperlink ChapterPointType = 4 // 外链
|
||||
ChapterPointTypeLive ChapterPointType = 5 // 直播
|
||||
ChapterPointTypeExam ChapterPointType = 6 // 考试
|
||||
)
|
||||
|
||||
// ChapterPoint 统一的章节任务点结构(与Card一一对应)
|
||||
@@ -245,6 +266,13 @@ type ChapterPoint struct {
|
||||
LiveSetEnc string
|
||||
LiveSwDsEnc string
|
||||
VideoCompletePercent float64
|
||||
|
||||
// 考试相关字段
|
||||
ExamID string // 考试ID
|
||||
PaperId string // 试卷ID
|
||||
ExamRelationId string // 考试关联ID
|
||||
TotalScore float64 // 总分
|
||||
ExamDuration int // 考试时长(分钟)
|
||||
}
|
||||
|
||||
// QuestionType 题目类型
|
||||
|
||||
@@ -10,8 +10,11 @@ import (
|
||||
)
|
||||
|
||||
// ApiVerifyCodeBytes 获取学习通验证码图片
|
||||
func ApiVerifyCodeBytes(client *resty.Client) ([]byte, error) {
|
||||
urlStr := "https://mooc1-api.chaoxing.com/processVerifyPng.ac?t=" + fmt.Sprintf("%d", time.Now().UnixMilli())
|
||||
func ApiVerifyCodeBytes(client *resty.Client, prefixUrl string) ([]byte, error) {
|
||||
if prefixUrl == "" {
|
||||
prefixUrl = "https://mooc1-api.chaoxing.com"
|
||||
}
|
||||
urlStr := prefixUrl + "/processVerifyPng.ac?t=" + fmt.Sprintf("%d", time.Now().UnixMilli())
|
||||
|
||||
req := client.R()
|
||||
req.Header.Add("User-Agent", GetUA("mobile"))
|
||||
@@ -31,12 +34,15 @@ func ApiVerifyCodeBytes(client *resty.Client) ([]byte, error) {
|
||||
}
|
||||
|
||||
// ApiPassVerifyCode 提交学习通验证码
|
||||
func ApiPassVerifyCode(client *resty.Client, code string, retryCount int) bool {
|
||||
func ApiPassVerifyCode(client *resty.Client, code string, prefixUrl string, retryCount int) bool {
|
||||
if retryCount < 0 {
|
||||
return false
|
||||
}
|
||||
if prefixUrl == "" {
|
||||
prefixUrl = "https://mooc1-api.chaoxing.com"
|
||||
}
|
||||
//urlStr := "https://mooc1-api.chaoxing.com/html/processVerify.ac"
|
||||
urlStr := "https://mooc1-api.chaoxing.com/json/processVerify.ac"
|
||||
urlStr := prefixUrl + "/html/processVerify.ac"
|
||||
client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
req := client.R()
|
||||
req.SetMultipartField("ucode", "", "", strings.NewReader(code))
|
||||
@@ -47,8 +53,8 @@ func ApiPassVerifyCode(client *resty.Client, code string, retryCount int) bool {
|
||||
req.Header.Add("Connection", "keep-alive")
|
||||
res, err := req.Post(urlStr)
|
||||
if err != nil {
|
||||
return ApiPassVerifyCode(client, code, retryCount-1)
|
||||
return ApiPassVerifyCode(client, code, prefixUrl, retryCount-1)
|
||||
}
|
||||
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
return res.StatusCode() == 302
|
||||
return res.StatusCode() == 302 || strings.Contains(res.String(), "解锁成功")
|
||||
}
|
||||
|
||||
@@ -232,13 +232,13 @@ func (c *XuexitongCourse) FetchChapterCards(ctx context.Context, course entity.X
|
||||
if strings.Contains(body, "请输入验证码") {
|
||||
logger.Logger.Warn("触发验证码,正在进行AI智能识别绕过.....")
|
||||
e := retry.Do(func() error {
|
||||
bytes, e := ApiVerifyCodeBytes(c.client)
|
||||
bytes, e := ApiVerifyCodeBytes(c.client, "")
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
codeResult := captcha.ApiSemiOCRVerification(bytes, 1, 23)
|
||||
time.Sleep(5 * time.Second)
|
||||
if ApiPassVerifyCode(c.client, codeResult, 5) {
|
||||
if ApiPassVerifyCode(c.client, codeResult, "", 5) {
|
||||
return nil
|
||||
}
|
||||
return errors2.NewBusinessError(503, "验证码识别失败")
|
||||
@@ -288,7 +288,9 @@ func (c *XuexitongCourse) FetchChapterCards(ctx context.Context, course entity.X
|
||||
logger.Logger.Debug("解析卡片失败", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if iframes == nil {
|
||||
continue
|
||||
}
|
||||
for _, iframe := range iframes {
|
||||
if !iframe.HasData {
|
||||
continue
|
||||
@@ -319,7 +321,7 @@ func (c *XuexitongCourse) FetchChapterCards(ctx context.Context, course entity.X
|
||||
point.ObjectID = objectID
|
||||
point.JobID = jobID
|
||||
|
||||
cords2, err := c.FetchChapterCards2(ctx, course, card.KnowledgeID)
|
||||
cords2, err := c.FetchChapterCards2(ctx, course, cardIndex, card.KnowledgeID)
|
||||
if err == nil && cords2 != nil {
|
||||
c.fillVideoPointFromCards2(cords2, &point, objectID)
|
||||
}
|
||||
@@ -524,9 +526,9 @@ func (c *XuexitongCourse) parseIframeData(htmlString string) ([]entity.IframeAtt
|
||||
}
|
||||
|
||||
// FetchChapterCards2 获取章节卡片(新接口,用于获取 otherInfo 等)
|
||||
func (c *XuexitongCourse) FetchChapterCards2(ctx context.Context, course entity.XueXiTCourse, knowledgeID int) (map[string]interface{}, error) {
|
||||
urlStr := fmt.Sprintf("%s?clazzid=%s&courseid=%s&knowledgeid=%d&num=0&ut=s&cpi=%d&v=2025-0424-1038-3&mooc2=1&isMicroCourse=false&editorPreview=0",
|
||||
ApiChapterCards2, course.Key, course.CourseID, knowledgeID, course.Cpi)
|
||||
func (c *XuexitongCourse) FetchChapterCards2(ctx context.Context, course entity.XueXiTCourse, num int, knowledgeID int) (map[string]interface{}, error) {
|
||||
urlStr := fmt.Sprintf("%s?clazzid=%s&courseid=%s&knowledgeid=%d&num=%d&ut=s&cpi=%d&v=2025-0424-1038-3&mooc2=1&isMicroCourse=false&editorPreview=0",
|
||||
ApiChapterCards2, course.Key, course.CourseID, knowledgeID, num, course.Cpi)
|
||||
|
||||
logger.Logger.Debug("FetchChapterCards2 请求",
|
||||
zap.String("url", urlStr),
|
||||
|
||||
@@ -105,12 +105,12 @@ func (c *XuexitongCourse) PullCourseList(ctx context.Context) []entity.XueXiTCou
|
||||
if strings.Contains(body, "请输入验证码") {
|
||||
logger.Logger.Warn("触发验证码,正在进行AI智能识别绕过.....")
|
||||
e := retry.Do(func() error {
|
||||
bytes, e := ApiVerifyCodeBytes(c.client)
|
||||
bytes, e := ApiVerifyCodeBytes(c.client, "")
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
codeResult := captcha.ApiSemiOCRVerification(bytes, 1, 23)
|
||||
if ApiPassVerifyCode(c.client, codeResult, 5) {
|
||||
if ApiPassVerifyCode(c.client, codeResult, "", 5) {
|
||||
return nil
|
||||
}
|
||||
return errors2.NewBusinessError(503, "验证码识别失败")
|
||||
|
||||
@@ -3,11 +3,13 @@ package xuexitong
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/application/quesbank"
|
||||
"likedream-goframe/internal/application/xuexitong/entity"
|
||||
"likedream-goframe/internal/common/db"
|
||||
"likedream-goframe/internal/ent/sysconfig"
|
||||
"likedream-goframe/internal/framework/errors"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"likedream-goframe/internal/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -37,7 +39,7 @@ func getAIConfig(ctx context.Context) *quesbank.AIConfig {
|
||||
func TestSchedule(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
xuexitongLogin := NewXuexitongLogin(ctx)
|
||||
err := xuexitongLogin.Login(context.Background(), "13151832241", "yyz@083422", 5)
|
||||
err := xuexitongLogin.Login(context.Background(), "15754238084", "1575423yuan.dby", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
@@ -54,7 +56,7 @@ func TestSchedule(t *testing.T) {
|
||||
logger.Logger.Warn("AI配置为空,作业将被跳过")
|
||||
}
|
||||
|
||||
err = courseService.AutoStudyByCourseIDWithOptions(ctx, "258109701", &AutoStudyOptions{
|
||||
err = courseService.AutoStudyByCourseIDWithOptions(ctx, "255690793", &AutoStudyOptions{
|
||||
AIConfig: config,
|
||||
AutoSubmit: true, // 默认不自动提交,仅保存
|
||||
Thread: 1,
|
||||
@@ -188,3 +190,229 @@ func TestSplitWords(t *testing.T) {
|
||||
split := strings.Split(str, "")
|
||||
t.Log(split)
|
||||
}
|
||||
|
||||
// TestExam 测试考试功能 - 完成整个考试流程但不提交(只暂存)
|
||||
func TestExam(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 登录
|
||||
xuexitongLogin := NewXuexitongLogin(ctx)
|
||||
err := xuexitongLogin.Login(ctx, "17320926947", "zhijie2001", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseService := NewXuexitongCourse(client)
|
||||
|
||||
// 获取AI配置(可选,用于AI答题兜底)
|
||||
aiConfig := getAIConfig(ctx)
|
||||
if aiConfig != nil {
|
||||
t.Logf("AI配置加载成功: %s %s", aiConfig.Type, aiConfig.Model)
|
||||
} else {
|
||||
t.Log("未配置AI,将仅使用题库答题")
|
||||
}
|
||||
|
||||
// 创建统一答题服务(本地题库 -> 第三方题库 -> AI答题)
|
||||
answerService := quesbank.NewUnifiedAnswerService(aiConfig)
|
||||
|
||||
// 获取课程列表,找到指定课程
|
||||
courseList := courseService.PullCourseList(ctx)
|
||||
var targetCourse *entity.XueXiTCourse
|
||||
for i, course := range courseList {
|
||||
if course.CourseID == "259828097" {
|
||||
targetCourse = &courseList[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetCourse == nil {
|
||||
t.Fatal("未找到指定课程")
|
||||
}
|
||||
|
||||
t.Logf("找到课程: %s (ID: %s)", targetCourse.CourseName, targetCourse.CourseID)
|
||||
|
||||
// 使用考试列表API获取考试
|
||||
examList, err := courseService.FetchExamTaskList(ctx, *targetCourse)
|
||||
if err != nil {
|
||||
t.Fatalf("获取考试列表失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("考试数量: %d", len(examList))
|
||||
for _, exam := range examList {
|
||||
t.Logf("考试: %s (状态: %s, TaskRefId: %s)",
|
||||
exam.Name, exam.Status, exam.TaskRefId)
|
||||
}
|
||||
|
||||
// 找一个待做的考试
|
||||
var targetExam *ExamTaskItem
|
||||
for i := range examList {
|
||||
if examList[i].Status == "待做" {
|
||||
targetExam = &examList[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetExam == nil {
|
||||
t.Log("没有待做的考试")
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("开始考试: %s", targetExam.Name)
|
||||
|
||||
// 进入考试
|
||||
err = courseService.EnterExam(ctx, targetExam)
|
||||
if err != nil {
|
||||
t.Fatalf("进入考试失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("进入考试成功, 题目数: %d, ExamRelationId: %s, AnswerId: %s",
|
||||
targetExam.QuestionTotal, targetExam.ExamRelationId, targetExam.AnswerId)
|
||||
|
||||
if targetExam.QuestionTotal == 0 {
|
||||
t.Log("考试没有题目")
|
||||
return
|
||||
}
|
||||
|
||||
// 遍历所有题目
|
||||
for i := 0; i < targetExam.QuestionTotal; i++ {
|
||||
t.Logf("========== 第 %d/%d 题 ==========", i, targetExam.QuestionTotal)
|
||||
|
||||
// 拉取题目
|
||||
question, err := courseService.PullExamQuestion(ctx, targetExam, i)
|
||||
if err != nil {
|
||||
t.Logf("拉取第%d题失败: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
t.Logf("题目类型: %s", question.QuestionTypeStr)
|
||||
t.Logf("题目内容: %s", question.Content)
|
||||
if len(question.Options) > 0 {
|
||||
t.Logf("选项: %v", question.Options)
|
||||
}
|
||||
|
||||
// 使用统一答题服务获取答案(本地题库 -> 第三方题库 -> AI答题)
|
||||
questionInfo := quesbank.QuestionInfo{
|
||||
Content: question.Content,
|
||||
TypeCode: question.QuestionTypeCode,
|
||||
TypeName: quesbank.GetQuestionTypeName(question.QuestionTypeCode),
|
||||
Options: question.Options,
|
||||
CourseName: targetCourse.CourseName,
|
||||
QuestionID: question.QuestionId,
|
||||
}
|
||||
|
||||
result, err := answerService.GetAnswer(ctx, questionInfo)
|
||||
if err != nil {
|
||||
t.Logf("获取答案失败: %v, 使用默认答案", err)
|
||||
question.Answers = []string{"A"}
|
||||
} else {
|
||||
question.Answers = result.Answers
|
||||
t.Logf("答案来源: %s, 答案: %v", result.Source, result.Answers)
|
||||
}
|
||||
|
||||
// 提交答案(暂存,不交卷)
|
||||
// tempSave=true 表示暂存,不交卷
|
||||
submitResult, err := courseService.SubmitExamAnswer(ctx, question, true)
|
||||
if err != nil {
|
||||
t.Logf("提交第%d题答案失败: %v", i, err)
|
||||
} else {
|
||||
t.Logf("提交第%d题答案成功: %s", i, submitResult)
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("========== 考试完成(已暂存,未交卷)==========")
|
||||
}
|
||||
|
||||
// buildExamQuestionPrompt 构建考试题目的AI提示词
|
||||
func buildExamQuestionPrompt(q *ExamQuestion) string {
|
||||
var prompt strings.Builder
|
||||
|
||||
switch q.QuestionTypeCode {
|
||||
case "0": // 单选题
|
||||
prompt.WriteString("题目类型:单选题\n")
|
||||
prompt.WriteString("题目内容:" + q.Content + "\n")
|
||||
for _, opt := range q.Options {
|
||||
prompt.WriteString(opt + "\n")
|
||||
}
|
||||
prompt.WriteString("\n请只回答选项字母,不要解释。格式:[\"A\"]")
|
||||
|
||||
case "1": // 多选题
|
||||
prompt.WriteString("题目类型:多选题\n")
|
||||
prompt.WriteString("题目内容:" + q.Content + "\n")
|
||||
for _, opt := range q.Options {
|
||||
prompt.WriteString(opt + "\n")
|
||||
}
|
||||
prompt.WriteString("\n请只回答选项字母,不要解释。格式:[\"A\",\"B\"]")
|
||||
|
||||
case "3": // 判断题
|
||||
prompt.WriteString("题目类型:判断题\n")
|
||||
prompt.WriteString("题目内容:" + q.Content + "\n")
|
||||
for _, opt := range q.Options {
|
||||
prompt.WriteString(opt + "\n")
|
||||
}
|
||||
prompt.WriteString("\n请只回答\"A\"(正确)或\"B\"(错误),不要解释。格式:[\"A\"]")
|
||||
|
||||
case "2": // 填空题
|
||||
prompt.WriteString("题目类型:填空题\n")
|
||||
prompt.WriteString("题目内容:" + q.Content + "\n")
|
||||
blankCount := len(q.Options)
|
||||
if blankCount == 0 {
|
||||
blankCount = 1
|
||||
}
|
||||
prompt.WriteString("共有" + strconv.Itoa(blankCount) + "个空\n")
|
||||
prompt.WriteString("\n请按顺序回答每个空的答案,不要解释。格式:[\"答案1\",\"答案2\"]")
|
||||
|
||||
case "4", "6": // 简答题、论述题
|
||||
prompt.WriteString("题目类型:简答题\n")
|
||||
prompt.WriteString("题目内容:" + q.Content + "\n")
|
||||
prompt.WriteString("\n请直接回答问题,格式:[\"你的答案\"]")
|
||||
|
||||
default:
|
||||
prompt.WriteString("题目:" + q.Content + "\n")
|
||||
prompt.WriteString("请回答这道题,格式:[\"答案\"]")
|
||||
}
|
||||
|
||||
return prompt.String()
|
||||
}
|
||||
|
||||
// TestExamDetail 测试获取考试详情
|
||||
func TestExamDetail(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 登录
|
||||
xuexitongLogin := NewXuexitongLogin(ctx)
|
||||
err := xuexitongLogin.Login(ctx, "13151832241", "yyz@083422", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseService := NewXuexitongCourse(client)
|
||||
|
||||
// 获取课程列表,找到指定课程
|
||||
courseList := courseService.PullCourseList(ctx)
|
||||
var targetCourse *entity.XueXiTCourse
|
||||
for i, course := range courseList {
|
||||
if course.CourseID == "258109701" {
|
||||
targetCourse = &courseList[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetCourse == nil {
|
||||
t.Fatal("未找到指定课程 courseId=258109701")
|
||||
}
|
||||
|
||||
// 获取考试列表
|
||||
examList, err := courseService.FetchExamTaskList(ctx, *targetCourse)
|
||||
if err != nil {
|
||||
t.Fatalf("获取考试列表失败: %v", err)
|
||||
}
|
||||
|
||||
if len(examList) == 0 {
|
||||
t.Log("该课程没有考试")
|
||||
return
|
||||
}
|
||||
|
||||
// 打印所有考试信息
|
||||
for i, exam := range examList {
|
||||
t.Logf("考试%d: 标题=%s, 状态=%s, TaskRefId=%s, CourseId=%s, ClazzId=%s",
|
||||
i+1, exam.Name, exam.Status, exam.TaskRefId, exam.CourseId, exam.ClazzId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,6 +252,7 @@ func (c *XuexitongCourse) attachmentDetectVideo(attachment *entity.AttachmentSet
|
||||
|
||||
// AutoPlayVideoByPoint 自动播放视频(使用统一的 ChapterPoint)
|
||||
func (c *XuexitongCourse) AutoPlayVideoByPoint(ctx context.Context, course entity.XueXiTCourse, p *entity.ChapterPoint) error {
|
||||
|
||||
if err := c.VideoDtoFetchByPoint(ctx, p); err != nil {
|
||||
return fmt.Errorf("获取视频信息失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ const (
|
||||
ConfigRedisCpdailyPhone = "price.cpdaily_phone"
|
||||
ConfigRedisXxt = "price.xxt"
|
||||
ConfigRedisSmarteduSc = "price.smartedu_sc"
|
||||
|
||||
// 第三方题库配置
|
||||
ConfigQuesBankUrl = "quesbank.url"
|
||||
ConfigQuesBankKey = "quesbank.key"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -17,5 +17,5 @@ var (
|
||||
RedisBalanceMigrateExpire = int64(3600) // 积分转移链接1小时有效
|
||||
RedisCpUserLoginExpire = int64(180)
|
||||
RedisCpUserAuthorizeExpire = int64(1800)
|
||||
RedisSchoolInfoExpire = int64(3600) // 学校信息1小时有效
|
||||
RedisSchoolInfoExpire = int64(3600 * 24 * 30) // 学校信息30天有效
|
||||
)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package cst
|
||||
|
||||
const (
|
||||
TaskCpdaily = "task:cpdaily"
|
||||
TaskEmail = "task:email"
|
||||
TaskResetDaily = "task:reset:daily"
|
||||
TaskXuexitong = "task:xuexitong"
|
||||
TaskXuexitongVio = "task:xuexitong:violence"
|
||||
TaskCpdaily = "task:cpdaily"
|
||||
TaskEmail = "task:email"
|
||||
TaskResetDaily = "task:reset:daily"
|
||||
TaskRefreshSchoolDetail = "task:refresh:school:detail"
|
||||
TaskXuexitong = "task:xuexitong"
|
||||
TaskXuexitongVio = "task:xuexitong:violence"
|
||||
|
||||
TaskCpdailyId = "cpdaily:task:schedule:%d"
|
||||
|
||||
|
||||
@@ -66,9 +66,9 @@ func (c CpUserCtrlV1) AuthorizeConfirm(ctx context.Context, req *v1.CpUserAuthor
|
||||
return service.Global.CpUser.AuthorizeConfirm(ctx, req), nil
|
||||
}
|
||||
|
||||
func (c CpUserCtrlV1) AuthorizeRandom(ctx context.Context, req *v1.CpUserAuthorizeRandomReq) (res *utils.Response[any], err error) {
|
||||
func (c CpUserCtrlV1) CustomDevice(ctx context.Context, req *v1.CpUserCustomDeviceReq) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
return service.Global.CpUser.AuthorizeRandom(ctx, req), nil
|
||||
return service.Global.CpUser.CustomDevice(ctx, req), nil
|
||||
}
|
||||
func (c CpUserCtrlV1) LoginGetCode(ctx context.Context, req *v1.LoginGetCode) (res *utils.Response[any], err error) {
|
||||
defer utils.Pass(&err)
|
||||
|
||||
@@ -25,8 +25,8 @@ type ICpUserCtrlV1 interface {
|
||||
AuthorizeDevice(ctx context.Context, req *v1.CpUserAuthorizeDeviceReq) (*utils.Response[any], error)
|
||||
// 验证授权是否成功
|
||||
AuthorizeConfirm(ctx context.Context, req *v1.CpUserAuthorizeConfirmReq) (*utils.Response[any], error)
|
||||
// 随机授权
|
||||
AuthorizeRandom(ctx context.Context, req *v1.CpUserAuthorizeRandomReq) (res *utils.Response[any], err error)
|
||||
// 自定义设备码
|
||||
CustomDevice(ctx context.Context, req *v1.CpUserCustomDeviceReq) (res *utils.Response[any], err error)
|
||||
|
||||
// 手机登录获取验证码
|
||||
LoginGetCode(ctx context.Context, req *v1.LoginGetCode) (*utils.Response[any], error)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package ent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"likedream-goframe/internal/ent/cptask"
|
||||
"likedream-goframe/internal/ent/cpuser"
|
||||
@@ -51,6 +52,8 @@ type CpTask struct {
|
||||
ExecuteMinute *int `json:"execute_minute,omitempty"`
|
||||
// NextExecuteTime holds the value of the "next_execute_time" field.
|
||||
NextExecuteTime *int64 `json:"next_execute_time,omitempty"`
|
||||
// 执行星期 1-7,1=周一,7=周日
|
||||
ExecuteDays []int `json:"execute_days,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the CpTaskQuery when eager-loading is set.
|
||||
Edges CpTaskEdges `json:"edges"`
|
||||
@@ -107,6 +110,8 @@ func (*CpTask) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case cptask.FieldExecuteDays:
|
||||
values[i] = new([]byte)
|
||||
case cptask.FieldCreateTime, cptask.FieldUpdateTime:
|
||||
values[i] = new(ent2.CustomTime)
|
||||
case cptask.FieldID, cptask.FieldCpUserID, cptask.FieldUserID, cptask.FieldFrequency, cptask.FieldSuccessCount, cptask.FieldExecuteHour, cptask.FieldExecuteMinute, cptask.FieldNextExecuteTime:
|
||||
@@ -233,6 +238,14 @@ func (_m *CpTask) assignValues(columns []string, values []any) error {
|
||||
_m.NextExecuteTime = new(int64)
|
||||
*_m.NextExecuteTime = value.Int64
|
||||
}
|
||||
case cptask.FieldExecuteDays:
|
||||
if value, ok := values[i].(*[]byte); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field execute_days", values[i])
|
||||
} else if value != nil && len(*value) > 0 {
|
||||
if err := json.Unmarshal(*value, &_m.ExecuteDays); err != nil {
|
||||
return fmt.Errorf("unmarshal field execute_days: %w", err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
@@ -337,6 +350,9 @@ func (_m *CpTask) String() string {
|
||||
builder.WriteString("next_execute_time=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("execute_days=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.ExecuteDays))
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ const (
|
||||
FieldExecuteMinute = "execute_minute"
|
||||
// FieldNextExecuteTime holds the string denoting the next_execute_time field in the database.
|
||||
FieldNextExecuteTime = "next_execute_time"
|
||||
// FieldExecuteDays holds the string denoting the execute_days field in the database.
|
||||
FieldExecuteDays = "execute_days"
|
||||
// EdgeCpUser holds the string denoting the cp_user edge name in mutations.
|
||||
EdgeCpUser = "cp_user"
|
||||
// EdgeSysUser holds the string denoting the sys_user edge name in mutations.
|
||||
@@ -98,6 +100,7 @@ var Columns = []string{
|
||||
FieldExecuteHour,
|
||||
FieldExecuteMinute,
|
||||
FieldNextExecuteTime,
|
||||
FieldExecuteDays,
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
|
||||
@@ -821,6 +821,16 @@ func NextExecuteTimeNotNil() predicate.CpTask {
|
||||
return predicate.CpTask(sql.FieldNotNull(FieldNextExecuteTime))
|
||||
}
|
||||
|
||||
// ExecuteDaysIsNil applies the IsNil predicate on the "execute_days" field.
|
||||
func ExecuteDaysIsNil() predicate.CpTask {
|
||||
return predicate.CpTask(sql.FieldIsNull(FieldExecuteDays))
|
||||
}
|
||||
|
||||
// ExecuteDaysNotNil applies the NotNil predicate on the "execute_days" field.
|
||||
func ExecuteDaysNotNil() predicate.CpTask {
|
||||
return predicate.CpTask(sql.FieldNotNull(FieldExecuteDays))
|
||||
}
|
||||
|
||||
// HasCpUser applies the HasEdge predicate on the "cp_user" edge.
|
||||
func HasCpUser() predicate.CpTask {
|
||||
return predicate.CpTask(func(s *sql.Selector) {
|
||||
|
||||
@@ -177,6 +177,12 @@ func (_c *CpTaskCreate) SetNillableNextExecuteTime(v *int64) *CpTaskCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetExecuteDays sets the "execute_days" field.
|
||||
func (_c *CpTaskCreate) SetExecuteDays(v []int) *CpTaskCreate {
|
||||
_c.mutation.SetExecuteDays(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetCpUser sets the "cp_user" edge to the CpUser entity.
|
||||
func (_c *CpTaskCreate) SetCpUser(v *CpUser) *CpTaskCreate {
|
||||
return _c.SetCpUserID(v.ID)
|
||||
@@ -420,6 +426,10 @@ func (_c *CpTaskCreate) createSpec() (*CpTask, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(cptask.FieldNextExecuteTime, field.TypeInt64, value)
|
||||
_node.NextExecuteTime = &value
|
||||
}
|
||||
if value, ok := _c.mutation.ExecuteDays(); ok {
|
||||
_spec.SetField(cptask.FieldExecuteDays, field.TypeJSON, value)
|
||||
_node.ExecuteDays = value
|
||||
}
|
||||
if nodes := _c.mutation.CpUserIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
@@ -765,6 +775,24 @@ func (u *CpTaskUpsert) ClearNextExecuteTime() *CpTaskUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetExecuteDays sets the "execute_days" field.
|
||||
func (u *CpTaskUpsert) SetExecuteDays(v []int) *CpTaskUpsert {
|
||||
u.Set(cptask.FieldExecuteDays, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateExecuteDays sets the "execute_days" field to the value that was provided on create.
|
||||
func (u *CpTaskUpsert) UpdateExecuteDays() *CpTaskUpsert {
|
||||
u.SetExcluded(cptask.FieldExecuteDays)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearExecuteDays clears the value of the "execute_days" field.
|
||||
func (u *CpTaskUpsert) ClearExecuteDays() *CpTaskUpsert {
|
||||
u.SetNull(cptask.FieldExecuteDays)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
@@ -1090,6 +1118,27 @@ func (u *CpTaskUpsertOne) ClearNextExecuteTime() *CpTaskUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetExecuteDays sets the "execute_days" field.
|
||||
func (u *CpTaskUpsertOne) SetExecuteDays(v []int) *CpTaskUpsertOne {
|
||||
return u.Update(func(s *CpTaskUpsert) {
|
||||
s.SetExecuteDays(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateExecuteDays sets the "execute_days" field to the value that was provided on create.
|
||||
func (u *CpTaskUpsertOne) UpdateExecuteDays() *CpTaskUpsertOne {
|
||||
return u.Update(func(s *CpTaskUpsert) {
|
||||
s.UpdateExecuteDays()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearExecuteDays clears the value of the "execute_days" field.
|
||||
func (u *CpTaskUpsertOne) ClearExecuteDays() *CpTaskUpsertOne {
|
||||
return u.Update(func(s *CpTaskUpsert) {
|
||||
s.ClearExecuteDays()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *CpTaskUpsertOne) Exec(ctx context.Context) error {
|
||||
if len(u.create.conflict) == 0 {
|
||||
@@ -1581,6 +1630,27 @@ func (u *CpTaskUpsertBulk) ClearNextExecuteTime() *CpTaskUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetExecuteDays sets the "execute_days" field.
|
||||
func (u *CpTaskUpsertBulk) SetExecuteDays(v []int) *CpTaskUpsertBulk {
|
||||
return u.Update(func(s *CpTaskUpsert) {
|
||||
s.SetExecuteDays(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateExecuteDays sets the "execute_days" field to the value that was provided on create.
|
||||
func (u *CpTaskUpsertBulk) UpdateExecuteDays() *CpTaskUpsertBulk {
|
||||
return u.Update(func(s *CpTaskUpsert) {
|
||||
s.UpdateExecuteDays()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearExecuteDays clears the value of the "execute_days" field.
|
||||
func (u *CpTaskUpsertBulk) ClearExecuteDays() *CpTaskUpsertBulk {
|
||||
return u.Update(func(s *CpTaskUpsert) {
|
||||
s.ClearExecuteDays()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *CpTaskUpsertBulk) Exec(ctx context.Context) error {
|
||||
if u.create.err != nil {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/dialect/sql/sqljson"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
@@ -300,6 +301,24 @@ func (_u *CpTaskUpdate) ClearNextExecuteTime() *CpTaskUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExecuteDays sets the "execute_days" field.
|
||||
func (_u *CpTaskUpdate) SetExecuteDays(v []int) *CpTaskUpdate {
|
||||
_u.mutation.SetExecuteDays(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AppendExecuteDays appends value to the "execute_days" field.
|
||||
func (_u *CpTaskUpdate) AppendExecuteDays(v []int) *CpTaskUpdate {
|
||||
_u.mutation.AppendExecuteDays(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearExecuteDays clears the value of the "execute_days" field.
|
||||
func (_u *CpTaskUpdate) ClearExecuteDays() *CpTaskUpdate {
|
||||
_u.mutation.ClearExecuteDays()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetCpUser sets the "cp_user" edge to the CpUser entity.
|
||||
func (_u *CpTaskUpdate) SetCpUser(v *CpUser) *CpTaskUpdate {
|
||||
return _u.SetCpUserID(v.ID)
|
||||
@@ -519,6 +538,17 @@ func (_u *CpTaskUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if _u.mutation.NextExecuteTimeCleared() {
|
||||
_spec.ClearField(cptask.FieldNextExecuteTime, field.TypeInt64)
|
||||
}
|
||||
if value, ok := _u.mutation.ExecuteDays(); ok {
|
||||
_spec.SetField(cptask.FieldExecuteDays, field.TypeJSON, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AppendedExecuteDays(); ok {
|
||||
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||
sqljson.Append(u, cptask.FieldExecuteDays, value)
|
||||
})
|
||||
}
|
||||
if _u.mutation.ExecuteDaysCleared() {
|
||||
_spec.ClearField(cptask.FieldExecuteDays, field.TypeJSON)
|
||||
}
|
||||
if _u.mutation.CpUserCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
@@ -920,6 +950,24 @@ func (_u *CpTaskUpdateOne) ClearNextExecuteTime() *CpTaskUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExecuteDays sets the "execute_days" field.
|
||||
func (_u *CpTaskUpdateOne) SetExecuteDays(v []int) *CpTaskUpdateOne {
|
||||
_u.mutation.SetExecuteDays(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AppendExecuteDays appends value to the "execute_days" field.
|
||||
func (_u *CpTaskUpdateOne) AppendExecuteDays(v []int) *CpTaskUpdateOne {
|
||||
_u.mutation.AppendExecuteDays(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearExecuteDays clears the value of the "execute_days" field.
|
||||
func (_u *CpTaskUpdateOne) ClearExecuteDays() *CpTaskUpdateOne {
|
||||
_u.mutation.ClearExecuteDays()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetCpUser sets the "cp_user" edge to the CpUser entity.
|
||||
func (_u *CpTaskUpdateOne) SetCpUser(v *CpUser) *CpTaskUpdateOne {
|
||||
return _u.SetCpUserID(v.ID)
|
||||
@@ -1169,6 +1217,17 @@ func (_u *CpTaskUpdateOne) sqlSave(ctx context.Context) (_node *CpTask, err erro
|
||||
if _u.mutation.NextExecuteTimeCleared() {
|
||||
_spec.ClearField(cptask.FieldNextExecuteTime, field.TypeInt64)
|
||||
}
|
||||
if value, ok := _u.mutation.ExecuteDays(); ok {
|
||||
_spec.SetField(cptask.FieldExecuteDays, field.TypeJSON, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AppendedExecuteDays(); ok {
|
||||
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||
sqljson.Append(u, cptask.FieldExecuteDays, value)
|
||||
})
|
||||
}
|
||||
if _u.mutation.ExecuteDaysCleared() {
|
||||
_spec.ClearField(cptask.FieldExecuteDays, field.TypeJSON)
|
||||
}
|
||||
if _u.mutation.CpUserCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
|
||||
+12
-1
@@ -37,6 +37,8 @@ type CpUser struct {
|
||||
MobileCode string `json:"mobile_code,omitempty"`
|
||||
// Cookie holds the value of the "cookie" field.
|
||||
Cookie string `json:"cookie,omitempty"`
|
||||
// Tgc holds the value of the "tgc" field.
|
||||
Tgc string `json:"tgc,omitempty"`
|
||||
// MobileType holds the value of the "mobile_type" field.
|
||||
MobileType cpuser.MobileType `json:"mobile_type,omitempty"`
|
||||
// LoginType holds the value of the "login_type" field.
|
||||
@@ -125,7 +127,7 @@ func (*CpUser) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new(ent2.CustomTime)
|
||||
case cpuser.FieldID, cpuser.FieldUserID:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case cpuser.FieldUsername, cpuser.FieldPassword, cpuser.FieldSchoolID, cpuser.FieldEmail, cpuser.FieldMobileCode, cpuser.FieldCookie, cpuser.FieldMobileType, cpuser.FieldLoginType:
|
||||
case cpuser.FieldUsername, cpuser.FieldPassword, cpuser.FieldSchoolID, cpuser.FieldEmail, cpuser.FieldMobileCode, cpuser.FieldCookie, cpuser.FieldTgc, cpuser.FieldMobileType, cpuser.FieldLoginType:
|
||||
values[i] = new(sql.NullString)
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
@@ -202,6 +204,12 @@ func (_m *CpUser) assignValues(columns []string, values []any) error {
|
||||
} else if value.Valid {
|
||||
_m.Cookie = value.String
|
||||
}
|
||||
case cpuser.FieldTgc:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field tgc", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Tgc = value.String
|
||||
}
|
||||
case cpuser.FieldMobileType:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field mobile_type", values[i])
|
||||
@@ -302,6 +310,9 @@ func (_m *CpUser) String() string {
|
||||
builder.WriteString("cookie=")
|
||||
builder.WriteString(_m.Cookie)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("tgc=")
|
||||
builder.WriteString(_m.Tgc)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("mobile_type=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.MobileType))
|
||||
builder.WriteString(", ")
|
||||
|
||||
@@ -34,6 +34,8 @@ const (
|
||||
FieldMobileCode = "mobile_code"
|
||||
// FieldCookie holds the string denoting the cookie field in the database.
|
||||
FieldCookie = "cookie"
|
||||
// FieldTgc holds the string denoting the tgc field in the database.
|
||||
FieldTgc = "tgc"
|
||||
// FieldMobileType holds the string denoting the mobile_type field in the database.
|
||||
FieldMobileType = "mobile_type"
|
||||
// FieldLoginType holds the string denoting the login_type field in the database.
|
||||
@@ -99,6 +101,7 @@ var Columns = []string{
|
||||
FieldEmail,
|
||||
FieldMobileCode,
|
||||
FieldCookie,
|
||||
FieldTgc,
|
||||
FieldMobileType,
|
||||
FieldLoginType,
|
||||
}
|
||||
@@ -137,6 +140,7 @@ type MobileType string
|
||||
const (
|
||||
MobileTypeRandom MobileType = "random"
|
||||
MobileTypeAuthorize MobileType = "authorize"
|
||||
MobileTypeManual MobileType = "manual"
|
||||
)
|
||||
|
||||
func (mt MobileType) String() string {
|
||||
@@ -146,7 +150,7 @@ func (mt MobileType) String() string {
|
||||
// MobileTypeValidator is a validator for the "mobile_type" field enum values. It is called by the builders before save.
|
||||
func MobileTypeValidator(mt MobileType) error {
|
||||
switch mt {
|
||||
case MobileTypeRandom, MobileTypeAuthorize:
|
||||
case MobileTypeRandom, MobileTypeAuthorize, MobileTypeManual:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("cpuser: invalid enum value for mobile_type field: %q", mt)
|
||||
@@ -232,6 +236,11 @@ func ByCookie(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCookie, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByTgc orders the results by the tgc field.
|
||||
func ByTgc(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldTgc, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByMobileType orders the results by the mobile_type field.
|
||||
func ByMobileType(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldMobileType, opts...).ToFunc()
|
||||
|
||||
@@ -101,6 +101,11 @@ func Cookie(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldEQ(FieldCookie, v))
|
||||
}
|
||||
|
||||
// Tgc applies equality check predicate on the "tgc" field. It's identical to TgcEQ.
|
||||
func Tgc(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldEQ(FieldTgc, v))
|
||||
}
|
||||
|
||||
// CreateTimeEQ applies the EQ predicate on the "create_time" field.
|
||||
func CreateTimeEQ(v *ent2.CustomTime) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldEQ(FieldCreateTime, v))
|
||||
@@ -691,6 +696,81 @@ func CookieContainsFold(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldContainsFold(FieldCookie, v))
|
||||
}
|
||||
|
||||
// TgcEQ applies the EQ predicate on the "tgc" field.
|
||||
func TgcEQ(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldEQ(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcNEQ applies the NEQ predicate on the "tgc" field.
|
||||
func TgcNEQ(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldNEQ(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcIn applies the In predicate on the "tgc" field.
|
||||
func TgcIn(vs ...string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldIn(FieldTgc, vs...))
|
||||
}
|
||||
|
||||
// TgcNotIn applies the NotIn predicate on the "tgc" field.
|
||||
func TgcNotIn(vs ...string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldNotIn(FieldTgc, vs...))
|
||||
}
|
||||
|
||||
// TgcGT applies the GT predicate on the "tgc" field.
|
||||
func TgcGT(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldGT(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcGTE applies the GTE predicate on the "tgc" field.
|
||||
func TgcGTE(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldGTE(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcLT applies the LT predicate on the "tgc" field.
|
||||
func TgcLT(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldLT(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcLTE applies the LTE predicate on the "tgc" field.
|
||||
func TgcLTE(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldLTE(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcContains applies the Contains predicate on the "tgc" field.
|
||||
func TgcContains(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldContains(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcHasPrefix applies the HasPrefix predicate on the "tgc" field.
|
||||
func TgcHasPrefix(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldHasPrefix(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcHasSuffix applies the HasSuffix predicate on the "tgc" field.
|
||||
func TgcHasSuffix(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldHasSuffix(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcIsNil applies the IsNil predicate on the "tgc" field.
|
||||
func TgcIsNil() predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldIsNull(FieldTgc))
|
||||
}
|
||||
|
||||
// TgcNotNil applies the NotNil predicate on the "tgc" field.
|
||||
func TgcNotNil() predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldNotNull(FieldTgc))
|
||||
}
|
||||
|
||||
// TgcEqualFold applies the EqualFold predicate on the "tgc" field.
|
||||
func TgcEqualFold(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldEqualFold(FieldTgc, v))
|
||||
}
|
||||
|
||||
// TgcContainsFold applies the ContainsFold predicate on the "tgc" field.
|
||||
func TgcContainsFold(v string) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldContainsFold(FieldTgc, v))
|
||||
}
|
||||
|
||||
// MobileTypeEQ applies the EQ predicate on the "mobile_type" field.
|
||||
func MobileTypeEQ(v MobileType) predicate.CpUser {
|
||||
return predicate.CpUser(sql.FieldEQ(FieldMobileType, v))
|
||||
|
||||
@@ -113,6 +113,20 @@ func (_c *CpUserCreate) SetNillableCookie(v *string) *CpUserCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetTgc sets the "tgc" field.
|
||||
func (_c *CpUserCreate) SetTgc(v string) *CpUserCreate {
|
||||
_c.mutation.SetTgc(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableTgc sets the "tgc" field if the given value is not nil.
|
||||
func (_c *CpUserCreate) SetNillableTgc(v *string) *CpUserCreate {
|
||||
if v != nil {
|
||||
_c.SetTgc(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetMobileType sets the "mobile_type" field.
|
||||
func (_c *CpUserCreate) SetMobileType(v cpuser.MobileType) *CpUserCreate {
|
||||
_c.mutation.SetMobileType(v)
|
||||
@@ -357,6 +371,10 @@ func (_c *CpUserCreate) createSpec() (*CpUser, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(cpuser.FieldCookie, field.TypeString, value)
|
||||
_node.Cookie = value
|
||||
}
|
||||
if value, ok := _c.mutation.Tgc(); ok {
|
||||
_spec.SetField(cpuser.FieldTgc, field.TypeString, value)
|
||||
_node.Tgc = value
|
||||
}
|
||||
if value, ok := _c.mutation.MobileType(); ok {
|
||||
_spec.SetField(cpuser.FieldMobileType, field.TypeEnum, value)
|
||||
_node.MobileType = value
|
||||
@@ -624,6 +642,24 @@ func (u *CpUserUpsert) ClearCookie() *CpUserUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetTgc sets the "tgc" field.
|
||||
func (u *CpUserUpsert) SetTgc(v string) *CpUserUpsert {
|
||||
u.Set(cpuser.FieldTgc, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateTgc sets the "tgc" field to the value that was provided on create.
|
||||
func (u *CpUserUpsert) UpdateTgc() *CpUserUpsert {
|
||||
u.SetExcluded(cpuser.FieldTgc)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearTgc clears the value of the "tgc" field.
|
||||
func (u *CpUserUpsert) ClearTgc() *CpUserUpsert {
|
||||
u.SetNull(cpuser.FieldTgc)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetMobileType sets the "mobile_type" field.
|
||||
func (u *CpUserUpsert) SetMobileType(v cpuser.MobileType) *CpUserUpsert {
|
||||
u.Set(cpuser.FieldMobileType, v)
|
||||
@@ -833,6 +869,27 @@ func (u *CpUserUpsertOne) ClearCookie() *CpUserUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetTgc sets the "tgc" field.
|
||||
func (u *CpUserUpsertOne) SetTgc(v string) *CpUserUpsertOne {
|
||||
return u.Update(func(s *CpUserUpsert) {
|
||||
s.SetTgc(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTgc sets the "tgc" field to the value that was provided on create.
|
||||
func (u *CpUserUpsertOne) UpdateTgc() *CpUserUpsertOne {
|
||||
return u.Update(func(s *CpUserUpsert) {
|
||||
s.UpdateTgc()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearTgc clears the value of the "tgc" field.
|
||||
func (u *CpUserUpsertOne) ClearTgc() *CpUserUpsertOne {
|
||||
return u.Update(func(s *CpUserUpsert) {
|
||||
s.ClearTgc()
|
||||
})
|
||||
}
|
||||
|
||||
// SetMobileType sets the "mobile_type" field.
|
||||
func (u *CpUserUpsertOne) SetMobileType(v cpuser.MobileType) *CpUserUpsertOne {
|
||||
return u.Update(func(s *CpUserUpsert) {
|
||||
@@ -1212,6 +1269,27 @@ func (u *CpUserUpsertBulk) ClearCookie() *CpUserUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetTgc sets the "tgc" field.
|
||||
func (u *CpUserUpsertBulk) SetTgc(v string) *CpUserUpsertBulk {
|
||||
return u.Update(func(s *CpUserUpsert) {
|
||||
s.SetTgc(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTgc sets the "tgc" field to the value that was provided on create.
|
||||
func (u *CpUserUpsertBulk) UpdateTgc() *CpUserUpsertBulk {
|
||||
return u.Update(func(s *CpUserUpsert) {
|
||||
s.UpdateTgc()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearTgc clears the value of the "tgc" field.
|
||||
func (u *CpUserUpsertBulk) ClearTgc() *CpUserUpsertBulk {
|
||||
return u.Update(func(s *CpUserUpsert) {
|
||||
s.ClearTgc()
|
||||
})
|
||||
}
|
||||
|
||||
// SetMobileType sets the "mobile_type" field.
|
||||
func (u *CpUserUpsertBulk) SetMobileType(v cpuser.MobileType) *CpUserUpsertBulk {
|
||||
return u.Update(func(s *CpUserUpsert) {
|
||||
|
||||
@@ -163,6 +163,26 @@ func (_u *CpUserUpdate) ClearCookie() *CpUserUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetTgc sets the "tgc" field.
|
||||
func (_u *CpUserUpdate) SetTgc(v string) *CpUserUpdate {
|
||||
_u.mutation.SetTgc(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableTgc sets the "tgc" field if the given value is not nil.
|
||||
func (_u *CpUserUpdate) SetNillableTgc(v *string) *CpUserUpdate {
|
||||
if v != nil {
|
||||
_u.SetTgc(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearTgc clears the value of the "tgc" field.
|
||||
func (_u *CpUserUpdate) ClearTgc() *CpUserUpdate {
|
||||
_u.mutation.ClearTgc()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetMobileType sets the "mobile_type" field.
|
||||
func (_u *CpUserUpdate) SetMobileType(v cpuser.MobileType) *CpUserUpdate {
|
||||
_u.mutation.SetMobileType(v)
|
||||
@@ -438,6 +458,12 @@ func (_u *CpUserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if _u.mutation.CookieCleared() {
|
||||
_spec.ClearField(cpuser.FieldCookie, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.Tgc(); ok {
|
||||
_spec.SetField(cpuser.FieldTgc, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.TgcCleared() {
|
||||
_spec.ClearField(cpuser.FieldTgc, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.MobileType(); ok {
|
||||
_spec.SetField(cpuser.FieldMobileType, field.TypeEnum, value)
|
||||
}
|
||||
@@ -802,6 +828,26 @@ func (_u *CpUserUpdateOne) ClearCookie() *CpUserUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetTgc sets the "tgc" field.
|
||||
func (_u *CpUserUpdateOne) SetTgc(v string) *CpUserUpdateOne {
|
||||
_u.mutation.SetTgc(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableTgc sets the "tgc" field if the given value is not nil.
|
||||
func (_u *CpUserUpdateOne) SetNillableTgc(v *string) *CpUserUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetTgc(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearTgc clears the value of the "tgc" field.
|
||||
func (_u *CpUserUpdateOne) ClearTgc() *CpUserUpdateOne {
|
||||
_u.mutation.ClearTgc()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetMobileType sets the "mobile_type" field.
|
||||
func (_u *CpUserUpdateOne) SetMobileType(v cpuser.MobileType) *CpUserUpdateOne {
|
||||
_u.mutation.SetMobileType(v)
|
||||
@@ -1107,6 +1153,12 @@ func (_u *CpUserUpdateOne) sqlSave(ctx context.Context) (_node *CpUser, err erro
|
||||
if _u.mutation.CookieCleared() {
|
||||
_spec.ClearField(cpuser.FieldCookie, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.Tgc(); ok {
|
||||
_spec.SetField(cpuser.FieldTgc, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.TgcCleared() {
|
||||
_spec.ClearField(cpuser.FieldTgc, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.MobileType(); ok {
|
||||
_spec.SetField(cpuser.FieldMobileType, field.TypeEnum, value)
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ var schemaGraph = func() *sqlgraph.Schema {
|
||||
cptask.FieldExecuteHour: {Type: field.TypeInt, Column: cptask.FieldExecuteHour},
|
||||
cptask.FieldExecuteMinute: {Type: field.TypeInt, Column: cptask.FieldExecuteMinute},
|
||||
cptask.FieldNextExecuteTime: {Type: field.TypeInt64, Column: cptask.FieldNextExecuteTime},
|
||||
cptask.FieldExecuteDays: {Type: field.TypeJSON, Column: cptask.FieldExecuteDays},
|
||||
},
|
||||
}
|
||||
graph.Nodes[5] = &sqlgraph.Node{
|
||||
@@ -189,6 +190,7 @@ var schemaGraph = func() *sqlgraph.Schema {
|
||||
cpuser.FieldEmail: {Type: field.TypeString, Column: cpuser.FieldEmail},
|
||||
cpuser.FieldMobileCode: {Type: field.TypeString, Column: cpuser.FieldMobileCode},
|
||||
cpuser.FieldCookie: {Type: field.TypeString, Column: cpuser.FieldCookie},
|
||||
cpuser.FieldTgc: {Type: field.TypeString, Column: cpuser.FieldTgc},
|
||||
cpuser.FieldMobileType: {Type: field.TypeEnum, Column: cpuser.FieldMobileType},
|
||||
cpuser.FieldLoginType: {Type: field.TypeEnum, Column: cpuser.FieldLoginType},
|
||||
},
|
||||
@@ -1381,6 +1383,11 @@ func (f *CpTaskFilter) WhereNextExecuteTime(p entql.Int64P) {
|
||||
f.Where(p.Field(cptask.FieldNextExecuteTime))
|
||||
}
|
||||
|
||||
// WhereExecuteDays applies the entql json.RawMessage predicate on the execute_days field.
|
||||
func (f *CpTaskFilter) WhereExecuteDays(p entql.BytesP) {
|
||||
f.Where(p.Field(cptask.FieldExecuteDays))
|
||||
}
|
||||
|
||||
// WhereHasCpUser applies a predicate to check if query has an edge cp_user.
|
||||
func (f *CpTaskFilter) WhereHasCpUser() {
|
||||
f.Where(entql.HasEdge("cp_user"))
|
||||
@@ -1630,6 +1637,11 @@ func (f *CpUserFilter) WhereCookie(p entql.StringP) {
|
||||
f.Where(p.Field(cpuser.FieldCookie))
|
||||
}
|
||||
|
||||
// WhereTgc applies the entql string predicate on the tgc field.
|
||||
func (f *CpUserFilter) WhereTgc(p entql.StringP) {
|
||||
f.Where(p.Field(cpuser.FieldTgc))
|
||||
}
|
||||
|
||||
// WhereMobileType applies the entql string predicate on the mobile_type field.
|
||||
func (f *CpUserFilter) WhereMobileType(p entql.StringP) {
|
||||
f.Where(p.Field(cpuser.FieldMobileType))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -134,6 +134,7 @@ var (
|
||||
{Name: "execute_hour", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "execute_minute", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "next_execute_time", Type: field.TypeInt64, Nullable: true},
|
||||
{Name: "execute_days", Type: field.TypeJSON, Nullable: true},
|
||||
{Name: "cp_user_id", Type: field.TypeInt64},
|
||||
{Name: "user_id", Type: field.TypeInt64},
|
||||
}
|
||||
@@ -145,13 +146,13 @@ var (
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "cp_tasks_cp_users_cptasks",
|
||||
Columns: []*schema.Column{CpTasksColumns[15]},
|
||||
Columns: []*schema.Column{CpTasksColumns[16]},
|
||||
RefColumns: []*schema.Column{CpUsersColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
{
|
||||
Symbol: "cp_tasks_sys_user_cp_task",
|
||||
Columns: []*schema.Column{CpTasksColumns[16]},
|
||||
Columns: []*schema.Column{CpTasksColumns[17]},
|
||||
RefColumns: []*schema.Column{SysUserColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
@@ -205,7 +206,8 @@ var (
|
||||
{Name: "email", Type: field.TypeString, Nullable: true},
|
||||
{Name: "mobile_code", Type: field.TypeString, Nullable: true},
|
||||
{Name: "cookie", Type: field.TypeString, Nullable: true, Size: 2147483647},
|
||||
{Name: "mobile_type", Type: field.TypeEnum, Enums: []string{"random", "authorize"}},
|
||||
{Name: "tgc", Type: field.TypeString, Nullable: true, Size: 2147483647},
|
||||
{Name: "mobile_type", Type: field.TypeEnum, Enums: []string{"random", "authorize", "manual"}},
|
||||
{Name: "login_type", Type: field.TypeEnum, Enums: []string{"account", "phone"}, Default: "account"},
|
||||
{Name: "school_id", Type: field.TypeString},
|
||||
{Name: "user_id", Type: field.TypeInt64},
|
||||
@@ -218,13 +220,13 @@ var (
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "cp_users_cp_schools_cpusers",
|
||||
Columns: []*schema.Column{CpUsersColumns[10]},
|
||||
Columns: []*schema.Column{CpUsersColumns[11]},
|
||||
RefColumns: []*schema.Column{CpSchoolsColumns[0]},
|
||||
OnDelete: schema.NoAction,
|
||||
},
|
||||
{
|
||||
Symbol: "cp_users_sys_user_cpuser",
|
||||
Columns: []*schema.Column{CpUsersColumns[11]},
|
||||
Columns: []*schema.Column{CpUsersColumns[12]},
|
||||
RefColumns: []*schema.Column{SysUserColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
|
||||
+165
-2
@@ -3766,6 +3766,8 @@ type CpTaskMutation struct {
|
||||
addexecute_minute *int
|
||||
next_execute_time *int64
|
||||
addnext_execute_time *int64
|
||||
execute_days *[]int
|
||||
appendexecute_days []int
|
||||
clearedFields map[string]struct{}
|
||||
cp_user *int64
|
||||
clearedcp_user bool
|
||||
@@ -4621,6 +4623,71 @@ func (m *CpTaskMutation) ResetNextExecuteTime() {
|
||||
delete(m.clearedFields, cptask.FieldNextExecuteTime)
|
||||
}
|
||||
|
||||
// SetExecuteDays sets the "execute_days" field.
|
||||
func (m *CpTaskMutation) SetExecuteDays(i []int) {
|
||||
m.execute_days = &i
|
||||
m.appendexecute_days = nil
|
||||
}
|
||||
|
||||
// ExecuteDays returns the value of the "execute_days" field in the mutation.
|
||||
func (m *CpTaskMutation) ExecuteDays() (r []int, exists bool) {
|
||||
v := m.execute_days
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldExecuteDays returns the old "execute_days" field's value of the CpTask entity.
|
||||
// If the CpTask object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *CpTaskMutation) OldExecuteDays(ctx context.Context) (v []int, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldExecuteDays is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldExecuteDays requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldExecuteDays: %w", err)
|
||||
}
|
||||
return oldValue.ExecuteDays, nil
|
||||
}
|
||||
|
||||
// AppendExecuteDays adds i to the "execute_days" field.
|
||||
func (m *CpTaskMutation) AppendExecuteDays(i []int) {
|
||||
m.appendexecute_days = append(m.appendexecute_days, i...)
|
||||
}
|
||||
|
||||
// AppendedExecuteDays returns the list of values that were appended to the "execute_days" field in this mutation.
|
||||
func (m *CpTaskMutation) AppendedExecuteDays() ([]int, bool) {
|
||||
if len(m.appendexecute_days) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
return m.appendexecute_days, true
|
||||
}
|
||||
|
||||
// ClearExecuteDays clears the value of the "execute_days" field.
|
||||
func (m *CpTaskMutation) ClearExecuteDays() {
|
||||
m.execute_days = nil
|
||||
m.appendexecute_days = nil
|
||||
m.clearedFields[cptask.FieldExecuteDays] = struct{}{}
|
||||
}
|
||||
|
||||
// ExecuteDaysCleared returns if the "execute_days" field was cleared in this mutation.
|
||||
func (m *CpTaskMutation) ExecuteDaysCleared() bool {
|
||||
_, ok := m.clearedFields[cptask.FieldExecuteDays]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetExecuteDays resets all changes to the "execute_days" field.
|
||||
func (m *CpTaskMutation) ResetExecuteDays() {
|
||||
m.execute_days = nil
|
||||
m.appendexecute_days = nil
|
||||
delete(m.clearedFields, cptask.FieldExecuteDays)
|
||||
}
|
||||
|
||||
// ClearCpUser clears the "cp_user" edge to the CpUser entity.
|
||||
func (m *CpTaskMutation) ClearCpUser() {
|
||||
m.clearedcp_user = true
|
||||
@@ -4776,7 +4843,7 @@ func (m *CpTaskMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *CpTaskMutation) Fields() []string {
|
||||
fields := make([]string, 0, 16)
|
||||
fields := make([]string, 0, 17)
|
||||
if m.create_time != nil {
|
||||
fields = append(fields, cptask.FieldCreateTime)
|
||||
}
|
||||
@@ -4825,6 +4892,9 @@ func (m *CpTaskMutation) Fields() []string {
|
||||
if m.next_execute_time != nil {
|
||||
fields = append(fields, cptask.FieldNextExecuteTime)
|
||||
}
|
||||
if m.execute_days != nil {
|
||||
fields = append(fields, cptask.FieldExecuteDays)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -4865,6 +4935,8 @@ func (m *CpTaskMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.ExecuteMinute()
|
||||
case cptask.FieldNextExecuteTime:
|
||||
return m.NextExecuteTime()
|
||||
case cptask.FieldExecuteDays:
|
||||
return m.ExecuteDays()
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -4906,6 +4978,8 @@ func (m *CpTaskMutation) OldField(ctx context.Context, name string) (ent.Value,
|
||||
return m.OldExecuteMinute(ctx)
|
||||
case cptask.FieldNextExecuteTime:
|
||||
return m.OldNextExecuteTime(ctx)
|
||||
case cptask.FieldExecuteDays:
|
||||
return m.OldExecuteDays(ctx)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown CpTask field %s", name)
|
||||
}
|
||||
@@ -5027,6 +5101,13 @@ func (m *CpTaskMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetNextExecuteTime(v)
|
||||
return nil
|
||||
case cptask.FieldExecuteDays:
|
||||
v, ok := value.([]int)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetExecuteDays(v)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown CpTask field %s", name)
|
||||
}
|
||||
@@ -5135,6 +5216,9 @@ func (m *CpTaskMutation) ClearedFields() []string {
|
||||
if m.FieldCleared(cptask.FieldNextExecuteTime) {
|
||||
fields = append(fields, cptask.FieldNextExecuteTime)
|
||||
}
|
||||
if m.FieldCleared(cptask.FieldExecuteDays) {
|
||||
fields = append(fields, cptask.FieldExecuteDays)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -5164,6 +5248,9 @@ func (m *CpTaskMutation) ClearField(name string) error {
|
||||
case cptask.FieldNextExecuteTime:
|
||||
m.ClearNextExecuteTime()
|
||||
return nil
|
||||
case cptask.FieldExecuteDays:
|
||||
m.ClearExecuteDays()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown CpTask nullable field %s", name)
|
||||
}
|
||||
@@ -5220,6 +5307,9 @@ func (m *CpTaskMutation) ResetField(name string) error {
|
||||
case cptask.FieldNextExecuteTime:
|
||||
m.ResetNextExecuteTime()
|
||||
return nil
|
||||
case cptask.FieldExecuteDays:
|
||||
m.ResetExecuteDays()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown CpTask field %s", name)
|
||||
}
|
||||
@@ -6251,6 +6341,7 @@ type CpUserMutation struct {
|
||||
email *string
|
||||
mobile_code *string
|
||||
cookie *string
|
||||
tgc *string
|
||||
mobile_type *cpuser.MobileType
|
||||
login_type *cpuser.LoginType
|
||||
clearedFields map[string]struct{}
|
||||
@@ -6746,6 +6837,55 @@ func (m *CpUserMutation) ResetCookie() {
|
||||
delete(m.clearedFields, cpuser.FieldCookie)
|
||||
}
|
||||
|
||||
// SetTgc sets the "tgc" field.
|
||||
func (m *CpUserMutation) SetTgc(s string) {
|
||||
m.tgc = &s
|
||||
}
|
||||
|
||||
// Tgc returns the value of the "tgc" field in the mutation.
|
||||
func (m *CpUserMutation) Tgc() (r string, exists bool) {
|
||||
v := m.tgc
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldTgc returns the old "tgc" field's value of the CpUser entity.
|
||||
// If the CpUser object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *CpUserMutation) OldTgc(ctx context.Context) (v string, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldTgc is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldTgc requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldTgc: %w", err)
|
||||
}
|
||||
return oldValue.Tgc, nil
|
||||
}
|
||||
|
||||
// ClearTgc clears the value of the "tgc" field.
|
||||
func (m *CpUserMutation) ClearTgc() {
|
||||
m.tgc = nil
|
||||
m.clearedFields[cpuser.FieldTgc] = struct{}{}
|
||||
}
|
||||
|
||||
// TgcCleared returns if the "tgc" field was cleared in this mutation.
|
||||
func (m *CpUserMutation) TgcCleared() bool {
|
||||
_, ok := m.clearedFields[cpuser.FieldTgc]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetTgc resets all changes to the "tgc" field.
|
||||
func (m *CpUserMutation) ResetTgc() {
|
||||
m.tgc = nil
|
||||
delete(m.clearedFields, cpuser.FieldTgc)
|
||||
}
|
||||
|
||||
// SetMobileType sets the "mobile_type" field.
|
||||
func (m *CpUserMutation) SetMobileType(ct cpuser.MobileType) {
|
||||
m.mobile_type = &ct
|
||||
@@ -7081,7 +7221,7 @@ func (m *CpUserMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *CpUserMutation) Fields() []string {
|
||||
fields := make([]string, 0, 11)
|
||||
fields := make([]string, 0, 12)
|
||||
if m.create_time != nil {
|
||||
fields = append(fields, cpuser.FieldCreateTime)
|
||||
}
|
||||
@@ -7109,6 +7249,9 @@ func (m *CpUserMutation) Fields() []string {
|
||||
if m.cookie != nil {
|
||||
fields = append(fields, cpuser.FieldCookie)
|
||||
}
|
||||
if m.tgc != nil {
|
||||
fields = append(fields, cpuser.FieldTgc)
|
||||
}
|
||||
if m.mobile_type != nil {
|
||||
fields = append(fields, cpuser.FieldMobileType)
|
||||
}
|
||||
@@ -7141,6 +7284,8 @@ func (m *CpUserMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.MobileCode()
|
||||
case cpuser.FieldCookie:
|
||||
return m.Cookie()
|
||||
case cpuser.FieldTgc:
|
||||
return m.Tgc()
|
||||
case cpuser.FieldMobileType:
|
||||
return m.MobileType()
|
||||
case cpuser.FieldLoginType:
|
||||
@@ -7172,6 +7317,8 @@ func (m *CpUserMutation) OldField(ctx context.Context, name string) (ent.Value,
|
||||
return m.OldMobileCode(ctx)
|
||||
case cpuser.FieldCookie:
|
||||
return m.OldCookie(ctx)
|
||||
case cpuser.FieldTgc:
|
||||
return m.OldTgc(ctx)
|
||||
case cpuser.FieldMobileType:
|
||||
return m.OldMobileType(ctx)
|
||||
case cpuser.FieldLoginType:
|
||||
@@ -7248,6 +7395,13 @@ func (m *CpUserMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetCookie(v)
|
||||
return nil
|
||||
case cpuser.FieldTgc:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetTgc(v)
|
||||
return nil
|
||||
case cpuser.FieldMobileType:
|
||||
v, ok := value.(cpuser.MobileType)
|
||||
if !ok {
|
||||
@@ -7307,6 +7461,9 @@ func (m *CpUserMutation) ClearedFields() []string {
|
||||
if m.FieldCleared(cpuser.FieldCookie) {
|
||||
fields = append(fields, cpuser.FieldCookie)
|
||||
}
|
||||
if m.FieldCleared(cpuser.FieldTgc) {
|
||||
fields = append(fields, cpuser.FieldTgc)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -7333,6 +7490,9 @@ func (m *CpUserMutation) ClearField(name string) error {
|
||||
case cpuser.FieldCookie:
|
||||
m.ClearCookie()
|
||||
return nil
|
||||
case cpuser.FieldTgc:
|
||||
m.ClearTgc()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown CpUser nullable field %s", name)
|
||||
}
|
||||
@@ -7368,6 +7528,9 @@ func (m *CpUserMutation) ResetField(name string) error {
|
||||
case cpuser.FieldCookie:
|
||||
m.ResetCookie()
|
||||
return nil
|
||||
case cpuser.FieldTgc:
|
||||
m.ResetTgc()
|
||||
return nil
|
||||
case cpuser.FieldMobileType:
|
||||
m.ResetMobileType()
|
||||
return nil
|
||||
|
||||
@@ -31,6 +31,7 @@ func (CpTask) Fields() []ent.Field {
|
||||
field.Int("execute_hour").Optional().Nillable(),
|
||||
field.Int("execute_minute").Optional().Nillable(),
|
||||
field.Int64("next_execute_time").Optional().Nillable(),
|
||||
field.JSON("execute_days", []int{}).Optional().Comment("执行星期 1-7,1=周一,7=周日"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ func (CpUser) Fields() []ent.Field {
|
||||
field.String("email").Optional(),
|
||||
field.String("mobile_code").Optional(),
|
||||
field.Text("cookie").Optional(),
|
||||
field.Enum("mobile_type").Values("random", "authorize"),
|
||||
field.Text("tgc").Optional(),
|
||||
field.Enum("mobile_type").Values("random", "authorize", "manual"),
|
||||
field.Enum("login_type").Values("account", "phone").Default("account"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ func init() {
|
||||
config := zap.NewProductionConfig()
|
||||
if IsDebug() {
|
||||
config.Level = zap.NewAtomicLevelAt(zap.DebugLevel) // Enable debug logs
|
||||
} else {
|
||||
config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
|
||||
}
|
||||
config.EncoderConfig.EncodeTime = BeijingTimeEncoder
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"likedream-goframe/internal/application/cpdaily/imageuploader"
|
||||
"likedream-goframe/internal/application/cpdaily/login"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
task2 "likedream-goframe/internal/application/cpdaily/task"
|
||||
"likedream-goframe/internal/application/cpdaily/task/formtype"
|
||||
"likedream-goframe/internal/application/cpdaily/types"
|
||||
"likedream-goframe/internal/common/db"
|
||||
errors2 "likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/ent"
|
||||
@@ -22,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/samber/oops"
|
||||
"go.uber.org/zap"
|
||||
@@ -43,15 +46,18 @@ func (c *CpdailyTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task)
|
||||
// 先注册错误处理,确保任何阶段的错误都能被捕获并重置状态
|
||||
var targetTask *ent.CpTask
|
||||
defer utils.HandleError(func(err error) {
|
||||
// 使用独立的 context,避免 asynq 超时 cancel ctx 后数据库操作失败导致状态无法更新
|
||||
bgCtx := context.Background()
|
||||
|
||||
// 如果 targetTask 还没查询到,只能重置状态为 not_start,让任务重新入队
|
||||
if targetTask == nil {
|
||||
db.Global.CpTask.UpdateOneID(taskPayload.CpTaskID).SetTaskStatus(cptask.TaskStatusNotStart).Exec(ctx)
|
||||
db.Global.CpTask.UpdateOneID(taskPayload.CpTaskID).SetTaskStatus(cptask.TaskStatusNotStart).Exec(bgCtx)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 执行失败应该更新下次执行时间
|
||||
executeTime := utils.GetCpdailyNextExecuteTime(targetTask)
|
||||
e := db.Global.CpTask.UpdateOneID(taskPayload.CpTaskID).SetNextExecuteTime(executeTime).SetTaskStatus(cptask.TaskStatusFail).Exec(ctx)
|
||||
e := db.Global.CpTask.UpdateOneID(taskPayload.CpTaskID).SetNextExecuteTime(executeTime).SetTaskStatus(cptask.TaskStatusFail).Exec(bgCtx)
|
||||
utils.LogError(ctx, "设置任务状态失败", oops.With("task_id", taskPayload.CpTaskID).Wrap(e))
|
||||
|
||||
var errorMessage string
|
||||
@@ -97,15 +103,19 @@ func (c *CpdailyTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task)
|
||||
|
||||
// 查询学校详情
|
||||
schoolDetail := school.GetSchoolDetail(ctx, targetTask.Edges.CpUser.SchoolID)
|
||||
cookie := targetTask.Edges.CpUser.Cookie
|
||||
restyClient := login.GetClient(ctx, cookie)
|
||||
if !login.CookieEnabled(ctx, schoolDetail.WecloudUrl, restyClient, targetTask.Edges.CpUser.LoginType) {
|
||||
loginExecutor := login.GetCpdailyLoginBySchoolInfo(ctx, &schoolDetail)
|
||||
loginExecutor.Login(ctx, &schoolDetail, targetTask.Edges.CpUser.Username, targetTask.Edges.CpUser.Password)
|
||||
cookie = loginExecutor.GetCookieString()
|
||||
db.Global.CpUser.UpdateOneID(targetTask.Edges.CpUser.ID).SetCookie(cookie).ExecX(ctx)
|
||||
restyClient = login.GetClient(ctx, cookie)
|
||||
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
|
||||
ID: targetTask.Edges.CpUser.ID,
|
||||
Cookie: targetTask.Edges.CpUser.Cookie,
|
||||
Tgc: targetTask.Edges.CpUser.Tgc,
|
||||
LoginType: targetTask.Edges.CpUser.LoginType,
|
||||
Username: targetTask.Edges.CpUser.Username,
|
||||
Password: targetTask.Edges.CpUser.Password,
|
||||
SchoolID: targetTask.Edges.CpUser.SchoolID,
|
||||
}, &schoolDetail)
|
||||
if refreshResult.Cookie != targetTask.Edges.CpUser.Cookie {
|
||||
db.Global.CpUser.UpdateOneID(targetTask.Edges.CpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
|
||||
}
|
||||
restyClient := refreshResult.Client
|
||||
// 获取任务执行handler
|
||||
taskExecutor := task2.GetCpdailyTaskService(ctx, targetTask.TaskType, restyClient, schoolDetail)
|
||||
|
||||
@@ -142,6 +152,22 @@ func (c *CpdailyTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task)
|
||||
deviceID := targetTask.Edges.CpUser.MobileCode
|
||||
// 固定获取图片id
|
||||
imgId := GetNextImgId(customFormItems, targetTask.UsedImg)
|
||||
// 兼容旧数据:旧数据存的是 S3 的 UUID,新数据存的是今日校园 previewUrl
|
||||
if imgId != "" && !strings.Contains(imgId, "/") && !strings.Contains(imgId, ":") {
|
||||
// 不含 / 和 : 说明不是 URL,可能是旧的 UUID,尝试从 S3 上传到今日校园
|
||||
if parsedUUID, uuidErr := uuid.Parse(imgId); uuidErr == nil {
|
||||
var imgTaskType types.TaskType
|
||||
switch targetTask.TaskType {
|
||||
case cptask.TaskTypeSign:
|
||||
imgTaskType = types.TaskTypeSign
|
||||
case cptask.TaskTypeSleepCheck:
|
||||
imgTaskType = types.TaskTypeSleepCheck
|
||||
case cptask.TaskTypeCollection:
|
||||
imgTaskType = types.TaskTypeCollection
|
||||
}
|
||||
imgId = imageuploader.UploadImageFromStorage(ctx, schoolDetail.WecloudUrl, parsedUUID, imgTaskType, restyClient)
|
||||
}
|
||||
}
|
||||
submitForm := taskExecutor.FillCustomForm(ctx, cpdailyRealTask, customFormItems, deviceID, imgId)
|
||||
taskExecutor.Submit(ctx, submitForm, targetTask.Edges.CpUser.Username, deviceID)
|
||||
|
||||
|
||||
@@ -61,11 +61,12 @@ func cleanupStuckTasks(ctx context.Context) {
|
||||
g.Log().Infof(ctx, "已清理 %d 个 archived 状态的 asynq 任务", len(archivedTasks))
|
||||
}
|
||||
|
||||
// 重置数据库中卡住的任务状态
|
||||
// 重置数据库中卡住的任务状态(queue 和 doing 都需要重置)
|
||||
affected, err := db.Global.CpTask.Update().
|
||||
Where(
|
||||
cptask.Or(
|
||||
cptask.TaskStatusEQ(cptask.TaskStatusQueue),
|
||||
cptask.TaskStatusEQ(cptask.TaskStatusDoing),
|
||||
),
|
||||
).
|
||||
SetTaskStatus(cptask.TaskStatusNotStart).
|
||||
|
||||
@@ -36,7 +36,6 @@ type periodicTaskConfigProvider struct{}
|
||||
func (p *periodicTaskConfigProvider) GetConfigs() ([]*asynq.PeriodicTaskConfig, error) {
|
||||
return []*asynq.PeriodicTaskConfig{
|
||||
{
|
||||
//Cronspec: "0 1 * * *", // 每天凌晨1点执行
|
||||
Cronspec: "0 1 * * * ",
|
||||
Task: asynq.NewTask(cst.TaskResetDaily, nil),
|
||||
Opts: []asynq.Option{
|
||||
@@ -45,5 +44,14 @@ func (p *periodicTaskConfigProvider) GetConfigs() ([]*asynq.PeriodicTaskConfig,
|
||||
asynq.Timeout(0), // 使用默认超时
|
||||
},
|
||||
},
|
||||
{
|
||||
Cronspec: "0 12 * * *",
|
||||
Task: asynq.NewTask(cst.TaskRefreshSchoolDetail, nil),
|
||||
Opts: []asynq.Option{
|
||||
asynq.Queue(cst.TaskQueueDefault),
|
||||
asynq.MaxRetry(1),
|
||||
asynq.Timeout(0),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -112,12 +112,27 @@ func (ts *TaskScanner) scanCpdailyTask() {
|
||||
func(selector *sql.Selector) {
|
||||
selector.Where(sql.ExprP("success_count < frequency"))
|
||||
}).
|
||||
Select(cptask.FieldID, cptask.FieldExecuteHour, cptask.FieldExecuteMinute, cptask.FieldNextExecuteTime).
|
||||
Select(cptask.FieldID, cptask.FieldExecuteHour, cptask.FieldExecuteMinute, cptask.FieldNextExecuteTime, cptask.FieldExecuteDays).
|
||||
AllX(ts.ctx)
|
||||
|
||||
var failedIDS []int64
|
||||
|
||||
// 当前星期几(ISO: 周一=1, 周日=7)
|
||||
todayWeekday := int(currentTime.Weekday())
|
||||
if todayWeekday == 0 {
|
||||
todayWeekday = 7
|
||||
}
|
||||
|
||||
for _, task := range allTasks {
|
||||
// 检查今天是否在执行日内
|
||||
if len(task.ExecuteDays) > 0 && !slices.Contains(task.ExecuteDays, todayWeekday) {
|
||||
// 今天不执行,重置到下一个执行日
|
||||
nextExecuteAt := utils.GetCpdailyNextExecuteTime(task)
|
||||
db.Global.CpTask.UpdateOneID(task.ID).Where(cptask.NextExecuteTimeNotNil()).SetNextExecuteTime(nextExecuteAt).Exec(ts.ctx)
|
||||
failedIDS = append(failedIDS, task.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
// 30分钟之前的任务不再执行,但是要重置下次执行时间
|
||||
if *task.NextExecuteTime < minExecuteTime {
|
||||
originExecuteTime := time.Unix(*task.NextExecuteTime, 0).In(cst2.TimeZoneShangHai).Format("2006-01-02 15:04:05")
|
||||
|
||||
@@ -68,6 +68,7 @@ func initServer(ctx context.Context) {
|
||||
auxMux := asynq.NewServeMux()
|
||||
auxMux.Handle(cst2.TaskEmail, &EmailTaskHandler{})
|
||||
auxMux.Handle(cst2.TaskResetDaily, &ResetTaskHandler{})
|
||||
auxMux.Handle(cst2.TaskRefreshSchoolDetail, &RefreshSchoolDetailTaskHandler{})
|
||||
|
||||
e := globalServer.Start(mainMux)
|
||||
utils.OkOrPanic(e)
|
||||
|
||||
@@ -26,7 +26,7 @@ func ExceptionHandler(r *ghttp.Request) {
|
||||
if err == nil && r.Response.BufferLength() > 0 {
|
||||
return
|
||||
}
|
||||
if r.Response.Header().Get("Content-Type") == "image/png" {
|
||||
if ct := r.Response.Header().Get("Content-Type"); ct != "" && len(ct) > 6 && ct[:6] == "image/" {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -59,19 +59,6 @@ func (s CourseServiceV1) List(ctx context.Context, req *v1.CourseListReq) *utils
|
||||
func (s CourseServiceV1) AddCourse(ctx context.Context, req *v1.AddCourseReq) *utils.Response[any] {
|
||||
user := utils.GetUserInfoFromCtx(ctx)
|
||||
|
||||
// 检查是否已经存在相同的刷课任务
|
||||
exists := db2.Global.CourseEntity.Query().
|
||||
Where(
|
||||
courseentity.HasSysUserWith(sysuser.ID(user.ID)),
|
||||
courseentity.Username(req.Username),
|
||||
courseentity.CourseID(req.CourseID),
|
||||
).
|
||||
ExistX(ctx)
|
||||
|
||||
if exists {
|
||||
utils.OkOrPanic(errors2.NewBusinessError(400, "该课程已存在刷课任务"))
|
||||
}
|
||||
|
||||
// 登录学习通获取节点信息
|
||||
loginClient := xuexitong.NewXuexitongLogin(ctx)
|
||||
err := loginClient.Login(ctx, req.Username, req.Password, 3)
|
||||
|
||||
@@ -4,12 +4,20 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
v1 "likedream-goframe/internal/api/v1"
|
||||
"likedream-goframe/internal/application/cpdaily/imageuploader"
|
||||
"likedream-goframe/internal/application/cpdaily/login"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/application/cpdaily/types"
|
||||
"likedream-goframe/internal/application/filestorage"
|
||||
"likedream-goframe/internal/common/db"
|
||||
errors2 "likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/ent/cpimage"
|
||||
"likedream-goframe/internal/ent/cptask"
|
||||
"likedream-goframe/internal/ent/cpuser"
|
||||
"likedream-goframe/internal/ent/sysuser"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/samber/oops"
|
||||
)
|
||||
@@ -19,36 +27,86 @@ type CpImageServiceV1 struct {
|
||||
|
||||
func (v CpImageServiceV1) Upload(ctx context.Context, req *v1.UploadImageReq) *utils.Response[any] {
|
||||
user := utils.GetUserInfoFromCtx(ctx)
|
||||
file := req.File
|
||||
open, err := file.Open()
|
||||
utils.OkOrPanic(err, oops.WithContext(ctx).Wrap)
|
||||
key := utils.GenerateFileName("cpdaily/")
|
||||
filestorage.GlobalS3.UploadFile(ctx, key, open)
|
||||
img := db.Global.CpImage.Create().
|
||||
SetUserID(user.ID).
|
||||
SetKey(key).
|
||||
SetContentType(file.Header.Get("Content-Type")).
|
||||
SetContentLength(file.Size).
|
||||
SaveX(ctx)
|
||||
// 校验 cpUser 归属
|
||||
cpUser := db.Global.CpUser.Query().
|
||||
Where(cpuser.ID(req.CpUserId), cpuser.HasSysuserWith(sysuser.ID(user.ID))).
|
||||
WithSchool().
|
||||
OnlyX(ctx)
|
||||
if cpUser == nil {
|
||||
utils.OkOrPanic(errors2.NewBusinessError(400, "非法的今日校园账号"))
|
||||
}
|
||||
|
||||
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
|
||||
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
|
||||
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
|
||||
LoginType: cpUser.LoginType, Username: cpUser.Username,
|
||||
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
|
||||
}, &schoolDetail)
|
||||
if refreshResult.Cookie != cpUser.Cookie {
|
||||
db.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
|
||||
}
|
||||
client := refreshResult.Client
|
||||
|
||||
taskType := convertTaskType(ctx, req.TaskType)
|
||||
previewUrl := imageuploader.UploadImageFromFile(ctx, schoolDetail.WecloudUrl, req.File, taskType, client)
|
||||
|
||||
return utils.SuccessWithData[any](v1.UploadImageRes{
|
||||
FileKey: img.ImageID.String(),
|
||||
FileSize: &file.Size,
|
||||
PreviewUrl: previewUrl,
|
||||
})
|
||||
}
|
||||
|
||||
func (v CpImageServiceV1) Preview(ctx context.Context, req *v1.PreviewImageReq) *utils.Response[any] {
|
||||
id, err := uuid.Parse(req.Key)
|
||||
// 解析 key,可能是 UUID(旧数据)或直接的 S3 key
|
||||
parsedUUID, uuidErr := uuid.Parse(req.Key)
|
||||
var s3Key string
|
||||
var contentType string
|
||||
|
||||
if uuidErr == nil {
|
||||
// 是 UUID,从 cpimage 表查询对应的 S3 key
|
||||
targetImage, e := db.Global.CpImage.Query().
|
||||
Where(cpimage.ImageID(parsedUUID)).
|
||||
Select(cpimage.FieldKey, cpimage.FieldContentType).
|
||||
Only(ctx)
|
||||
if e != nil {
|
||||
return utils.Fail(404, "图片不存在")
|
||||
}
|
||||
s3Key = targetImage.Key
|
||||
contentType = targetImage.ContentType
|
||||
} else {
|
||||
// 不是 UUID,直接当作 S3 key 使用
|
||||
s3Key = req.Key
|
||||
contentType = "image/jpeg"
|
||||
}
|
||||
|
||||
// 从 S3 获取图片
|
||||
file := filestorage.GlobalS3.GetFile(ctx, s3Key)
|
||||
defer file.Close()
|
||||
|
||||
// 直接写入 HTTP 响应流
|
||||
r := ghttp.RequestFromCtx(ctx)
|
||||
r.Response.Header().Set("Content-Type", contentType)
|
||||
r.Response.Header().Set("Cache-Control", "public, max-age=86400")
|
||||
_, err := io.Copy(r.Response.Writer, file)
|
||||
utils.OkOrPanic(err, oops.WithContext(ctx).Wrap)
|
||||
cpImage := db.Global.CpImage.Query().Where(cpimage.ImageID(id)).OnlyX(ctx)
|
||||
file := filestorage.GlobalS3.GetFile(ctx, cpImage.Key)
|
||||
r := g.RequestFromCtx(ctx)
|
||||
r.Response.Header().Set("Content-Type", "image/png")
|
||||
// 将文件写入
|
||||
io.Copy(r.Response.Writer, file)
|
||||
r.Exit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertTaskType 将 ent 的 TaskType 转换为 cpdaily types.TaskType
|
||||
func convertTaskType(ctx context.Context, tt cptask.TaskType) types.TaskType {
|
||||
switch tt {
|
||||
case cptask.TaskTypeCollection:
|
||||
return types.TaskTypeCollection
|
||||
case cptask.TaskTypeSign:
|
||||
return types.TaskTypeSign
|
||||
case cptask.TaskTypeSleepCheck:
|
||||
return types.TaskTypeSleepCheck
|
||||
default:
|
||||
utils.OkOrPanic(errors2.NewBusinessError(400, "不支持的任务类型"), oops.WithContext(ctx).Wrap)
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func NewCpImageServiceV1() *CpImageServiceV1 {
|
||||
return &CpImageServiceV1{}
|
||||
}
|
||||
|
||||
@@ -41,17 +41,16 @@ func (v CpLeaveServiceV1) GetLeaveListFromSystem(ctx context.Context, req *v1.Ge
|
||||
|
||||
// 获取学校详情
|
||||
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
|
||||
cookie := cpUser.Cookie
|
||||
client := login.GetClient(ctx, cookie)
|
||||
|
||||
// 检查cookie是否有效
|
||||
if !login.CookieEnabled(ctx, schoolDetail.WecloudUrl, client, cpUser.LoginType) {
|
||||
loginExecutor := login.GetCpdailyLoginBySchoolInfo(ctx, &schoolDetail)
|
||||
loginExecutor.Login(ctx, &schoolDetail, cpUser.Username, cpUser.Password)
|
||||
cookie = loginExecutor.GetCookieString()
|
||||
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(cookie).ExecX(ctx)
|
||||
client = login.GetClient(ctx, cookie)
|
||||
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
|
||||
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
|
||||
LoginType: cpUser.LoginType, Username: cpUser.Username,
|
||||
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
|
||||
}, &schoolDetail)
|
||||
if refreshResult.Cookie != cpUser.Cookie {
|
||||
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
|
||||
}
|
||||
client := refreshResult.Client
|
||||
|
||||
// 创建销假服务
|
||||
reportApp := leave.NewCpdailyReportApp(client, schoolDetail.WecloudUrl)
|
||||
@@ -149,17 +148,16 @@ func (v CpLeaveServiceV1) Report(ctx context.Context, req *v1.CpLeaveReportReq)
|
||||
|
||||
// 获取学校详情
|
||||
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
|
||||
cookie := cpUser.Cookie
|
||||
client := login.GetClient(ctx, cookie)
|
||||
|
||||
// 检查cookie是否有效
|
||||
if !login.CookieEnabled(ctx, schoolDetail.WecloudUrl, client, cpUser.LoginType) {
|
||||
loginExecutor := login.GetCpdailyLoginBySchoolInfo(ctx, &schoolDetail)
|
||||
loginExecutor.Login(ctx, &schoolDetail, cpUser.Username, cpUser.Password)
|
||||
cookie = loginExecutor.GetCookieString()
|
||||
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(cookie).ExecX(ctx)
|
||||
client = login.GetClient(ctx, cookie)
|
||||
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
|
||||
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
|
||||
LoginType: cpUser.LoginType, Username: cpUser.Username,
|
||||
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
|
||||
}, &schoolDetail)
|
||||
if refreshResult.Cookie != cpUser.Cookie {
|
||||
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
|
||||
}
|
||||
client := refreshResult.Client
|
||||
|
||||
// 创建销假服务
|
||||
reportApp := leave.NewCpdailyReportApp(client, schoolDetail.WecloudUrl)
|
||||
@@ -191,14 +189,15 @@ func (v CpLeaveServiceV1) Upload(ctx context.Context, req *v1.CpLeaveUploadReq)
|
||||
utils.OkOrPanic(errors2.NewBusinessError(400, "非法的今日校园账号"))
|
||||
}
|
||||
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
|
||||
client := login.GetClient(ctx, cpUser.Cookie)
|
||||
if !login.CookieEnabled(ctx, schoolDetail.WecloudUrl, client, cpUser.LoginType) {
|
||||
loginExecutor := login.GetCpdailyLoginBySchoolInfo(ctx, &schoolDetail)
|
||||
loginExecutor.Login(ctx, &schoolDetail, cpUser.Username, cpUser.Password)
|
||||
cookie := loginExecutor.GetCookieString()
|
||||
db2.Global.CpUser.UpdateOne(cpUser).SetCookie(cookie).ExecX(ctx)
|
||||
client = login.GetClient(ctx, cookie)
|
||||
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
|
||||
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
|
||||
LoginType: cpUser.LoginType, Username: cpUser.Username,
|
||||
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
|
||||
}, &schoolDetail)
|
||||
if refreshResult.Cookie != cpUser.Cookie {
|
||||
db2.Global.CpUser.UpdateOne(cpUser).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
|
||||
}
|
||||
client := refreshResult.Client
|
||||
// 创建销假服务
|
||||
reportApp := leave.NewCpdailyReportApp(client, schoolDetail.WecloudUrl)
|
||||
|
||||
|
||||
@@ -43,14 +43,15 @@ func (v CpTaskServiceV1) GetRecentTasks(ctx context.Context, req *v1.GetRecentTa
|
||||
utils.OkOrPanic(errors2.NewBusinessError(400, "非法的今日校园账号"))
|
||||
}
|
||||
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
|
||||
client := login.GetClient(ctx, cpUser.Cookie)
|
||||
if !login.CookieEnabled(ctx, schoolDetail.WecloudUrl, client, cpUser.LoginType) {
|
||||
loginExecutor := login.GetCpdailyLoginBySchoolInfo(ctx, &schoolDetail)
|
||||
loginExecutor.Login(ctx, &schoolDetail, cpUser.Username, cpUser.Password)
|
||||
cookie := loginExecutor.GetCookieString()
|
||||
db2.Global.CpUser.UpdateOne(cpUser).SetCookie(cookie).ExecX(ctx)
|
||||
client = login.GetClient(ctx, cookie)
|
||||
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
|
||||
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
|
||||
LoginType: cpUser.LoginType, Username: cpUser.Username,
|
||||
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
|
||||
}, &schoolDetail)
|
||||
if refreshResult.Cookie != cpUser.Cookie {
|
||||
db2.Global.CpUser.UpdateOne(cpUser).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
|
||||
}
|
||||
client := refreshResult.Client
|
||||
taskService := task.GetCpdailyTaskService(ctx, req.Type, client, schoolDetail)
|
||||
titleList := taskService.GetRecentTaskTitle(ctx)
|
||||
return utils.SuccessWithData[[]string](titleList)
|
||||
@@ -118,17 +119,19 @@ func (v CpTaskServiceV1) TaskFormData(ctx context.Context, req *v1.GetCpTaskForm
|
||||
}
|
||||
schoolDetail := school.GetSchoolDetail(ctx, cpTask.Edges.CpUser.SchoolID)
|
||||
cookie := cpUser.Cookie
|
||||
client := login.GetClient(ctx, cookie)
|
||||
if !login.CookieEnabled(ctx, schoolDetail.WecloudUrl, client, cpUser.LoginType) {
|
||||
loginExecutor := login.GetCpdailyLoginBySchoolInfo(ctx, &schoolDetail)
|
||||
loginExecutor.Login(ctx, &schoolDetail, cpUser.Username, cpUser.Password)
|
||||
cookie = loginExecutor.GetCookieString()
|
||||
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(cookie).ExecX(ctx)
|
||||
client = login.GetClient(ctx, cookie)
|
||||
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
|
||||
ID: cpUser.ID, Cookie: cookie, Tgc: cpUser.Tgc,
|
||||
LoginType: cpUser.LoginType, Username: cpUser.Username,
|
||||
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
|
||||
}, &schoolDetail)
|
||||
if refreshResult.Cookie != cookie {
|
||||
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
|
||||
}
|
||||
client := refreshResult.Client
|
||||
taskService := task.GetCpdailyTaskService(ctx, cpTask.TaskType, client, schoolDetail)
|
||||
formMap := taskService.QueryFormByTitle(ctx, cpTask.Title)
|
||||
formDetail := taskService.ParseFormJson(ctx, formMap)
|
||||
|
||||
return utils.SuccessWithData[any](formDetail)
|
||||
}
|
||||
|
||||
@@ -196,7 +199,8 @@ func (v CpTaskServiceV1) PostExecute(ctx context.Context, req *v1.PostCpTaskExec
|
||||
SetTaskStatus(cptask.TaskStatusNotStart).
|
||||
SetNotificationType(req.NotificationType).
|
||||
SetExecuteHour(req.ExecuteHour).
|
||||
SetExecuteMinute(req.ExecuteMinute)
|
||||
SetExecuteMinute(req.ExecuteMinute).
|
||||
SetExecuteDays(req.ExecuteDays)
|
||||
if cpTask.Step == cptask.StepExecute {
|
||||
update.SetTaskStatus(cptask.TaskStatusNotStart)
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ func (v CpUserServiceV1) List(ctx context.Context, req *v1.CpUserListReq) *utils
|
||||
for _, v := range data {
|
||||
v.Password = ""
|
||||
v.Cookie = ""
|
||||
v.Tgc = ""
|
||||
}
|
||||
return utils.SuccessWithTotal[[]*ent.CpUser](data, total)
|
||||
}
|
||||
@@ -169,14 +170,14 @@ func (v CpUserServiceV1) AuthorizeConfirm(ctx context.Context, req *v1.CpUserAut
|
||||
return utils.SuccessWithMsg("已确认授权")
|
||||
}
|
||||
|
||||
func (v CpUserServiceV1) AuthorizeRandom(ctx context.Context, req *v1.CpUserAuthorizeRandomReq) *utils.Response[any] {
|
||||
func (v CpUserServiceV1) CustomDevice(ctx context.Context, req *v1.CpUserCustomDeviceReq) *utils.Response[any] {
|
||||
user := utils.GetUserInfoFromCtx(ctx)
|
||||
cpUser := db.Global.CpUser.Query().Where(cpuser.ID(req.Id), cpuser.HasSysuserWith(sysuser.ID(user.ID))).FirstX(ctx)
|
||||
if cpUser == nil {
|
||||
return utils.FailParam("用户不存在")
|
||||
}
|
||||
db.Global.CpUser.UpdateOne(cpUser).SetMobileCode(utils.UUID()).SetMobileType(cpuser.MobileTypeRandom).ExecX(ctx)
|
||||
return utils.SuccessWithMsg("已改为随机设备码")
|
||||
db.Global.CpUser.UpdateOne(cpUser).SetMobileCode(req.MobileCode).SetMobileType(cpuser.MobileType(req.MobileType)).ExecX(ctx)
|
||||
return utils.SuccessWithMsg("自定义设备码设置成功")
|
||||
}
|
||||
|
||||
func (v CpUserServiceV1) LoginGetCode(ctx context.Context, req *v1.LoginGetCode) *utils.Response[any] {
|
||||
@@ -203,6 +204,8 @@ func (v CpUserServiceV1) AddByCode(ctx context.Context, req *v1.AddByCode) *util
|
||||
loginExecutor := login.NewSmsWithoutDeviceID()
|
||||
data := loginExecutor.LoginByCode(context.Background(), req.Mobile, req.Code, jar)
|
||||
loginExecutor.SetLoginCookie(context.Background(), jar, data)
|
||||
// SetLoginCookie 之后保存完整 cookie 作为 TGC(包含 CASTGC,有效期10年)
|
||||
tgc := loginExecutor.GetCookieString()
|
||||
// 扣费
|
||||
//basePoint, err := decimal.NewFromString(g.Cfg().MustGet(ctx, cst.ConfigBasePointCpdailyPhone).String())
|
||||
basePoint, err := decimal.NewFromString(db.Global.SysConfig.Query().Where(sysconfig.KeyEQ(cst.ConfigRedisCpdailyPhone)).OnlyX(ctx).Value)
|
||||
@@ -217,6 +220,7 @@ func (v CpUserServiceV1) AddByCode(ctx context.Context, req *v1.AddByCode) *util
|
||||
SetEmail(req.Email).
|
||||
SetSchoolID(req.SchoolId).
|
||||
SetCookie(loginExecutor.GetCookieString()).
|
||||
SetTgc(tgc).
|
||||
SetMobileCode(utils.UUID()).
|
||||
SetMobileType(cpuser.MobileTypeRandom).
|
||||
SetSysuserID(user.ID).
|
||||
@@ -226,6 +230,7 @@ func (v CpUserServiceV1) AddByCode(ctx context.Context, req *v1.AddByCode) *util
|
||||
} else {
|
||||
db.Global.CpUser.UpdateOneID(req.Id).
|
||||
SetCookie(loginExecutor.GetCookieString()).
|
||||
SetTgc(tgc).
|
||||
ExecX(ctx)
|
||||
return utils.SuccessWithMsg("更新成功")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
// 如果任务的执行时间为nil,则返回 math.MaxInt64
|
||||
// 如果任务的执行时间不为nil,则返回下次执行时间的Unix时间
|
||||
// 注意:如果任务的执行时间在当前时间之前,则返回明天的执行时间
|
||||
// 同时考虑 execute_days(周几执行),跳过不在执行日的日期
|
||||
func GetCpdailyNextExecuteTime(task *ent.CpTask) int64 {
|
||||
executeHour, executeMinute := task.ExecuteHour, task.ExecuteMinute
|
||||
if executeHour == nil || executeMinute == nil {
|
||||
@@ -18,15 +19,41 @@ func GetCpdailyNextExecuteTime(task *ent.CpTask) int64 {
|
||||
}
|
||||
current := time.Now().In(cst.TimeZoneShangHai)
|
||||
|
||||
// 如果当天执行时间未过,返回当天执行时间
|
||||
if current.Hour() < *executeHour || (current.Hour() == *executeHour && current.Minute() < *executeMinute) {
|
||||
executeTime := time.Date(current.Year(), current.Month(), current.Day(), *executeHour, *executeMinute, 0, 0, cst.TimeZoneShangHai)
|
||||
return executeTime.Unix()
|
||||
// 先确定候选日期:如果当天时间未过用当天,否则从明天开始
|
||||
candidate := time.Date(current.Year(), current.Month(), current.Day(), *executeHour, *executeMinute, 0, 0, cst.TimeZoneShangHai)
|
||||
if !current.Before(candidate) {
|
||||
candidate = candidate.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
// 如果当天执行时间已过,返回明天的执行时间
|
||||
executeTime := time.Date(current.Year(), current.Month(), current.Day(), *executeHour, *executeMinute, 0, 0, cst.TimeZoneShangHai)
|
||||
executeTime = executeTime.AddDate(0, 0, 1)
|
||||
// 如果没有设置 execute_days 或为空,视为每天执行
|
||||
if len(task.ExecuteDays) == 0 {
|
||||
return candidate.Unix()
|
||||
}
|
||||
|
||||
return executeTime.Unix()
|
||||
// 构建快速查找 set
|
||||
daySet := make(map[int]bool, len(task.ExecuteDays))
|
||||
for _, d := range task.ExecuteDays {
|
||||
daySet[d] = true
|
||||
}
|
||||
|
||||
// 最多查找7天,找到第一个匹配的执行日
|
||||
for i := 0; i < 7; i++ {
|
||||
weekday := isoWeekday(candidate)
|
||||
if daySet[weekday] {
|
||||
return candidate.Unix()
|
||||
}
|
||||
candidate = candidate.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
// 理论上不会到这里(7天内必有匹配),兜底返回
|
||||
return math.MaxInt64
|
||||
}
|
||||
|
||||
// isoWeekday 返回 ISO 星期几:周一=1, 周日=7
|
||||
func isoWeekday(t time.Time) int {
|
||||
d := int(t.Weekday())
|
||||
if d == 0 {
|
||||
return 7 // Sunday
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package utils
|
||||
|
||||
import "time"
|
||||
|
||||
func GetCurrentTimeMillis() int64 {
|
||||
return time.Now().UnixMilli()
|
||||
}
|
||||
Reference in New Issue
Block a user