Files
likedream/internal/application/cpdaily/task/sleepcheck.go
T
2025-12-05 10:08:43 +08:00

711 lines
19 KiB
Go

package task
import (
"context"
"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"
"strconv"
"strings"
"time"
"github.com/avast/retry-go/v4"
"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"
)
type CpdailySleepCheckTask struct {
client *resty.Client
host string
}
var _ CpdailyTask = (*CpdailySleepCheckTask)(nil)
func NewSleepCheckTask(client *resty.Client, host string) *CpdailySleepCheckTask {
return &CpdailySleepCheckTask{
client: client,
host: host,
}
}
func (c *CpdailySleepCheckTask) getReqUrl(path ...string) string {
appendPath := strings.Join(path, "/")
return fmt.Sprintf("%s/wec-counselor-attendance-apps/student/attendance/%s", c.host, appendPath)
}
func (c *CpdailySleepCheckTask) GetRecentTaskTitle(ctx context.Context) []string {
queryTaskTitleInThisMonth := func() []string {
timeNow := time.Now().In(cst.TimeZoneShangHai)
return c.queryTitleByYearMonth(ctx, strconv.Itoa(timeNow.Year()), strconv.Itoa(int(timeNow.Month())))
}
response, err := c.client.R().
SetBody(map[string]any{}).
WithContext(ctx).
Post(c.getReqUrl("getStuAttendacesInOneDay"))
utils.OkOrPanic(err, func(err error) error {
return oops.
With("host", c.host).
In("CpdailySleepCheckTask.GetRecentTaskTitle").
Wrap(err)
})
responseJSON := utils.ReadGjsonResult(response.Body)
datas := responseJSON.Get("datas")
if !datas.Exists() {
return queryTaskTitleInThisMonth()
}
unsignedTasks := datas.Get("unSignedTasks").Array()
leaveTasks := datas.Get("leaveTasks").Array()
signedTasks := datas.Get("signedTasks").Array()
allTasks := append(append(unsignedTasks, leaveTasks...), signedTasks...)
titleSet := map[string]bool{}
for _, task := range allTasks {
title := task.Get("taskName").String()
if title != "" {
titleSet[title] = true
}
}
titles := make([]string, 0, len(titleSet))
for title := range titleSet {
titles = append(titles, title)
}
if len(titles) == 0 {
return queryTaskTitleInThisMonth()
}
return titles
}
func (c *CpdailySleepCheckTask) queryTitleByYearMonth(ctx context.Context, year, month string) []string {
if len(month) == 1 {
month = "0" + month
}
response, err := c.client.R().
SetBody(map[string]any{
"statisticYearMonth": fmt.Sprintf("%s-%s", year, month),
}).
WithContext(ctx).
Post(c.getReqUrl("getStuSignInfosByWeekMonth"))
utils.OkOrPanic(err, func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
In("CpdailySleepCheckTask.queryTitleByYearMonth").
Wrap(err)
})
responseJSON := utils.ReadGjsonResult(response.Body)
rows := responseJSON.Get("datas.rows")
if !rows.Exists() {
utils.OkOrPanic(errors.NewBusinessError(503, "没有查询到查寝任务"), func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
In("CpdailySleepCheckTask.queryTitleByYearMonth").
Wrap(err)
})
}
var taskJsons []gjson.Result
for _, row := range rows.Array() {
unsignedTasks := row.Get("unSignedTasks").Array()
leaveTasks := row.Get("leaveTasks").Array()
signedTasks := row.Get("signedTasks").Array()
taskJsons = append(taskJsons, append(append(unsignedTasks, leaveTasks...), signedTasks...)...)
}
titleSet := map[string]bool{}
for _, task := range taskJsons {
title := task.Get("taskName").String()
if title != "" {
titleSet[title] = true
}
}
titles := make([]string, 0, len(titleSet))
for title := range titleSet {
titles = append(titles, title)
}
if len(titles) == 0 {
utils.OkOrPanic(errors.NewBusinessError(503, "没有查询到查寝任务"), func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
In("CpdailySleepCheckTask.queryTitleByYearMonth").
Wrap(err)
})
}
if len(titles) == 0 {
utils.OkOrPanic(errors.NewBusinessError(503, "没有查询到查寝任务"), func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
In("CpdailySleepCheckTask.Count").
Wrap(err)
})
}
return titles
}
func (c *CpdailySleepCheckTask) QueryFormByTitle(ctx context.Context, titleGlob string) map[string]*gvar.Var {
response, err := c.client.R().
SetBody(map[string]any{}).
WithContext(ctx).
Post(c.getReqUrl("getStuAttendacesInOneDay"))
utils.OkOrPanic(err, func(err error) error {
return oops.With("host", c.host).
In("CpdailySleepCheckTask.QueryFormByTitle").
Wrap(err)
})
responseJSON := utils.ReadJson[map[string]*gvar.Var](response.Body)
findMatchTitle := func(jsonArr []any) map[string]*gvar.Var {
for _, rawJson := range jsonArr {
item := gvar.New(rawJson).MapStrVar()
if len(item) == 0 {
continue
}
taskName, exist := item["taskName"]
if !exist || taskName.String() == "" {
continue
}
if match.Match(taskName.String(), titleGlob) {
return item
}
}
return nil
}
datas := responseJSON["datas"].MapStrVar()
unSignedTasks := datas["unSignedTasks"].Array()
leaveTasks := datas["leaveTasks"].Array()
signedTasks := datas["signedTasks"].Array()
var matchTask map[string]*gvar.Var
matchTask = findMatchTitle(unSignedTasks)
if matchTask == nil {
matchTask = findMatchTitle(leaveTasks)
if matchTask == nil {
matchTask = findMatchTitle(signedTasks)
if matchTask == nil {
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("没有查询到查寝任务: %s", titleGlob)), func(err error) error {
return oops.With("host", c.host).
In("CpdailySleepCheckTask.QueryFormByTitle").
Wrap(err)
})
panic("unreachable code")
}
}
}
realTitle := matchTask["taskName"].String()
detailResponse, err := c.client.R().
SetBody(map[string]*gvar.Var{
"taskName": gvar.New(realTitle),
"signWid": matchTask["signWid"],
"signInstanceWid": matchTask["signInstanceWid"],
}).
WithContext(ctx).
Post(c.getReqUrl("detailSignInstance"))
utils.OkOrPanic(err, func(err error) error {
return oops.With("host", c.host).
In("CpdailySleepCheckTask.QueryFormByTitle").
With("titleGlob", titleGlob).
Wrap(err)
},
)
detailResponseJSON := utils.ReadJson[map[string]*gvar.Var](detailResponse.Body)
datas = detailResponseJSON["datas"].MapStrVar()
if len(datas) == 0 {
utils.OkOrPanic(errors.NewBusinessError(503, "未找到表单数据"), func(err error) error {
return oops.With("host", c.host).
In("CpdailySleepCheckTask.QueryFormByTitle").
With("titleGlob", titleGlob).
Wrap(err)
})
}
result := make(map[string]*gvar.Var)
result["realTitle"] = gvar.New(realTitle)
result["taskInfo"] = gvar.New(matchTask)
result["rows"] = gvar.New(datas)
return result
}
func (c *CpdailySleepCheckTask) QueryFormByYearMonth(ctx context.Context, titleGlob string, year, month string) map[string]*gvar.Var {
if len(month) == 1 {
month = "0" + month
}
response, err := c.client.R().
SetBody(map[string]any{
"statisticYearMonth": fmt.Sprintf("%s-%s", year, month),
}).
WithContext(ctx).
Post(c.getReqUrl("getStuSignInfosByWeekMonth"))
utils.OkOrPanic(err, func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
In("CpdailySleepCheckTask.QueryFormByYearMonth").
Wrap(err)
})
responseJSON := utils.ReadJson[map[string]*gvar.Var](response.Body)
datas := responseJSON["datas"].MapStrVar()
if len(datas) == 0 {
utils.OkOrPanic(errors.NewBusinessError(500, "查询查寝信息失败: 信息为空"), func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
In("CpdailySleepCheckTask.QueryFormByYearMonth").
Wrap(err)
})
}
rows := datas["rows"].Vars()
if len(rows) == 0 {
utils.OkOrPanic(errors.NewBusinessError(500, "查询查寝信息失败: 信息为空"), func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
In("CpdailySleepCheckTask.QueryFormByYearMonth").
Wrap(err)
})
}
// 收集所有任务
var allUnSignedTasks []*gvar.Var
var allLeaveTasks []*gvar.Var
var allSignedTasks []*gvar.Var
for _, row := range rows {
rowMap := row.MapStrVar()
if unSignedTasks := rowMap["unSignedTasks"].Vars(); len(unSignedTasks) > 0 {
allUnSignedTasks = append(allUnSignedTasks, unSignedTasks...)
}
if leaveTasks := rowMap["leaveTasks"].Vars(); len(leaveTasks) > 0 {
allLeaveTasks = append(allLeaveTasks, leaveTasks...)
}
if signedTasks := rowMap["signedTasks"].Vars(); len(signedTasks) > 0 {
allSignedTasks = append(allSignedTasks, signedTasks...)
}
}
findMatchTask := func(jsonArr []*gvar.Var) map[string]*gvar.Var {
for _, rawJson := range jsonArr {
item := rawJson.MapStrVar()
taskName, exist := item["taskName"]
if !exist || taskName.String() == "" {
continue
}
if match.Match(taskName.String(), titleGlob) {
return item
}
}
return nil
}
var matchTask map[string]*gvar.Var
// 按优先级查找匹配任务:未签到 > 请假 > 已签到
matchTask = findMatchTask(allUnSignedTasks)
if matchTask == nil {
matchTask = findMatchTask(allLeaveTasks)
if matchTask == nil {
matchTask = findMatchTask(allSignedTasks)
if matchTask == nil {
utils.OkOrPanic(errors.NewBusinessError(404, fmt.Sprintf("未找到匹配的任务: %s", titleGlob)), func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
With("titleGlob", titleGlob).
In("CpdailySleepCheckTask.QueryFormByYearMonth").
Wrap(err)
})
}
}
}
realTitle := matchTask["taskName"].String()
detailResponse, err := c.client.R().
SetBody(map[string]*gvar.Var{
"signInstanceWid": matchTask["signInstanceWid"],
"signWid": matchTask["signWid"],
"taskName": gvar.New(realTitle),
}).
WithContext(ctx).
Post(c.getReqUrl("detailSignInstance"))
utils.OkOrPanic(err, func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
With("titleGlob", titleGlob).
In("CpdailySleepCheckTask.QueryFormByYearMonth").
Wrap(err)
})
detailResponseJSON := utils.ReadJson[map[string]*gvar.Var](detailResponse.Body)
formDatas := detailResponseJSON["datas"].MapStrVar()
if len(formDatas) == 0 {
utils.OkOrPanic(errors.NewBusinessError(404, "未找到表单数据"), func(err error) error {
return oops.
With("host", c.host).
With("year", year).
With("month", month).
With("titleGlob", titleGlob).
In("CpdailySleepCheckTask.QueryFormByYearMonth").
Wrap(err)
})
}
result := make(map[string]*gvar.Var)
result["rows"] = gvar.New(formDatas)
result["taskInfo"] = gvar.New(matchTask)
result["realTitle"] = gvar.New(realTitle)
return result
}
func (c *CpdailySleepCheckTask) ParseFormJson(ctx context.Context, rawJson map[string]*gvar.Var) []*formtype.CpTaskFormItem {
rows := rawJson["rows"].MapStrVar()
if len(rows) == 0 {
utils.OkOrPanic(errors.NewBusinessError(503, "任务表单为空"), oops.WithContext(ctx).Wrap)
}
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)
dto := &formtype.CpTaskFormItem{
Title: "经纬度",
ColName: "latAndLon",
Type: formtype.ItemTypeLatAndLon,
Required: true,
Tip: "您可以通过地址搜索签到经纬度,如果觉得不够准确可以自行修改",
}
list = append(list, dto)
dto = &formtype.CpTaskFormItem{
Title: "查寝具体地址",
ColName: "address",
Type: formtype.ItemTypeInput,
Required: true,
Tip: "这个就是指签到完毕后查看时最底部那个最详细的地址信息哦",
}
list = append(list, dto)
if isPhoto := rows["isPhoto"]; isPhoto != nil && isPhoto.Int() == 1 {
dto = &formtype.CpTaskFormItem{
Title: "查寝照片",
ColName: "signImg",
Type: formtype.ItemTypeImage,
Required: true,
Tip: "您可以上传多张图片,会顺序轮换更新的哦~",
}
list = append(list, dto)
}
// 是否范围外签到
dto = &formtype.CpTaskFormItem{
Title: "是否范围外签到",
ColName: "isMalposition",
Type: formtype.ItemTypeSingle,
Required: true,
Options: []formtype.CpTaskFormItemOption{
{
Content: "是",
ShowLogic: []string{"abnormalReason"},
IsOtherItems: true,
},
{
Content: "否",
IsOtherItems: false,
},
},
Tip: "一般在学校就是选否,如果在外实习、请假之类的才选是",
}
list = append(list, dto)
for key, item := range list {
list[key].Show = item.LogicWid == ""
}
return list
}
func (c *CpdailySleepCheckTask) FillCustomForm(ctx context.Context, form map[string]*gvar.Var, paramList []*formtype.CpTaskFormItem, deviceId, imgId string) map[string]*gvar.Var {
var resultForm map[string]*gvar.Var
// 填充必要变量
rows := form["rows"].MapStrVar()
resultForm = make(map[string]*gvar.Var)
// 填充必要变量
// 获取经纬度、地址
var lat, lon, address string
for _, v := range paramList {
switch v.ColName {
case "lat":
lat = v.Content
case "lon":
lon = v.Content
case "address":
address = v.Content
case "latAndLon":
{
res := strings.Split(v.Content, ",")
lat, lon = res[1], res[0]
}
}
}
resultForm["latitude"] = gvar.New(lat)
resultForm["longitude"] = gvar.New(lon)
resultForm["position"] = gvar.New(address)
resultForm["uaIsCpadaily"] = gvar.New(true)
// 处理验证码
submitCaptchaResolver := submitcaptcha.NewNormalResolver(c.client, c.host, deviceId, types.TaskTypeSleepCheck)
//submitCaptchaResolver := submitcaptcha.NewAIResolver(c.client, c.host, deviceId, types.TaskTypeSleepCheck)
captchaInfo := submitCaptchaResolver.GetValidationInfo(ctx)
if captchaInfo.Validation {
var ticket string
e := retry.Do(func() error {
return utils.Catch(func() {
ticket = submitCaptchaResolver.GetCaptchaTicket(ctx, captchaInfo)
})
}, retry.Delay(10*time.Second), retry.Attempts(5), retry.Context(ctx))
if e != nil {
utils.OkOrPanic(errors.NewBusinessError(503, "提交验证码识别失败").WithInnerError(e), oops.WithContext(ctx).Wrap)
}
resultForm["ticket"] = gvar.New(ticket)
} else {
resultForm["ticket"] = gvar.New("")
}
if isPhoto := rows["isPhoto"]; isPhoto != nil && isPhoto.Int() == 1 {
var targetParam *formtype.CpTaskFormItem
for _, v := range paramList {
if v.ColName == "signImg" {
targetParam = v
break
}
}
if targetParam == nil || targetParam.Content == "" {
utils.OkOrPanic(errors.NewBusinessError(503, "未找到查寝照片参数,请上传照片"), oops.WithContext(ctx).Wrap)
}
if imgId == "" {
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)
}
// 判断是否存在二维码查寝
// 查找 qrUuid 参数
var qrUuidParam *formtype.CpTaskFormItem
for _, v := range paramList {
if v.ColName == "qrUuid" {
qrUuidParam = v
break
}
}
if qrUuidParam != nil {
// 如果存在 qrUuid,直接填充
resultForm["qrUuid"] = gvar.New(qrUuidParam.Content)
} else {
// 否则,填充查寝其他参数
if signInstanceWidVar := rows["signInstanceWid"]; signInstanceWidVar != nil {
resultForm["SignInstanceWid"] = gvar.New(signInstanceWidVar.String())
} else {
resultForm["SignInstanceWid"] = gvar.New("")
}
// 查找 isMalposition 参数
var isMalpositionParam *formtype.CpTaskFormItem
for _, v := range paramList {
if v.ColName == "isMalposition" {
isMalpositionParam = v
break
}
}
if isMalpositionParam != nil && isMalpositionParam.Content == "是" {
resultForm["isMalposition"] = gvar.New(1)
// 查找 abnormalReason 参数
var abnormalReason string
for _, v := range paramList {
if v.ColName == "abnormalReason" {
abnormalReason = v.Content
break
}
}
resultForm["abnormalReason"] = gvar.New(abnormalReason)
} else {
resultForm["isMalposition"] = gvar.New(0)
resultForm["abnormalReason"] = gvar.New("")
}
}
return resultForm
}
func (c *CpdailySleepCheckTask) Submit(ctx context.Context, form map[string]*gvar.Var, username, deviceId string) {
cpdailyInfo := cpdaily.GetInfo(ctx)
extensionForm := map[string]*gvar.Var{
"lon": form["longitude"],
"lat": form["latitude"],
"model": gvar.New(uuid.New().String()),
"appVersion": gvar.New(cpdailyInfo.AppVersion),
"systemVersion": gvar.New(cpdailyInfo.SystemVersion),
"userId": gvar.New(username),
"systemName": gvar.New("android"),
"deviceId": gvar.New(deviceId),
"cpdailyVersion": gvar.New(cpdailyInfo.AppVersion),
}
extensionString := taskhelper.GetCpdailyExtension(ctx, extensionForm)
bodyString := taskhelper.GetBodyString(ctx, form)
// 构造提交数据
submitData := map[string]*gvar.Var{
"lon": form["longitude"],
"version": gvar.New(cryptoutil.EncryptVersion),
"calVersion": gvar.New(cpdailyInfo.CalVersion),
"deviceId": extensionForm["deviceId"],
//"userId": gvar.New(username),
"systemName": gvar.New("android"),
"bodyString": gvar.New(bodyString),
"lat": form["latitude"],
"systemVersion": gvar.New(cpdailyInfo.SystemVersion),
"appVersion": gvar.New(cpdailyInfo.AppVersion),
"model": extensionForm["model"],
}
sign := taskhelper.GetSubmitSign(submitData)
submitData["sign"] = gvar.New(sign)
response, e := c.client.R().
SetBody(submitData).
WithContext(ctx).
SetHeader("Cpdaily-Extension", extensionString).
Post(c.getReqUrl("submitSign"))
utils.OkOrPanic(e, func(err error) error {
return oops.
With("host", c.host).
With("username", username).
With("deviceId", deviceId).
In("CpdailySleepCheckTask.Submit").
Wrap(err)
})
responseJSON := utils.ReadGjsonResult(response.Body)
if responseJSON.Get("code").String() != "0" {
utils.OkOrPanic(errors.NewBusinessError(503, responseJSON.Get("message").String()), oops.WithContext(ctx).Wrap)
}
}
func (c *CpdailySleepCheckTask) GetYearAndMonth(ctx context.Context) (string, string) {
// Get current time in Shanghai timezone
timeNow := time.Now().In(cst.TimeZoneShangHai)
// Check if current day is within first 2 days of the month
if timeNow.Day() <= 2 {
// Return previous month
prevTime := timeNow.AddDate(0, -1, 0)
return strconv.Itoa(prevTime.Year()), strconv.Itoa(int(prevTime.Month()))
} else {
// Return current month
return strconv.Itoa(timeNow.Year()), strconv.Itoa(int(timeNow.Month()))
}
}