Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2f0f91712 | |||
| ac93a5385e | |||
| 644ceed436 | |||
| 77ea55eda2 | |||
| 0f83cfc9b9 | |||
| 83749fc647 |
@@ -46,7 +46,11 @@ bucket = "likedream"
|
||||
appVersion = "9.7.2"
|
||||
systemVersion = "iOS 17"
|
||||
calVersion = "firstv"
|
||||
sliderIds = ["1018764144934965", "sdhyxy", "hqdx"]
|
||||
|
||||
|
||||
|
||||
[basePoint]
|
||||
cpdaily = "0.5"
|
||||
[task]
|
||||
concurrency = 20
|
||||
@@ -1,3 +1,56 @@
|
||||
package captcha
|
||||
|
||||
func ApiSemiOCRVerification() {}
|
||||
import (
|
||||
"encoding/base64"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func ApiSemiOCRVerification(bytes []byte, shape1, shape2 int64) string {
|
||||
data := Bytes2Base64(bytes)
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/semiOcr"
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"base64": data,
|
||||
"shape1": shape1,
|
||||
"shape2": shape2,
|
||||
}).Post(baseUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
With("SemiOCRVerification", baseUrl).
|
||||
WithContext(ctx).
|
||||
Wrap(err)
|
||||
})
|
||||
result := post.String()
|
||||
return gjson.Get(result, "data").String()
|
||||
}
|
||||
|
||||
func ApiAutoOCRVerification(bytes []byte) string {
|
||||
data := Bytes2Base64(bytes)
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/ocr"
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"base64": data,
|
||||
}).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)
|
||||
}
|
||||
|
||||
@@ -8,20 +8,21 @@ import (
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type domainMap map[string]cookieMap
|
||||
|
||||
// AllSavePersistCookieJar 支持泛域名的 Cookie Jar
|
||||
// 不同的二级域名会共享相同主域名的 cookies
|
||||
// 例如: passport2.chaoxing.com 和 mooc1-api.chaoxing.com 共享 .chaoxing.com 的 cookies
|
||||
// AllSavePersistCookieJar 完全无限制的 Cookie Jar
|
||||
// 所有域名的 cookies 都保存在一起,访问任何域名都返回所有 cookies
|
||||
// 不检查域名、路径等限制,适用于需要跨域共享 session 的场景
|
||||
type AllSavePersistCookieJar struct {
|
||||
domainMap domainMap
|
||||
*sync.RWMutex
|
||||
}
|
||||
|
||||
const globalDomain = "*" // 使用全局域名存储所有 cookies
|
||||
|
||||
func NewAllSavePersistCookieJar() *AllSavePersistCookieJar {
|
||||
return &AllSavePersistCookieJar{
|
||||
domainMap: make(domainMap),
|
||||
@@ -29,47 +30,22 @@ func NewAllSavePersistCookieJar() *AllSavePersistCookieJar {
|
||||
}
|
||||
}
|
||||
|
||||
// extractMainDomain 提取主域名
|
||||
// 例如: passport2.chaoxing.com -> chaoxing.com
|
||||
//
|
||||
// mooc1-api.chaoxing.com -> chaoxing.com
|
||||
// www.example.co.uk -> example.co.uk
|
||||
func extractMainDomain(host string) string {
|
||||
// 移除端口号
|
||||
if colonIndex := strings.Index(host, ":"); colonIndex != -1 {
|
||||
host = host[:colonIndex]
|
||||
}
|
||||
|
||||
parts := strings.Split(host, ".")
|
||||
if len(parts) <= 2 {
|
||||
// 已经是主域名或单级域名
|
||||
return host
|
||||
}
|
||||
|
||||
// 处理特殊的二级顶级域名,如 .co.uk, .com.cn 等
|
||||
// 简化处理:取最后两部分
|
||||
// 更复杂的情况可以使用 publicsuffix 包
|
||||
return strings.Join(parts[len(parts)-2:], ".")
|
||||
}
|
||||
|
||||
func (a *AllSavePersistCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
// 提取主域名
|
||||
mainDomain := extractMainDomain(u.Host)
|
||||
|
||||
if _, exists := a.domainMap[mainDomain]; !exists {
|
||||
a.domainMap[mainDomain] = make(cookieMap)
|
||||
// 使用全局域名,所有 cookies 保存在一起
|
||||
if _, exists := a.domainMap[globalDomain]; !exists {
|
||||
a.domainMap[globalDomain] = make(cookieMap)
|
||||
}
|
||||
|
||||
for _, cookie := range cookies {
|
||||
if a.domainMap[mainDomain] == nil {
|
||||
a.domainMap[mainDomain] = make(cookieMap)
|
||||
if a.domainMap[globalDomain] == nil {
|
||||
a.domainMap[globalDomain] = make(cookieMap)
|
||||
}
|
||||
|
||||
// 查找是否存在相同 name, path 和 domain 的 cookie
|
||||
existingCookies := a.domainMap[mainDomain][cookie.Name]
|
||||
// 查找是否存在相同 name 的 cookie(忽略 path 和 domain)
|
||||
existingCookies := a.domainMap[globalDomain][cookie.Name]
|
||||
found := false
|
||||
for i, existing := range existingCookies {
|
||||
if existing.Path == cookie.Path && existing.Domain == cookie.Domain {
|
||||
@@ -82,7 +58,7 @@ func (a *AllSavePersistCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie)
|
||||
|
||||
if !found {
|
||||
// 没有找到匹配的,添加新的 cookie
|
||||
a.domainMap[mainDomain][cookie.Name] = append(a.domainMap[mainDomain][cookie.Name], cookie)
|
||||
a.domainMap[globalDomain][cookie.Name] = append(a.domainMap[globalDomain][cookie.Name], cookie)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,15 +67,9 @@ func (a *AllSavePersistCookieJar) Cookies(u *url.URL) []*http.Cookie {
|
||||
a.RLock()
|
||||
defer a.RUnlock()
|
||||
|
||||
// 提取主域名
|
||||
mainDomain := extractMainDomain(u.Host)
|
||||
|
||||
if cookiesList, exists := a.domainMap[mainDomain]; exists {
|
||||
// 直接返回全局域名下的所有 cookies,不检查域名和路径
|
||||
if cookiesList, exists := a.domainMap[globalDomain]; exists {
|
||||
result := make([]*http.Cookie, 0, len(cookiesList))
|
||||
requestPath := u.Path
|
||||
if requestPath == "" {
|
||||
requestPath = "/"
|
||||
}
|
||||
|
||||
for _, cookies := range cookiesList {
|
||||
// 如果只有一个 cookie,直接返回
|
||||
@@ -108,28 +78,9 @@ func (a *AllSavePersistCookieJar) Cookies(u *url.URL) []*http.Cookie {
|
||||
continue
|
||||
}
|
||||
|
||||
// 有多个同名 cookie,选择最匹配的(path 最长且匹配请求路径的)
|
||||
var bestMatch *http.Cookie
|
||||
bestMatchLen := -1
|
||||
|
||||
for _, cookie := range cookies {
|
||||
cookiePath := cookie.Path
|
||||
if cookiePath == "" {
|
||||
cookiePath = "/"
|
||||
}
|
||||
|
||||
// 检查 path 是否匹配
|
||||
if pathMatch(requestPath, cookiePath) {
|
||||
if len(cookiePath) > bestMatchLen {
|
||||
bestMatch = cookie
|
||||
bestMatchLen = len(cookiePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到匹配的,添加到结果中
|
||||
if bestMatch != nil {
|
||||
result = append(result, bestMatch)
|
||||
// 有多个同名 cookie,返回最后设置的那个(列表最后一个)
|
||||
if len(cookies) > 0 {
|
||||
result = append(result, cookies[len(cookies)-1])
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -0,0 +1,838 @@
|
||||
package cryptoutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RSA Constants
|
||||
const (
|
||||
biRadixBits = 16
|
||||
bitsPerDigit = biRadixBits
|
||||
biRadix = 1 << 16 // 65536
|
||||
biHalfRadix = biRadix >> 1
|
||||
biRadixSquared = biRadix * biRadix
|
||||
maxDigitVal = biRadix - 1
|
||||
maxInteger = 9999999999999998
|
||||
dpl10 = 15
|
||||
)
|
||||
|
||||
var (
|
||||
maxDigits int
|
||||
zeroArray []int
|
||||
bigZero *BigInt
|
||||
bigOne *BigInt
|
||||
lr10 *BigInt
|
||||
highBitMasks = []int{0x0000, 0x8000, 0xC000, 0xE000, 0xF000, 0xF800,
|
||||
0xFC00, 0xFE00, 0xFF00, 0xFF80, 0xFFC0, 0xFFE0,
|
||||
0xFFF0, 0xFFF8, 0xFFFC, 0xFFFE, 0xFFFF}
|
||||
lowBitMasks = []int{0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
|
||||
0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
|
||||
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF}
|
||||
hexToChar = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'a', 'b', 'c', 'd', 'e', 'f'}
|
||||
hexatrigesimalToChar = []byte{
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
|
||||
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z'}
|
||||
)
|
||||
|
||||
// BigInt represents a big integer
|
||||
type BigInt struct {
|
||||
digits []int
|
||||
isNeg bool
|
||||
}
|
||||
|
||||
// NewBigInt creates a new BigInt
|
||||
func NewBigInt() *BigInt {
|
||||
digits := make([]int, len(zeroArray))
|
||||
copy(digits, zeroArray)
|
||||
return &BigInt{
|
||||
digits: digits,
|
||||
isNeg: false,
|
||||
}
|
||||
}
|
||||
|
||||
// NewBigIntEmpty creates an empty BigInt
|
||||
func NewBigIntEmpty() *BigInt {
|
||||
return &BigInt{
|
||||
digits: nil,
|
||||
isNeg: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SetMaxDigits sets the maximum number of digits
|
||||
func SetMaxDigits(value int) {
|
||||
maxDigits = value
|
||||
zeroArray = make([]int, maxDigits)
|
||||
bigZero = NewBigInt()
|
||||
bigOne = NewBigInt()
|
||||
bigOne.digits[0] = 1
|
||||
lr10 = BiFromNumber(1000000000000000)
|
||||
}
|
||||
|
||||
// BiFromNumber creates a BigInt from a number
|
||||
func BiFromNumber(i int64) *BigInt {
|
||||
result := NewBigInt()
|
||||
result.isNeg = i < 0
|
||||
i = int64(math.Abs(float64(i)))
|
||||
j := 0
|
||||
for i > 0 {
|
||||
result.digits[j] = int(i & maxDigitVal)
|
||||
j++
|
||||
i = i / biRadix
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiFromDecimal creates a BigInt from a decimal string
|
||||
func BiFromDecimal(s string) *BigInt {
|
||||
isNeg := len(s) > 0 && s[0] == '-'
|
||||
i := 0
|
||||
if isNeg {
|
||||
i = 1
|
||||
}
|
||||
|
||||
// Skip leading zeros
|
||||
for i < len(s) && s[i] == '0' {
|
||||
i++
|
||||
}
|
||||
|
||||
if i == len(s) {
|
||||
return NewBigInt()
|
||||
}
|
||||
|
||||
digitCount := len(s) - i
|
||||
fgl := digitCount % dpl10
|
||||
if fgl == 0 {
|
||||
fgl = dpl10
|
||||
}
|
||||
|
||||
num := parseNumber(s[i : i+fgl])
|
||||
result := BiFromNumber(num)
|
||||
i += fgl
|
||||
|
||||
for i < len(s) {
|
||||
result = BiAdd(BiMultiply(result, lr10), BiFromNumber(parseNumber(s[i:min(i+dpl10, len(s))])))
|
||||
i += dpl10
|
||||
}
|
||||
|
||||
result.isNeg = isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiCopy copies a BigInt
|
||||
func BiCopy(bi *BigInt) *BigInt {
|
||||
result := NewBigIntEmpty()
|
||||
result.digits = make([]int, len(bi.digits))
|
||||
copy(result.digits, bi.digits)
|
||||
result.isNeg = bi.isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// ReverseStr reverses a string
|
||||
func ReverseStr(s string) string {
|
||||
runes := []rune(s)
|
||||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// BiToString converts BigInt to string with given radix
|
||||
func BiToString(x *BigInt, radix int) string {
|
||||
b := NewBigInt()
|
||||
b.digits[0] = radix
|
||||
qr := BiDivideModulo(x, b)
|
||||
result := string(hexatrigesimalToChar[qr[1].digits[0]])
|
||||
|
||||
for BiCompare(qr[0], bigZero) == 1 {
|
||||
qr = BiDivideModulo(qr[0], b)
|
||||
result += string(hexatrigesimalToChar[qr[1].digits[0]])
|
||||
}
|
||||
|
||||
prefix := ""
|
||||
if x.isNeg {
|
||||
prefix = "-"
|
||||
}
|
||||
return prefix + ReverseStr(result)
|
||||
}
|
||||
|
||||
// BiToDecimal converts BigInt to decimal string
|
||||
func BiToDecimal(x *BigInt) string {
|
||||
b := NewBigInt()
|
||||
b.digits[0] = 10
|
||||
qr := BiDivideModulo(x, b)
|
||||
result := fmt.Sprintf("%d", qr[1].digits[0])
|
||||
|
||||
for BiCompare(qr[0], bigZero) == 1 {
|
||||
qr = BiDivideModulo(qr[0], b)
|
||||
result += fmt.Sprintf("%d", qr[1].digits[0])
|
||||
}
|
||||
|
||||
prefix := ""
|
||||
if x.isNeg {
|
||||
prefix = "-"
|
||||
}
|
||||
return prefix + ReverseStr(result)
|
||||
}
|
||||
|
||||
// DigitToHex converts a digit to hex string
|
||||
func DigitToHex(n int) string {
|
||||
mask := 0xf
|
||||
result := ""
|
||||
for i := 0; i < 4; i++ {
|
||||
result += string(hexToChar[n&mask])
|
||||
n >>= 4
|
||||
}
|
||||
return ReverseStr(result)
|
||||
}
|
||||
|
||||
// BiToHex converts BigInt to hex string
|
||||
func BiToHex(x *BigInt) string {
|
||||
result := ""
|
||||
for i := BiHighIndex(x); i > -1; i-- {
|
||||
result += DigitToHex(x.digits[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// CharToHex converts a character code to hex value
|
||||
func CharToHex(c byte) int {
|
||||
const (
|
||||
ZERO = 48
|
||||
NINE = ZERO + 9
|
||||
littleA = 97
|
||||
littleZ = littleA + 25
|
||||
bigA = 65
|
||||
bigZ = 65 + 25
|
||||
)
|
||||
|
||||
if c >= ZERO && c <= NINE {
|
||||
return int(c - ZERO)
|
||||
} else if c >= bigA && c <= bigZ {
|
||||
return 10 + int(c-bigA)
|
||||
} else if c >= littleA && c <= littleZ {
|
||||
return 10 + int(c-littleA)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// HexToDigit converts hex string to digit
|
||||
func HexToDigit(s string) int {
|
||||
result := 0
|
||||
sl := min(len(s), 4)
|
||||
for i := 0; i < sl; i++ {
|
||||
result <<= 4
|
||||
result |= CharToHex(s[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiFromHex creates BigInt from hex string
|
||||
func BiFromHex(s string) *BigInt {
|
||||
result := NewBigInt()
|
||||
sl := len(s)
|
||||
for i, j := sl, 0; i > 0; i, j = i-4, j+1 {
|
||||
start := max(i-4, 0)
|
||||
length := min(i, 4)
|
||||
if start+length > sl {
|
||||
length = sl - start
|
||||
}
|
||||
result.digits[j] = HexToDigit(s[start : start+length])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiFromString creates BigInt from string with given radix
|
||||
func BiFromString(s string, radix int) *BigInt {
|
||||
isNeg := len(s) > 0 && s[0] == '-'
|
||||
istop := 0
|
||||
if isNeg {
|
||||
istop = 1
|
||||
}
|
||||
|
||||
result := NewBigInt()
|
||||
place := NewBigInt()
|
||||
place.digits[0] = 1
|
||||
|
||||
for i := len(s) - 1; i >= istop; i-- {
|
||||
c := s[i]
|
||||
digit := CharToHex(c)
|
||||
biDigit := BiMultiplyDigit(place, digit)
|
||||
result = BiAdd(result, biDigit)
|
||||
place = BiMultiplyDigit(place, radix)
|
||||
}
|
||||
|
||||
result.isNeg = isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiAdd adds two BigInts
|
||||
func BiAdd(x, y *BigInt) *BigInt {
|
||||
var result *BigInt
|
||||
|
||||
if x.isNeg != y.isNeg {
|
||||
y.isNeg = !y.isNeg
|
||||
result = BiSubtract(x, y)
|
||||
y.isNeg = !y.isNeg
|
||||
} else {
|
||||
result = NewBigInt()
|
||||
c := 0
|
||||
for i := 0; i < len(x.digits); i++ {
|
||||
n := x.digits[i] + y.digits[i] + c
|
||||
result.digits[i] = n % biRadix
|
||||
if n >= biRadix {
|
||||
c = 1
|
||||
} else {
|
||||
c = 0
|
||||
}
|
||||
}
|
||||
result.isNeg = x.isNeg
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// BiSubtract subtracts two BigInts
|
||||
func BiSubtract(x, y *BigInt) *BigInt {
|
||||
var result *BigInt
|
||||
|
||||
if x.isNeg != y.isNeg {
|
||||
y.isNeg = !y.isNeg
|
||||
result = BiAdd(x, y)
|
||||
y.isNeg = !y.isNeg
|
||||
} else {
|
||||
result = NewBigInt()
|
||||
c := 0
|
||||
|
||||
for i := 0; i < len(x.digits); i++ {
|
||||
n := x.digits[i] - y.digits[i] + c
|
||||
result.digits[i] = n % biRadix
|
||||
if result.digits[i] < 0 {
|
||||
result.digits[i] += biRadix
|
||||
}
|
||||
if n < 0 {
|
||||
c = -1
|
||||
} else {
|
||||
c = 0
|
||||
}
|
||||
}
|
||||
|
||||
if c == -1 {
|
||||
c = 0
|
||||
for i := 0; i < len(x.digits); i++ {
|
||||
n := 0 - result.digits[i] + c
|
||||
result.digits[i] = n % biRadix
|
||||
if result.digits[i] < 0 {
|
||||
result.digits[i] += biRadix
|
||||
}
|
||||
if n < 0 {
|
||||
c = -1
|
||||
} else {
|
||||
c = 0
|
||||
}
|
||||
}
|
||||
result.isNeg = !x.isNeg
|
||||
} else {
|
||||
result.isNeg = x.isNeg
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// BiHighIndex returns the highest non-zero digit index
|
||||
func BiHighIndex(x *BigInt) int {
|
||||
result := len(x.digits) - 1
|
||||
for result > 0 && x.digits[result] == 0 {
|
||||
result--
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiNumBits returns the number of bits
|
||||
func BiNumBits(x *BigInt) int {
|
||||
n := BiHighIndex(x)
|
||||
d := x.digits[n]
|
||||
m := (n + 1) * bitsPerDigit
|
||||
result := m
|
||||
for result > m-bitsPerDigit {
|
||||
if (d & 0x8000) != 0 {
|
||||
break
|
||||
}
|
||||
d <<= 1
|
||||
result--
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BiMultiply multiplies two BigInts
|
||||
func BiMultiply(x, y *BigInt) *BigInt {
|
||||
result := NewBigInt()
|
||||
n := BiHighIndex(x)
|
||||
t := BiHighIndex(y)
|
||||
|
||||
for i := 0; i <= t; i++ {
|
||||
c := 0
|
||||
k := i
|
||||
for j := 0; j <= n; j, k = j+1, k+1 {
|
||||
uv := result.digits[k] + x.digits[j]*y.digits[i] + c
|
||||
result.digits[k] = uv & maxDigitVal
|
||||
c = uv >> biRadixBits
|
||||
}
|
||||
result.digits[i+n+1] = c
|
||||
}
|
||||
|
||||
result.isNeg = x.isNeg != y.isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiMultiplyDigit multiplies BigInt by a digit
|
||||
func BiMultiplyDigit(x *BigInt, y int) *BigInt {
|
||||
result := NewBigInt()
|
||||
n := BiHighIndex(x)
|
||||
c := 0
|
||||
|
||||
for j := 0; j <= n; j++ {
|
||||
uv := result.digits[j] + x.digits[j]*y + c
|
||||
result.digits[j] = uv & maxDigitVal
|
||||
c = uv >> biRadixBits
|
||||
}
|
||||
result.digits[1+n] = c
|
||||
return result
|
||||
}
|
||||
|
||||
// ArrayCopy copies array elements
|
||||
func ArrayCopy(src []int, srcStart int, dest []int, destStart int, n int) {
|
||||
m := min(srcStart+n, len(src))
|
||||
for i, j := srcStart, destStart; i < m; i, j = i+1, j+1 {
|
||||
dest[j] = src[i]
|
||||
}
|
||||
}
|
||||
|
||||
// BiShiftLeft shifts BigInt left by n bits
|
||||
func BiShiftLeft(x *BigInt, n int) *BigInt {
|
||||
digitCount := n / bitsPerDigit
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, 0, result.digits, digitCount, len(result.digits)-digitCount)
|
||||
|
||||
bits := n % bitsPerDigit
|
||||
rightBits := bitsPerDigit - bits
|
||||
|
||||
for i, i1 := len(result.digits)-1, len(result.digits)-2; i > 0; i, i1 = i-1, i1-1 {
|
||||
result.digits[i] = ((result.digits[i] << bits) & maxDigitVal) |
|
||||
((result.digits[i1] & highBitMasks[bits]) >> rightBits)
|
||||
}
|
||||
result.digits[0] = ((result.digits[0] << bits) & maxDigitVal)
|
||||
result.isNeg = x.isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiShiftRight shifts BigInt right by n bits
|
||||
func BiShiftRight(x *BigInt, n int) *BigInt {
|
||||
digitCount := n / bitsPerDigit
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, digitCount, result.digits, 0, len(x.digits)-digitCount)
|
||||
|
||||
bits := n % bitsPerDigit
|
||||
leftBits := bitsPerDigit - bits
|
||||
|
||||
for i, i1 := 0, 1; i < len(result.digits)-1; i, i1 = i+1, i1+1 {
|
||||
result.digits[i] = (result.digits[i] >> bits) |
|
||||
((result.digits[i1] & lowBitMasks[bits]) << leftBits)
|
||||
}
|
||||
result.digits[len(result.digits)-1] >>= bits
|
||||
result.isNeg = x.isNeg
|
||||
return result
|
||||
}
|
||||
|
||||
// BiMultiplyByRadixPower multiplies BigInt by radix^n
|
||||
func BiMultiplyByRadixPower(x *BigInt, n int) *BigInt {
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, 0, result.digits, n, len(result.digits)-n)
|
||||
return result
|
||||
}
|
||||
|
||||
// BiDivideByRadixPower divides BigInt by radix^n
|
||||
func BiDivideByRadixPower(x *BigInt, n int) *BigInt {
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, n, result.digits, 0, len(result.digits)-n)
|
||||
return result
|
||||
}
|
||||
|
||||
// BiModuloByRadixPower computes BigInt mod radix^n
|
||||
func BiModuloByRadixPower(x *BigInt, n int) *BigInt {
|
||||
result := NewBigInt()
|
||||
ArrayCopy(x.digits, 0, result.digits, 0, n)
|
||||
return result
|
||||
}
|
||||
|
||||
// BiCompare compares two BigInts
|
||||
func BiCompare(x, y *BigInt) int {
|
||||
if x.isNeg != y.isNeg {
|
||||
if x.isNeg {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
for i := len(x.digits) - 1; i >= 0; i-- {
|
||||
if x.digits[i] != y.digits[i] {
|
||||
if x.isNeg {
|
||||
if x.digits[i] > y.digits[i] {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
} else {
|
||||
if x.digits[i] < y.digits[i] {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// BiDivideModulo divides two BigInts and returns quotient and remainder
|
||||
func BiDivideModulo(x, y *BigInt) []*BigInt {
|
||||
nb := BiNumBits(x)
|
||||
tb := BiNumBits(y)
|
||||
origYIsNeg := y.isNeg
|
||||
var q, r *BigInt
|
||||
|
||||
if nb < tb {
|
||||
if x.isNeg {
|
||||
q = BiCopy(bigOne)
|
||||
q.isNeg = !y.isNeg
|
||||
x.isNeg = false
|
||||
y.isNeg = false
|
||||
r = BiSubtract(y, x)
|
||||
x.isNeg = true
|
||||
y.isNeg = origYIsNeg
|
||||
} else {
|
||||
q = NewBigInt()
|
||||
r = BiCopy(x)
|
||||
}
|
||||
return []*BigInt{q, r}
|
||||
}
|
||||
|
||||
q = NewBigInt()
|
||||
r = x
|
||||
|
||||
t := int(math.Ceil(float64(tb)/float64(bitsPerDigit))) - 1
|
||||
lambda := 0
|
||||
for y.digits[t] < biHalfRadix {
|
||||
y = BiShiftLeft(y, 1)
|
||||
lambda++
|
||||
tb++
|
||||
t = int(math.Ceil(float64(tb)/float64(bitsPerDigit))) - 1
|
||||
}
|
||||
|
||||
r = BiShiftLeft(r, lambda)
|
||||
nb += lambda
|
||||
n := int(math.Ceil(float64(nb)/float64(bitsPerDigit))) - 1
|
||||
|
||||
b := BiMultiplyByRadixPower(y, n-t)
|
||||
for BiCompare(r, b) != -1 {
|
||||
q.digits[n-t]++
|
||||
r = BiSubtract(r, b)
|
||||
}
|
||||
|
||||
for i := n; i > t; i-- {
|
||||
ri := 0
|
||||
if i < len(r.digits) {
|
||||
ri = r.digits[i]
|
||||
}
|
||||
ri1 := 0
|
||||
if i-1 < len(r.digits) {
|
||||
ri1 = r.digits[i-1]
|
||||
}
|
||||
ri2 := 0
|
||||
if i-2 < len(r.digits) {
|
||||
ri2 = r.digits[i-2]
|
||||
}
|
||||
yt := 0
|
||||
if t < len(y.digits) {
|
||||
yt = y.digits[t]
|
||||
}
|
||||
yt1 := 0
|
||||
if t-1 < len(y.digits) {
|
||||
yt1 = y.digits[t-1]
|
||||
}
|
||||
|
||||
if ri == yt {
|
||||
q.digits[i-t-1] = maxDigitVal
|
||||
} else {
|
||||
q.digits[i-t-1] = (ri*biRadix + ri1) / yt
|
||||
}
|
||||
|
||||
c1 := q.digits[i-t-1] * ((yt * biRadix) + yt1)
|
||||
c2 := (ri * biRadixSquared) + ((ri1 * biRadix) + ri2)
|
||||
|
||||
for c1 > c2 {
|
||||
q.digits[i-t-1]--
|
||||
c1 = q.digits[i-t-1] * ((yt * biRadix) | yt1)
|
||||
c2 = (ri * biRadix * biRadix) + ((ri1 * biRadix) + ri2)
|
||||
}
|
||||
|
||||
b = BiMultiplyByRadixPower(y, i-t-1)
|
||||
r = BiSubtract(r, BiMultiplyDigit(b, q.digits[i-t-1]))
|
||||
if r.isNeg {
|
||||
r = BiAdd(r, b)
|
||||
q.digits[i-t-1]--
|
||||
}
|
||||
}
|
||||
|
||||
r = BiShiftRight(r, lambda)
|
||||
q.isNeg = x.isNeg != origYIsNeg
|
||||
|
||||
if x.isNeg {
|
||||
if origYIsNeg {
|
||||
q = BiAdd(q, bigOne)
|
||||
} else {
|
||||
q = BiSubtract(q, bigOne)
|
||||
}
|
||||
y = BiShiftRight(y, lambda)
|
||||
r = BiSubtract(y, r)
|
||||
}
|
||||
|
||||
if r.digits[0] == 0 && BiHighIndex(r) == 0 {
|
||||
r.isNeg = false
|
||||
}
|
||||
|
||||
return []*BigInt{q, r}
|
||||
}
|
||||
|
||||
// BiDivide divides two BigInts
|
||||
func BiDivide(x, y *BigInt) *BigInt {
|
||||
return BiDivideModulo(x, y)[0]
|
||||
}
|
||||
|
||||
// BiModulo computes x mod y
|
||||
func BiModulo(x, y *BigInt) *BigInt {
|
||||
return BiDivideModulo(x, y)[1]
|
||||
}
|
||||
|
||||
// BiMultiplyMod computes (x * y) mod m
|
||||
func BiMultiplyMod(x, y, m *BigInt) *BigInt {
|
||||
return BiModulo(BiMultiply(x, y), m)
|
||||
}
|
||||
|
||||
// BiPowMod computes x^y mod m
|
||||
func BiPowMod(x, y, m *BigInt) *BigInt {
|
||||
result := BiCopy(bigOne)
|
||||
a := x
|
||||
k := y
|
||||
|
||||
for {
|
||||
if (k.digits[0] & 1) != 0 {
|
||||
result = BiMultiplyMod(result, a, m)
|
||||
}
|
||||
k = BiShiftRight(k, 1)
|
||||
if k.digits[0] == 0 && BiHighIndex(k) == 0 {
|
||||
break
|
||||
}
|
||||
a = BiMultiplyMod(a, a, m)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// BarrettMu represents Barrett reduction
|
||||
type BarrettMu struct {
|
||||
modulus *BigInt
|
||||
k int
|
||||
mu *BigInt
|
||||
bkplus1 *BigInt
|
||||
}
|
||||
|
||||
// NewBarrettMu creates a new BarrettMu
|
||||
func NewBarrettMu(m *BigInt) *BarrettMu {
|
||||
bm := &BarrettMu{
|
||||
modulus: BiCopy(m),
|
||||
k: BiHighIndex(m) + 1,
|
||||
}
|
||||
|
||||
b2k := NewBigInt()
|
||||
if 2*bm.k < len(b2k.digits) {
|
||||
b2k.digits[2*bm.k] = 1
|
||||
}
|
||||
bm.mu = BiDivide(b2k, bm.modulus)
|
||||
|
||||
bm.bkplus1 = NewBigInt()
|
||||
if bm.k+1 < len(bm.bkplus1.digits) {
|
||||
bm.bkplus1.digits[bm.k+1] = 1
|
||||
}
|
||||
|
||||
return bm
|
||||
}
|
||||
|
||||
// Modulo performs Barrett modulo
|
||||
func (bm *BarrettMu) Modulo(x *BigInt) *BigInt {
|
||||
q1 := BiDivideByRadixPower(x, bm.k-1)
|
||||
q2 := BiMultiply(q1, bm.mu)
|
||||
q3 := BiDivideByRadixPower(q2, bm.k+1)
|
||||
r1 := BiModuloByRadixPower(x, bm.k+1)
|
||||
r2term := BiMultiply(q3, bm.modulus)
|
||||
r2 := BiModuloByRadixPower(r2term, bm.k+1)
|
||||
r := BiSubtract(r1, r2)
|
||||
|
||||
if r.isNeg {
|
||||
r = BiAdd(r, bm.bkplus1)
|
||||
}
|
||||
|
||||
for BiCompare(r, bm.modulus) >= 0 {
|
||||
r = BiSubtract(r, bm.modulus)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// MultiplyMod performs modular multiplication
|
||||
func (bm *BarrettMu) MultiplyMod(x, y *BigInt) *BigInt {
|
||||
xy := BiMultiply(x, y)
|
||||
return bm.Modulo(xy)
|
||||
}
|
||||
|
||||
// PowMod performs modular exponentiation
|
||||
func (bm *BarrettMu) PowMod(x, y *BigInt) *BigInt {
|
||||
result := NewBigInt()
|
||||
result.digits[0] = 1
|
||||
a := x
|
||||
k := y
|
||||
|
||||
for {
|
||||
if (k.digits[0] & 1) != 0 {
|
||||
result = bm.MultiplyMod(result, a)
|
||||
}
|
||||
k = BiShiftRight(k, 1)
|
||||
if k.digits[0] == 0 && BiHighIndex(k) == 0 {
|
||||
break
|
||||
}
|
||||
a = bm.MultiplyMod(a, a)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NutRSAKeyPair represents an RSA key pair
|
||||
type NutRSAKeyPair struct {
|
||||
e *BigInt
|
||||
d *BigInt
|
||||
m *BigInt
|
||||
chunkSize int
|
||||
radix int
|
||||
barrett *BarrettMu
|
||||
}
|
||||
|
||||
// NewNutRSAKeyPair creates a new RSA key pair
|
||||
func NewNutRSAKeyPair(encryptionExponent, decryptionExponent, modulus string) *NutRSAKeyPair {
|
||||
kp := &NutRSAKeyPair{
|
||||
e: BiFromHex(encryptionExponent),
|
||||
d: BiFromHex(decryptionExponent),
|
||||
m: BiFromHex(modulus),
|
||||
radix: 16,
|
||||
}
|
||||
kp.chunkSize = 2 * BiHighIndex(kp.m)
|
||||
kp.barrett = NewBarrettMu(kp.m)
|
||||
return kp
|
||||
}
|
||||
|
||||
// EncryptedString encrypts a string
|
||||
func (kp *NutRSAKeyPair) EncryptedString(s string) string {
|
||||
a := make([]int, 0)
|
||||
for i := 0; i < len(s); i++ {
|
||||
a = append(a, int(s[i]))
|
||||
}
|
||||
|
||||
// Padding
|
||||
for len(a)%kp.chunkSize != 0 {
|
||||
a = append(a, 0)
|
||||
}
|
||||
|
||||
result := ""
|
||||
for i := 0; i < len(a); i += kp.chunkSize {
|
||||
block := NewBigInt()
|
||||
j := 0
|
||||
for k := i; k < i+kp.chunkSize; j++ {
|
||||
if k < len(a) {
|
||||
block.digits[j] = a[k]
|
||||
k++
|
||||
}
|
||||
if k < len(a) {
|
||||
block.digits[j] += a[k] << 8
|
||||
k++
|
||||
}
|
||||
}
|
||||
crypt := kp.barrett.PowMod(block, kp.e)
|
||||
var text string
|
||||
if kp.radix == 16 {
|
||||
text = BiToHex(crypt)
|
||||
} else {
|
||||
text = BiToString(crypt, kp.radix)
|
||||
}
|
||||
result += text + " "
|
||||
}
|
||||
|
||||
if len(result) > 0 {
|
||||
return result[:len(result)-1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DecryptedString decrypts a string
|
||||
func (kp *NutRSAKeyPair) DecryptedString(s string) string {
|
||||
blocks := strings.Split(s, " ")
|
||||
result := ""
|
||||
|
||||
for i := 0; i < len(blocks); i++ {
|
||||
var bi *BigInt
|
||||
if kp.radix == 16 {
|
||||
bi = BiFromHex(blocks[i])
|
||||
} else {
|
||||
bi = BiFromString(blocks[i], kp.radix)
|
||||
}
|
||||
block := kp.barrett.PowMod(bi, kp.d)
|
||||
|
||||
for j := 0; j <= BiHighIndex(block); j++ {
|
||||
result += string(rune(block.digits[j] & 255))
|
||||
result += string(rune(block.digits[j] >> 8))
|
||||
}
|
||||
}
|
||||
|
||||
// Remove trailing null
|
||||
if len(result) > 0 && result[len(result)-1] == 0 {
|
||||
result = result[:len(result)-1]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func parseNumber(s string) int64 {
|
||||
var result int64
|
||||
fmt.Sscanf(s, "%d", &result)
|
||||
return result
|
||||
}
|
||||
|
||||
func init() {
|
||||
SetMaxDigits(130)
|
||||
}
|
||||
@@ -13,6 +13,9 @@ func GetCpdailyLoginBySchoolInfo(ctx context.Context, info *school.SchoolDetailI
|
||||
// 安徽建筑大学
|
||||
case "ahjzu":
|
||||
return NewAhjzu(ctx)
|
||||
// 南宁师范大学
|
||||
case "2ba0ea59-a764-4482-bb2b-c924b726b04c":
|
||||
return NewNnnu(ctx)
|
||||
// 河北石油职业技术大学
|
||||
case "01e270d1-00e2-4ce9-b0af-5d5fbd462b8a":
|
||||
return NewIap(ctx)
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/avast/retry-go/v4"
|
||||
"github.com/deatil/go-cryptobin/cryptobin/crypto"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
"resty.dev/v3"
|
||||
@@ -192,7 +193,8 @@ func (c *CpdailyCasLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
}
|
||||
|
||||
// 声明需要滑块验证码的学校id
|
||||
var SliderCaptchaIds = []string{"1018764144934965", "sdhyxy"}
|
||||
var SliderCaptchaIds = g.Cfg().MustGet(ctx, cst.ConfigSliderSchoolIds).Strings()
|
||||
// []string{"1018764144934965", "sdhyxy", "hqdx"}
|
||||
// 判断是否需要验证码
|
||||
if isCasNeedCaptcha(ctx, loginResponseUrl, loginType, username, c.client) {
|
||||
// 如果是滑块验证码
|
||||
@@ -389,6 +391,10 @@ func handleCasLoginSuccess(ctx context.Context, response *resty.Response, school
|
||||
utils.OkOrPanic(errors.NewBusinessError(201, "多因子验证码认证").WithData(key))
|
||||
// 否则应该是登录成功
|
||||
} else if statusCode == 200 || statusCode == 404 {
|
||||
all, _ := io.ReadAll(verifyResponse.Body)
|
||||
if strings.Contains(string(all), "完善资料") || strings.Contains(string(all), "为了你的帐号安全") {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "请手动学工号登录完善信息,修改密码").WithData(schoolInfo.WecloudUrl))
|
||||
}
|
||||
ActivateSession(ctx, schoolInfo.WecloudUrl, client)
|
||||
} else {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "未知错误导致今日校园登录失败"),
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// 南宁师范大学登录实现
|
||||
type CpdailyNnnuLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func (c CpdailyNnnuLogin) 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)
|
||||
|
||||
defer loginPageResponse.Body.Close()
|
||||
loginPageBytes, err := io.ReadAll(loginPageResponse.Body)
|
||||
utils.OkOrPanic(err)
|
||||
loginResponseUrl := loginPageResponse.RawResponse.Request.URL
|
||||
// 关闭client的重定向
|
||||
c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
// 解析登录页面
|
||||
loginPage, err := goquery.NewDocumentFromReader(bytes.NewReader(loginPageBytes))
|
||||
utils.OkOrPanic(err)
|
||||
// 判断登录类型
|
||||
var (
|
||||
allInputs = map[string]string{}
|
||||
form = loginPage.Find("form").First()
|
||||
)
|
||||
inputs := form.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 getNuuuCaptchaUrl(url *url.URL) string {
|
||||
return fmt.Sprintf("%s://%s/sso/code/code.jsp", url.Scheme, url.Host)
|
||||
}
|
||||
|
||||
func (c CpdailyNnnuLogin) GetCookieJar() http.CookieJar {
|
||||
return c.client.CookieJar()
|
||||
}
|
||||
|
||||
func (c CpdailyNnnuLogin) GetCookieString() string {
|
||||
cookieJar := c.GetCookieJar()
|
||||
serializableJar := cookieJar.(*cookiejar.PersistCookieJar)
|
||||
return serializableJar.Serialize()
|
||||
}
|
||||
|
||||
func (c CpdailyNnnuLogin) GetClient() *resty.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func NewNnnu(ctx context.Context) CpdailyLogin {
|
||||
return &CpdailyNnnuLogin{
|
||||
client: httpclient.NewCpdailyClient(),
|
||||
}
|
||||
}
|
||||
@@ -81,10 +81,8 @@ func (c CpdailyNutLogin) Login(ctx context.Context, schoolDetail *school.SchoolD
|
||||
pubKeyBytes, err := io.ReadAll(pubKeyResponse.Body)
|
||||
utils.OkOrPanic(err)
|
||||
sonic.Unmarshal(pubKeyBytes, &pubKey)
|
||||
pair, err := cryptoutil.NewRSAKeyPair(pubKey.Exponent, "", pubKey.Modulus)
|
||||
utils.OkOrPanic(err)
|
||||
password, err = pair.Encrypt(reverseStringASCII(password))
|
||||
utils.OkOrPanic(err)
|
||||
rsa := cryptoutil.NewNutRSAKeyPair(pubKey.Exponent, "", pubKey.Modulus)
|
||||
password = rsa.EncryptedString(reverseStringASCII(password))
|
||||
allInputs["password"] = password
|
||||
if isNutNeedCaptcha(ctx, loginResponseUrl, c.client) {
|
||||
// 解决常规图片验证码
|
||||
|
||||
@@ -1,56 +1,18 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"image"
|
||||
"io"
|
||||
"likedream-goframe/internal/application/httpclient"
|
||||
"likedream-goframe/internal/common/cst"
|
||||
"likedream-goframe/internal/application/captcha"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
func ResolveOcrCaptcha(ctx context.Context, url string, client *resty.Client) string {
|
||||
byteData := DownCode2Byte(ctx, url, client)
|
||||
//img, err := BytesToImage(byteData)
|
||||
//utils.OkOrPanic(err, func(err error) error {
|
||||
// return oops.
|
||||
// With("BytesToImage", url).
|
||||
// WithContext(ctx).
|
||||
// Wrap(err)
|
||||
//})
|
||||
//data := ddddocr.AutoOCRVerification(img)
|
||||
//return data
|
||||
return ApiAutoOCRVerification(Bytes2Base64(byteData))
|
||||
}
|
||||
|
||||
func Bytes2Base64(data []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
func ApiAutoOCRVerification(data string) string {
|
||||
ctx := gctx.GetInitCtx()
|
||||
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/ocr"
|
||||
client := httpclient.Cpdaily.Clone(ctx)
|
||||
client.SetDoNotParseResponse(false)
|
||||
post, err := client.R().SetBody(map[string]any{
|
||||
"base64": data,
|
||||
}).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()
|
||||
return captcha.ApiAutoOCRVerification(byteData)
|
||||
}
|
||||
|
||||
func DownCode2Byte(ctx context.Context, url string, client *resty.Client) []byte {
|
||||
@@ -73,12 +35,3 @@ func DownCode2Byte(ctx context.Context, url string, client *resty.Client) []byte
|
||||
})
|
||||
return all
|
||||
}
|
||||
|
||||
// 通用解码函数,自动检测格式
|
||||
func BytesToImage(imgBytes []byte) (image.Image, error) {
|
||||
img, _, err := image.Decode(bytes.NewReader(imgBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
@@ -42,9 +42,7 @@ type ApiResponse struct {
|
||||
type ITStarResponse struct {
|
||||
Code json.Number `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
SuccCode []string `json:"succCode"`
|
||||
} `json:"data"`
|
||||
Data []string `json:"data"`
|
||||
}
|
||||
|
||||
type SubmitCaptchaResolver interface {
|
||||
|
||||
@@ -145,10 +145,7 @@ func (r *NormalSubmitCaptchaResolver) getResultFromItStar(rawResponse json.RawMe
|
||||
err := sonic.Unmarshal(rawResponse, &responseMap)
|
||||
utils.OkOrPanic(err)
|
||||
|
||||
responseMap["authKey"] = "ITStarYYDS"
|
||||
|
||||
itstarUrl := strings.TrimSuffix(r.getITStarUrl(), "/")
|
||||
url := itstarUrl + "/captcha/unLimitValidate"
|
||||
url := r.getITStarUrl()
|
||||
|
||||
resp, err := r.client.R().
|
||||
SetBody(responseMap).
|
||||
@@ -161,7 +158,7 @@ func (r *NormalSubmitCaptchaResolver) getResultFromItStar(rawResponse json.RawMe
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, fmt.Sprintf("验证码识别出错: %s", itstarResp.Msg)))
|
||||
}
|
||||
|
||||
return itstarResp.Data.SuccCode
|
||||
return itstarResp.Data
|
||||
}
|
||||
|
||||
// submitCaptcha 提交验证码
|
||||
|
||||
@@ -551,7 +551,7 @@ func (c *CpdailySleepCheckTask) FillCustomForm(ctx context.Context, form map[str
|
||||
return utils.Catch(func() {
|
||||
ticket = submitCaptchaResolver.GetCaptchaTicket(ctx, captchaInfo)
|
||||
})
|
||||
}, retry.Delay(15*time.Second), retry.Attempts(5), retry.Context(ctx))
|
||||
}, retry.Delay(10*time.Second), retry.Attempts(5), retry.Context(ctx))
|
||||
if e != nil {
|
||||
utils.OkOrPanic(errors.NewBusinessError(503, "提交验证码识别失败").WithInnerError(e), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func NewXuexitongClient() *resty.Client {
|
||||
client := resty.New().
|
||||
SetCookieJar(nil).
|
||||
SetTimeout(30*time.Second).
|
||||
SetDoNotParseResponse(true).
|
||||
SetDoNotParseResponse(false).
|
||||
SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}).
|
||||
|
||||
@@ -70,3 +70,6 @@ type XueXiTCourse struct {
|
||||
IsStart bool `json:"isstart"` //是否开课了,开课了为true,没开课为false。一般来说开课才能刷,不然刷不了的
|
||||
State int `json:"state"` //课程状态,0为正常,1为课程已经结束
|
||||
}
|
||||
|
||||
type XueXiTCourseChapter struct {
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ func NewXuexitongLogin(ctx context.Context) *XuexitongLogin {
|
||||
}
|
||||
}
|
||||
func (c *XuexitongLogin) Login(ctx context.Context, username, password string, retry int) (err error) {
|
||||
jar := cookiejar.NewAllSavePersistCookieJar()
|
||||
c.client.SetCookieJar(jar)
|
||||
usernameEncrypted, err := cryptoutil.LoginEncrypt(username)
|
||||
if err != nil {
|
||||
logger.Logger.Error("LoginEncrypt err:", zap.Error(err))
|
||||
|
||||
@@ -3,10 +3,6 @@ package xuexitong
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"likedream-goframe/internal/utils"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -15,10 +11,8 @@ import (
|
||||
|
||||
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"}
|
||||
|
||||
func ApiVerifyCode(client *resty.Client, retry int) (string, error) {
|
||||
if retry < 0 {
|
||||
return "", fmt.Errorf("retry limit exceeded")
|
||||
}
|
||||
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()
|
||||
@@ -28,40 +22,14 @@ func ApiVerifyCode(client *resty.Client, retry int) (string, error) {
|
||||
req.Header.Add("Connection", "keep-alive")
|
||||
res, err := req.Get(urlStr)
|
||||
if err != nil {
|
||||
if retry > 0 {
|
||||
return ApiVerifyCode(client, retry-1)
|
||||
} else {
|
||||
return res.String(), err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
codeFileName := "code" + randChar[rand.Intn(len(randChar))] //生成验证码文件名称
|
||||
for i := 0; i < 10; i++ {
|
||||
codeFileName += randChar[rand.Intn(len(randChar))]
|
||||
}
|
||||
codeFileName += ".png"
|
||||
utils.PathExistForCreate("./assets/code/") //检测是否存在路径,如果不存在则创建
|
||||
filepath := fmt.Sprintf("./assets/code/%s", codeFileName)
|
||||
file, err := os.Create(filepath)
|
||||
defer res.Body.Close()
|
||||
all, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
res.Body.Close() //立即释放
|
||||
log.Println(err)
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = io.Copy(file, res.Body)
|
||||
if err != nil {
|
||||
res.Body.Close() //立即释放
|
||||
log.Println(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
file.Close()
|
||||
if utils.IsBadImg(filepath) {
|
||||
res.Body.Close() //立即释放
|
||||
utils.DeleteFile(filepath) //删除坏的文件
|
||||
return ApiVerifyCode(client, retry-1)
|
||||
}
|
||||
return filepath, nil
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func ApiPassVerifyCode(client *resty.Client, code string, retry int) bool {
|
||||
|
||||
@@ -1,102 +1,143 @@
|
||||
package xuexitong
|
||||
|
||||
//
|
||||
//import (
|
||||
// "context"
|
||||
// "errors"
|
||||
// "likedream-goframe/internal/application/recognizer/ddddocr"
|
||||
// "likedream-goframe/internal/application/xuexitong/entity"
|
||||
// "likedream-goframe/internal/framework/logger"
|
||||
// "likedream-goframe/internal/utils"
|
||||
// "strings"
|
||||
//
|
||||
// "github.com/bytedance/sonic"
|
||||
// ort "github.com/yalue/onnxruntime_go"
|
||||
// "resty.dev/v3"
|
||||
//)
|
||||
//
|
||||
//const (
|
||||
// ApiPullCourses = "https://mooc1-api.chaoxing.com/mycourse/backclazzdata"
|
||||
//)
|
||||
//
|
||||
//type XuexitongCourse struct {
|
||||
// client *resty.Client
|
||||
//}
|
||||
//
|
||||
//func NewXuexitongCourse(client *resty.Client) *XuexitongCourse {
|
||||
// return &XuexitongCourse{
|
||||
// client: client,
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// PullCourseList 拉取学习通所有课程列表并返回
|
||||
//func (c *XuexitongCourse) PullCourseList(ctx context.Context, retry int) (courseList []entity.XueXiTCourse, err error) {
|
||||
// req := c.client.R()
|
||||
// req.Header.Add("User-Agent", GetUA("mobile"))
|
||||
// get, err := req.Get(ApiPullCourses)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// body := get.String()
|
||||
// if strings.Contains(body, "很抱歉,您所浏览的页面不存在") && retry > 0 { //防止学习通抽风
|
||||
// return c.PullCourseList(ctx, retry-1)
|
||||
// }
|
||||
// if strings.Contains(body, "请输入验证码") {
|
||||
// logger.Logger.Warn("触发验证码,正在进行AI智能识别绕过.....")
|
||||
// //todo ddddocr识别
|
||||
// succ := false
|
||||
// for i := 0; i < 5; i++ {
|
||||
// codePath, err := ApiVerifyCode(c.client, 5)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if codePath == "" { //如果path为空,那么可能是账号问题
|
||||
// return nil, errors.New("无法正常获取学习通验证码,请检查对应url是否正常")
|
||||
// }
|
||||
// img, _ := utils.ReadImg(codePath) //读取验证码图片
|
||||
// codeResult := ddddocr.SemiOCRVerification(img, ort.NewShape(1, 23))
|
||||
// utils.DeleteFile(codePath) //删除验证码文件、
|
||||
// if ApiPassVerifyCode(c.client, codeResult, 5) {
|
||||
// succ = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if !succ {
|
||||
// return nil, errors.New("AI验证码识别失败,请检查对应url是否正常")
|
||||
// }
|
||||
// }
|
||||
// var courseResult entity.XueXiTCourseJson
|
||||
// err = sonic.Unmarshal([]byte(body), &courseResult)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// for _, channel := range courseResult.ChannelList {
|
||||
// if channel.Content.Course.Data == nil {
|
||||
// continue
|
||||
// }
|
||||
// for _, v := range channel.Content.Course.Data {
|
||||
// flag := false
|
||||
// course := entity.XueXiTCourse{
|
||||
// Cpi: channel.Cpi,
|
||||
// Key: strings.Split(strings.Split(v.CourseSquareUrl, "classId=")[1], "&userId")[0],
|
||||
// CourseID: strings.Split(strings.Split(v.CourseSquareUrl, "courseId=")[1], "&personId")[0],
|
||||
// CourseTeacher: v.Teacherfactor,
|
||||
// CourseImage: v.Name,
|
||||
// CourseDataID: v.Id,
|
||||
// ContentID: channel.Content.Id,
|
||||
// IsStart: channel.Content.Isstart,
|
||||
// State: channel.Content.State,
|
||||
// }
|
||||
// for _, subCourse := range courseList {
|
||||
// if subCourse.CourseID == course.CourseID {
|
||||
// flag = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if !flag {
|
||||
// courseList = append(courseList, course)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return courseList, nil
|
||||
//}
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/application/captcha"
|
||||
"likedream-goframe/internal/application/xuexitong/entity"
|
||||
errors2 "likedream-goframe/internal/common/errors"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"likedream-goframe/internal/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/avast/retry-go/v4"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/samber/oops"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
ApiPullCourses = "https://mooc1-api.chaoxing.com/mycourse/backclazzdata"
|
||||
ApiPullChapter = "https://mooc1-api.chaoxing.com/gas/clazz"
|
||||
)
|
||||
|
||||
type XuexitongCourse struct {
|
||||
client *resty.Client
|
||||
}
|
||||
|
||||
func NewXuexitongCourse(client *resty.Client) *XuexitongCourse {
|
||||
return &XuexitongCourse{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// PullCourseList 拉取学习通所有课程列表并返回
|
||||
func (c *XuexitongCourse) PullCourseList(ctx context.Context) []entity.XueXiTCourse {
|
||||
var body string
|
||||
err := retry.Do(func() error {
|
||||
req := c.client.R()
|
||||
req.Header.Add("User-Agent", GetUA("mobile"))
|
||||
get, err := req.Get(ApiPullCourses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body = get.String()
|
||||
if strings.Contains(body, "很抱歉,您所浏览的页面不存在") { //防止学习通抽风
|
||||
return errors2.NewBusinessError(503, "课程列表访问失败")
|
||||
}
|
||||
if strings.Contains(body, "请输入验证码") {
|
||||
logger.Logger.Warn("触发验证码,正在进行AI智能识别绕过.....")
|
||||
|
||||
e := retry.Do(func() error {
|
||||
bytes, e := ApiVerifyCodeBytes(c.client)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
codeResult := captcha.ApiSemiOCRVerification(bytes, 1, 23)
|
||||
if ApiPassVerifyCode(c.client, codeResult, 5) {
|
||||
return nil
|
||||
} else {
|
||||
return errors2.NewBusinessError(503, "验证码识别失败")
|
||||
}
|
||||
}, retry.Attempts(5), retry.Delay(5*time.Second))
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}, retry.Attempts(5), retry.Delay(5*time.Second))
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors2.NewBusinessError(503, "拉取课程列表失败").WithInnerError(err), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
|
||||
var courseResult entity.XueXiTCourseJson
|
||||
err = sonic.Unmarshal([]byte(body), &courseResult)
|
||||
if err != nil {
|
||||
utils.OkOrPanic(errors2.NewBusinessError(503, "解析课程列表失败").WithInnerError(err), oops.WithContext(ctx).Wrap)
|
||||
}
|
||||
var courseList []entity.XueXiTCourse
|
||||
for _, channel := range courseResult.ChannelList {
|
||||
if channel.Content.Course.Data == nil {
|
||||
continue
|
||||
}
|
||||
for _, v := range channel.Content.Course.Data {
|
||||
flag := false
|
||||
course := entity.XueXiTCourse{
|
||||
Cpi: channel.Cpi,
|
||||
Key: strings.Split(strings.Split(v.CourseSquareUrl, "classId=")[1], "&userId")[0],
|
||||
CourseID: strings.Split(strings.Split(v.CourseSquareUrl, "courseId=")[1], "&personId")[0],
|
||||
ChatID: channel.Content.Chatid,
|
||||
CourseTeacher: v.Teacherfactor,
|
||||
CourseName: v.Name,
|
||||
CourseImage: v.Imageurl,
|
||||
CourseDataID: v.Id,
|
||||
ContentID: channel.Content.Id,
|
||||
IsStart: channel.Content.Isstart,
|
||||
State: channel.Content.State,
|
||||
}
|
||||
for _, subCourse := range courseList {
|
||||
if subCourse.CourseID == course.CourseID {
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !flag {
|
||||
courseList = append(courseList, course)
|
||||
}
|
||||
}
|
||||
}
|
||||
return courseList
|
||||
}
|
||||
|
||||
// PullCourseChapterList 拉取学习通课程章节列表并返回
|
||||
func (c *XuexitongCourse) PullCourseChapterList(ctx context.Context, courseId string) []entity.XueXiTCourseChapter {
|
||||
courseList := c.PullCourseList(ctx)
|
||||
var course entity.XueXiTCourse
|
||||
for _, cour := range courseList {
|
||||
if cour.CourseID == courseId {
|
||||
course = cour
|
||||
break
|
||||
}
|
||||
}
|
||||
var chapterString string
|
||||
e := retry.Do(func() error {
|
||||
get, err := c.client.R().SetHeaders(map[string]string{
|
||||
"User-Agent": GetUA("mobile"),
|
||||
"Accept-Language": "zh_CN",
|
||||
"Host": "mooc1-api.chaoxing.com",
|
||||
"Connection": "Keep-Alive",
|
||||
}).SetQueryParams(
|
||||
map[string]string{
|
||||
"id": course.Key,
|
||||
"persinid": strconv.Itoa(course.Cpi),
|
||||
"fields": "id,bbsid,classscore,isstart,allowdownload,chatid,name,state,isfiled,visiblescore,hideclazz,begindate,forbidintoclazz,coursesetting.fields(id,courseid,hiddencoursecover,coursefacecheck),course.fields(id,belongschoolid,name,infocontent,objectid,app,bulletformat,mappingcourseid,imageurl,teacherfactor,jobcount,knowledge.fields(id,name,indexOrder,parentnodeid,status,isReview,layer,label,jobcount,begintime,endtime,attachment.fields(id,type,objectid,extension).type(video)))",
|
||||
"view": "json",
|
||||
}).Get(ApiPullChapter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chapterString = get.String()
|
||||
return nil
|
||||
}, retry.Attempts(3), retry.Delay(5*time.Second))
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).With("chapterString", chapterString).Wrap)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestPullCourse(t *testing.T) {
|
||||
utils.OkOrPanic(err)
|
||||
client := xuexitongLogin.GetClient()
|
||||
courseAction := NewXuexitongCourse(client)
|
||||
list, err := courseAction.PullCourseList(context.Background(), 5)
|
||||
list := courseAction.PullCourseList(context.Background())
|
||||
utils.OkOrPanic(err)
|
||||
t.Log(list)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ const (
|
||||
ConfigCpdailyAppVersion = "cpdaily.appVersion"
|
||||
ConfigCpdailySystemVersion = "cpdaily.systemVersion"
|
||||
ConfigCpdailyCalVersion = "cpdaily.calVersion"
|
||||
ConfigSliderSchoolIds = "cpdaily.sliderIds"
|
||||
|
||||
ConfigRedisUrl = "redis.url"
|
||||
|
||||
@@ -34,6 +35,9 @@ const (
|
||||
ConfigEmailSMTPPassword = "email.smtpPassword"
|
||||
ConfigEmailFromEmail = "email.fromEmail"
|
||||
ConfigEmailFromName = "email.fromName"
|
||||
|
||||
// 基础积分配置
|
||||
ConfigBasePointCpdaily = "basePoint.cpdaily"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -180,7 +180,8 @@ func (v CpTaskServiceV1) PostExecute(ctx context.Context, req *v1.PostCpTaskExec
|
||||
}
|
||||
|
||||
//扣积分
|
||||
basePoint := decimal.NewFromInt(cst2.BasePointCpdaily)
|
||||
basePoint, err := decimal.NewFromString(g.Cfg().MustGet(ctx, cst2.ConfigBasePointCpdaily).String())
|
||||
utils.OkOrPanic(err)
|
||||
if user.Edges.Agent != nil {
|
||||
basePoint = basePoint.Mul(user.Edges.Agent.Rate)
|
||||
}
|
||||
|
||||
@@ -364,6 +364,8 @@ func (s *UserInfoServiceV1) BalanceMigrate(ctx context.Context, req *v1.BalanceM
|
||||
if newUser.Edges.Agent != nil {
|
||||
b = utils.TransformBalance(b, newUser.Edges.Agent.Rate)
|
||||
}
|
||||
// 缩小5倍,新系统按1:1比例的 老系统是1:5
|
||||
b = b.Div(decimal.NewFromInt32(5))
|
||||
balance.BalanceService.ChangeUserBalance(ctx, newUser.ID, b)
|
||||
|
||||
// 清空旧系统积分
|
||||
|
||||
Reference in New Issue
Block a user