Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d14ddc212d | |||
| 55fc94c37f | |||
| a590f58c6c |
@@ -121,7 +121,9 @@ func (c *XuexitongCourse) AutoCompleteWorkWithAI(ctx context.Context, p *entity.
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取作业题目失败: %w", err)
|
||||
}
|
||||
|
||||
if workDto.IsPassed {
|
||||
return "", errors.New("作业已完成")
|
||||
}
|
||||
if len(workDto.Questions) == 0 {
|
||||
return "", errors.New("作业无题目")
|
||||
}
|
||||
|
||||
@@ -271,7 +271,8 @@ type WorkQuestion struct {
|
||||
|
||||
// WorkDto 作业详情
|
||||
type WorkDto struct {
|
||||
Title string `json:"title"` // 作业标题
|
||||
Title string `json:"title"` // 作业标题
|
||||
IsPassed bool `json:"isPassed"`
|
||||
Cpi string `json:"cpi"` // cpi
|
||||
JobId string `json:"jobId"` // 任务ID
|
||||
WorkId string `json:"workId"` // 作业ID
|
||||
|
||||
@@ -194,7 +194,7 @@ func (c *XuexitongCourse) AutoStudyCourseWithOptions(ctx context.Context, course
|
||||
return err
|
||||
}
|
||||
|
||||
if chapter.JobCount > 0 || chapter.PointTotal > chapter.PointFinish {
|
||||
if chapter.JobCount > 0 && chapter.PointTotal > chapter.PointFinish {
|
||||
err := c.AutoStudyChapterWithOptions(ctx, course, chapter, opts)
|
||||
if err != nil {
|
||||
if err == ErrTimeRestriction {
|
||||
@@ -212,7 +212,7 @@ func (c *XuexitongCourse) AutoStudyCourseWithOptions(ctx context.Context, course
|
||||
// 筛选需要处理的章节
|
||||
var targetChapters []entity.XueXiTCourseChapter
|
||||
for _, chapter := range chapters {
|
||||
if chapter.JobCount > 0 || chapter.PointTotal > chapter.PointFinish {
|
||||
if chapter.JobCount > 0 && chapter.PointTotal > chapter.PointFinish {
|
||||
targetChapters = append(targetChapters, chapter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,12 @@ func ApiPassVerifyCode(client *resty.Client, code string, retryCount int) bool {
|
||||
if retryCount < 0 {
|
||||
return false
|
||||
}
|
||||
urlStr := "https://mooc1-api.chaoxing.com/html/processVerify.ac"
|
||||
//urlStr := "https://mooc1-api.chaoxing.com/html/processVerify.ac"
|
||||
urlStr := "https://mooc1-api.chaoxing.com/json/processVerify.ac"
|
||||
client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
req := client.R()
|
||||
req.SetMultipartField("ucode", "", "", strings.NewReader(code))
|
||||
req.SetMultipartField("app", "", "", strings.NewReader("0"))
|
||||
req.SetMultipartField("app", "", "", strings.NewReader("1"))
|
||||
req.Header.Add("User-Agent", GetUA("mobile"))
|
||||
req.Header.Add("Accept", "*/*")
|
||||
req.Header.Add("Host", "mooc1-api.chaoxing.com")
|
||||
|
||||
@@ -224,7 +224,10 @@ func (c *XuexitongCourse) FetchChapterCards(ctx context.Context, course entity.X
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 触发202 需要重新登录获取cookie
|
||||
if resp.StatusCode() == 202 {
|
||||
|
||||
}
|
||||
body = resp.String()
|
||||
if strings.Contains(body, "请输入验证码") {
|
||||
logger.Logger.Warn("触发验证码,正在进行AI智能识别绕过.....")
|
||||
@@ -332,7 +335,7 @@ func (c *XuexitongCourse) FetchChapterCards(ctx context.Context, course entity.X
|
||||
point.JobID = jobID
|
||||
point.DocumentType = "document"
|
||||
|
||||
case "insertbook":
|
||||
case "insertbook", "insertreadV2":
|
||||
jobID, _ := iframe.Data["_jobid"].(string)
|
||||
if jobID == "" {
|
||||
continue
|
||||
|
||||
@@ -31,6 +31,7 @@ const (
|
||||
ApiVideoPlayLog = "https://mooc1.chaoxing.com/mooc-ans/multimedia/log/a"
|
||||
ApiDocumentReport = "https://mooc1.chaoxing.com/ananas/job/document"
|
||||
ApiDocumentBook = "https://mooc1.chaoxing.com/ananas/job"
|
||||
ApiDocumentReadV2 = "https://mooc1.chaoxing.com/ananas/job/readv2"
|
||||
ApiWorkFetch = "https://mooc1-api.chaoxing.com/android/mworkspecial"
|
||||
ApiWorkSubmit = "https://mooc1.chaoxing.com/mooc-ans/work/addStudentWorkNew"
|
||||
VideoEncKey = "d_yHJ!$pdA~5"
|
||||
|
||||
@@ -16,44 +16,9 @@ import (
|
||||
|
||||
// attachmentDetectDocument 从附件设置中检测并填充文档信息
|
||||
func (c *XuexitongCourse) attachmentDetectDocument(attachment *entity.AttachmentSetting, p *entity.ChapterPoint) {
|
||||
for _, att := range attachment.Attachments {
|
||||
property, ok := att["property"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
jobid, _ := property["jobid"].(string)
|
||||
if jobid != p.JobID && p.JobID != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if name, ok := property["name"].(string); ok {
|
||||
p.Title = name
|
||||
}
|
||||
if bookname, ok := property["bookname"].(string); ok {
|
||||
p.Title = bookname
|
||||
}
|
||||
if jid, ok := property["jobid"].(string); ok {
|
||||
p.JobID = jid
|
||||
}
|
||||
if jtoken, ok := att["jtoken"].(string); ok {
|
||||
p.Jtoken = jtoken
|
||||
}
|
||||
if isJob, ok := att["job"].(bool); ok {
|
||||
p.IsJob = isJob
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// AttachmentsDetectionDocument 从附件设置中检测并填充文档信息
|
||||
func (c *XuexitongCourse) AttachmentsDetectionDocument(attachment *entity.AttachmentSetting, p *entity.PointDocumentDto) error {
|
||||
if attachment == nil {
|
||||
return errors2.NewBusinessError(503, "附件设置为空")
|
||||
}
|
||||
|
||||
for _, att := range attachment.Attachments {
|
||||
typeStr := ""
|
||||
// 进行不同方案的类型值获取
|
||||
if t, ok := att["type"].(string); ok {
|
||||
typeStr = t
|
||||
} else if property, ok := att["property"].(map[string]interface{}); ok {
|
||||
@@ -67,37 +32,75 @@ func (c *XuexitongCourse) AttachmentsDetectionDocument(attachment *entity.Attach
|
||||
continue
|
||||
}
|
||||
|
||||
jobid, _ := property["jobid"].(string)
|
||||
if jobid != p.JobID && p.JobID != "" {
|
||||
continue
|
||||
}
|
||||
// 如果未给出文档类型,先进行文档解析尝试
|
||||
if typeStr == "" {
|
||||
if name, ok := property["name"].(string); ok {
|
||||
p.Title = name
|
||||
}
|
||||
if jid, ok := property["jobid"].(string); ok {
|
||||
p.JobID = jid
|
||||
}
|
||||
if jtoken, ok := att["jtoken"].(string); ok {
|
||||
p.Jtoken = jtoken
|
||||
}
|
||||
if isJob, ok := att["job"].(bool); ok {
|
||||
p.IsJob = isJob
|
||||
} else {
|
||||
p.IsJob = false
|
||||
}
|
||||
if bookname, ok := property["bookname"].(string); ok {
|
||||
p.Title = bookname
|
||||
}
|
||||
// 过滤匹配
|
||||
objectid, _ := property["objectid"].(string)
|
||||
jobid, _ := property["jobid"].(string)
|
||||
if (p.ObjectID != "" && objectid == p.ObjectID) || (jobid != "" && p.JobID == jobid) {
|
||||
break
|
||||
}
|
||||
} else if typeStr == "insertbook" || typeStr == "read" {
|
||||
if jtoken, ok := att["jtoken"].(string); ok {
|
||||
p.Jtoken = jtoken
|
||||
}
|
||||
if isJob, ok := att["job"].(bool); ok {
|
||||
p.IsJob = isJob
|
||||
} else {
|
||||
p.IsJob = false
|
||||
}
|
||||
if bookname, ok := property["bookname"].(string); ok {
|
||||
p.Title = bookname
|
||||
}
|
||||
if typeStr == "insertbook" {
|
||||
p.DocumentType = "insertbook"
|
||||
} else {
|
||||
p.DocumentType = "read"
|
||||
}
|
||||
|
||||
if name, ok := property["name"].(string); ok {
|
||||
p.Title = name
|
||||
// 过滤匹配
|
||||
objectid, _ := property["objectid"].(string)
|
||||
jobid, _ := property["jobid"].(string)
|
||||
if (p.ObjectID != "" && objectid == p.ObjectID) || (jobid != "" && p.JobID == jobid) {
|
||||
break
|
||||
}
|
||||
} else if typeStr == "document" || typeStr == "insertdoc" {
|
||||
if isJob, ok := att["job"].(bool); ok {
|
||||
p.IsJob = isJob
|
||||
}
|
||||
objectid, _ := property["objectid"].(string)
|
||||
if objectid == p.ObjectID {
|
||||
if name, ok := property["name"].(string); ok {
|
||||
p.Title = name
|
||||
}
|
||||
if jid, ok := property["jobid"].(string); ok {
|
||||
p.JobID = jid
|
||||
}
|
||||
if jtoken, ok := att["jtoken"].(string); ok {
|
||||
p.Jtoken = jtoken
|
||||
}
|
||||
p.DocumentType = "document"
|
||||
break
|
||||
}
|
||||
}
|
||||
if bookname, ok := property["bookname"].(string); ok {
|
||||
p.Title = bookname
|
||||
}
|
||||
if jid, ok := property["jobid"].(string); ok {
|
||||
p.JobID = jid
|
||||
}
|
||||
if jtoken, ok := att["jtoken"].(string); ok {
|
||||
p.Jtoken = jtoken
|
||||
}
|
||||
if isJob, ok := att["job"].(bool); ok {
|
||||
p.IsJob = isJob
|
||||
}
|
||||
|
||||
if typeStr == "document" || typeStr == "insertdoc" {
|
||||
p.Type = "document"
|
||||
} else if typeStr == "insertbook" {
|
||||
p.Type = "insertbook"
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DocumentDtoReadingReport 提交文档阅读完成
|
||||
@@ -111,7 +114,7 @@ func (c *XuexitongCourse) DocumentDtoReadingReport(ctx context.Context, p *entit
|
||||
params.Add("_dc", strconv.FormatInt(time.Now().UnixMilli(), 10))
|
||||
|
||||
apiUrl := ApiDocumentReport
|
||||
if p.Type == "insertbook" {
|
||||
if p.Cpi == "insertbook" {
|
||||
apiUrl = ApiDocumentBook
|
||||
}
|
||||
|
||||
@@ -137,39 +140,26 @@ func (c *XuexitongCourse) DocumentDtoReadingReport(ctx context.Context, p *entit
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// AutoReadDocument 自动阅读文档(刷课)
|
||||
func (c *XuexitongCourse) AutoReadDocument(ctx context.Context, course entity.XueXiTCourse, p *entity.PointDocumentDto) error {
|
||||
if !p.IsJob {
|
||||
logger.Logger.Info("文档不是任务点,跳过", zap.String("jobID", p.JobID), zap.String("title", p.Title))
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Logger.Info("开始刷文档",
|
||||
zap.String("jobID", p.JobID),
|
||||
zap.String("title", p.Title))
|
||||
|
||||
report, err := c.DocumentDtoReadingReport(ctx, p)
|
||||
// AutoReadDocumentByPoint 自动阅读文档(使用统一的 ChapterPoint)
|
||||
func (c *XuexitongCourse) AutoReadDocumentByPoint(ctx context.Context, course entity.XueXiTCourse, p *entity.ChapterPoint) error {
|
||||
// 获取附件信息填充 Jtoken、IsJob 等参数
|
||||
attachment, _, err := c.PageMobileChapterCard(ctx, course, p.KnowledgeID, p.CardIndex)
|
||||
if err != nil {
|
||||
logger.Logger.Error("获取章节卡片失败", zap.String("title", p.Title), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(report), &result); err == nil {
|
||||
if status, ok := result["status"].(bool); ok && status {
|
||||
logger.Logger.Info("文档刷课完成", zap.String("jobID", p.JobID), zap.String("title", p.Title))
|
||||
c.onTaskComplete()
|
||||
return nil
|
||||
}
|
||||
if msg, ok := result["msg"].(string); ok {
|
||||
logger.Logger.Warn("文档无法正常学习", zap.String("msg", msg))
|
||||
}
|
||||
if attachment != nil {
|
||||
c.attachmentDetectDocument(attachment, p)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
logger.Logger.Debug("文档参数",
|
||||
zap.String("jobID", p.JobID),
|
||||
zap.String("objectID", p.ObjectID),
|
||||
zap.String("jtoken", p.Jtoken),
|
||||
zap.Bool("isJob", p.IsJob),
|
||||
zap.String("documentType", p.DocumentType))
|
||||
|
||||
// AutoReadDocumentByPoint 自动阅读文档(使用统一的 ChapterPoint)
|
||||
func (c *XuexitongCourse) AutoReadDocumentByPoint(ctx context.Context, course entity.XueXiTCourse, p *entity.ChapterPoint) error {
|
||||
if !p.IsJob {
|
||||
logger.Logger.Info("文档不是任务点,跳过", zap.String("jobID", p.JobID), zap.String("title", p.Title))
|
||||
return nil
|
||||
@@ -201,24 +191,28 @@ func (c *XuexitongCourse) AutoReadDocumentByPoint(ctx context.Context, course en
|
||||
|
||||
// DocumentDtoReadingReportByPoint 提交文档阅读完成(使用统一的 ChapterPoint)
|
||||
func (c *XuexitongCourse) DocumentDtoReadingReportByPoint(ctx context.Context, p *entity.ChapterPoint) (string, error) {
|
||||
params := url.Values{}
|
||||
params.Add("jobid", p.JobID)
|
||||
params.Add("knowledgeid", strconv.Itoa(p.KnowledgeID))
|
||||
params.Add("courseid", p.CourseID)
|
||||
params.Add("clazzid", p.ClassID)
|
||||
params.Add("jtoken", p.Jtoken)
|
||||
params.Add("_dc", strconv.FormatInt(time.Now().UnixMilli(), 10))
|
||||
formParams := map[string]string{
|
||||
"jobid": p.JobID,
|
||||
"knowledgeid": strconv.Itoa(p.KnowledgeID),
|
||||
"courseid": p.CourseID,
|
||||
"clazzid": p.ClassID,
|
||||
"jtoken": p.Jtoken,
|
||||
"_dc": strconv.FormatInt(time.Now().UnixMilli(), 10),
|
||||
}
|
||||
|
||||
apiUrl := ApiDocumentReport
|
||||
if p.DocumentType == "insertbook" {
|
||||
apiUrl = ApiDocumentBook
|
||||
} else if p.DocumentType == "read" {
|
||||
apiUrl = ApiDocumentReadV2
|
||||
}
|
||||
// read 类型使用 document 接口
|
||||
|
||||
var body string
|
||||
err := retry.Do(func() error {
|
||||
resp, err := c.client.R().SetHeaders(map[string]string{
|
||||
"User-Agent": GetUA("mobile"),
|
||||
}).Get(apiUrl + "?" + params.Encode())
|
||||
}).SetQueryParams(formParams).Get(apiUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"likedream-goframe/internal/framework/errors"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"likedream-goframe/internal/utils"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
@@ -56,7 +57,7 @@ func TestSchedule(t *testing.T) {
|
||||
err = courseService.AutoStudyByCourseIDWithOptions(ctx, "258109701", &AutoStudyOptions{
|
||||
AIConfig: config,
|
||||
AutoSubmit: true, // 默认不自动提交,仅保存
|
||||
Thread: 5,
|
||||
Thread: 1,
|
||||
})
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessErrorWithDetail(503, "刷课失败", err))
|
||||
@@ -181,3 +182,9 @@ func TestAutoStudyAllCourses(t *testing.T) {
|
||||
|
||||
t.Log("所有课程刷课完成")
|
||||
}
|
||||
|
||||
func TestSplitWords(t *testing.T) {
|
||||
str := "ABCD"
|
||||
split := strings.Split(str, "")
|
||||
t.Log(split)
|
||||
}
|
||||
|
||||
@@ -496,9 +496,9 @@ func (c *XuexitongCourse) SubmitWorkAnswers(ctx context.Context, workDto *entity
|
||||
case entity.QuestionTypeTrueFalse:
|
||||
answer := ""
|
||||
for _, a := range q.Answers {
|
||||
if a == "正确" {
|
||||
if a == "正确" || a == "对" {
|
||||
answer = "true"
|
||||
} else if a == "错误" {
|
||||
} else if a == "错误" || a == "错" {
|
||||
answer = "false"
|
||||
} else {
|
||||
answer = a
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
|
||||
func IsDebug() bool {
|
||||
return isGolandDebug() || isDebugByPath()
|
||||
|
||||
}
|
||||
|
||||
func isDebugByPath() bool {
|
||||
exePath := strings.ToLower(os.Args[0])
|
||||
// 获取绝对路径
|
||||
absPath, err := filepath.Abs(exePath)
|
||||
if err != nil {
|
||||
absPath = exePath
|
||||
}
|
||||
|
||||
// 检查路径中是否包含常见的临时目录或"go-build"字样
|
||||
tmpDir := strings.ToLower(getTempDir())
|
||||
isTemp := strings.Contains(absPath, tmpDir) || strings.Contains(absPath, "go-build") || strings.Contains(absPath, "go_build_")
|
||||
return isTemp
|
||||
}
|
||||
func getTempDir() string {
|
||||
tempDir := os.TempDir()
|
||||
|
||||
// 尝试转换为长路径
|
||||
if longPath, err := filepath.EvalSymlinks(tempDir); err == nil {
|
||||
if absPath, err := filepath.Abs(longPath); err == nil {
|
||||
return absPath
|
||||
}
|
||||
return longPath
|
||||
}
|
||||
|
||||
return filepath.Dir(tempDir)
|
||||
}
|
||||
|
||||
func isGolandDebug() bool {
|
||||
// 获取当前进程ID
|
||||
pid := os.Getpid()
|
||||
|
||||
// 获取当前进程信息
|
||||
currentProcess, err := process.NewProcess(int32(pid))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取父进程
|
||||
parent, err := currentProcess.Parent()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取父进程名称
|
||||
name, err := parent.Name()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查父进程是否是dlv(Go调试器)或GoLand相关进程
|
||||
name = strings.ToLower(name)
|
||||
debugProcessNames := []string{
|
||||
"dlv.exe", "dlv",
|
||||
"goland.exe", "goland64.exe",
|
||||
"goland", "idea.exe",
|
||||
}
|
||||
|
||||
for _, debugName := range debugProcessNames {
|
||||
if strings.Contains(name, debugName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -13,6 +13,9 @@ var Logger *zap.Logger
|
||||
func init() {
|
||||
// Create a production config and customize the time encoder
|
||||
config := zap.NewProductionConfig()
|
||||
if IsDebug() {
|
||||
config.Level = zap.NewAtomicLevelAt(zap.DebugLevel) // Enable debug logs
|
||||
}
|
||||
config.EncoderConfig.EncodeTime = BeijingTimeEncoder
|
||||
|
||||
// Build the logger with the custom config
|
||||
|
||||
@@ -193,7 +193,7 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task
|
||||
err = courseService.AutoStudyByCourseIDWithOptions(ctx, targetCourse.CourseID, &xuexitong.AutoStudyOptions{
|
||||
AIConfig: aiConfig,
|
||||
AutoSubmit: true, // 默认不自动提交,仅保存
|
||||
Thread: 5,
|
||||
Thread: 1,
|
||||
})
|
||||
if err != nil {
|
||||
// 如果是时间限制导致的停止,不算失败
|
||||
|
||||
Reference in New Issue
Block a user