✨ 新增适配安徽建筑大学登录
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
package cryptoutil
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// MD5 对输入字符串进行 MD5 加密,返回 32 位小写十六进制字符串
|
||||
func MD5(input string) string {
|
||||
hash := md5.Sum([]byte(input))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// 南阳理工学院登录实现 RSA加密 & 验证码必出
|
||||
type CpdailyAhjzuLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func (c CpdailyAhjzuLogin) Login(ctx context.Context, schoolDetail *school.SchoolDetailInfo, username, password string) {
|
||||
jar := cookiejar.NewPersistCookieJar()
|
||||
|
||||
c.client.SetCookieJar(jar).SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
|
||||
// 获取合法的登录URL
|
||||
loginUrl := getCasValidLoginUrl(schoolDetail)
|
||||
|
||||
// 请求登录页面并获取真正的登录URL
|
||||
loginPageResponse := getAhjzuLoginPageResponse(ctx, c.client, loginUrl)
|
||||
|
||||
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{}
|
||||
fm1Node = loginPage.Find("#credentials").First()
|
||||
)
|
||||
inputs := fm1Node.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
|
||||
|
||||
// 处理密码
|
||||
allInputs["password"] = cryptoutil.MD5(password)
|
||||
|
||||
// 解决常规图片验证码
|
||||
captchaUrl := getAhjzuCaptchaUrl(loginResponseUrl)
|
||||
captchaResult := ResolveOcrCaptcha(ctx, captchaUrl, c.client)
|
||||
|
||||
allInputs["validateCode"] = captchaResult
|
||||
|
||||
// 登录提交
|
||||
loginPostResponse, err := c.client.R().
|
||||
SetFormData(allInputs).
|
||||
WithContext(ctx).
|
||||
Post(loginResponseUrl.String())
|
||||
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
defer loginPostResponse.Body.Close()
|
||||
|
||||
switch loginPostResponse.StatusCode() {
|
||||
case 302:
|
||||
handleCasLoginSuccess(ctx, loginPostResponse, schoolDetail, username, password, c.client)
|
||||
case 200:
|
||||
fallthrough
|
||||
case 401:
|
||||
doc, err := goquery.NewDocumentFromReader(loginPostResponse.Body)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
if isCasNeedKickOut(doc) {
|
||||
kickoutResponse := submitCasKickOut(ctx, loginResponseUrl.String(), doc, c.client)
|
||||
handleCasLoginSuccess(ctx, kickoutResponse, schoolDetail, username, password, c.client)
|
||||
return
|
||||
}
|
||||
|
||||
message := getCasErrorMessage(doc)
|
||||
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("今日校园登录失败: %s", message)), func(err error) error {
|
||||
return oops.
|
||||
WithContext(ctx).
|
||||
With("loginResponseUrl", loginResponseUrl).
|
||||
With("allInputs", allInputs).
|
||||
With("errorMessage", message).
|
||||
Wrap(errors.NewBusinessError(503, message))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getAhjzuLoginPageResponse(ctx context.Context, client *resty.Client, loginUrl string) *resty.Response {
|
||||
res1, err := client.R().Get(loginUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
if res1.StatusCode() != http.StatusFound && res1.StatusCode() != http.StatusMovedPermanently {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res1.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
location := res1.RawResponse.Header.Get("Location")
|
||||
res2, err := client.R().Get(location)
|
||||
if res2.StatusCode() != http.StatusFound && res2.StatusCode() != http.StatusMovedPermanently {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res2.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
location = res2.RawResponse.Header.Get("Location")
|
||||
res3, err := client.R().Get(location)
|
||||
if res3.StatusCode() != http.StatusFound && res3.StatusCode() != http.StatusMovedPermanently {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res3.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
location = res3.RawResponse.Header.Get("Location")
|
||||
res4, err := client.R().Get(location)
|
||||
if res4.StatusCode() != http.StatusFound && res3.StatusCode() != http.StatusMovedPermanently {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res4.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
location = res4.RawResponse.Header.Get("Location")
|
||||
res5, err := client.R().Get(location)
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "今日校园登录失败: 登录页面获取失败"), func(err error) error {
|
||||
return oops.
|
||||
With("loginUrl", loginUrl).
|
||||
With("statusCode", res4.StatusCode()).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
}
|
||||
return res5
|
||||
}
|
||||
func getAhjzuCaptchaUrl(url *url.URL) string {
|
||||
return fmt.Sprintf("%s://%s/sso/code/code.jsp", url.Scheme, url.Host)
|
||||
}
|
||||
|
||||
func (c CpdailyAhjzuLogin) GetCookieJar() http.CookieJar {
|
||||
return c.client.CookieJar()
|
||||
}
|
||||
|
||||
func (c CpdailyAhjzuLogin) GetCookieString() string {
|
||||
cookieJar := c.GetCookieJar()
|
||||
serializableJar := cookieJar.(*cookiejar.PersistCookieJar)
|
||||
return serializableJar.Serialize()
|
||||
}
|
||||
|
||||
func (c CpdailyAhjzuLogin) GetClient() *resty.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func NewAhjzu(ctx context.Context) *CpdailyAhjzuLogin {
|
||||
return &CpdailyAhjzuLogin{
|
||||
client: httpclient.Cpdaily.Clone(ctx),
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@ func GetCpdailyLoginBySchoolInfo(ctx context.Context, info *school.SchoolDetailI
|
||||
// 南昌理工学院
|
||||
case "d6e8f2ca-10b2-4599-bdb8-4b33d419658d":
|
||||
return NewNut(ctx)
|
||||
// 安徽建筑大学
|
||||
case "ahjzu":
|
||||
return NewAhjzu(ctx)
|
||||
default:
|
||||
if info.IsIap() {
|
||||
return NewIap(ctx)
|
||||
|
||||
Reference in New Issue
Block a user