Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b857d55e25 | |||
| ca20be8b7e | |||
| ce98ce4afe | |||
| c65318c545 | |||
| 63b91629fa |
@@ -89,4 +89,5 @@ type CpUserCustomDeviceReq struct {
|
||||
g.Meta `path:"/user/customDevice" method:"post" tags:"CpUser" summary:"自定义设备码"`
|
||||
Id int64 `json:"id" v:"required#请指定用户"`
|
||||
MobileCode string `json:"mobileCode" v:"required#请输入设备码"`
|
||||
MobileType string `json:"mobileType" v:"required|in:random,manual#请指定设备码类型|设备码类型无效"`
|
||||
}
|
||||
|
||||
@@ -48,22 +48,8 @@ func (p *PersistCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
delete(globalCookies, cookie.Name)
|
||||
}
|
||||
|
||||
// 查找是否存在相同 name, path 和 domain 的 cookie
|
||||
existingCookies := p.hostMap[host][cookie.Name]
|
||||
found := false
|
||||
for i, existing := range existingCookies {
|
||||
if existing.Path == cookie.Path && existing.Domain == cookie.Domain {
|
||||
// 找到完全匹配的 cookie,替换它
|
||||
existingCookies[i] = cookie
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
// 没有找到匹配的,添加新的 cookie
|
||||
p.hostMap[host][cookie.Name] = append(p.hostMap[host][cookie.Name], cookie)
|
||||
}
|
||||
// 同一 host 下同名 cookie 直接覆盖,避免多个同名 cookie 导致旧值被选中
|
||||
p.hostMap[host][cookie.Name] = []*http.Cookie{cookie}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"likedream-goframe/internal/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/samber/oops"
|
||||
@@ -284,25 +285,88 @@ func (s *SmsLoginHandler) GetCookieString() string {
|
||||
|
||||
// RefreshByTgc 使用保存的完整 cookie(包含 CASTGC 等长期凭证)重新去学校换取 CAS cookie。
|
||||
// tgc 参数是 SetLoginCookie 之后序列化的完整 cookie jar,包含 CASTGC(有效期10年)、
|
||||
// sessionToken 等认证信息,可以直接用来重新激活 session。
|
||||
// sessionToken 等认证信息。流程:先用 sessionToken 调用 getImUserToken 换取新的 CAS cookie,
|
||||
// 再激活 session。
|
||||
func (s *SmsLoginHandler) RefreshByTgc(ctx context.Context, tgc string, schoolId string) (cookie string, newTgc string) {
|
||||
jar := cookiejar.NewPersistCookieJar()
|
||||
jar.Deserialize(tgc)
|
||||
// 合并所有 host 的 cookie 到 global,消除因多 host 存储导致的重复 cookie
|
||||
jar.RemoveCookieInfo()
|
||||
s.client = httpclient.NewCpdailyClient()
|
||||
s.client.SetCookieJar(jar)
|
||||
|
||||
detail := school.GetSchoolDetail(ctx, schoolId)
|
||||
|
||||
logger.Logger.Info("[TGC] RefreshByTgc 开始用CASTGC重新激活session",
|
||||
logger.Logger.Info("[TGC] RefreshByTgc 开始用TGC换取CAS cookie",
|
||||
zap.String("schoolId", schoolId),
|
||||
zap.String("wecloudUrl", detail.WecloudUrl),
|
||||
zap.Int("tgcLen", len(tgc)),
|
||||
)
|
||||
|
||||
// 用保存的 cookie(含 CASTGC)重新激活 session
|
||||
ActivateSession(ctx, detail.WecloudUrl, s.client)
|
||||
// 从 cookie jar 中提取 sessionToken,用于换取新的 CAS cookie
|
||||
schoolUrl, _ := url.Parse(detail.WecloudUrl)
|
||||
sessionToken := ""
|
||||
for _, c := range jar.Cookies(schoolUrl) {
|
||||
if c.Name == "sessionToken" {
|
||||
sessionToken = c.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
logger.Logger.Info("[TGC] RefreshByTgc ActivateSession完成")
|
||||
if sessionToken != "" {
|
||||
// 换取新的 CAS cookie(与 SetLoginCookie 相同的流程)
|
||||
getCookieUrl := fmt.Sprintf("%s%s", detail.WecloudUrl, "/wec-im-group/group/imUserToken/getImUserToken")
|
||||
|
||||
ampCookieMap := map[string]any{
|
||||
"AMP2": []map[string]any{
|
||||
{"path": "/", "value": sessionToken, "domain": "", "name": "sessionToken"},
|
||||
},
|
||||
"AMP1": []map[string]any{
|
||||
{"path": "/", "value": sessionToken, "domain": "", "name": "sessionToken"},
|
||||
},
|
||||
}
|
||||
|
||||
ampTokenString, e := sonic.Marshal(ampCookieMap)
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)
|
||||
|
||||
ampTokenEncrypted := cryptoutil.DesEncrypt(ampTokenString, cryptoutil.Normal)
|
||||
ampToken := base64.StdEncoding.EncodeToString(ampTokenEncrypted)
|
||||
|
||||
ctx = utils.NewCpdailySmsContextBuilder().
|
||||
WithDeviceID(s.deviceID).
|
||||
WithTenantID(detail.Id).
|
||||
WithSessionToken(sessionToken).
|
||||
Build(ctx)
|
||||
|
||||
// 使用不复制 Cookie 头的重定向策略,避免旧 Cookie 覆盖 jar 中更新的值
|
||||
s.client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||
if len(via) >= 50 {
|
||||
return fmt.Errorf("resty: stopped after 50 redirects")
|
||||
}
|
||||
// 只复制非 Cookie 头,让 jar 自动管理 Cookie
|
||||
for key, val := range via[0].Header {
|
||||
if !strings.EqualFold(key, "Cookie") {
|
||||
req.Header[key] = val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
req := s.client.R().
|
||||
SetHeader("AmpCookies", ampToken).
|
||||
SetContext(ctx)
|
||||
|
||||
setSmsRequestHeaders(ctx, req)
|
||||
|
||||
resp, e := req.Get(getCookieUrl)
|
||||
utils.OkOrPanic(e, oops.WithContext(ctx).Wrap)
|
||||
|
||||
respJson := utils.ReadJson[cpdailySmsCommonResponse[any]](resp.Body)
|
||||
respJson.EnsureSuccess()
|
||||
|
||||
logger.Logger.Info("[TGC] RefreshByTgc 换取CAS cookie完成")
|
||||
} else {
|
||||
logger.Logger.Warn("[TGC] RefreshByTgc 未找到sessionToken,跳过换取CAS cookie步骤")
|
||||
}
|
||||
|
||||
newTgc = tgc // TGC 保持不变(CASTGC 有效期10年)
|
||||
cookie = s.GetCookieString()
|
||||
|
||||
@@ -17,5 +17,5 @@ var (
|
||||
RedisBalanceMigrateExpire = int64(3600) // 积分转移链接1小时有效
|
||||
RedisCpUserLoginExpire = int64(180)
|
||||
RedisCpUserAuthorizeExpire = int64(1800)
|
||||
RedisSchoolInfoExpire = int64(3600) // 学校信息1小时有效
|
||||
RedisSchoolInfoExpire = int64(3600 * 24 * 30) // 学校信息30天有效
|
||||
)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package cst
|
||||
|
||||
const (
|
||||
TaskCpdaily = "task:cpdaily"
|
||||
TaskEmail = "task:email"
|
||||
TaskResetDaily = "task:reset:daily"
|
||||
TaskXuexitong = "task:xuexitong"
|
||||
TaskXuexitongVio = "task:xuexitong:violence"
|
||||
TaskCpdaily = "task:cpdaily"
|
||||
TaskEmail = "task:email"
|
||||
TaskResetDaily = "task:reset:daily"
|
||||
TaskRefreshSchoolDetail = "task:refresh:school:detail"
|
||||
TaskXuexitong = "task:xuexitong"
|
||||
TaskXuexitongVio = "task:xuexitong:violence"
|
||||
|
||||
TaskCpdailyId = "cpdaily:task:schedule:%d"
|
||||
|
||||
|
||||
@@ -140,6 +140,7 @@ type MobileType string
|
||||
const (
|
||||
MobileTypeRandom MobileType = "random"
|
||||
MobileTypeAuthorize MobileType = "authorize"
|
||||
MobileTypeManual MobileType = "manual"
|
||||
)
|
||||
|
||||
func (mt MobileType) String() string {
|
||||
@@ -149,7 +150,7 @@ func (mt MobileType) String() string {
|
||||
// MobileTypeValidator is a validator for the "mobile_type" field enum values. It is called by the builders before save.
|
||||
func MobileTypeValidator(mt MobileType) error {
|
||||
switch mt {
|
||||
case MobileTypeRandom, MobileTypeAuthorize:
|
||||
case MobileTypeRandom, MobileTypeAuthorize, MobileTypeManual:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("cpuser: invalid enum value for mobile_type field: %q", mt)
|
||||
|
||||
@@ -207,7 +207,7 @@ var (
|
||||
{Name: "mobile_code", Type: field.TypeString, Nullable: true},
|
||||
{Name: "cookie", Type: field.TypeString, Nullable: true, Size: 2147483647},
|
||||
{Name: "tgc", Type: field.TypeString, Nullable: true, Size: 2147483647},
|
||||
{Name: "mobile_type", Type: field.TypeEnum, Enums: []string{"random", "authorize"}},
|
||||
{Name: "mobile_type", Type: field.TypeEnum, Enums: []string{"random", "authorize", "manual"}},
|
||||
{Name: "login_type", Type: field.TypeEnum, Enums: []string{"account", "phone"}, Default: "account"},
|
||||
{Name: "school_id", Type: field.TypeString},
|
||||
{Name: "user_id", Type: field.TypeInt64},
|
||||
|
||||
@@ -25,7 +25,7 @@ func (CpUser) Fields() []ent.Field {
|
||||
field.String("mobile_code").Optional(),
|
||||
field.Text("cookie").Optional(),
|
||||
field.Text("tgc").Optional(),
|
||||
field.Enum("mobile_type").Values("random", "authorize"),
|
||||
field.Enum("mobile_type").Values("random", "authorize", "manual"),
|
||||
field.Enum("login_type").Values("account", "phone").Default("account"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ type periodicTaskConfigProvider struct{}
|
||||
func (p *periodicTaskConfigProvider) GetConfigs() ([]*asynq.PeriodicTaskConfig, error) {
|
||||
return []*asynq.PeriodicTaskConfig{
|
||||
{
|
||||
//Cronspec: "0 1 * * *", // 每天凌晨1点执行
|
||||
Cronspec: "0 1 * * * ",
|
||||
Task: asynq.NewTask(cst.TaskResetDaily, nil),
|
||||
Opts: []asynq.Option{
|
||||
@@ -45,5 +44,14 @@ func (p *periodicTaskConfigProvider) GetConfigs() ([]*asynq.PeriodicTaskConfig,
|
||||
asynq.Timeout(0), // 使用默认超时
|
||||
},
|
||||
},
|
||||
{
|
||||
Cronspec: "0 12 * * *",
|
||||
Task: asynq.NewTask(cst.TaskRefreshSchoolDetail, nil),
|
||||
Opts: []asynq.Option{
|
||||
asynq.Queue(cst.TaskQueueDefault),
|
||||
asynq.MaxRetry(1),
|
||||
asynq.Timeout(0),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package schedule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"likedream-goframe/internal/application/cpdaily/school"
|
||||
"likedream-goframe/internal/framework/logger"
|
||||
"likedream-goframe/internal/utils"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type RefreshSchoolDetailTaskHandler struct{}
|
||||
|
||||
// ProcessTask 每天中午刷新一次全部学校详情缓存
|
||||
func (h *RefreshSchoolDetailTaskHandler) ProcessTask(ctx context.Context, _ *asynq.Task) (err error) {
|
||||
defer utils.Pass(&err)
|
||||
logger.Logger.Info("开始批量刷新学校详情缓存信息")
|
||||
schools := school.GetSchoolList(ctx)
|
||||
for _, item := range schools {
|
||||
func() {
|
||||
defer utils.HandleError(func(err error) {
|
||||
logger.Logger.Warn("刷新学校详情缓存失败", zap.String("school_id", item.Id), zap.Error(err))
|
||||
})
|
||||
school.GetSchoolDetail(ctx, item.Id)
|
||||
}()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -68,6 +68,7 @@ func initServer(ctx context.Context) {
|
||||
auxMux := asynq.NewServeMux()
|
||||
auxMux.Handle(cst2.TaskEmail, &EmailTaskHandler{})
|
||||
auxMux.Handle(cst2.TaskResetDaily, &ResetTaskHandler{})
|
||||
auxMux.Handle(cst2.TaskRefreshSchoolDetail, &RefreshSchoolDetailTaskHandler{})
|
||||
|
||||
e := globalServer.Start(mainMux)
|
||||
utils.OkOrPanic(e)
|
||||
|
||||
@@ -176,7 +176,7 @@ func (v CpUserServiceV1) CustomDevice(ctx context.Context, req *v1.CpUserCustomD
|
||||
if cpUser == nil {
|
||||
return utils.FailParam("用户不存在")
|
||||
}
|
||||
db.Global.CpUser.UpdateOne(cpUser).SetMobileCode(req.MobileCode).SetMobileType(cpuser.MobileTypeAuthorize).ExecX(ctx)
|
||||
db.Global.CpUser.UpdateOne(cpUser).SetMobileCode(req.MobileCode).SetMobileType(cpuser.MobileType(req.MobileType)).ExecX(ctx)
|
||||
return utils.SuccessWithMsg("自定义设备码设置成功")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user