210 lines
6.2 KiB
Go
210 lines
6.2 KiB
Go
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.NewCpdailyClient(),
|
|
}
|
|
}
|