package actionutils import ( "context" "encoding/json" "errors" "fmt" "github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/langs" "github.com/TeaOSLab/EdgeCommon/pkg/langs/codes" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/userconfigs" "github.com/TeaOSLab/EdgeUser/internal/configloaders" "github.com/TeaOSLab/EdgeUser/internal/nodes/serverutils" "github.com/TeaOSLab/EdgeUser/internal/oplogs" "github.com/TeaOSLab/EdgeUser/internal/remotelogs" "github.com/TeaOSLab/EdgeUser/internal/rpc" "github.com/TeaOSLab/EdgeUser/internal/utils" "github.com/TeaOSLab/EdgeUser/internal/web/actions/default/login/loginutils" "github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/types" "net" "net/http" "strconv" ) type ParentAction struct { actions.ActionObject rpcClient *rpc.RPCClient } // Parent 可以调用自身的一个简便方法 func (this *ParentAction) Parent() *ParentAction { return this } func (this *ParentAction) ErrorPage(err error) { if err == nil { return } if !rpc.IsConnError(err) { remotelogs.Error("ERROR_PAGE", this.Request.URL.String()+": "+err.Error()) } // 日志 this.CreateLog(oplogs.LevelError, codes.UserCommon_LogSystemError, err.Error()) if this.Request.Method == http.MethodGet { FailPage(this, err) } else { Fail(this, err) } } func (this *ParentAction) ErrorText(err string) { this.ErrorPage(errors.New(err)) } func (this *ParentAction) NotFound(name string, itemId int64) { this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found")) } func (this *ParentAction) ForbidPage() { this.ResponseWriter.WriteHeader(http.StatusForbidden) } func (this *ParentAction) NewPage(total int64, size ...int64) *Page { if len(size) > 0 { return NewActionPage(this, total, size[0]) } return NewActionPage(this, total, 10) } func (this *ParentAction) Nav(mainMenu string, tab string, firstMenu string) { this.Data["mainMenu"] = mainMenu this.Data["mainTab"] = tab this.Data["firstMenuItem"] = firstMenu } func (this *ParentAction) FirstMenu(menuItem string) { this.Data["firstMenuItem"] = menuItem } func (this *ParentAction) SecondMenu(menuItem string) { this.Data["secondMenuItem"] = menuItem } func (this *ParentAction) TinyMenu(menuItem string) { this.Data["tinyMenuItem"] = menuItem } func (this *ParentAction) UserId() int64 { return this.Context.GetInt64("userId") } func (this *ParentAction) CreateLog(level string, messageCode langs.MessageCode, args ...any) { var description = messageCode.For(this.LangCode()) var desc = fmt.Sprintf(description, args...) if level == oplogs.LevelInfo { if this.Code != 200 { level = oplogs.LevelWarn if len(this.Message) > 0 { desc += " 失败:" + this.Message } } } err := dao.SharedLogDAO.CreateUserLog(this.UserContext(), level, this.Request.URL.Path, desc, loginutils.RemoteIP(&this.ActionObject)) if err != nil { utils.PrintError(err) } } func (this *ParentAction) CreateLogInfo(messageCode langs.MessageCode, args ...any) { this.CreateLog(oplogs.LevelInfo, messageCode, args...) } func (this *ParentAction) LangCode() langs.LangCode { var langCode = this.Data.GetString("teaLang") if len(langCode) == 0 { langCode = langs.DefaultManager().DefaultLang() } return langCode } // RPC 获取RPC func (this *ParentAction) RPC() *rpc.RPCClient { if this.rpcClient != nil { return this.rpcClient } // 所有集群 rpcClient, err := rpc.SharedRPC() if err != nil { logs.Fatal(err) return nil } this.rpcClient = rpcClient return rpcClient } // UserContext 获取Context // 每个请求的context都必须是一个新的实例 func (this *ParentAction) UserContext() context.Context { if this.rpcClient == nil { rpcClient, err := rpc.SharedRPC() if err != nil { logs.Fatal(err) return nil } this.rpcClient = rpcClient } return this.rpcClient.Context(this.UserId()) } // 一个不包含用户ID的上下文 func (this *ParentAction) NullUserContext() context.Context { if this.rpcClient == nil { rpcClient, err := rpc.SharedRPC() if err != nil { logs.Fatal(err) return nil } this.rpcClient = rpcClient } return this.rpcClient.Context(0) } // ValidateFeature 校验Feature func (this *ParentAction) ValidateFeature(featureCode string, serverId int64) bool { b, err := this.validateFeature(featureCode, serverId) if err != nil { remotelogs.Error("FEATURE", "validate feature: "+err.Error()) } return b } func (this *ParentAction) validateFeature(featureCode string, serverId int64) (bool, error) { // 检查审核和认证 if configloaders.RequireVerification() && !this.Context.GetBool("isVerified") { return false, nil } if configloaders.RequireIdentity() && !this.Context.GetBool("isIdentified") { return false, nil } var featureDef = userconfigs.FindUserFeature(featureCode) if featureDef == nil { return false, errors.New("invalid feature code '" + featureCode + "'") } // 检查网站绑定的套餐 if serverId > 0 && featureDef.SupportPlan { userPlanResp, err := this.RPC().ServerRPC().FindServerUserPlan(this.UserContext(), &pb.FindServerUserPlanRequest{ServerId: serverId}) if err != nil { return false, err } if userPlanResp.UserPlan != nil && userPlanResp.UserPlan.IsOn && userPlanResp.UserPlan.Plan != nil /** 不需要判断套餐是否启用(即不需要判断是否为UserPlan.Plan.isOn),以便于兼容套餐被删除的情况 **/ { var plan = userPlanResp.UserPlan.Plan if plan.HasFullFeatures { return true, nil } var supportedFeatureCodes []string if len(plan.FeaturesJSON) > 0 { err = json.Unmarshal(plan.FeaturesJSON, &supportedFeatureCodes) if err != nil { return false, err } } return lists.ContainsString(supportedFeatureCodes, featureCode), nil } } // 用户功能 userFeatureResp, err := this.RPC().UserRPC().FindUserFeatures(this.UserContext(), &pb.FindUserFeaturesRequest{UserId: this.UserId()}) if err != nil { return false, err } var userFeatureCodes = []string{} for _, feature := range userFeatureResp.Features { userFeatureCodes = append(userFeatureCodes, feature.Code) } return lists.ContainsString(userFeatureCodes, featureCode), nil } func (this *ParentAction) CheckUserStatus() bool { // 审核和认证 if configloaders.RequireVerification() && !this.Context.GetBool("isVerified") { this.RedirectURL("/") return false } if configloaders.RequireIdentity() && !this.Context.GetBool("isIdentified") { this.RedirectURL("/") return false } return true } func (this *ParentAction) FindServerIdWithWebId(webId int64) (serverId int64, err error) { serverIdResp, err := this.RPC().HTTPWebRPC().FindServerIdWithHTTPWebId(this.UserContext(), &pb.FindServerIdWithHTTPWebIdRequest{ HttpWebId: webId, }) if err != nil { this.ErrorPage(err) return } serverId = serverIdResp.ServerId return } func (this *ParentAction) CheckHTTPSRedirecting() { if this.Request.TLS == nil { httpsPort, _ := serverutils.ReadServerHTTPS() if httpsPort > 0 { currentHost, _, hostErr := net.SplitHostPort(this.Request.Host) if hostErr != nil { currentHost = this.Request.Host } var newHost = configutils.QuoteIP(currentHost) if httpsPort != 443 /** default https port **/ { newHost += ":" + types.String(httpsPort) } // 如果没有前端反向代理,则跳转 if len(this.Request.Header.Get("X-Forwarded-For")) == 0 && len(this.Request.Header.Get("X-Real-Ip")) == 0 { this.RedirectURL("https://" + newHost + this.Request.RequestURI) return } } } }