✨ 学习通 & 销假
This commit is contained in:
+3
-1
@@ -51,6 +51,8 @@ sliderIds = ["1018764144934965", "sdhyxy", "hqdx"]
|
||||
|
||||
|
||||
[basePoint]
|
||||
cpdaily = "0.5"
|
||||
cpdailyTask = "0.5"
|
||||
cpdailyLeave = "4"
|
||||
|
||||
[task]
|
||||
concurrency = 20
|
||||
@@ -59,6 +59,7 @@ func StartServer(ctx context.Context) {
|
||||
cpdailyGroup.Bind(cpdaily.NewCpTaskCtrlV1())
|
||||
cpdailyGroup.Bind(cpdaily.NewCpImageCtrlV1())
|
||||
cpdailyGroup.Bind(cpdaily.NewCpTaskLogCtrlV1())
|
||||
cpdailyGroup.Bind(cpdaily.NewCpLeaveCtrlV1())
|
||||
// 娱乐
|
||||
musicGroup := group.Group("/music").Middleware(middleware.AuthHandler, middleware.PermissionHandler)
|
||||
musicGroup.Bind(music.NewMusicCtrlV1())
|
||||
|
||||
@@ -62,12 +62,17 @@ func (c *CpdailyReportApp) GetLeaveList() []leavetype.LeaveListOne {
|
||||
|
||||
var listList []leavetype.LeaveListOne
|
||||
for _, row := range rows.Array() {
|
||||
// 已完成
|
||||
if row.Get("status").String() == "4" {
|
||||
continue
|
||||
}
|
||||
leaveOne := leavetype.LeaveListOne{
|
||||
Id: row.Get("id").String(),
|
||||
LeaveType: row.Get("leaveType").String(),
|
||||
CreateTime: row.Get("createTime").String(),
|
||||
Reason: row.Get("reason").String(),
|
||||
AceEndTime: row.Get("aceEndTime").String(),
|
||||
Reason: row.Get("leaveReason").String(),
|
||||
AceEndTime: row.Get("actEndTime").String(),
|
||||
Status: row.Get("status").String(),
|
||||
}
|
||||
listList = append(listList, leaveOne)
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ type LeaveListOne struct {
|
||||
CreateTime string `json:"createTime"`
|
||||
AceEndTime string `json:"aceEndTime"`
|
||||
Reason string `json:"reason"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
@@ -62,44 +62,33 @@ type XueXiTCourse struct {
|
||||
CourseID string `json:"courseId"` // 课程ID
|
||||
ChatID string `json:"chatId"`
|
||||
CourseTeacher string `json:"courseTeacher"` // 课程老师
|
||||
CourseName string `json:"courseName"` //课程名
|
||||
CourseName string `json:"courseName"` // 课程名
|
||||
CourseImage string `json:"courseImage"`
|
||||
// 两个标识 暂时不知道有什么用
|
||||
CourseDataID int `json:"courseDataId"`
|
||||
ContentID int `json:"ContentID"`
|
||||
IsStart bool `json:"isstart"` //是否开课了,开课了为true,没开课为false。一般来说开课才能刷,不然刷不了的
|
||||
State int `json:"state"` //课程状态,0为正常,1为课程已经结束
|
||||
IsStart bool `json:"isstart"` // 是否开课了,开课了为true,没开课为false
|
||||
State int `json:"state"` // 课程状态,0为正常,1为课程已经结束
|
||||
}
|
||||
|
||||
// XueXiTCourseChapter 课程章节信息
|
||||
type XueXiTCourseChapter struct {
|
||||
ID string `json:"id"` // 章节ID
|
||||
ID int `json:"id"` // 章节ID
|
||||
Name string `json:"name"` // 章节名称
|
||||
ParentNodeID string `json:"parentnodeid"` // 父节点ID
|
||||
ParentNodeID int `json:"parentnodeid"` // 父节点ID
|
||||
Layer int `json:"layer"` // 层级
|
||||
Label string `json:"label"` // 标签
|
||||
IndexOrder int `json:"indexOrder"` // 排序索引
|
||||
JobCount int `json:"jobcount"` // 任务点数量
|
||||
Status int `json:"status"` // 状态
|
||||
Status string `json:"status"` // 状态
|
||||
IsReview int `json:"isReview"` // 是否复习
|
||||
BeginTime int64 `json:"begintime"` // 开始时间
|
||||
EndTime int64 `json:"endtime"` // 结束时间
|
||||
Attachment []XueXiTAttachment `json:"attachment"` // 附件列表
|
||||
PointTotal int `json:"pointTotal"` // 总任务点
|
||||
PointFinish int `json:"pointFinish"` // 已完成任务点
|
||||
Knowledge []XueXiTCourseChapter `json:"knowledge"` // 子章节列表
|
||||
}
|
||||
|
||||
// XueXiTAttachment 附件信息
|
||||
type XueXiTAttachment struct {
|
||||
ID string `json:"id"` // 附件ID
|
||||
Type string `json:"type"` // 附件类型(video/document等)
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
Extension string `json:"extension"` // 扩展信息
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
Property string `json:"property"` // 属性
|
||||
Name string `json:"name"` // 附件名称
|
||||
IsPassed bool `json:"isPassed"` // 是否已完成
|
||||
}
|
||||
|
||||
// ChapterData 章节数据响应
|
||||
type ChapterData struct {
|
||||
Data []struct {
|
||||
@@ -133,20 +122,7 @@ type ChapterData struct {
|
||||
Belongschoolid string `json:"belongschoolid"`
|
||||
Objectid string `json:"objectid"`
|
||||
Knowledge struct {
|
||||
Data []struct {
|
||||
Jobcount int `json:"jobcount"`
|
||||
Isreview int `json:"isreview"`
|
||||
Attachment struct {
|
||||
Data []interface{} `json:"data"`
|
||||
} `json:"attachment"`
|
||||
Indexorder int `json:"indexorder"`
|
||||
Name string `json:"name"`
|
||||
ID int `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Layer int `json:"layer"`
|
||||
Parentnodeid int `json:"parentnodeid"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
Data []KnowledgeData `json:"data"`
|
||||
} `json:"knowledge"`
|
||||
} `json:"data"`
|
||||
} `json:"course"`
|
||||
@@ -157,152 +133,15 @@ type ChapterData struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// Card 任务点卡片信息
|
||||
type Card struct {
|
||||
ID string `json:"id"` // 卡片ID
|
||||
Title string `json:"title"` // 标题
|
||||
Description string `json:"description"` // 描述
|
||||
KnowledgeID string `json:"knowledgeid"` // 知识点ID
|
||||
Type string `json:"type"` // 类型(video/document/work等)
|
||||
IsPassed bool `json:"isPassed"` // 是否完成
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
ResourceID string `json:"resourceid"` // 资源ID
|
||||
Attachment *IframeAttributes `json:"attachment,omitempty"` // iframe属性
|
||||
}
|
||||
|
||||
// IframeAttributes iframe属性
|
||||
type IframeAttributes struct {
|
||||
Src string `json:"src"` // iframe地址
|
||||
Module string `json:"module"` // 模块名
|
||||
ModToken string `json:"modtoken"` // 模块token
|
||||
Width string `json:"width"` // 宽度
|
||||
Height string `json:"height"` // 高度
|
||||
Scrolling string `json:"scrolling"` // 滚动
|
||||
ClassID string `json:"classid"` // 课程ID
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
Enc string `json:"enc"` // 加密参数
|
||||
UtEnc string `json:"utenc"` // ut加密参数
|
||||
OtherInfo string `json:"otherinfo"` // 其他信息
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
KnowledgeID string `json:"knowledgeid"` // 知识点ID
|
||||
CourseFaceCheck int `json:"coursefacecheck"` // 人脸识别
|
||||
}
|
||||
|
||||
// DataItem 任务点数据项
|
||||
type DataItem struct {
|
||||
ID string `json:"id"`
|
||||
IndexOrder int `json:"indexorder"`
|
||||
KnowledgeID string `json:"knowledgeid"`
|
||||
Attachment struct {
|
||||
ObjectID string `json:"objectid"`
|
||||
Property struct {
|
||||
Name string `json:"name"`
|
||||
IsPassed bool `json:"ispassed"`
|
||||
JobID string `json:"jobid"`
|
||||
} `json:"property"`
|
||||
Type string `json:"type"`
|
||||
} `json:"attachment"`
|
||||
PointList []interface{} `json:"pointList"`
|
||||
JobCount int `json:"jobcount"`
|
||||
Duration int64 `json:"duration"`
|
||||
ClazzID string `json:"clazzid"`
|
||||
CourseID string `json:"courseid"`
|
||||
Module string `json:"module"`
|
||||
}
|
||||
|
||||
// VideoDto 视频信息
|
||||
type VideoDto struct {
|
||||
VideoPath string `json:"videoPath"` // 视频路径
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
OtherInfo string `json:"otherInfo"` // 其他信息
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
Duration int `json:"duration"` // 时长(秒)
|
||||
DToken string `json:"dtoken"` // dtoken
|
||||
IsAudio bool `json:"isAudio"` // 是否是音频
|
||||
IsPassed bool `json:"isPassed"` // 是否已完成
|
||||
}
|
||||
|
||||
// DocumentDto 文档信息
|
||||
type DocumentDto struct {
|
||||
ObjectID string `json:"objectid"` // 对象ID
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
OtherInfo string `json:"otherInfo"` // 其他信息
|
||||
JToken string `json:"jtoken"` // jtoken
|
||||
PageCount int `json:"pageCount"` // 页数
|
||||
IsPassed bool `json:"isPassed"` // 是否已完成
|
||||
}
|
||||
|
||||
// FaceQRCodeResponse 人脸识别二维码响应
|
||||
type FaceQRCodeResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
QR string `json:"qr"` // 二维码base64数据
|
||||
Enc string `json:"enc"` // 加密参数
|
||||
}
|
||||
|
||||
// FaceTokenResponse 人脸识别上传令牌响应
|
||||
type FaceTokenResponse struct {
|
||||
Result int `json:"result"`
|
||||
UploadToken string `json:"uploadtoken"`
|
||||
}
|
||||
|
||||
// FaceUploadResponse 人脸识别上传响应
|
||||
type FaceUploadResponse struct {
|
||||
Result bool `json:"result"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
FileID string `json:"fileId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// FaceVerifyResponse 人脸识别验证响应
|
||||
type FaceVerifyResponse struct {
|
||||
Status bool `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// WorkDto 作业信息
|
||||
type WorkDto struct {
|
||||
WorkID string `json:"workid"` // 作业ID
|
||||
CourseID string `json:"courseid"` // 课程ID
|
||||
ClassID string `json:"classid"` // 班级ID
|
||||
KnowledgeID string `json:"knowledgeid"` // 知识点ID
|
||||
JobID string `json:"jobid"` // 任务ID
|
||||
Enc string `json:"enc"` // 加密参数
|
||||
UTEnc string `json:"utenc"` // UT加密参数
|
||||
Title string `json:"title"` // 作业标题
|
||||
IsPassed bool `json:"isPassed"` // 是否已完成
|
||||
Questions []WorkQuestion `json:"questions"` // 题目列表
|
||||
}
|
||||
|
||||
// WorkQuestion 作业题目
|
||||
type WorkQuestion struct {
|
||||
QuestionID string `json:"questionId"` // 题目ID
|
||||
Type int `json:"type"` // 题目类型: 0单选 1多选 2判断 3填空 4简答
|
||||
Title string `json:"title"` // 题目标题
|
||||
Description string `json:"description"` // 题目描述
|
||||
Options []string `json:"options"` // 选项列表(单选/多选/判断)
|
||||
Answer string `json:"answer"` // 答案
|
||||
Score float64 `json:"score"` // 分值
|
||||
}
|
||||
|
||||
// WorkSubmitRequest 作业提交请求
|
||||
type WorkSubmitRequest struct {
|
||||
WorkID string `json:"workid"`
|
||||
TotalScore string `json:"totalScore"`
|
||||
AnswerList []WorkAnswerItem `json:"answerlist"`
|
||||
UserId string `json:"userId"`
|
||||
ClassId string `json:"classId"`
|
||||
CourseId string `json:"courseId"`
|
||||
KnowledgeId string `json:"knowledgeid"`
|
||||
JobId string `json:"jobid"`
|
||||
Enc string `json:"enc"`
|
||||
UTEnc string `json:"utenc"`
|
||||
}
|
||||
|
||||
// WorkAnswerItem 作业答案项
|
||||
type WorkAnswerItem struct {
|
||||
QuestionID string `json:"questionId"`
|
||||
Answer string `json:"answer"`
|
||||
// KnowledgeData 章节知识点数据
|
||||
type KnowledgeData struct {
|
||||
Jobcount int `json:"jobcount"`
|
||||
Isreview int `json:"isreview"`
|
||||
Indexorder int `json:"indexorder"`
|
||||
Name string `json:"name"`
|
||||
ID int `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Layer int `json:"layer"`
|
||||
Parentnodeid int `json:"parentnodeid"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
package entity
|
||||
|
||||
// PointType 任务点类型
|
||||
type PointType string
|
||||
|
||||
const (
|
||||
PointTypeVideo PointType = "insertvideo"
|
||||
PointTypeDocument PointType = "insertdoc"
|
||||
PointTypeDocument2 PointType = "document"
|
||||
PointTypeBook PointType = "insertbook"
|
||||
PointTypeWork PointType = "insertwork"
|
||||
PointTypeHyperlink PointType = "insertlink"
|
||||
PointTypeLive PointType = "insertlive"
|
||||
)
|
||||
|
||||
// PointVideoDto 视频任务点
|
||||
type PointVideoDto struct {
|
||||
CardIndex int
|
||||
CourseID string
|
||||
ClassID string
|
||||
KnowledgeID int
|
||||
Cpi string
|
||||
ObjectID string
|
||||
// 从SSR视图中获取
|
||||
IsPassed bool
|
||||
FID int
|
||||
DToken string
|
||||
PlayTime int
|
||||
Duration int
|
||||
JobID string
|
||||
OtherInfo string
|
||||
Title string
|
||||
RT float64
|
||||
VideoFaceCaptureEnc string
|
||||
RandomCaptureTime string
|
||||
AttDurationEnc string
|
||||
Enc string
|
||||
Mid string
|
||||
}
|
||||
|
||||
// PointDocumentDto 文档查看任务点
|
||||
type PointDocumentDto struct {
|
||||
CardIndex int
|
||||
CourseID string
|
||||
ClassID string
|
||||
KnowledgeID int
|
||||
Cpi string
|
||||
ObjectID string
|
||||
Title string
|
||||
JobID string
|
||||
Jtoken string
|
||||
IsJob bool // 是否是任务点
|
||||
Type string // document / insertbook
|
||||
}
|
||||
|
||||
// PointWorkDto 测验/作业任务点
|
||||
type PointWorkDto struct {
|
||||
CardIndex int
|
||||
CourseID string
|
||||
ClassID string
|
||||
KnowledgeID int
|
||||
Cpi string
|
||||
WorkID string
|
||||
SchoolID string
|
||||
JobID string
|
||||
PUID string
|
||||
KToken string
|
||||
Enc string
|
||||
Title string
|
||||
IsPassed bool
|
||||
}
|
||||
|
||||
// PointHyperlinkDto 外链任务点
|
||||
type PointHyperlinkDto struct {
|
||||
CardIndex int
|
||||
CourseID string
|
||||
ClassID string
|
||||
KnowledgeID int
|
||||
Cpi string
|
||||
ObjectID string
|
||||
Title string
|
||||
JobID string
|
||||
Jtoken string
|
||||
LinkType int // 外链类型
|
||||
IsJob bool // 是否是任务点
|
||||
}
|
||||
|
||||
// PointLiveDto 直播任务点
|
||||
type PointLiveDto struct {
|
||||
CardIndex int
|
||||
CourseID string
|
||||
ClassID string
|
||||
KnowledgeID int
|
||||
Cpi string
|
||||
UserId string // 用户ID
|
||||
Live bool
|
||||
LiveId string // 直播ID
|
||||
Vdoid string
|
||||
Mid string
|
||||
Title string // 直播标题
|
||||
JobID string
|
||||
StreamName string // 直播流名称
|
||||
LiveStatusStr string // 直播状态文字
|
||||
LiveStatusCode int // 直播状态码 0=未开始
|
||||
VideoDuration int // 视频时长
|
||||
Aid string
|
||||
Module string // 类型
|
||||
IsJob bool // 是否为任务点
|
||||
AuthEnc string
|
||||
LiveDragEnc string
|
||||
LiveSetEnc string
|
||||
OtherInfo string
|
||||
Enc string
|
||||
LiveSwDsEnc string
|
||||
VideoCompletePercent float64 // 观看进度
|
||||
}
|
||||
|
||||
// Card 任务点卡片信息
|
||||
type Card struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
CardOrder int `json:"cardorder"`
|
||||
KnowledgeID int `json:"knowledgeid"`
|
||||
}
|
||||
|
||||
// CardAPIResponse 卡片API响应
|
||||
type CardAPIResponse struct {
|
||||
Data []CardDataItem `json:"data"`
|
||||
}
|
||||
|
||||
// CardDataItem 卡片数据项
|
||||
type CardDataItem struct {
|
||||
ClickCount int `json:"clickcount"`
|
||||
Createtime int64 `json:"createtime"`
|
||||
OpenLock int `json:"openlock"`
|
||||
IndexOrder int `json:"indexorder"`
|
||||
Name string `json:"name"`
|
||||
LastModifyTime int64 `json:"lastmodifytime"`
|
||||
ID int `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Layer int `json:"layer"`
|
||||
Card struct {
|
||||
Data []Card `json:"data"`
|
||||
} `json:"card"`
|
||||
ParentNodeID int `json:"parentnodeid"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// IframeAttributes iframe 的属性
|
||||
type IframeAttributes struct {
|
||||
Data map[string]interface{} `json:"data"`
|
||||
Other map[string]string
|
||||
HasData bool
|
||||
}
|
||||
|
||||
// ChapterPointStatus 章节任务点状态
|
||||
type ChapterPointStatus map[string]struct {
|
||||
ClickCount int `json:"clickcount"`
|
||||
FinishCount int `json:"finishcount"`
|
||||
TotalCount int `json:"totalcount"`
|
||||
OpenLock int `json:"openlock"`
|
||||
UnFinishCount int `json:"unfinishcount"`
|
||||
}
|
||||
|
||||
// AttachmentSetting 附件设置(从页面JS中解析)
|
||||
type AttachmentSetting struct {
|
||||
Attachments []map[string]interface{} `json:"attachments"`
|
||||
Defaults map[string]interface{} `json:"defaults"`
|
||||
}
|
||||
|
||||
// ChapterPoints 章节所有任务点集合
|
||||
type ChapterPoints struct {
|
||||
Videos []PointVideoDto
|
||||
Documents []PointDocumentDto
|
||||
Works []PointWorkDto
|
||||
Hyperlinks []PointHyperlinkDto
|
||||
Lives []PointLiveDto
|
||||
}
|
||||
|
||||
// QuestionType 题目类型
|
||||
type QuestionType int
|
||||
|
||||
const (
|
||||
QuestionTypeSingleChoice QuestionType = 0 // 单选题
|
||||
QuestionTypeMultipleChoice QuestionType = 1 // 多选题
|
||||
QuestionTypeFillBlank QuestionType = 2 // 填空题
|
||||
QuestionTypeTrueFalse QuestionType = 3 // 判断题
|
||||
QuestionTypeShortAnswer QuestionType = 4 // 简答题
|
||||
)
|
||||
|
||||
// WorkQuestion 作业题目
|
||||
type WorkQuestion struct {
|
||||
Qid string `json:"qid"` // 题目ID
|
||||
Type QuestionType `json:"type"` // 题目类型
|
||||
Text string `json:"text"` // 题目内容
|
||||
Options map[string]string `json:"options"` // 选项 (A: xxx, B: xxx)
|
||||
Answers []string `json:"answers"` // 答案
|
||||
BlankCount int `json:"blankCount"` // 填空题空格数量
|
||||
BlankAnswer map[string]string `json:"blankAnswer"` // 填空题答案 (第1空: xxx)
|
||||
}
|
||||
|
||||
// WorkDto 作业详情
|
||||
type WorkDto struct {
|
||||
Title string `json:"title"` // 作业标题
|
||||
Cpi string `json:"cpi"` // cpi
|
||||
JobId string `json:"jobId"` // 任务ID
|
||||
WorkId string `json:"workId"` // 作业ID
|
||||
ClassId string `json:"classId"` // 班级ID
|
||||
CourseId string `json:"courseId"` // 课程ID
|
||||
KnowledgeId string `json:"knowledgeId"` // 知识点ID
|
||||
UserId string `json:"userId"` // 用户ID
|
||||
FullScore string `json:"fullScore"` // 满分
|
||||
OldWorkId string `json:"oldWorkId"` // 原始作业ID
|
||||
OldSchoolId string `json:"oldSchoolId"` // 原始学校ID
|
||||
Api string `json:"api"` // API值
|
||||
WorkRelationId string `json:"workRelationId"` // 作业关联ID
|
||||
EncWork string `json:"encWork"` // 加密参数
|
||||
WorkAnswerId string `json:"workAnswerId"` // 作业答案ID
|
||||
AnswerId string `json:"answerId"` // 答案ID
|
||||
TotalQuestionNum string `json:"totalQuestionNum"` // 题目总数
|
||||
Questions []WorkQuestion `json:"questions"` // 题目列表
|
||||
}
|
||||
@@ -9,10 +9,8 @@ import (
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
var randChar []string = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F"}
|
||||
|
||||
// ApiVerifyCodeBytes 获取学习通验证码图片
|
||||
func ApiVerifyCodeBytes(client *resty.Client) ([]byte, error) {
|
||||
|
||||
urlStr := "https://mooc1-api.chaoxing.com/processVerifyPng.ac?t=" + fmt.Sprintf("%d", time.Now().UnixMilli())
|
||||
|
||||
req := client.R()
|
||||
@@ -32,8 +30,9 @@ func ApiVerifyCodeBytes(client *resty.Client) ([]byte, error) {
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func ApiPassVerifyCode(client *resty.Client, code string, retry int) bool {
|
||||
if retry < 0 {
|
||||
// ApiPassVerifyCode 提交学习通验证码
|
||||
func ApiPassVerifyCode(client *resty.Client, code string, retryCount int) bool {
|
||||
if retryCount < 0 {
|
||||
return false
|
||||
}
|
||||
urlStr := "https://mooc1-api.chaoxing.com/html/processVerify.ac"
|
||||
@@ -47,7 +46,7 @@ func ApiPassVerifyCode(client *resty.Client, code string, retry int) bool {
|
||||
req.Header.Add("Connection", "keep-alive")
|
||||
res, err := req.Post(urlStr)
|
||||
if err != nil {
|
||||
return ApiPassVerifyCode(client, code, retry-1)
|
||||
return ApiPassVerifyCode(client, code, retryCount-1)
|
||||
}
|
||||
client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
return res.StatusCode() == 302
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,27 @@ package xuexitong
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/application/xuexitong/entity"
|
||||
"likedream-goframe/internal/framework/errors"
|
||||
"likedream-goframe/internal/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSchedule(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
xuexitongLogin := NewXuexitongLogin(ctx)
|
||||
err := xuexitongLogin.Login(context.Background(), "13151832241", "yyz@083422", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseService := NewXuexitongCourse(client)
|
||||
|
||||
// 人人必修的人工智能课
|
||||
err = courseService.AutoStudyByCourseID(ctx, "258109701")
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessErrorWithDetail(503, "刷课失败", err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestXuexitongLogin(t *testing.T) {
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
@@ -39,7 +55,7 @@ func TestPullChapter(t *testing.T) {
|
||||
}
|
||||
|
||||
// 获取第一个课程的章节
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), courseList[0].CourseID)
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), courseList[0])
|
||||
t.Logf("课程 %s 的章节数量: %d", courseList[0].CourseName, len(chapters))
|
||||
|
||||
for _, chapter := range chapters {
|
||||
@@ -64,147 +80,48 @@ func TestFetchChapterCards(t *testing.T) {
|
||||
course := courseList[0]
|
||||
|
||||
// 获取章节列表
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), course.CourseID)
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), course)
|
||||
if len(chapters) == 0 {
|
||||
t.Fatal("没有找到章节")
|
||||
}
|
||||
|
||||
// 找一个有任务点的章节
|
||||
var targetChapter *entity.XueXiTCourseChapter
|
||||
for i := range chapters {
|
||||
if chapters[i].JobCount > 0 {
|
||||
targetChapter = &chapters[i]
|
||||
var targetChapterID int
|
||||
for _, ch := range chapters {
|
||||
if ch.JobCount > 0 {
|
||||
targetChapterID = ch.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetChapter == nil {
|
||||
if targetChapterID == 0 {
|
||||
t.Fatal("没有找到有任务点的章节")
|
||||
}
|
||||
|
||||
// 获取任务点卡片
|
||||
cards, err := courseAction.FetchChapterCards(context.Background(), course, targetChapter.ID, targetChapter.ID)
|
||||
cards, points, err := courseAction.FetchChapterCards(context.Background(), course, targetChapterID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
t.Logf("章节 %s 的任务点数量: %d", targetChapter.Name, len(cards))
|
||||
t.Logf("卡片数量: %d", len(cards))
|
||||
if points != nil {
|
||||
t.Logf("视频数量: %d, 文档数量: %d, 作业数量: %d, 外链数量: %d, 直播数量: %d",
|
||||
len(points.Videos), len(points.Documents), len(points.Works),
|
||||
len(points.Hyperlinks), len(points.Lives))
|
||||
|
||||
for _, card := range cards {
|
||||
t.Logf("任务点: 类型=%s, JobID=%s, ObjectID=%s", card.Type, card.JobID, card.ObjectID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoPlayVideo(t *testing.T) {
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseAction := NewXuexitongCourse(client)
|
||||
|
||||
// 获取课程列表
|
||||
courseList := courseAction.PullCourseList(context.Background())
|
||||
if len(courseList) == 0 {
|
||||
t.Fatal("没有找到课程")
|
||||
}
|
||||
|
||||
course := courseList[0]
|
||||
|
||||
// 获取章节列表
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), course.CourseID)
|
||||
if len(chapters) == 0 {
|
||||
t.Fatal("没有找到章节")
|
||||
}
|
||||
|
||||
// 找一个有任务点的章节
|
||||
var targetChapter *entity.XueXiTCourseChapter
|
||||
for i := range chapters {
|
||||
if chapters[i].JobCount > 0 {
|
||||
targetChapter = &chapters[i]
|
||||
break
|
||||
for _, video := range points.Videos {
|
||||
t.Logf("视频任务点: JobID=%s, ObjectID=%s", video.JobID, video.ObjectID)
|
||||
}
|
||||
}
|
||||
|
||||
if targetChapter == nil {
|
||||
t.Fatal("没有找到有任务点的章节")
|
||||
}
|
||||
|
||||
// 获取任务点卡片
|
||||
cards, err := courseAction.FetchChapterCards(context.Background(), course, targetChapter.ID, targetChapter.ID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 找视频任务点
|
||||
for _, card := range cards {
|
||||
if card.Type == "video" {
|
||||
t.Logf("开始处理视频任务点: %s", card.JobID)
|
||||
|
||||
// 获取视频信息
|
||||
videoDto, err := courseAction.FetchVideoDto(context.Background(), card.ObjectID, "", card.JobID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 自动播放视频
|
||||
err = courseAction.AutoPlayVideo(context.Background(), course, card, videoDto)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
t.Logf("视频任务点完成: %s", card.JobID)
|
||||
break // 只测试一个视频
|
||||
for _, doc := range points.Documents {
|
||||
t.Logf("文档任务点: JobID=%s, Type=%s", doc.JobID, doc.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutoReadDocument(t *testing.T) {
|
||||
xuexitongLogin := NewXuexitongLogin(context.Background())
|
||||
err := xuexitongLogin.Login(context.Background(), "13551503012", "xiaozhi520", 5)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseAction := NewXuexitongCourse(client)
|
||||
|
||||
// 获取课程列表
|
||||
courseList := courseAction.PullCourseList(context.Background())
|
||||
if len(courseList) == 0 {
|
||||
t.Fatal("没有找到课程")
|
||||
}
|
||||
|
||||
course := courseList[0]
|
||||
|
||||
// 获取章节列表
|
||||
chapters := courseAction.PullCourseChapterList(context.Background(), course.CourseID)
|
||||
if len(chapters) == 0 {
|
||||
t.Fatal("没有找到章节")
|
||||
}
|
||||
|
||||
// 找一个有任务点的章节
|
||||
var targetChapter *entity.XueXiTCourseChapter
|
||||
for i := range chapters {
|
||||
if chapters[i].JobCount > 0 {
|
||||
targetChapter = &chapters[i]
|
||||
break
|
||||
for _, work := range points.Works {
|
||||
t.Logf("作业任务点: JobID=%s, WorkID=%s", work.JobID, work.WorkID)
|
||||
}
|
||||
}
|
||||
|
||||
if targetChapter == nil {
|
||||
t.Fatal("没有找到有任务点的章节")
|
||||
}
|
||||
|
||||
// 获取任务点卡片
|
||||
cards, err := courseAction.FetchChapterCards(context.Background(), course, targetChapter.ID, targetChapter.ID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 找文档任务点
|
||||
for _, card := range cards {
|
||||
if card.Type == "document" {
|
||||
t.Logf("开始处理文档任务点: %s", card.JobID)
|
||||
|
||||
// 获取文档信息
|
||||
documentDto, err := courseAction.FetchDocumentDto(context.Background(), card.ObjectID, "", card.JobID)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
// 自动阅读文档
|
||||
err = courseAction.AutoReadDocument(context.Background(), course, card, documentDto)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
t.Logf("文档任务点完成: %s", card.JobID)
|
||||
break // 只测试一个文档
|
||||
for _, link := range points.Hyperlinks {
|
||||
t.Logf("外链任务点: JobID=%s", link.JobID)
|
||||
}
|
||||
for _, live := range points.Lives {
|
||||
t.Logf("直播任务点: JobID=%s, LiveId=%s, Title=%s", live.JobID, live.LiveId, live.Title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package xuexitong
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
crand "crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -34,7 +34,7 @@ func GetUA(uaType string) string {
|
||||
|
||||
return strings.Join([]string{
|
||||
fmt.Sprintf("Mozilla/5.0 (Linux; %s; %s Build/OPM1.171019.019; wv)", ANDROID_VERSION, DEVICE_VENDOR),
|
||||
fmt.Sprintf("AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36"),
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36",
|
||||
fmt.Sprintf("(schild:%s)", MobileUASign(DEVICE_VENDOR, "zh_CN", APP_VERSION, BUILD, IMEI)),
|
||||
fmt.Sprintf("(device:%s)", DEVICE_VENDOR),
|
||||
"Language/zh_CN",
|
||||
@@ -48,15 +48,14 @@ func GetUA(uaType string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// 用于生成IMEI
|
||||
func TokenHex(len int) string {
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
// TokenHex 用于生成IMEI
|
||||
func TokenHex(length int) string {
|
||||
b := make([]byte, length)
|
||||
_, err := crand.Read(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
token := hex.EncodeToString(b)
|
||||
return token
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
func MobileUASign(model, locale, version, build, imei string) string {
|
||||
|
||||
@@ -37,7 +37,8 @@ const (
|
||||
ConfigEmailFromName = "email.fromName"
|
||||
|
||||
// 基础积分配置
|
||||
ConfigBasePointCpdaily = "basePoint.cpdaily"
|
||||
ConfigBasePointCpdailyTask = "basePoint.cpdailyTask"
|
||||
ConfigBasePointCpdailyLeave = "basePoint.cpdailyLeave"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"likedream-goframe/internal/application/xuexitong"
|
||||
"likedream-goframe/internal/application/xuexitong/entity"
|
||||
"likedream-goframe/internal/common/db"
|
||||
errors2 "likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/ent"
|
||||
@@ -105,13 +106,25 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task
|
||||
// 刷课完成后,统计任务情况
|
||||
g.Log().Infof(ctx, "刷课完成,正在统计任务情况...")
|
||||
|
||||
// 获取章节列表
|
||||
chapters := courseService.PullCourseChapterList(ctx, targetCourse.CourseID)
|
||||
// 先获取课程列表找到目标课程
|
||||
courseList := courseService.PullCourseList(ctx)
|
||||
var course *entity.XueXiTCourse
|
||||
for _, c := range courseList {
|
||||
if c.CourseID == targetCourse.CourseID {
|
||||
course = &c
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 统计总任务数(累加所有章节的JobCount)
|
||||
totalTasks := 0
|
||||
for _, chapter := range chapters {
|
||||
totalTasks += chapter.JobCount
|
||||
if course != nil {
|
||||
// 获取章节列表
|
||||
chapters := courseService.PullCourseChapterList(ctx, *course)
|
||||
|
||||
// 统计总任务数(累加所有章节的JobCount)
|
||||
for _, chapter := range chapters {
|
||||
totalTasks += chapter.JobCount
|
||||
}
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "课程 %s 统计完成: 总任务点 %d", targetCourse.CourseName, totalTasks)
|
||||
|
||||
@@ -18,6 +18,9 @@ type BalanceServiceV1 struct {
|
||||
}
|
||||
|
||||
func (v *BalanceServiceV1) ChangeUserBalance(ctx context.Context, userId int64, balance decimal.Decimal, reason string) {
|
||||
if decimal.Zero.Equal(balance) {
|
||||
return
|
||||
}
|
||||
err := db.WithTx(ctx, func(c context.Context, tx *ent.Tx) {
|
||||
// 使用 SELECT ... FOR UPDATE 锁定用户记录
|
||||
user, err := tx.SysUser.Query().
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"likedream-goframe/internal/ent/sysuser"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"likedream-goframe/internal/utils"
|
||||
"strconv"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"go.uber.org/zap"
|
||||
@@ -78,9 +79,19 @@ func (s CourseServiceV1) AddCourse(ctx context.Context, req *v1.AddCourseReq) *u
|
||||
|
||||
client := loginClient.GetClient()
|
||||
courseService := xuexitong.NewXuexitongCourse(client)
|
||||
|
||||
var targetCourse *entity.XueXiTCourse
|
||||
// 获取课程章节列表
|
||||
chapters := courseService.PullCourseChapterList(ctx, req.CourseID)
|
||||
courseList := courseService.PullCourseList(ctx)
|
||||
for _, course := range courseList {
|
||||
if course.CourseID == req.CourseID {
|
||||
targetCourse = &course
|
||||
break
|
||||
}
|
||||
}
|
||||
if targetCourse == nil {
|
||||
utils.OkOrPanic(errors2.NewBusinessError(400, "未找到该课程"))
|
||||
}
|
||||
chapters := courseService.PullCourseChapterList(ctx, *targetCourse)
|
||||
|
||||
// 定义节点信息结构
|
||||
type NodeInfo struct {
|
||||
@@ -94,46 +105,79 @@ func (s CourseServiceV1) AddCourse(ctx context.Context, req *v1.AddCourseReq) *u
|
||||
var allNodes []NodeInfo
|
||||
var completedNodes []string
|
||||
|
||||
// 获取课程信息用于API调用
|
||||
courses := courseService.PullCourseList(ctx)
|
||||
var targetCourse *entity.XueXiTCourse
|
||||
for _, c := range courses {
|
||||
if c.CourseID == req.CourseID {
|
||||
targetCourse = &c
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetCourse == nil {
|
||||
utils.OkOrPanic(errors2.NewBusinessError(400, "未找到该课程"))
|
||||
}
|
||||
|
||||
// 递归获取所有章节的任务点
|
||||
var collectNodes func(chapters []entity.XueXiTCourseChapter)
|
||||
collectNodes = func(chapters []entity.XueXiTCourseChapter) {
|
||||
for _, chapter := range chapters {
|
||||
// 如果有任务点,获取卡片信息
|
||||
if chapter.JobCount > 0 {
|
||||
cards, err := courseService.FetchChapterCards(ctx, *targetCourse, chapter.ID, chapter.ID)
|
||||
cards, points, err := courseService.FetchChapterCards(ctx, *targetCourse, chapter.ID)
|
||||
if err != nil {
|
||||
logger.Logger.Warn("获取章节任务点失败", zap.String("chapter", chapter.Name), zap.Error(err))
|
||||
} else {
|
||||
for _, card := range cards {
|
||||
} else if points != nil {
|
||||
chapterIDStr := strconv.Itoa(chapter.ID)
|
||||
// 统计视频任务点
|
||||
for _, video := range points.Videos {
|
||||
nodeInfo := NodeInfo{
|
||||
ChapterID: chapter.ID,
|
||||
ChapterID: chapterIDStr,
|
||||
ChapterName: chapter.Name,
|
||||
CardID: card.ID,
|
||||
CardTitle: card.Title,
|
||||
CardType: card.Type,
|
||||
CardID: video.JobID,
|
||||
CardTitle: video.Title,
|
||||
CardType: "video",
|
||||
}
|
||||
allNodes = append(allNodes, nodeInfo)
|
||||
|
||||
// 检查是否已完成
|
||||
if card.IsPassed {
|
||||
nodeKey := chapter.ID + ":" + card.ID
|
||||
completedNodes = append(completedNodes, nodeKey)
|
||||
if video.IsPassed {
|
||||
completedNodes = append(completedNodes, chapterIDStr+":"+video.JobID)
|
||||
}
|
||||
}
|
||||
// 统计文档任务点
|
||||
for _, doc := range points.Documents {
|
||||
nodeInfo := NodeInfo{
|
||||
ChapterID: chapterIDStr,
|
||||
ChapterName: chapter.Name,
|
||||
CardID: doc.JobID,
|
||||
CardTitle: doc.Title,
|
||||
CardType: "document",
|
||||
}
|
||||
allNodes = append(allNodes, nodeInfo)
|
||||
}
|
||||
// 统计作业任务点
|
||||
for _, work := range points.Works {
|
||||
nodeInfo := NodeInfo{
|
||||
ChapterID: chapterIDStr,
|
||||
ChapterName: chapter.Name,
|
||||
CardID: work.JobID,
|
||||
CardTitle: work.Title,
|
||||
CardType: "work",
|
||||
}
|
||||
allNodes = append(allNodes, nodeInfo)
|
||||
if work.IsPassed {
|
||||
completedNodes = append(completedNodes, chapterIDStr+":"+work.JobID)
|
||||
}
|
||||
}
|
||||
// 统计外链任务点
|
||||
for _, link := range points.Hyperlinks {
|
||||
nodeInfo := NodeInfo{
|
||||
ChapterID: chapterIDStr,
|
||||
ChapterName: chapter.Name,
|
||||
CardID: link.JobID,
|
||||
CardTitle: link.Title,
|
||||
CardType: "hyperlink",
|
||||
}
|
||||
allNodes = append(allNodes, nodeInfo)
|
||||
}
|
||||
// 统计直播任务点
|
||||
for _, live := range points.Lives {
|
||||
nodeInfo := NodeInfo{
|
||||
ChapterID: chapterIDStr,
|
||||
ChapterName: chapter.Name,
|
||||
CardID: live.JobID,
|
||||
CardTitle: live.Title,
|
||||
CardType: "live",
|
||||
}
|
||||
allNodes = append(allNodes, nodeInfo)
|
||||
}
|
||||
_ = cards // cards 暂时不使用
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,14 +120,14 @@ func (v CpLeaveServiceV1) Report(ctx context.Context, req *v1.CpLeaveReportReq)
|
||||
}
|
||||
|
||||
// 计算扣款金额
|
||||
basePoint, err := decimal.NewFromString(g.Cfg().MustGet(ctx, cst2.ConfigBasePointCpdaily).String())
|
||||
basePoint, err := decimal.NewFromString(g.Cfg().MustGet(ctx, cst2.ConfigBasePointCpdailyLeave).String())
|
||||
utils.OkOrPanic(err)
|
||||
if user.Edges.Agent != nil {
|
||||
basePoint = basePoint.Mul(user.Edges.Agent.Rate)
|
||||
}
|
||||
|
||||
// 先扣款
|
||||
balance.BalanceService.ChangeUserBalance(ctx, user.ID, basePoint.Neg(), "销假")
|
||||
balance.BalanceService.ChangeUserBalance(ctx, user.ID, basePoint.Neg(), "今日校园销假")
|
||||
|
||||
// 创建销假记录
|
||||
leaveRecord := db2.Global.CpLeave.Create().
|
||||
|
||||
@@ -180,7 +180,7 @@ func (v CpTaskServiceV1) PostExecute(ctx context.Context, req *v1.PostCpTaskExec
|
||||
}
|
||||
|
||||
//扣积分
|
||||
basePoint, err := decimal.NewFromString(g.Cfg().MustGet(ctx, cst2.ConfigBasePointCpdaily).String())
|
||||
basePoint, err := decimal.NewFromString(g.Cfg().MustGet(ctx, cst2.ConfigBasePointCpdailyTask).String())
|
||||
utils.OkOrPanic(err)
|
||||
if user.Edges.Agent != nil {
|
||||
basePoint = basePoint.Mul(user.Edges.Agent.Rate)
|
||||
|
||||
Reference in New Issue
Block a user