Compare commits
9 Commits
v1.0.7
...
v1.0.7.beta11
| Author | SHA1 | Date | |
|---|---|---|---|
| 8974bd26a5 | |||
| 6f25652183 | |||
| a8f4a2afa6 | |||
| d14ddc212d | |||
| 55fc94c37f | |||
| a590f58c6c | |||
| 0959ca117c | |||
| 3641fb1782 | |||
| d0e7652bf6 |
+2
-2
@@ -50,8 +50,8 @@ func Run() {
|
||||
g.Log().Info(ctx, "server is going to shut down...")
|
||||
api.StopServer()
|
||||
g.Log().Info(ctx, "api server stopped")
|
||||
//schedule.Stop()
|
||||
//g.Log().Info(ctx, "schedule stopped")
|
||||
schedule.Stop()
|
||||
g.Log().Info(ctx, "schedule stopped")
|
||||
redis.Shutdown()
|
||||
g.Log().Info(ctx, "redis client closed")
|
||||
os.Exit(0)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -89,39 +89,7 @@ func (c *XuexitongCourse) AutoStudyAllCourses(ctx context.Context) error {
|
||||
|
||||
// AutoStudyCourse 自动学习指定课程的所有章节
|
||||
func (c *XuexitongCourse) AutoStudyCourse(ctx context.Context, course entity.XueXiTCourse) error {
|
||||
logger.Logger.Info(fmt.Sprintf("开始刷课程: %s", course.CourseName))
|
||||
|
||||
chapters := c.PullCourseChapterList(ctx, course)
|
||||
if len(chapters) == 0 {
|
||||
logger.Logger.Warn(fmt.Sprintf("课程 %s 没有章节", course.CourseName))
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := c.FetchChapterPointStatus(ctx, course, chapters)
|
||||
if err != nil {
|
||||
logger.Logger.Warn("获取章节状态失败", zap.Error(err))
|
||||
}
|
||||
|
||||
for _, chapter := range chapters {
|
||||
if err := c.checkTimeRestriction(); err != nil {
|
||||
logger.Logger.Info("时间限制,暂停刷课", zap.String("course", course.CourseName))
|
||||
return err
|
||||
}
|
||||
|
||||
if chapter.JobCount > 0 || chapter.PointTotal > chapter.PointFinish {
|
||||
err := c.AutoStudyChapter(ctx, course, chapter)
|
||||
if err != nil {
|
||||
if err == ErrTimeRestriction {
|
||||
return err
|
||||
}
|
||||
logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", chapter.Name), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Logger.Info(fmt.Sprintf("课程完成: %s", course.CourseName))
|
||||
return nil
|
||||
return c.AutoStudyCourseWithOptions(ctx, course, nil)
|
||||
}
|
||||
|
||||
// AutoStudyChapter 自动学习指定章节的所有任务点
|
||||
@@ -154,6 +122,7 @@ func (c *XuexitongCourse) AutoStudyChapter(ctx context.Context, course entity.Xu
|
||||
|
||||
for i := range points {
|
||||
if err := c.checkTimeRestriction(); err != nil {
|
||||
// 更新任务状态为waiting
|
||||
logger.Logger.Info("时间限制,暂停刷课", zap.String("chapter", chapter.Name))
|
||||
return err
|
||||
}
|
||||
@@ -193,6 +162,7 @@ func (c *XuexitongCourse) AutoStudyChapter(ctx context.Context, course entity.Xu
|
||||
type AutoStudyOptions struct {
|
||||
AIConfig *AIConfig // AI配置(用于作业答题)
|
||||
AutoSubmit bool // 是否自动提交作业
|
||||
Thread int // 线程数
|
||||
}
|
||||
|
||||
// AutoStudyCourseWithOptions 带选项的自动学习课程
|
||||
@@ -210,21 +180,90 @@ func (c *XuexitongCourse) AutoStudyCourseWithOptions(ctx context.Context, course
|
||||
logger.Logger.Warn("获取章节状态失败", zap.Error(err))
|
||||
}
|
||||
|
||||
for _, chapter := range chapters {
|
||||
if err := c.checkTimeRestriction(); err != nil {
|
||||
logger.Logger.Info("时间限制,暂停刷课", zap.String("course", course.CourseName))
|
||||
return err
|
||||
// 获取线程数配置,默认为1(单线程)
|
||||
threadCount := 1
|
||||
if opts != nil && opts.Thread > 0 {
|
||||
threadCount = opts.Thread
|
||||
}
|
||||
|
||||
// 如果是单线程,使用原有逻辑
|
||||
if threadCount == 1 {
|
||||
for _, chapter := range chapters {
|
||||
if err := c.checkTimeRestriction(); err != nil {
|
||||
logger.Logger.Info("时间限制,暂停刷课", zap.String("course", course.CourseName))
|
||||
return err
|
||||
}
|
||||
|
||||
if chapter.JobCount > 0 && chapter.PointTotal > chapter.PointFinish {
|
||||
err := c.AutoStudyChapterWithOptions(ctx, course, chapter, opts)
|
||||
if err != nil {
|
||||
if err == ErrTimeRestriction {
|
||||
return err
|
||||
}
|
||||
logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", chapter.Name), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 多线程并发处理
|
||||
logger.Logger.Info(fmt.Sprintf("使用 %d 线程并发刷课", threadCount))
|
||||
|
||||
// 筛选需要处理的章节
|
||||
var targetChapters []entity.XueXiTCourseChapter
|
||||
for _, chapter := range chapters {
|
||||
if chapter.JobCount > 0 && chapter.PointTotal > chapter.PointFinish {
|
||||
targetChapters = append(targetChapters, chapter)
|
||||
}
|
||||
}
|
||||
|
||||
if chapter.JobCount > 0 && chapter.PointTotal > chapter.PointFinish {
|
||||
//logger.Logger.Info("开始处理章节", zap.String("chapter", chapter.Name))
|
||||
err := c.AutoStudyChapterWithOptions(ctx, course, chapter, opts)
|
||||
if err != nil {
|
||||
if err == ErrTimeRestriction {
|
||||
return err
|
||||
if len(targetChapters) == 0 {
|
||||
logger.Logger.Info("没有需要处理的章节")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建信号量控制并发数
|
||||
semaphore := make(chan struct{}, threadCount)
|
||||
errChan := make(chan error, len(targetChapters))
|
||||
doneChan := make(chan bool, len(targetChapters))
|
||||
|
||||
// 并发处理章节
|
||||
for _, chapter := range targetChapters {
|
||||
semaphore <- struct{}{} // 获取信号量
|
||||
|
||||
go func(ch entity.XueXiTCourseChapter) {
|
||||
defer func() {
|
||||
<-semaphore // 释放信号量
|
||||
doneChan <- true
|
||||
}()
|
||||
|
||||
if err := c.checkTimeRestriction(); err != nil {
|
||||
logger.Logger.Info("时间限制,暂停刷课", zap.String("chapter", ch.Name))
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", chapter.Name), zap.Error(err))
|
||||
continue
|
||||
|
||||
err := c.AutoStudyChapterWithOptions(ctx, course, ch, opts)
|
||||
if err != nil {
|
||||
if err == ErrTimeRestriction {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", ch.Name), zap.Error(err))
|
||||
}
|
||||
}(chapter)
|
||||
}
|
||||
|
||||
// 等待所有任务完成
|
||||
for i := 0; i < len(targetChapters); i++ {
|
||||
<-doneChan
|
||||
}
|
||||
close(errChan)
|
||||
|
||||
// 检查是否有时间限制错误
|
||||
for err := range errChan {
|
||||
if err == ErrTimeRestriction {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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智能识别绕过.....")
|
||||
@@ -285,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
|
||||
@@ -302,6 +307,7 @@ func (c *XuexitongCourse) FetchChapterCards(ctx context.Context, course entity.X
|
||||
CourseID: course.CourseID,
|
||||
ClassID: course.Key,
|
||||
Cpi: cpiStr,
|
||||
Title: card.Title,
|
||||
}
|
||||
|
||||
switch pointType {
|
||||
@@ -315,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)
|
||||
}
|
||||
@@ -331,7 +337,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
|
||||
@@ -520,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),
|
||||
|
||||
@@ -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"
|
||||
@@ -36,7 +37,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()
|
||||
@@ -53,9 +54,10 @@ 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,
|
||||
})
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessErrorWithDetail(503, "刷课失败", err))
|
||||
@@ -180,3 +182,9 @@ func TestAutoStudyAllCourses(t *testing.T) {
|
||||
|
||||
t.Log("所有课程刷课完成")
|
||||
}
|
||||
|
||||
func TestSplitWords(t *testing.T) {
|
||||
str := "ABCD"
|
||||
split := strings.Split(str, "")
|
||||
t.Log(split)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -439,32 +439,32 @@ func cleanText(text string) string {
|
||||
|
||||
// SubmitWorkAnswers 提交作业答案
|
||||
func (c *XuexitongCourse) SubmitWorkAnswers(ctx context.Context, workDto *entity.WorkDto, isSave bool) (string, error) {
|
||||
formData := url.Values{}
|
||||
|
||||
pyFlag := ""
|
||||
if isSave {
|
||||
pyFlag = "1"
|
||||
}
|
||||
formData.Set("pyFlag", pyFlag)
|
||||
formData.Set("courseId", workDto.CourseId)
|
||||
formData.Set("classId", workDto.ClassId)
|
||||
formData.Set("api", workDto.Api)
|
||||
formData.Set("workAnswerId", workDto.WorkAnswerId)
|
||||
formData.Set("answerId", workDto.AnswerId)
|
||||
formData.Set("totalQuestionNum", workDto.TotalQuestionNum)
|
||||
formData.Set("fullScore", workDto.FullScore)
|
||||
formData.Set("knowledgeid", workDto.KnowledgeId)
|
||||
formData.Set("oldSchoolId", workDto.OldSchoolId)
|
||||
formData.Set("oldWorkId", workDto.OldWorkId)
|
||||
formData.Set("jobid", workDto.JobId)
|
||||
formData.Set("workRelationId", workDto.WorkRelationId)
|
||||
formData.Set("enc", "")
|
||||
formData.Set("enc_work", workDto.EncWork)
|
||||
formData.Set("userId", workDto.UserId)
|
||||
formData.Set("cpi", workDto.Cpi)
|
||||
formData.Set("workTimesEnc", "")
|
||||
formData.Set("randomOptions", "false")
|
||||
formData.Set("isAccessibleCustomFid", "0")
|
||||
|
||||
formMap := map[string]string{
|
||||
"pyFlag": pyFlag,
|
||||
"courseId": workDto.CourseId,
|
||||
"classId": workDto.ClassId,
|
||||
"api": workDto.Api,
|
||||
"mooc": "0",
|
||||
"workAnswerId": workDto.WorkAnswerId,
|
||||
"answerId": "",
|
||||
"totalQuestionNum": workDto.TotalQuestionNum,
|
||||
"fullScore": workDto.FullScore,
|
||||
"knowledgeid": workDto.KnowledgeId,
|
||||
"oldSchoolId": workDto.OldSchoolId,
|
||||
"oldWorkId": workDto.OldWorkId,
|
||||
"jobid": workDto.JobId,
|
||||
"workRelationId": workDto.WorkRelationId,
|
||||
"enc_work": workDto.EncWork,
|
||||
"isphone": "true",
|
||||
"userId": workDto.UserId,
|
||||
"workTimesEnc": "",
|
||||
"isAccessibleCustomFid": "0",
|
||||
}
|
||||
answerwqbid := ""
|
||||
for _, q := range workDto.Questions {
|
||||
if q.Qid != "" {
|
||||
@@ -474,46 +474,46 @@ func (c *XuexitongCourse) SubmitWorkAnswers(ctx context.Context, workDto *entity
|
||||
switch q.Type {
|
||||
case entity.QuestionTypeSingleChoice:
|
||||
answer := strings.Join(q.Answers, "")
|
||||
formData.Set("answer"+q.Qid, answer)
|
||||
formData.Set("answertype"+q.Qid, "0")
|
||||
formMap["answer"+q.Qid] = answer
|
||||
formMap["answertype"+q.Qid] = "0"
|
||||
|
||||
case entity.QuestionTypeMultipleChoice:
|
||||
answer := strings.Join(q.Answers, "")
|
||||
formData.Set("answer"+q.Qid, answer)
|
||||
formData.Set("answertype"+q.Qid, "1")
|
||||
formMap["answer"+q.Qid] = answer
|
||||
formMap["answertype"+q.Qid] = "1"
|
||||
|
||||
case entity.QuestionTypeFillBlank:
|
||||
for key, val := range q.BlankAnswer {
|
||||
re := regexp.MustCompile(`\d+`)
|
||||
numbers := re.FindAllString(key, -1)
|
||||
if len(numbers) > 0 {
|
||||
formData.Set("answer"+q.Qid+numbers[0], val)
|
||||
formMap["answer"+q.Qid+numbers[0]] = val
|
||||
}
|
||||
}
|
||||
formData.Set("tiankongsize"+q.Qid, strconv.Itoa(q.BlankCount))
|
||||
formData.Set("answertype"+q.Qid, "2")
|
||||
formMap["tiankongsize"+q.Qid] = strconv.Itoa(q.BlankCount)
|
||||
formMap["answertype"+q.Qid] = "2"
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
formData.Set("answer"+q.Qid, answer)
|
||||
formData.Set("answertype"+q.Qid, "3")
|
||||
formMap["answer"+q.Qid] = answer
|
||||
formMap["answertype"+q.Qid] = "3"
|
||||
|
||||
case entity.QuestionTypeShortAnswer:
|
||||
answer := strings.Join(q.Answers, "")
|
||||
formData.Set("answer"+q.Qid, answer)
|
||||
formData.Set("answertype"+q.Qid, "4")
|
||||
formMap["answer"+q.Qid] = answer
|
||||
formMap["answertype"+q.Qid] = "4"
|
||||
}
|
||||
}
|
||||
formData.Set("answerwqbid", answerwqbid)
|
||||
formMap["answerwqbid"] = answerwqbid
|
||||
|
||||
urlStr := fmt.Sprintf("%s?_classId=%s&courseid=%s&token=%s&totalQuestionNum=%s&ua=pc&formType=post&saveStatus=1&version=1&tempsave=1",
|
||||
ApiWorkSubmit, workDto.ClassId, workDto.CourseId, workDto.EncWork, workDto.TotalQuestionNum)
|
||||
@@ -523,7 +523,7 @@ func (c *XuexitongCourse) SubmitWorkAnswers(ctx context.Context, workDto *entity
|
||||
resp, err := c.client.R().SetHeaders(map[string]string{
|
||||
"User-Agent": GetUA("mobile"),
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}).SetBody(formData.Encode()).Post(urlStr)
|
||||
}).SetFormData(formMap).Post(urlStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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,11 @@ 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
|
||||
} else {
|
||||
config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
|
||||
}
|
||||
config.EncoderConfig.EncodeTime = BeijingTimeEncoder
|
||||
|
||||
// Build the logger with the custom config
|
||||
|
||||
@@ -78,10 +78,6 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task
|
||||
currentMinutes := currentHour*60 + currentMinute
|
||||
|
||||
if currentMinutes < startTime || currentMinutes >= endTime {
|
||||
// logger.Logger.Debug("非暴力模式,当前时间不在执行时间范围内(8:30-17:30),跳过执行",
|
||||
// zap.Int64("courseID", targetCourse.ID),
|
||||
// zap.Int("currentHour", currentHour),
|
||||
// zap.Int("currentMinute", currentMinute))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -134,8 +130,12 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task
|
||||
startTime := 8*60 + 30 // 8:30
|
||||
endTime := 17*60 + 30 // 17:30
|
||||
currentMinutes := currentHour*60 + currentMinute
|
||||
|
||||
return currentMinutes >= startTime && currentMinutes < endTime
|
||||
needStudy := currentMinutes >= startTime && currentMinutes < endTime
|
||||
if !needStudy {
|
||||
// 更新状态为等待中
|
||||
db.Global.CourseEntity.UpdateOneID(taskPayload.CourseID).SetStatus(courseentity.StatusWaiting).ExecX(ctx)
|
||||
}
|
||||
return needStudy
|
||||
})
|
||||
}
|
||||
|
||||
@@ -192,7 +192,8 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task
|
||||
// 执行刷课(带AI配置)
|
||||
err = courseService.AutoStudyByCourseIDWithOptions(ctx, targetCourse.CourseID, &xuexitong.AutoStudyOptions{
|
||||
AIConfig: aiConfig,
|
||||
AutoSubmit: false, // 默认不自动提交,仅保存
|
||||
AutoSubmit: true, // 默认不自动提交,仅保存
|
||||
Thread: 1,
|
||||
})
|
||||
if err != nil {
|
||||
// 如果是时间限制导致的停止,不算失败
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package utils
|
||||
|
||||
import "time"
|
||||
|
||||
func GetCurrentTimeMillis() int64 {
|
||||
return time.Now().UnixMilli()
|
||||
}
|
||||
Reference in New Issue
Block a user