| @@ -60,6 +60,15 @@ const ( | |||
| ActionCreateGPUTrainTask //31 | |||
| ActionCreateGrampusNPUTrainTask //32 | |||
| ActionCreateGrampusGPUTrainTask //33 | |||
| ActionBindWechat //34 | |||
| ActionCreateCloudbrainTask //35 | |||
| ActionDatasetRecommended //36 | |||
| ActionCreateImage //37 | |||
| ActionImageRecommend //38 | |||
| ActionChangeUserAvatar //39 | |||
| ActionPushCommits //40 | |||
| ActionForkRepo //41 | |||
| ) | |||
| // Action represents user operation type and other information to | |||
| @@ -81,6 +90,18 @@ type Action struct { | |||
| IsTransformed bool `xorm:"INDEX NOT NULL DEFAULT false"` | |||
| Content string `xorm:"TEXT"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| Cloudbrain *Cloudbrain `xorm:"-"` | |||
| } | |||
| type ActionShow struct { | |||
| OpType ActionType | |||
| RepoLink string | |||
| ShortRepoFullDisplayName string | |||
| Content string | |||
| RefName string | |||
| IssueInfos []string | |||
| CommentLink string | |||
| Cloudbrain *CloudbrainShow4Action | |||
| } | |||
| // GetOpType gets the ActionType of this action. | |||
| @@ -218,6 +239,47 @@ func (a *Action) GetRepoLink() string { | |||
| return "/" + a.GetRepoPath() | |||
| } | |||
| func (a *Action) ToShow() *ActionShow { | |||
| actionShow := &ActionShow{} | |||
| actionShow.OpType = GetTaskOptType(*a) | |||
| actionShow.Content = a.Content | |||
| actionShow.RefName = a.RefName | |||
| if strings.Contains(a.Content, "|") && a.IsIssueAction() { | |||
| actionShow.IssueInfos = a.GetIssueInfos() | |||
| } | |||
| if a.Repo != nil { | |||
| actionShow.RepoLink = a.GetRepoLink() | |||
| actionShow.ShortRepoFullDisplayName = a.ShortRepoFullDisplayName() | |||
| } | |||
| if a.Comment != nil { | |||
| actionShow.CommentLink = a.GetCommentLink() | |||
| } | |||
| if a.Cloudbrain != nil { | |||
| c := &CloudbrainShow4Action{ | |||
| ID: a.Cloudbrain.ID, | |||
| JobID: a.Cloudbrain.JobID, | |||
| Type: a.Cloudbrain.Type, | |||
| JobType: a.Cloudbrain.JobType, | |||
| DisplayJobName: a.Cloudbrain.DisplayJobName, | |||
| ComputeResource: a.Cloudbrain.ComputeResource, | |||
| } | |||
| actionShow.Cloudbrain = c | |||
| } | |||
| return actionShow | |||
| } | |||
| func GetTaskOptType(action Action) ActionType { | |||
| //Convert all types of cloudbrain tasks action into ActionCreateCloudbrainTask | |||
| if action.IsCloudbrainAction() { | |||
| return ActionCreateCloudbrainTask | |||
| } | |||
| return action.OpType | |||
| } | |||
| // GetRepositoryFromMatch returns a *Repository from a username and repo strings | |||
| func GetRepositoryFromMatch(ownerName string, repoName string) (*Repository, error) { | |||
| var err error | |||
| @@ -315,6 +377,39 @@ func (a *Action) GetIssueContent() string { | |||
| return issue.Content | |||
| } | |||
| func (a *Action) IsCloudbrainAction() bool { | |||
| switch a.OpType { | |||
| case ActionCreateDebugGPUTask, | |||
| ActionCreateDebugNPUTask, | |||
| ActionCreateTrainTask, | |||
| ActionCreateInferenceTask, | |||
| ActionCreateBenchMarkTask, | |||
| ActionCreateGPUTrainTask, | |||
| ActionCreateGrampusNPUTrainTask, | |||
| ActionCreateGrampusGPUTrainTask: | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| func (a *Action) IsIssueAction() bool { | |||
| switch a.OpType { | |||
| case ActionCreateIssue, | |||
| ActionCloseIssue, | |||
| ActionClosePullRequest, | |||
| ActionReopenIssue, | |||
| ActionReopenPullRequest, | |||
| ActionCommentPull, | |||
| ActionCommentIssue, | |||
| ActionCreatePullRequest, | |||
| ActionApprovePullRequest, | |||
| ActionRejectPullRequest, | |||
| ActionMergePullRequest: | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| // GetFeedsOptions options for retrieving feeds | |||
| type GetFeedsOptions struct { | |||
| RequestedUser *User // the user we want activity for | |||
| @@ -404,3 +499,18 @@ func GetUnTransformedActions() ([]*Action, error) { | |||
| Find(&actions) | |||
| return actions, err | |||
| } | |||
| func GetActionByIds(ids []int64) ([]*Action, error) { | |||
| if len(ids) == 0 { | |||
| return nil, nil | |||
| } | |||
| actions := make([]*Action, 0) | |||
| err := x.In("id", ids).Find(&actions) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if err := ActionList(actions).LoadAllAttributes(); err != nil { | |||
| return nil, fmt.Errorf("ActionList loadAttributes: %v", err) | |||
| } | |||
| return actions, nil | |||
| } | |||
| @@ -4,7 +4,11 @@ | |||
| package models | |||
| import "fmt" | |||
| import ( | |||
| "fmt" | |||
| "strconv" | |||
| "xorm.io/builder" | |||
| ) | |||
| // ActionList defines a list of actions | |||
| type ActionList []*Action | |||
| @@ -26,6 +30,9 @@ func (actions ActionList) loadUsers(e Engine) ([]*User, error) { | |||
| userIDs := actions.getUserIDs() | |||
| userMaps := make(map[int64]*User, len(userIDs)) | |||
| if len(userIDs) == 0 { | |||
| return make([]*User, 0), nil | |||
| } | |||
| err := e. | |||
| In("id", userIDs). | |||
| Find(&userMaps) | |||
| @@ -61,6 +68,9 @@ func (actions ActionList) loadRepositories(e Engine) ([]*Repository, error) { | |||
| repoIDs := actions.getRepoIDs() | |||
| repoMaps := make(map[int64]*Repository, len(repoIDs)) | |||
| if len(repoIDs) == 0 { | |||
| return make([]*Repository, 0), nil | |||
| } | |||
| err := e. | |||
| In("id", repoIDs). | |||
| Find(&repoMaps) | |||
| @@ -79,6 +89,133 @@ func (actions ActionList) LoadRepositories() ([]*Repository, error) { | |||
| return actions.loadRepositories(x) | |||
| } | |||
| func (actions ActionList) getCommentIDs() []int64 { | |||
| commentIDs := make(map[int64]struct{}, len(actions)) | |||
| for _, action := range actions { | |||
| if action.CommentID == 0 { | |||
| continue | |||
| } | |||
| if _, ok := commentIDs[action.CommentID]; !ok { | |||
| commentIDs[action.CommentID] = struct{}{} | |||
| } | |||
| } | |||
| return keysInt64(commentIDs) | |||
| } | |||
| func (actions ActionList) loadComments(e Engine) ([]*Comment, error) { | |||
| if len(actions) == 0 { | |||
| return nil, nil | |||
| } | |||
| commentIDs := actions.getCommentIDs() | |||
| commentMaps := make(map[int64]*Comment, len(commentIDs)) | |||
| if len(commentIDs) == 0 { | |||
| return make([]*Comment, 0), nil | |||
| } | |||
| err := e. | |||
| In("id", commentIDs). | |||
| Find(&commentMaps) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("find comment: %v", err) | |||
| } | |||
| for _, action := range actions { | |||
| if action.CommentID > 0 { | |||
| action.Comment = commentMaps[action.CommentID] | |||
| } | |||
| } | |||
| return valuesComment(commentMaps), nil | |||
| } | |||
| // LoadComments loads actions' all comments | |||
| func (actions ActionList) LoadComments() ([]*Comment, error) { | |||
| return actions.loadComments(x) | |||
| } | |||
| func (actions ActionList) getCloudbrainIDs() []int64 { | |||
| cloudbrainIDs := make(map[int64]struct{}, 0) | |||
| for _, action := range actions { | |||
| if !action.IsCloudbrainAction() { | |||
| continue | |||
| } | |||
| cloudbrainId, _ := strconv.ParseInt(action.Content, 10, 64) | |||
| if _, ok := cloudbrainIDs[cloudbrainId]; !ok { | |||
| cloudbrainIDs[cloudbrainId] = struct{}{} | |||
| } | |||
| } | |||
| return keysInt64(cloudbrainIDs) | |||
| } | |||
| func (actions ActionList) getCloudbrainJobIDs() []string { | |||
| cloudbrainJobIDs := make(map[string]struct{}, 0) | |||
| for _, action := range actions { | |||
| if !action.IsCloudbrainAction() { | |||
| continue | |||
| } | |||
| if _, ok := cloudbrainJobIDs[action.Content]; !ok { | |||
| cloudbrainJobIDs[action.Content] = struct{}{} | |||
| } | |||
| } | |||
| return keysString(cloudbrainJobIDs) | |||
| } | |||
| func (actions ActionList) loadCloudbrains(e Engine) ([]*Cloudbrain, error) { | |||
| if len(actions) == 0 { | |||
| return nil, nil | |||
| } | |||
| cloudbrainIDs := actions.getCloudbrainIDs() | |||
| cloudbrainJobIDs := actions.getCloudbrainJobIDs() | |||
| cloudbrainMaps := make(map[int64]*Cloudbrain, len(cloudbrainIDs)) | |||
| if len(cloudbrainIDs) == 0 { | |||
| return make([]*Cloudbrain, 0), nil | |||
| } | |||
| //由于各个类型的云脑任务在发布action的时候,content字段保存的ID含义不同,部分取的是ID,部分取的是jobId | |||
| //所以在查询action对应的cloudbrain对象时,以这两个字段做为条件查询 | |||
| cond := builder.Or(builder.In("id", cloudbrainIDs)).Or(builder.In("job_id", cloudbrainJobIDs)) | |||
| err := e. | |||
| Where(cond).Unscoped(). | |||
| Find(&cloudbrainMaps) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("find cloudbrain: %v", err) | |||
| } | |||
| cloudBrainJobIdMap := make(map[string]*Cloudbrain, len(cloudbrainIDs)) | |||
| for _, v := range cloudbrainMaps { | |||
| cloudBrainJobIdMap[v.JobID] = v | |||
| } | |||
| for _, action := range actions { | |||
| if !action.IsCloudbrainAction() { | |||
| continue | |||
| } | |||
| cloudbrainId, _ := strconv.ParseInt(action.Content, 10, 64) | |||
| if cloudbrainId > 0 { | |||
| if c, ok := cloudbrainMaps[cloudbrainId]; ok { | |||
| if c.DisplayJobName == action.RefName || c.JobName == action.RefName { | |||
| action.Cloudbrain = c | |||
| continue | |||
| } | |||
| } | |||
| } | |||
| if c, ok := cloudBrainJobIdMap[action.Content]; ok { | |||
| if c.DisplayJobName == action.RefName || c.JobName == action.RefName { | |||
| action.Cloudbrain = c | |||
| continue | |||
| } | |||
| } | |||
| } | |||
| return valuesCloudbrain(cloudbrainMaps), nil | |||
| } | |||
| // LoadComments loads actions' all comments | |||
| func (actions ActionList) LoadCloudbrains() ([]*Comment, error) { | |||
| return actions.loadComments(x) | |||
| } | |||
| // loadAttributes loads all attributes | |||
| func (actions ActionList) loadAttributes(e Engine) (err error) { | |||
| if _, err = actions.loadUsers(e); err != nil { | |||
| @@ -96,3 +233,30 @@ func (actions ActionList) loadAttributes(e Engine) (err error) { | |||
| func (actions ActionList) LoadAttributes() error { | |||
| return actions.loadAttributes(x) | |||
| } | |||
| // LoadAllAttributes loads all attributes of the actions | |||
| // compare with LoadAttributes() ,LoadAllAttributes() loads Comment and Cloudbrain attribute | |||
| func (actions ActionList) LoadAllAttributes() error { | |||
| return actions.loadAllAttributes(x) | |||
| } | |||
| // loadAllAttributes | |||
| func (actions ActionList) loadAllAttributes(e Engine) (err error) { | |||
| if _, err = actions.loadUsers(e); err != nil { | |||
| return | |||
| } | |||
| if _, err = actions.loadRepositories(e); err != nil { | |||
| return | |||
| } | |||
| if _, err = actions.loadComments(e); err != nil { | |||
| return | |||
| } | |||
| if _, err = actions.loadCloudbrains(e); err != nil { | |||
| return | |||
| } | |||
| return nil | |||
| } | |||
| @@ -701,3 +701,11 @@ func Attachments(opts *AttachmentsOptions) ([]*AttachmentInfo, int64, error) { | |||
| return attachments, count, nil | |||
| } | |||
| func GetAllDatasetContributorByDatasetId(datasetId int64) ([]*User, error) { | |||
| r := make([]*User, 0) | |||
| if err := x.Select("distinct(public.user.*)").Table("attachment").Join("LEFT", "user", "public.user.ID = attachment.uploader_id").Where("attachment.dataset_id = ?", datasetId).Find(&r); err != nil { | |||
| return nil, err | |||
| } | |||
| return r, nil | |||
| } | |||
| @@ -200,6 +200,45 @@ type Cloudbrain struct { | |||
| Spec *Specification `xorm:"-"` | |||
| } | |||
| type CloudbrainShow struct { | |||
| ID int64 | |||
| JobID string | |||
| RepoFullName string | |||
| Type int | |||
| JobType string | |||
| DisplayJobName string | |||
| Duration string | |||
| ResourceSpec *Specification | |||
| ComputeResource string | |||
| AiCenter string | |||
| } | |||
| type CloudbrainShow4Action struct { | |||
| ID int64 | |||
| JobID string | |||
| Type int | |||
| JobType string | |||
| DisplayJobName string | |||
| ComputeResource string | |||
| } | |||
| func (task *Cloudbrain) ToShow() *CloudbrainShow { | |||
| c := &CloudbrainShow{ | |||
| ID: task.ID, | |||
| JobID: task.JobID, | |||
| JobType: task.JobType, | |||
| Type: task.Type, | |||
| DisplayJobName: task.DisplayJobName, | |||
| Duration: task.TrainJobDuration, | |||
| ResourceSpec: task.Spec, | |||
| ComputeResource: task.ComputeResource, | |||
| } | |||
| if task.Repo != nil { | |||
| c.RepoFullName = task.Repo.FullName() | |||
| } | |||
| return c | |||
| } | |||
| func (task *Cloudbrain) ComputeAndSetDuration() { | |||
| var d int64 | |||
| if task.StartTime == 0 { | |||
| @@ -597,11 +636,23 @@ type ResourceSpecs struct { | |||
| } | |||
| type ResourceSpec struct { | |||
| Id int `json:"id"` | |||
| CpuNum int `json:"cpu"` | |||
| GpuNum int `json:"gpu"` | |||
| MemMiB int `json:"memMiB"` | |||
| ShareMemMiB int `json:"shareMemMiB"` | |||
| Id int `json:"id"` | |||
| CpuNum int `json:"cpu"` | |||
| GpuNum int `json:"gpu"` | |||
| MemMiB int `json:"memMiB"` | |||
| ShareMemMiB int `json:"shareMemMiB"` | |||
| UnitPrice int64 `json:"unitPrice"` | |||
| } | |||
| type FlavorInfos struct { | |||
| FlavorInfo []*FlavorInfo `json:"flavor_info"` | |||
| } | |||
| type FlavorInfo struct { | |||
| Id int `json:"id"` | |||
| Value string `json:"value"` | |||
| Desc string `json:"desc"` | |||
| UnitPrice int64 `json:"unitPrice"` | |||
| } | |||
| type SpecialPools struct { | |||
| @@ -2221,6 +2272,27 @@ func CloudbrainAllStatic(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, er | |||
| return cloudbrains, count, nil | |||
| } | |||
| func GetStartedCloudbrainTaskByUpdatedUnix(startTime, endTime time.Time) ([]Cloudbrain, error) { | |||
| r := make([]Cloudbrain, 0) | |||
| err := x.Where("updated_unix >= ? and updated_unix <= ? and start_time > 0", startTime.Unix(), endTime.Unix()).Unscoped().Find(&r) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return r, nil | |||
| } | |||
| func GetCloudbrainByIds(ids []int64) ([]*Cloudbrain, error) { | |||
| if len(ids) == 0 { | |||
| return nil, nil | |||
| } | |||
| cloudbrains := make([]*Cloudbrain, 0) | |||
| err := x.In("id", ids).Unscoped().Find(&cloudbrains) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return cloudbrains, nil | |||
| } | |||
| type DatasetInfo struct { | |||
| DataLocalPath string | |||
| Name string | |||
| @@ -72,6 +72,8 @@ func NewCloudBrainSpec(cloudbrainId int64, s Specification) CloudbrainSpec { | |||
| } | |||
| } | |||
| var StatusChangeChan = make(chan *Cloudbrain, 50) | |||
| func InsertCloudbrainSpec(c CloudbrainSpec) (int64, error) { | |||
| return x.Insert(&c) | |||
| } | |||
| @@ -107,3 +109,24 @@ func CountNoSpecHistoricTask() (int64, error) { | |||
| } | |||
| return n, nil | |||
| } | |||
| // GetResourceSpecMapByCloudbrainIDs | |||
| func GetResourceSpecMapByCloudbrainIDs(ids []int64) (map[int64]*Specification, error) { | |||
| specs := make([]*CloudbrainSpec, 0) | |||
| if err := x.In("cloudbrain_id", ids).Find(&specs); err != nil { | |||
| return nil, err | |||
| } | |||
| r := make(map[int64]*Specification, len(ids)) | |||
| for _, s := range specs { | |||
| r[s.CloudbrainID] = s.ConvertToSpecification() | |||
| } | |||
| return r, nil | |||
| } | |||
| func GetCloudbrainTaskUnitPrice(cloudbrainId int64) (int, error) { | |||
| s, err := GetCloudbrainSpecByID(cloudbrainId) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| return s.UnitPrice, nil | |||
| } | |||
| @@ -2012,3 +2012,27 @@ func IsErrTagNotExist(err error) bool { | |||
| _, ok := err.(ErrTagNotExist) | |||
| return ok | |||
| } | |||
| type ErrRecordNotExist struct { | |||
| } | |||
| func IsErrRecordNotExist(err error) bool { | |||
| _, ok := err.(ErrRecordNotExist) | |||
| return ok | |||
| } | |||
| func (err ErrRecordNotExist) Error() string { | |||
| return fmt.Sprintf("record not exist in database") | |||
| } | |||
| type ErrInsufficientPointsBalance struct { | |||
| } | |||
| func IsErrInsufficientPointsBalance(err error) bool { | |||
| _, ok := err.(ErrInsufficientPointsBalance) | |||
| return ok | |||
| } | |||
| func (err ErrInsufficientPointsBalance) Error() string { | |||
| return fmt.Sprintf("Insufficient points balance") | |||
| } | |||
| @@ -11,6 +11,13 @@ func keysInt64(m map[int64]struct{}) []int64 { | |||
| } | |||
| return keys | |||
| } | |||
| func keysString(m map[string]struct{}) []string { | |||
| var keys = make([]string, 0, len(m)) | |||
| for k := range m { | |||
| keys = append(keys, k) | |||
| } | |||
| return keys | |||
| } | |||
| func valuesRepository(m map[int64]*Repository) []*Repository { | |||
| var values = make([]*Repository, 0, len(m)) | |||
| @@ -27,3 +34,18 @@ func valuesUser(m map[int64]*User) []*User { | |||
| } | |||
| return values | |||
| } | |||
| func valuesComment(m map[int64]*Comment) []*Comment { | |||
| var values = make([]*Comment, 0, len(m)) | |||
| for _, v := range m { | |||
| values = append(values, v) | |||
| } | |||
| return values | |||
| } | |||
| func valuesCloudbrain(m map[int64]*Cloudbrain) []*Cloudbrain { | |||
| var values = make([]*Cloudbrain, 0, len(m)) | |||
| for _, v := range m { | |||
| values = append(values, v) | |||
| } | |||
| return values | |||
| } | |||
| @@ -0,0 +1,184 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "xorm.io/builder" | |||
| ) | |||
| type LimitType string | |||
| const ( | |||
| LimitTypeTask LimitType = "TASK" | |||
| LimitTypeRewardPoint LimitType = "REWARD_POINT" | |||
| ) | |||
| func (l LimitType) Name() string { | |||
| switch l { | |||
| case LimitTypeTask: | |||
| return "TASK" | |||
| case LimitTypeRewardPoint: | |||
| return "REWARD_POINT" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| type LimitScope string | |||
| const ( | |||
| LimitScopeAllUsers LimitScope = "ALL_USERS" | |||
| LimitScopeSingleUser LimitScope = "SINGLE_USER" | |||
| ) | |||
| func (l LimitScope) Name() string { | |||
| switch l { | |||
| case LimitScopeAllUsers: | |||
| return "ALL_USERS" | |||
| case LimitScopeSingleUser: | |||
| return "SINGLE_USER" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| type LimiterRejectPolicy string | |||
| const ( | |||
| JustReject LimiterRejectPolicy = "JUST_REJECT" | |||
| PermittedOnce LimiterRejectPolicy = "PERMITTED_ONCE" | |||
| FillUp LimiterRejectPolicy = "FillUp" | |||
| ) | |||
| type LimitConfig struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| Title string | |||
| RefreshRate string `xorm:"NOT NULL"` | |||
| Scope string `xorm:"NOT NULL"` | |||
| LimitNum int64 `xorm:"NOT NULL"` | |||
| LimitCode string | |||
| LimitType string `xorm:"NOT NULL"` | |||
| RelatedId int64 `xorm:"INDEX"` | |||
| CreatorId int64 `xorm:"NOT NULL"` | |||
| CreatorName string | |||
| DeleterId int64 | |||
| DeleterName string | |||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||
| DeletedAt timeutil.TimeStamp `xorm:"deleted"` | |||
| } | |||
| type LimitConfigQueryOpts struct { | |||
| RefreshRate string | |||
| Scope LimitScope | |||
| LimitCode string | |||
| LimitType LimitType | |||
| } | |||
| type LimitConfigVO struct { | |||
| ID int64 | |||
| Title string | |||
| RefreshRate string | |||
| Scope string | |||
| LimitNum int64 | |||
| LimitCode string | |||
| LimitType string | |||
| Creator string | |||
| CreatedUnix timeutil.TimeStamp | |||
| } | |||
| func (l *LimitConfig) ToLimitConfigVO() *LimitConfigVO { | |||
| return &LimitConfigVO{ | |||
| ID: l.ID, | |||
| Title: l.Title, | |||
| RefreshRate: l.RefreshRate, | |||
| Scope: l.Scope, | |||
| LimitNum: l.LimitNum, | |||
| LimitCode: l.LimitCode, | |||
| LimitType: l.LimitType, | |||
| Creator: l.CreatorName, | |||
| CreatedUnix: l.CreatedUnix, | |||
| } | |||
| } | |||
| func GetLimitConfigByLimitType(limitType LimitType) ([]LimitConfig, error) { | |||
| r := make([]LimitConfig, 0) | |||
| err := x.Where(" limit_type = ?", limitType.Name()).Find(&r) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if len(r) == 0 { | |||
| return nil, ErrRecordNotExist{} | |||
| } | |||
| return r, nil | |||
| } | |||
| func GetLimitersByRelatedIdWithDeleted(limitType LimitType) ([]LimitConfig, error) { | |||
| r := make([]LimitConfig, 0) | |||
| err := x.Unscoped().Where(" = ?", limitType.Name()).Find(&r) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if len(r) == 0 { | |||
| return nil, ErrRecordNotExist{} | |||
| } | |||
| return r, nil | |||
| } | |||
| func AddLimitConfig(l *LimitConfig) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| //delete old limit config | |||
| cond := builder.NewCond() | |||
| cond = cond.And(builder.Eq{"limit_type": l.LimitType}) | |||
| cond = cond.And(builder.Eq{"scope": l.Scope}) | |||
| if l.LimitCode == "" { | |||
| subCond := builder.NewCond() | |||
| subCond = subCond.Or(builder.IsNull{"limit_code"}) | |||
| subCond = subCond.Or(builder.Eq{"limit_code": ""}) | |||
| cond = cond.And(subCond) | |||
| } else { | |||
| cond = cond.And(builder.Eq{"limit_code": l.LimitCode}) | |||
| } | |||
| _, err := sess.Where(cond).Delete(&LimitConfig{}) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| //add new config | |||
| _, err = sess.Insert(l) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| func DeleteLimitConfig(config LimitConfig, deleterId int64, deleterName string) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| _, err := x.ID(config.ID).Update(&LimitConfig{DeleterName: deleterName, DeleterId: deleterId}) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| _, err = x.ID(config.ID).Delete(&LimitConfig{}) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| func GetLimitConfigById(id int64) (*LimitConfig, error) { | |||
| r := &LimitConfig{} | |||
| isOk, err := x.ID(id).Get(r) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !isOk { | |||
| return nil, nil | |||
| } | |||
| return r, nil | |||
| } | |||
| @@ -144,6 +144,14 @@ func init() { | |||
| new(WechatBindLog), | |||
| new(OrgStatistic), | |||
| new(SearchRecord), | |||
| new(TaskConfig), | |||
| new(TaskAccomplishLog), | |||
| new(RewardOperateRecord), | |||
| new(LimitConfig), | |||
| new(RewardPeriodicTask), | |||
| new(PointAccountLog), | |||
| new(PointAccount), | |||
| new(RewardAdminLog), | |||
| new(AiModelConvert), | |||
| new(ResourceQueue), | |||
| new(ResourceSpecification), | |||
| @@ -0,0 +1,142 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| ) | |||
| type PointAccountStatus int | |||
| // Possible PointAccountStatus types. | |||
| const ( | |||
| PointAccountNormal int = iota + 1 // 1 | |||
| PointAccountFreeze // 2 | |||
| PointAccountDeleted // 3 | |||
| ) | |||
| type PointAccount struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| AccountCode string `xorm:"INDEX NOT NULL"` | |||
| Balance int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| TotalEarned int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| TotalConsumed int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| UserId int64 `xorm:"INDEX NOT NULL"` | |||
| Status int `xorm:"NOT NULL"` | |||
| Version int64 `xorm:"NOT NULL"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| } | |||
| func (account *PointAccount) Increase(amount int64, sourceId string) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| sql := "update point_account set balance = balance + ?,total_earned = total_earned + ? ,version = version + 1 where account_code = ? " | |||
| _, err := sess.Exec(sql, amount, amount, account.AccountCode) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| accountLog := &PointAccountLog{ | |||
| AccountCode: account.AccountCode, | |||
| UserId: account.UserId, | |||
| Type: IncreaseAccountBalance, | |||
| SourceId: sourceId, | |||
| PointsAmount: amount, | |||
| BalanceBefore: account.Balance, | |||
| BalanceAfter: account.Balance + amount, | |||
| AccountVersion: account.Version, | |||
| } | |||
| _, err = sess.Insert(accountLog) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| func (account *PointAccount) Decrease(amount int64, sourceId string) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| sql := "update point_account set balance = balance - ?,total_consumed = total_consumed + ? ,version = version + 1 where account_code = ? " | |||
| _, err := sess.Exec(sql, amount, amount, account.AccountCode) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| accountLog := &PointAccountLog{ | |||
| AccountCode: account.AccountCode, | |||
| UserId: account.UserId, | |||
| Type: DecreaseAccountBalance, | |||
| SourceId: sourceId, | |||
| PointsAmount: amount, | |||
| BalanceBefore: account.Balance, | |||
| BalanceAfter: account.Balance - amount, | |||
| AccountVersion: account.Version, | |||
| } | |||
| _, err = sess.Insert(accountLog) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| func GetAccountByUserId(userId int64) (*PointAccount, error) { | |||
| p := &PointAccount{} | |||
| has, err := x.Where("user_id = ?", userId).Get(p) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if !has { | |||
| return nil, ErrRecordNotExist{} | |||
| } | |||
| return p, nil | |||
| } | |||
| func InsertAccount(tl *PointAccount) (int64, error) { | |||
| return x.Insert(tl) | |||
| } | |||
| type SearchPointAccountOpts struct { | |||
| ListOptions | |||
| Keyword string | |||
| } | |||
| type SearchPointAccountResponse struct { | |||
| Records []*UserPointAccount | |||
| PageSize int | |||
| Page int | |||
| Total int64 | |||
| } | |||
| type UserPointAccount struct { | |||
| UserId int64 | |||
| UserName string | |||
| Email string | |||
| Balance int64 | |||
| TotalEarned int64 | |||
| TotalConsumed int64 | |||
| } | |||
| func (UserPointAccount) TableName() string { | |||
| return "user" | |||
| } | |||
| func GetPointAccountMapByUserIds(userIds []int64) (map[int64]*PointAccount, error) { | |||
| if len(userIds) == 0 { | |||
| return make(map[int64]*PointAccount, 0), nil | |||
| } | |||
| accounts := make([]*PointAccount, 0) | |||
| err := x.In("user_id", userIds).Find(&accounts) | |||
| if err != nil { | |||
| log.Error("GetPointAccountMapByUserIds error.%v", err) | |||
| return nil, err | |||
| } | |||
| accountMap := make(map[int64]*PointAccount, 0) | |||
| for _, v := range accounts { | |||
| accountMap[v.UserId] = v | |||
| } | |||
| return accountMap, nil | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| package models | |||
| import "code.gitea.io/gitea/modules/timeutil" | |||
| const ( | |||
| IncreaseAccountBalance = "increase" | |||
| DecreaseAccountBalance = "decrease" | |||
| ) | |||
| type PointAccountLog struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| AccountCode string `xorm:"INDEX NOT NULL"` | |||
| UserId int64 `xorm:"INDEX NOT NULL"` | |||
| Type string `xorm:"NOT NULL"` | |||
| SourceId string `xorm:"INDEX NOT NULL"` | |||
| PointsAmount int64 `xorm:"NOT NULL"` | |||
| BalanceBefore int64 `xorm:"NOT NULL"` | |||
| BalanceAfter int64 `xorm:"NOT NULL"` | |||
| AccountVersion int64 `xorm:"NOT NULL"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| } | |||
| @@ -237,6 +237,12 @@ type Repository struct { | |||
| LowerAlias string `xorm:"INDEX"` | |||
| } | |||
| type RepositoryShow struct { | |||
| Name string | |||
| RepoType RepoType | |||
| Alias string | |||
| } | |||
| // SanitizedOriginalURL returns a sanitized OriginalURL | |||
| func (repo *Repository) SanitizedOriginalURL() string { | |||
| if repo.OriginalURL == "" { | |||
| @@ -25,6 +25,7 @@ const ( | |||
| ) | |||
| var ActionChan = make(chan *Action, 200) | |||
| var ActionChan4Task = make(chan Action, 200) | |||
| // Watch is connection request for receiving repository notification. | |||
| type Watch struct { | |||
| @@ -199,6 +200,14 @@ func notifyWatchers(e Engine, actions ...*Action) error { | |||
| if _, err = e.InsertOne(act); err != nil { | |||
| return fmt.Errorf("insert new actioner: %v", err) | |||
| } | |||
| // After InsertOne(act),the act has ID | |||
| // Send the act to task chan | |||
| ActionChan4Task <- *act | |||
| // If it has nothing to do with repo, return directly | |||
| if act.Repo == nil && act.RepoID == 0 { | |||
| return nil | |||
| } | |||
| if repoChanged { | |||
| act.loadRepo() | |||
| @@ -279,7 +288,6 @@ func notifyWatchers(e Engine, actions ...*Action) error { | |||
| // NotifyWatchers creates batch of actions for every watcher. | |||
| func NotifyWatchers(actions ...*Action) error { | |||
| error := notifyWatchers(x, actions...) | |||
| producer(actions...) | |||
| return error | |||
| @@ -287,7 +295,7 @@ func NotifyWatchers(actions ...*Action) error { | |||
| func producer(actions ...*Action) { | |||
| for _, action := range actions { | |||
| if !action.IsPrivate{ | |||
| if !action.IsPrivate { | |||
| ActionChan <- action | |||
| } | |||
| } | |||
| @@ -0,0 +1,79 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| ) | |||
| const ( | |||
| RewardAdminLogProcessing = 1 | |||
| RewardAdminLogSuccess = 2 | |||
| RewardAdminLogFailed = 3 | |||
| ) | |||
| type RewardAdminLog struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| LogId string `xorm:"INDEX NOT NULL"` | |||
| Amount int64 `xorm:"NOT NULL"` | |||
| RewardType string | |||
| Remark string | |||
| Status int | |||
| TargetUserId int64 `xorm:"INDEX NOT NULL"` | |||
| CreatorId int64 `xorm:"NOT NULL"` | |||
| CreatorName string | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| } | |||
| func (r *RewardAdminLog) ToShow() *RewardAdminLogShow { | |||
| return &RewardAdminLogShow{ | |||
| CreatorName: r.CreatorName, | |||
| } | |||
| } | |||
| type RewardAdminLogShow struct { | |||
| CreatorName string | |||
| } | |||
| type AdminLogAndUser struct { | |||
| AdminRewardAdminLog RewardAdminLog `xorm:"extends"` | |||
| User User `xorm:"extends"` | |||
| } | |||
| func getRewardAdminLog(ra *RewardAdminLog) (*RewardAdminLog, error) { | |||
| has, err := x.Get(ra) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, ErrRecordNotExist{} | |||
| } | |||
| return ra, nil | |||
| } | |||
| func InsertRewardAdminLog(ra *RewardAdminLog) (int64, error) { | |||
| return x.Insert(ra) | |||
| } | |||
| func UpdateRewardAdminLogStatus(logId string, oldStatus, newStatus int) error { | |||
| _, err := x.Where("log_id = ? and status = ?", logId, oldStatus).Update(&RewardAdminLog{Status: newStatus}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| func GetRewardAdminLogByLogIds(logIds []string) ([]*RewardAdminLog, error) { | |||
| if len(logIds) == 0 { | |||
| return nil, nil | |||
| } | |||
| adminLogs := make([]*AdminLogAndUser, 0) | |||
| err := x.Table("reward_admin_log").Join("LEFT", "user", "reward_admin_log.creator_id = public.user.id").In("reward_admin_log.log_id", logIds).Find(&adminLogs) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| r := make([]*RewardAdminLog, len(adminLogs)) | |||
| for i, v := range adminLogs { | |||
| temp := &v.AdminRewardAdminLog | |||
| temp.CreatorName = v.User.Name | |||
| r[i] = temp | |||
| } | |||
| return r, nil | |||
| } | |||
| @@ -0,0 +1,459 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "fmt" | |||
| "strconv" | |||
| "strings" | |||
| "xorm.io/builder" | |||
| ) | |||
| type SourceType string | |||
| const ( | |||
| SourceTypeAccomplishTask SourceType = "ACCOMPLISH_TASK" | |||
| SourceTypeAdminOperate SourceType = "ADMIN_OPERATE" | |||
| SourceTypeRunCloudbrainTask SourceType = "RUN_CLOUDBRAIN_TASK" | |||
| ) | |||
| func (r SourceType) Name() string { | |||
| switch r { | |||
| case SourceTypeAccomplishTask: | |||
| return "ACCOMPLISH_TASK" | |||
| case SourceTypeAdminOperate: | |||
| return "ADMIN_OPERATE" | |||
| case SourceTypeRunCloudbrainTask: | |||
| return "RUN_CLOUDBRAIN_TASK" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| type RewardType string | |||
| const ( | |||
| RewardTypePoint RewardType = "POINT" | |||
| ) | |||
| func (r RewardType) Name() string { | |||
| switch r { | |||
| case RewardTypePoint: | |||
| return "POINT" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| func (r RewardType) Show() string { | |||
| switch r { | |||
| case RewardTypePoint: | |||
| return "积分" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| func GetRewardTypeInstance(s string) RewardType { | |||
| switch s { | |||
| case RewardTypePoint.Name(): | |||
| return RewardTypePoint | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| type RewardOperateType string | |||
| func (r RewardOperateType) Name() string { | |||
| switch r { | |||
| case OperateTypeIncrease: | |||
| return "INCREASE" | |||
| case OperateTypeDecrease: | |||
| return "DECREASE" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| func (r RewardOperateType) Show() string { | |||
| switch r { | |||
| case OperateTypeIncrease: | |||
| return "奖励" | |||
| case OperateTypeDecrease: | |||
| return "扣减" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| func GetRewardOperateTypeInstance(s string) RewardOperateType { | |||
| switch s { | |||
| case OperateTypeIncrease.Name(): | |||
| return OperateTypeIncrease | |||
| case OperateTypeDecrease.Name(): | |||
| return OperateTypeDecrease | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| const ( | |||
| OperateTypeIncrease RewardOperateType = "INCREASE" | |||
| OperateTypeDecrease RewardOperateType = "DECREASE" | |||
| OperateTypeNull RewardOperateType = "NIL" | |||
| ) | |||
| const ( | |||
| OperateStatusOperating = "OPERATING" | |||
| OperateStatusSucceeded = "SUCCEEDED" | |||
| OperateStatusFailed = "FAILED" | |||
| ) | |||
| const Semicolon = ";" | |||
| type RewardOperateOrderBy string | |||
| const ( | |||
| RewardOrderByIDDesc RewardOperateOrderBy = "reward_operate_record.id desc" | |||
| ) | |||
| type RewardRecordList []*RewardOperateRecord | |||
| type RewardRecordShowList []*RewardOperateRecordShow | |||
| func (l RewardRecordShowList) loadAttribute(isAdmin bool) { | |||
| l.loadAction() | |||
| l.loadCloudbrain() | |||
| if isAdmin { | |||
| l.loadAdminLog() | |||
| } | |||
| } | |||
| func (l RewardRecordShowList) loadAction() error { | |||
| if len(l) == 0 { | |||
| return nil | |||
| } | |||
| actionIds := make([]int64, 0) | |||
| actionIdMap := make(map[int64]*RewardOperateRecordShow, 0) | |||
| for _, r := range l { | |||
| if r.SourceType != SourceTypeAccomplishTask.Name() { | |||
| continue | |||
| } | |||
| i, _ := strconv.ParseInt(r.SourceId, 10, 64) | |||
| actionIds = append(actionIds, i) | |||
| actionIdMap[i] = r | |||
| } | |||
| actions, err := GetActionByIds(actionIds) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| for _, v := range actions { | |||
| actionIdMap[v.ID].Action = v.ToShow() | |||
| } | |||
| return nil | |||
| } | |||
| func (l RewardRecordShowList) loadCloudbrain() error { | |||
| if len(l) == 0 { | |||
| return nil | |||
| } | |||
| cloudbrainIds := make([]int64, 0) | |||
| cloudbrainMap := make(map[int64]*RewardOperateRecordShow, 0) | |||
| for _, r := range l { | |||
| if r.SourceType != SourceTypeRunCloudbrainTask.Name() { | |||
| continue | |||
| } | |||
| i, _ := strconv.ParseInt(r.SourceId, 10, 64) | |||
| cloudbrainIds = append(cloudbrainIds, i) | |||
| cloudbrainMap[i] = r | |||
| } | |||
| cloudbrains, err := GetCloudbrainByIds(cloudbrainIds) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| var repoIds []int64 | |||
| var taskIds []int64 | |||
| for _, task := range cloudbrains { | |||
| repoIds = append(repoIds, task.RepoID) | |||
| taskIds = append(taskIds, task.ID) | |||
| } | |||
| repositoryMap, err := GetRepositoriesMapByIDs(repoIds) | |||
| specMap, err := GetResourceSpecMapByCloudbrainIDs(taskIds) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| for _, v := range cloudbrains { | |||
| v.Repo = repositoryMap[v.RepoID] | |||
| v.Spec = specMap[v.ID] | |||
| cloudbrainMap[v.ID].Cloudbrain = v.ToShow() | |||
| } | |||
| return nil | |||
| } | |||
| func (l RewardRecordShowList) loadAdminLog() error { | |||
| if len(l) == 0 { | |||
| return nil | |||
| } | |||
| logIds := make([]string, 0) | |||
| logMap := make(map[string]*RewardOperateRecordShow, 0) | |||
| for _, r := range l { | |||
| if r.SourceType != SourceTypeAdminOperate.Name() { | |||
| continue | |||
| } | |||
| logIds = append(logIds, r.SourceId) | |||
| logMap[r.SourceId] = r | |||
| } | |||
| adminLogs, err := GetRewardAdminLogByLogIds(logIds) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| for _, v := range adminLogs { | |||
| logMap[v.LogId].AdminLog = v.ToShow() | |||
| } | |||
| return nil | |||
| } | |||
| type RewardOperateRecord struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| SerialNo string `xorm:"INDEX NOT NULL"` | |||
| UserId int64 `xorm:"INDEX NOT NULL"` | |||
| Amount int64 `xorm:"NOT NULL"` | |||
| LossAmount int64 | |||
| Title string | |||
| RewardType string `xorm:"NOT NULL"` | |||
| SourceType string `xorm:"NOT NULL"` | |||
| SourceId string `xorm:"INDEX NOT NULL"` | |||
| SourceTemplateId string | |||
| RequestId string `xorm:"INDEX NOT NULL"` | |||
| OperateType string `xorm:"NOT NULL"` | |||
| Status string `xorm:"NOT NULL"` | |||
| Remark string | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| LastOperateUnix timeutil.TimeStamp `xorm:"INDEX"` | |||
| } | |||
| type AdminRewardOperateReq struct { | |||
| TargetUserId int64 `binding:"Required"` | |||
| OperateType RewardOperateType `binding:"Required"` | |||
| Amount int64 `binding:"Required;Range(1,100000)"` | |||
| Remark string | |||
| RewardType RewardType | |||
| } | |||
| type RewardOperateRecordShow struct { | |||
| SerialNo string | |||
| Status string | |||
| OperateType string | |||
| SourceId string | |||
| Amount int64 | |||
| LossAmount int64 | |||
| BalanceAfter int64 | |||
| Remark string | |||
| SourceType string | |||
| UserName string | |||
| LastOperateDate timeutil.TimeStamp | |||
| UnitPrice int64 | |||
| SuccessCount int | |||
| Action *ActionShow | |||
| Cloudbrain *CloudbrainShow | |||
| AdminLog *RewardAdminLogShow | |||
| } | |||
| func getPointOperateRecord(tl *RewardOperateRecord) (*RewardOperateRecord, error) { | |||
| has, err := x.Get(tl) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, ErrRecordNotExist{} | |||
| } | |||
| return tl, nil | |||
| } | |||
| func GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId, operateType string) (*RewardOperateRecord, error) { | |||
| t := &RewardOperateRecord{ | |||
| SourceType: sourceType, | |||
| RequestId: requestId, | |||
| OperateType: operateType, | |||
| } | |||
| return getPointOperateRecord(t) | |||
| } | |||
| func GetPointOperateRecordBySerialNo(serialNo string) (*RewardOperateRecord, error) { | |||
| t := &RewardOperateRecord{ | |||
| SerialNo: serialNo, | |||
| } | |||
| return getPointOperateRecord(t) | |||
| } | |||
| func InsertRewardOperateRecord(tl *RewardOperateRecord) (int64, error) { | |||
| return x.Insert(tl) | |||
| } | |||
| func UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus string) (int64, error) { | |||
| r := &RewardOperateRecord{ | |||
| Status: newStatus, | |||
| LastOperateUnix: timeutil.TimeStampNow(), | |||
| } | |||
| return x.Cols("status", "last_operate_unix").Where("source_type=? and request_id=? and status=?", sourceType, requestId, OperateStatusOperating).Update(r) | |||
| } | |||
| func SumRewardAmountInTaskPeriod(rewardType string, sourceType string, userId int64, period *PeriodResult) (int64, error) { | |||
| var cond = builder.NewCond() | |||
| if period != nil { | |||
| cond = cond.And(builder.Gte{"created_unix": period.StartTime.Unix()}) | |||
| cond = cond.And(builder.Lt{"created_unix": period.EndTime.Unix()}) | |||
| } | |||
| if sourceType != "" { | |||
| cond = cond.And(builder.Eq{"source_type": sourceType}) | |||
| } | |||
| cond = cond.And(builder.Eq{"reward_type": rewardType}) | |||
| cond = cond.And(builder.Eq{"user_id": userId}) | |||
| return x.Where(cond).SumInt(&RewardOperateRecord{}, "amount") | |||
| } | |||
| type RewardOperateContext struct { | |||
| SourceType SourceType | |||
| SourceId string | |||
| SourceTemplateId string | |||
| Title string | |||
| Remark string | |||
| Reward Reward | |||
| TargetUserId int64 | |||
| RequestId string | |||
| OperateType RewardOperateType | |||
| RejectPolicy LimiterRejectPolicy | |||
| PermittedNegative bool | |||
| LossAmount int64 | |||
| } | |||
| type Reward struct { | |||
| Amount int64 | |||
| Type RewardType | |||
| } | |||
| type UserRewardOperationRedis struct { | |||
| UserId int64 | |||
| Amount int64 | |||
| RewardType RewardType | |||
| OperateType RewardOperateType | |||
| } | |||
| type UserRewardOperation struct { | |||
| UserId int64 | |||
| Msg string | |||
| } | |||
| func AppendRemark(remark, appendStr string) string { | |||
| return strings.TrimPrefix(remark+Semicolon+appendStr, Semicolon) | |||
| } | |||
| type RewardRecordListOpts struct { | |||
| ListOptions | |||
| UserId int64 | |||
| UserName string | |||
| OperateType RewardOperateType | |||
| RewardType RewardType | |||
| SourceType string | |||
| ActionType int | |||
| SerialNo string | |||
| OrderBy RewardOperateOrderBy | |||
| IsAdmin bool | |||
| Status string | |||
| } | |||
| func (opts *RewardRecordListOpts) toCond() builder.Cond { | |||
| if opts.Page <= 0 { | |||
| opts.Page = 1 | |||
| } | |||
| if len(opts.OrderBy) == 0 { | |||
| opts.OrderBy = RewardOrderByIDDesc | |||
| } | |||
| cond := builder.NewCond() | |||
| if opts.UserId > 0 { | |||
| cond = cond.And(builder.Eq{"reward_operate_record.user_id": opts.UserId}) | |||
| } | |||
| if opts.OperateType != OperateTypeNull { | |||
| cond = cond.And(builder.Eq{"reward_operate_record.operate_type": opts.OperateType.Name()}) | |||
| } | |||
| if opts.SourceType != "" { | |||
| cond = cond.And(builder.Eq{"reward_operate_record.source_type": opts.SourceType}) | |||
| } | |||
| if opts.ActionType > 0 { | |||
| cond = cond.And(builder.Eq{"reward_operate_record.source_template_id": fmt.Sprint(opts.ActionType)}) | |||
| } | |||
| if opts.SerialNo != "" { | |||
| cond = cond.And(builder.Like{"reward_operate_record.serial_no", opts.SerialNo}) | |||
| } | |||
| if opts.Status != "" { | |||
| cond = cond.And(builder.Like{"reward_operate_record.status", opts.Status}) | |||
| } | |||
| cond = cond.And(builder.Eq{"reward_operate_record.reward_type": opts.RewardType.Name()}) | |||
| cond = cond.And(builder.Gt{"reward_operate_record.amount": 0}) | |||
| return cond | |||
| } | |||
| type TestTT struct { | |||
| SerialNo string | |||
| UserId int64 | |||
| Amount int64 | |||
| UserName string | |||
| } | |||
| func GetRewardRecordShowList(opts *RewardRecordListOpts) (RewardRecordShowList, int64, error) { | |||
| cond := opts.toCond() | |||
| count, err := x.Where(cond).Count(&RewardOperateRecord{}) | |||
| if err != nil { | |||
| return nil, 0, err | |||
| } | |||
| r := make([]*RewardOperateRecordShow, 0) | |||
| err = x.Table("reward_operate_record").Cols("reward_operate_record.source_id", "reward_operate_record.serial_no", | |||
| "reward_operate_record.status", "reward_operate_record.operate_type", "reward_operate_record.amount", | |||
| "reward_operate_record.loss_amount", "reward_operate_record.remark", "reward_operate_record.source_type", | |||
| "reward_operate_record.last_operate_unix as last_operate_date"). | |||
| Where(cond).Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).OrderBy(string(opts.OrderBy)).Find(&r) | |||
| if err != nil { | |||
| return nil, 0, err | |||
| } | |||
| RewardRecordShowList(r).loadAttribute(false) | |||
| return r, count, nil | |||
| } | |||
| func GetAdminRewardRecordShowList(opts *RewardRecordListOpts) (RewardRecordShowList, int64, error) { | |||
| cond := opts.toCond() | |||
| count, err := x.Where(cond).Count(&RewardOperateRecord{}) | |||
| if err != nil { | |||
| return nil, 0, err | |||
| } | |||
| r := make([]*RewardOperateRecordShow, 0) | |||
| switch opts.OperateType { | |||
| case OperateTypeIncrease: | |||
| err = x.Table("reward_operate_record").Cols("reward_operate_record.source_id", "reward_operate_record.serial_no", | |||
| "reward_operate_record.status", "reward_operate_record.operate_type", "reward_operate_record.amount", | |||
| "reward_operate_record.loss_amount", "reward_operate_record.remark", "reward_operate_record.source_type", | |||
| "reward_operate_record.last_operate_unix as last_operate_date", "public.user.name as user_name", | |||
| "point_account_log.balance_after"). | |||
| Join("LEFT", "public.user", "reward_operate_record.user_id = public.user.id"). | |||
| Join("LEFT", "point_account_log", " reward_operate_record.serial_no = point_account_log.source_id"). | |||
| Where(cond).Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).OrderBy(string(opts.OrderBy)).Find(&r) | |||
| case OperateTypeDecrease: | |||
| err = x.Table("reward_operate_record").Cols("reward_operate_record.source_id", "reward_operate_record.serial_no", | |||
| "reward_operate_record.status", "reward_operate_record.operate_type", "reward_operate_record.amount", | |||
| "reward_operate_record.loss_amount", "reward_operate_record.remark", "reward_operate_record.source_type", | |||
| "reward_operate_record.last_operate_unix as last_operate_date", "public.user.name as user_name", | |||
| "reward_periodic_task.amount as unit_price", "reward_periodic_task.success_count"). | |||
| Join("LEFT", "public.user", "reward_operate_record.user_id = public.user.id"). | |||
| Join("LEFT", "reward_periodic_task", "reward_operate_record.serial_no = reward_periodic_task.operate_serial_no"). | |||
| Where(cond).Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).OrderBy(string(opts.OrderBy)).Find(&r) | |||
| } | |||
| if err != nil { | |||
| return nil, 0, err | |||
| } | |||
| RewardRecordShowList(r).loadAttribute(true) | |||
| return r, count, nil | |||
| } | |||
| @@ -0,0 +1,115 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "time" | |||
| ) | |||
| type PeriodicTaskStatus int | |||
| const ( | |||
| PeriodicTaskStatusRunning = iota + 1 // 1 | |||
| PeriodicTaskStatusFinished // 2 | |||
| ) | |||
| type PeriodType string | |||
| const ( | |||
| PeriodType30MinutesFree1HourCost PeriodType = "30MF1HC" | |||
| ) | |||
| func (r PeriodType) Name() string { | |||
| switch r { | |||
| case PeriodType30MinutesFree1HourCost: | |||
| return "30MF1HC" | |||
| default: | |||
| return "" | |||
| } | |||
| } | |||
| type RewardPeriodicTask struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| OperateSerialNo string `xorm:"INDEX NOT NULL"` | |||
| DelaySeconds int64 | |||
| IntervalSeconds int64 | |||
| Amount int64 `xorm:"NOT NULL"` | |||
| NextExecuteTime timeutil.TimeStamp `xorm:"INDEX NOT NULL"` | |||
| SuccessCount int `xorm:"NOT NULL default 0"` | |||
| Status int `xorm:"NOT NULL"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| FinishedUnix timeutil.TimeStamp `xorm:"INDEX"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| } | |||
| type StartPeriodicTaskOpts struct { | |||
| SourceType SourceType | |||
| SourceId string | |||
| Remark string | |||
| Title string | |||
| TargetUserId int64 | |||
| RequestId string | |||
| OperateType RewardOperateType | |||
| Delay time.Duration | |||
| Interval time.Duration | |||
| UnitAmount int | |||
| RewardType RewardType | |||
| StartTime time.Time | |||
| } | |||
| func InsertPeriodicTask(tl *RewardPeriodicTask) (int64, error) { | |||
| return x.Insert(tl) | |||
| } | |||
| func GetRunningRewardTask(now time.Time) ([]RewardPeriodicTask, error) { | |||
| r := make([]RewardPeriodicTask, 0) | |||
| err := x.Where("next_execute_time <= ? and status = ?", now.Unix(), PeriodicTaskStatusRunning).Find(&r) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return r, err | |||
| } | |||
| func IncrRewardTaskSuccessCount(t RewardPeriodicTask, count int64, nextTime timeutil.TimeStamp) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| _, err := sess.Exec("update reward_periodic_task set success_count = success_count + ? , next_execute_time = ?, updated_unix = ? where id = ?", count, nextTime, timeutil.TimeStampNow(), t.ID) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| _, err = sess.Exec("update reward_operate_record set amount = amount + ? ,updated_unix = ? ,last_operate_unix = ? where serial_no = ?", t.Amount, timeutil.TimeStampNow(), timeutil.TimeStampNow(), t.OperateSerialNo) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| func GetPeriodicTaskBySourceIdAndType(sourceType SourceType, sourceId string, operateType RewardOperateType) (*RewardPeriodicTask, error) { | |||
| r := RewardPeriodicTask{} | |||
| _, err := x.SQL("select rpt.* from reward_periodic_task rpt "+ | |||
| "inner join reward_operate_record ror on rpt.operate_serial_no = ror.serial_no"+ | |||
| " where ror.source_type = ? and ror.source_id = ? and ror.operate_type = ? ", sourceType.Name(), sourceId, operateType.Name()).Get(&r) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return &r, nil | |||
| } | |||
| func StopPeriodicTask(taskId int64, operateSerialNo string, stopTime time.Time) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| _, err := sess.Where("id = ? and status = ?", taskId, PeriodicTaskStatusRunning).Update(&RewardPeriodicTask{Status: PeriodicTaskStatusFinished, FinishedUnix: timeutil.TimeStamp(stopTime.Unix())}) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| _, err = sess.Where("serial_no = ? and status = ?", operateSerialNo, OperateStatusOperating).Update(&RewardOperateRecord{Status: OperateStatusSucceeded}) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "time" | |||
| ) | |||
| type TaskAccomplishLog struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| ConfigId int64 `xorm:"NOT NULL"` | |||
| TaskCode string `xorm:"NOT NULL"` | |||
| UserId int64 `xorm:"INDEX NOT NULL"` | |||
| ActionId int64 | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| } | |||
| type PeriodResult struct { | |||
| StartTime time.Time | |||
| EndTime time.Time | |||
| LeftTime time.Duration | |||
| } | |||
| func getTaskAccomplishLog(tl *TaskAccomplishLog) (*TaskAccomplishLog, error) { | |||
| has, err := x.Get(tl) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, ErrRecordNotExist{} | |||
| } | |||
| return tl, nil | |||
| } | |||
| func CountTaskAccomplishLogInTaskPeriod(taskCode string, userId int64, period *PeriodResult) (int64, error) { | |||
| if period == nil { | |||
| return x.Where("task_code = ? and user_id = ?", taskCode, userId).Count(&TaskAccomplishLog{}) | |||
| } else { | |||
| return x.Where("task_code = ? and user_id = ? and created_unix >= ? and created_unix < ? ", taskCode, userId, period.StartTime.Unix(), period.EndTime.Unix()).Count(&TaskAccomplishLog{}) | |||
| } | |||
| } | |||
| func InsertTaskAccomplishLog(tl *TaskAccomplishLog) (int64, error) { | |||
| return x.Insert(tl) | |||
| } | |||
| @@ -0,0 +1,302 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "fmt" | |||
| "xorm.io/builder" | |||
| ) | |||
| const ( | |||
| PeriodNotCycle = "NOT_CYCLE" | |||
| PeriodDaily = "DAILY" | |||
| ) | |||
| //PointTaskConfig Only add and delete are allowed, edit is not allowed | |||
| //so if you want to edit config for some task code,please delete first and add new one | |||
| type TaskConfig struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| TaskCode string `xorm:"NOT NULL"` | |||
| Title string | |||
| AwardType string `xorm:"NOT NULL"` | |||
| AwardAmount int64 `xorm:"NOT NULL"` | |||
| CreatorId int64 `xorm:"NOT NULL"` | |||
| CreatorName string | |||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||
| DeletedAt timeutil.TimeStamp `xorm:"deleted"` | |||
| DeleterId int64 | |||
| DeleterName string | |||
| } | |||
| type TaskConfigWithLimit struct { | |||
| ID int64 | |||
| TaskCode string | |||
| Title string | |||
| AwardType string | |||
| AwardAmount int64 | |||
| Creator string | |||
| IsDeleted bool | |||
| CreatedUnix timeutil.TimeStamp | |||
| DeleteAt timeutil.TimeStamp | |||
| Limiters []*LimitConfigVO | |||
| } | |||
| type TaskConfigWithLimitResponse struct { | |||
| Records []*TaskConfigWithSingleLimit | |||
| Total int64 | |||
| PageSize int | |||
| Page int | |||
| } | |||
| type TaskConfigWithSingleLimit struct { | |||
| ID int64 | |||
| TaskCode string | |||
| AwardType string | |||
| AwardAmount int64 | |||
| Creator string | |||
| IsDeleted bool | |||
| CreatedUnix timeutil.TimeStamp | |||
| DeleteAt timeutil.TimeStamp | |||
| RefreshRate string | |||
| LimitNum int64 | |||
| } | |||
| type TaskAndLimiterConfig struct { | |||
| TaskConfig TaskConfig `xorm:"extends"` | |||
| LimitConfig LimitConfig `xorm:"extends"` | |||
| } | |||
| func (TaskAndLimiterConfig) TableName() string { | |||
| return "task_config" | |||
| } | |||
| type BatchLimitConfigVO struct { | |||
| ConfigList []TaskConfigWithLimit | |||
| } | |||
| func getTaskConfig(t *TaskConfig) (*TaskConfig, error) { | |||
| has, err := x.Get(t) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, ErrRecordNotExist{} | |||
| } | |||
| return t, nil | |||
| } | |||
| func GetTaskConfigByTaskCode(taskCode string) (*TaskConfig, error) { | |||
| t := &TaskConfig{ | |||
| TaskCode: taskCode, | |||
| } | |||
| return getTaskConfig(t) | |||
| } | |||
| func GetTaskConfigByID(id int64) (*TaskConfig, error) { | |||
| t := &TaskConfig{ | |||
| ID: id, | |||
| } | |||
| return getTaskConfig(t) | |||
| } | |||
| func GetTaskConfigList() ([]*TaskConfig, error) { | |||
| r := make([]*TaskConfig, 0) | |||
| err := x.Find(&r) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if len(r) == 0 { | |||
| return nil, ErrRecordNotExist{} | |||
| } | |||
| return r, nil | |||
| } | |||
| type GetTaskConfigOpts struct { | |||
| ListOptions | |||
| Status int //1 normal 2 deleted | |||
| ActionType int | |||
| } | |||
| func GetTaskConfigPageWithDeleted(opt GetTaskConfigOpts) ([]*TaskAndLimiterConfig, int64, error) { | |||
| if opt.Page <= 0 { | |||
| opt.Page = 1 | |||
| } | |||
| cond := builder.NewCond() | |||
| if opt.ActionType > 0 { | |||
| cond = cond.And(builder.Eq{"task_code": fmt.Sprint(opt.ActionType)}) | |||
| } | |||
| var count int64 | |||
| var err error | |||
| if opt.Status == 1 { | |||
| subCond := builder.NewCond() | |||
| subCond = subCond.Or(builder.IsNull{"task_config.deleted_at"}) | |||
| subCond = subCond.Or(builder.Eq{"task_config.deleted_at": 0}) | |||
| cond = cond.And(subCond) | |||
| } else if opt.Status == 2 { | |||
| cond = cond.And(builder.Gt{"task_config.deleted_at": 0}) | |||
| } | |||
| count, err = x.Unscoped().Where(cond).Count(&TaskConfig{}) | |||
| if err != nil { | |||
| return nil, 0, err | |||
| } | |||
| r := make([]*TaskAndLimiterConfig, 0) | |||
| err = x.Join("LEFT", "limit_config", "task_config.id = limit_config.related_id"). | |||
| Unscoped().Where(cond).Limit(opt.PageSize, (opt.Page-1)*opt.PageSize). | |||
| OrderBy("task_config.deleted_at desc,task_config.id desc").Find(&r) | |||
| if len(r) == 0 { | |||
| return nil, 0, ErrRecordNotExist{} | |||
| } | |||
| return r, count, nil | |||
| } | |||
| func EditTaskConfig(config TaskConfigWithLimit, doer *User) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| //delete old task config | |||
| p := &TaskConfig{ | |||
| ID: config.ID, | |||
| } | |||
| _, err := sess.Delete(p) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| //update deleter | |||
| p.DeleterId = doer.ID | |||
| p.DeleterName = doer.Name | |||
| sess.Where("id = ?", config.ID).Unscoped().Update(p) | |||
| //add new config | |||
| t := &TaskConfig{ | |||
| TaskCode: config.TaskCode, | |||
| Title: config.Title, | |||
| AwardType: config.AwardType, | |||
| AwardAmount: config.AwardAmount, | |||
| CreatorId: doer.ID, | |||
| CreatorName: doer.Name, | |||
| } | |||
| _, err = sess.InsertOne(t) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| //delete old limiter config | |||
| lp := &LimitConfig{ | |||
| RelatedId: config.ID, | |||
| } | |||
| _, err = sess.Delete(lp) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| lp.DeleterName = doer.Name | |||
| lp.DeleterId = doer.ID | |||
| //update deleter | |||
| sess.Where("related_id = ?", config.ID).Unscoped().Update(lp) | |||
| //add new limiter config | |||
| if config.Limiters != nil && len(config.Limiters) > 0 { | |||
| for _, v := range config.Limiters { | |||
| //add new config | |||
| l := &LimitConfig{ | |||
| Title: v.Title, | |||
| RefreshRate: v.RefreshRate, | |||
| Scope: v.Scope, | |||
| LimitNum: v.LimitNum, | |||
| LimitCode: config.TaskCode, | |||
| LimitType: LimitTypeTask.Name(), | |||
| CreatorId: doer.ID, | |||
| CreatorName: doer.Name, | |||
| RelatedId: t.ID, | |||
| } | |||
| _, err = sess.Insert(l) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| } | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| func NewTaskConfig(config TaskConfigWithLimit, doer *User) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| //add new config | |||
| t := &TaskConfig{ | |||
| TaskCode: config.TaskCode, | |||
| Title: config.Title, | |||
| AwardType: config.AwardType, | |||
| AwardAmount: config.AwardAmount, | |||
| CreatorId: doer.ID, | |||
| CreatorName: doer.Name, | |||
| } | |||
| _, err := sess.InsertOne(t) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| //add new limiter config | |||
| if config.Limiters != nil && len(config.Limiters) > 0 { | |||
| for _, v := range config.Limiters { | |||
| //add new config | |||
| l := &LimitConfig{ | |||
| RelatedId: t.ID, | |||
| Title: v.Title, | |||
| RefreshRate: v.RefreshRate, | |||
| Scope: v.Scope, | |||
| LimitNum: v.LimitNum, | |||
| LimitCode: config.TaskCode, | |||
| LimitType: LimitTypeTask.Name(), | |||
| CreatorId: doer.ID, | |||
| CreatorName: doer.Name, | |||
| } | |||
| _, err = sess.Insert(l) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| } | |||
| } | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| func DelTaskConfig(id int64, doer *User) error { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| //delete old task config | |||
| p := &TaskConfig{ | |||
| ID: id, | |||
| } | |||
| _, err := sess.Delete(p) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| //update deleter | |||
| p.DeleterId = doer.ID | |||
| p.DeleterName = doer.Name | |||
| sess.Where("id = ?", id).Unscoped().Update(p) | |||
| //delete old limiter config | |||
| lp := &LimitConfig{ | |||
| RelatedId: id, | |||
| } | |||
| _, err = sess.Delete(lp) | |||
| if err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| lp.DeleterName = doer.Name | |||
| lp.DeleterId = doer.ID | |||
| //update deleter | |||
| sess.Where("related_id = ?", id).Unscoped().Update(lp) | |||
| sess.Commit() | |||
| return nil | |||
| } | |||
| @@ -188,6 +188,10 @@ type User struct { | |||
| PhoneNumber string `xorm:"VARCHAR(255)"` | |||
| } | |||
| type UserShow struct { | |||
| Name string | |||
| } | |||
| // SearchOrganizationsOptions options to filter organizations | |||
| type SearchOrganizationsOptions struct { | |||
| ListOptions | |||
| @@ -96,3 +96,7 @@ func UnbindWechatOpenId(userId int64, oldWechatOpenID string) error { | |||
| sess.Insert(logParam) | |||
| return sess.Commit() | |||
| } | |||
| func CountWechatBindLog(wechatOpenId string, action WechatBindAction) (int64, error) { | |||
| return x.Where("wechat_open_id = ? and action = ?", action, wechatOpenId).Count(&WechatBindLog{}) | |||
| } | |||
| @@ -8,14 +8,12 @@ import ( | |||
| "code.gitea.io/gitea/modules/redis/redis_lock" | |||
| ) | |||
| const EMPTY_REDIS_VAL = "Nil" | |||
| var accessTokenLock = redis_lock.NewDistributeLock(redis_key.AccessTokenLockKey()) | |||
| func GetWechatAccessToken() string { | |||
| token, _ := redis_client.Get(redis_key.WechatAccessTokenKey()) | |||
| if token != "" { | |||
| if token == EMPTY_REDIS_VAL { | |||
| if token == redis_key.EMPTY_REDIS_VAL { | |||
| return "" | |||
| } | |||
| live, _ := redis_client.TTL(redis_key.WechatAccessTokenKey()) | |||
| @@ -29,18 +27,22 @@ func GetWechatAccessToken() string { | |||
| } | |||
| func refreshAccessToken() { | |||
| if ok := accessTokenLock.Lock(3 * time.Second); ok { | |||
| if ok, _ := accessTokenLock.Lock(3 * time.Second); ok { | |||
| defer accessTokenLock.UnLock() | |||
| callAccessTokenAndUpdateCache() | |||
| } | |||
| } | |||
| func refreshAndGetAccessToken() string { | |||
| if ok := accessTokenLock.LockWithWait(3*time.Second, 3*time.Second); ok { | |||
| isOk, err := accessTokenLock.LockWithWait(3*time.Second, 3*time.Second) | |||
| if err != nil { | |||
| return "" | |||
| } | |||
| if isOk { | |||
| defer accessTokenLock.UnLock() | |||
| token, _ := redis_client.Get(redis_key.WechatAccessTokenKey()) | |||
| if token != "" { | |||
| if token == EMPTY_REDIS_VAL { | |||
| if token == redis_key.EMPTY_REDIS_VAL { | |||
| return "" | |||
| } | |||
| return token | |||
| @@ -60,7 +62,7 @@ func callAccessTokenAndUpdateCache() string { | |||
| } | |||
| if token == "" { | |||
| redis_client.Setex(redis_key.WechatAccessTokenKey(), EMPTY_REDIS_VAL, 10*time.Second) | |||
| redis_client.Setex(redis_key.WechatAccessTokenKey(), redis_key.EMPTY_REDIS_VAL, 10*time.Second) | |||
| return "" | |||
| } | |||
| redis_client.Setex(redis_key.WechatAccessTokenKey(), token, time.Duration(r.Expires_in)*time.Second) | |||
| @@ -38,7 +38,7 @@ func (err WechatBindError) Error() string { | |||
| } | |||
| func BindWechat(userId int64, wechatOpenId string) error { | |||
| if !IsWechatAccountAvailable(userId, wechatOpenId) { | |||
| if !IsWechatAccountUsed(userId, wechatOpenId) { | |||
| log.Error("bind wechat failed, because user use wrong wechat account to bind,userId=%d wechatOpenId=%s", userId, wechatOpenId) | |||
| return NewWechatBindError(BIND_REPLY_WECHAT_ACCOUNT_USED) | |||
| } | |||
| @@ -60,9 +60,9 @@ func IsUserAvailableForWechatBind(userId int64, wechatOpenId string) bool { | |||
| return currentOpenId == "" || currentOpenId == wechatOpenId | |||
| } | |||
| //IsWechatAccountAvailable if wechat account used by another account,return false | |||
| //IsWechatAccountUsed if wechat account used by another account,return false | |||
| //if wechat account not used or used by the given user,return true | |||
| func IsWechatAccountAvailable(userId int64, wechatOpenId string) bool { | |||
| func IsWechatAccountUsed(userId int64, wechatOpenId string) bool { | |||
| user := models.GetUserByWechatOpenId(wechatOpenId) | |||
| if user != nil && user.WechatOpenId != "" && user.ID != userId { | |||
| return false | |||
| @@ -95,6 +95,7 @@ func getWechatRestyClient() *resty.Client { | |||
| func callAccessToken() *AccessTokenResponse { | |||
| client := getWechatRestyClient() | |||
| log.Info("start to get wechat access token") | |||
| var result AccessTokenResponse | |||
| _, err := client.R(). | |||
| SetQueryParam("grant_type", GRANT_TYPE). | |||
| @@ -106,6 +107,7 @@ func callAccessToken() *AccessTokenResponse { | |||
| log.Error("get wechat access token failed,e=%v", err) | |||
| return nil | |||
| } | |||
| log.Info("get wechat access token result=%v", result) | |||
| return &result | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| package cloudbrain | |||
| import ( | |||
| "code.gitea.io/gitea/modules/notification" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| @@ -25,10 +26,10 @@ var ( | |||
| const ( | |||
| JobHasBeenStopped = "S410" | |||
| errInvalidToken = "S401" | |||
| Public = "public" | |||
| Custom = "custom" | |||
| LogPageSize = 500 | |||
| errInvalidToken = "S401" | |||
| LogPageTokenExpired = "5m" | |||
| pageSize = 15 | |||
| QueuesDetailUrl = "/rest-server/api/v2/queuesdetail" | |||
| @@ -234,7 +235,7 @@ func getQueryString(page int, size int, name string) string { | |||
| return fmt.Sprintf("pageIndex=%d&pageSize=%d&name=%s", page, size, name) | |||
| } | |||
| func CommitImage(jobID string, params models.CommitImageParams) error { | |||
| func CommitImage(jobID string, params models.CommitImageParams, doer *models.User) error { | |||
| imageTag := strings.TrimSpace(params.ImageTag) | |||
| dbImage, err := models.GetImageByTag(imageTag) | |||
| @@ -339,11 +340,12 @@ sendjob: | |||
| }) | |||
| if err == nil { | |||
| go updateImageStatus(image, isSetCreatedUnix, createTime) | |||
| notification.NotifyCreateImage(doer, image) | |||
| } | |||
| return err | |||
| } | |||
| func CommitAdminImage(params models.CommitImageParams) error { | |||
| func CommitAdminImage(params models.CommitImageParams, doer *models.User) error { | |||
| imageTag := strings.TrimSpace(params.ImageTag) | |||
| exist, err := models.IsImageExist(imageTag) | |||
| @@ -380,6 +382,9 @@ func CommitAdminImage(params models.CommitImageParams) error { | |||
| } | |||
| return nil | |||
| }) | |||
| if err == nil { | |||
| notification.NotifyCreateImage(doer, image) | |||
| } | |||
| return err | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| package context | |||
| import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/services/reward/point/account" | |||
| "gitea.com/macaron/macaron" | |||
| ) | |||
| // PointAccount returns a macaron to get request user's point account | |||
| func PointAccount() macaron.Handler { | |||
| return func(ctx *Context) { | |||
| a, err := account.GetAccount(ctx.User.ID) | |||
| if err != nil { | |||
| ctx.ServerError("GetPointAccount", err) | |||
| return | |||
| } | |||
| ctx.Data["PointAccount"] = a | |||
| ctx.Data["CloudBrainPaySwitch"] = setting.CloudBrainPaySwitch | |||
| ctx.Next() | |||
| } | |||
| } | |||
| @@ -5,6 +5,7 @@ | |||
| package cron | |||
| import ( | |||
| "code.gitea.io/gitea/services/reward" | |||
| "code.gitea.io/gitea/services/cloudbrain/resource" | |||
| "code.gitea.io/gitea/modules/modelarts" | |||
| "context" | |||
| @@ -209,6 +210,28 @@ func registerSyncCloudbrainStatus() { | |||
| }) | |||
| } | |||
| func registerRewardPeriodTask() { | |||
| RegisterTaskFatal("reward_period_task", &BaseConfig{ | |||
| Enabled: true, | |||
| RunAtStart: true, | |||
| Schedule: "@every 1m", | |||
| }, func(ctx context.Context, _ *models.User, _ Config) error { | |||
| reward.StartRewardTask() | |||
| return nil | |||
| }) | |||
| } | |||
| func registerCloudbrainPointDeductTask() { | |||
| RegisterTaskFatal("cloudbrain_point_deduct_task", &BaseConfig{ | |||
| Enabled: true, | |||
| RunAtStart: true, | |||
| Schedule: "@every 1m", | |||
| }, func(ctx context.Context, _ *models.User, _ Config) error { | |||
| reward.StartCloudbrainPointDeductTask() | |||
| return nil | |||
| }) | |||
| } | |||
| func registerSyncResourceSpecs() { | |||
| RegisterTaskFatal("sync_grampus_specs", &BaseConfig{ | |||
| Enabled: true, | |||
| @@ -253,4 +276,7 @@ func initBasicTasks() { | |||
| registerHandleOrgStatistic() | |||
| registerSyncResourceSpecs() | |||
| registerSyncModelArtsTempJobs() | |||
| //registerRewardPeriodTask() | |||
| registerCloudbrainPointDeductTask() | |||
| } | |||
| @@ -5,6 +5,7 @@ | |||
| package eventsource | |||
| import ( | |||
| "code.gitea.io/gitea/services/reward" | |||
| "context" | |||
| "time" | |||
| @@ -24,8 +25,28 @@ func (m *Manager) Init() { | |||
| func (m *Manager) Run(ctx context.Context) { | |||
| then := timeutil.TimeStampNow().Add(-2) | |||
| timer := time.NewTicker(setting.UI.Notification.EventSourceUpdateTime) | |||
| rewardThen := then | |||
| rewardTimer := time.NewTicker(setting.UI.Notification.RewardNotifyUpdateTime) | |||
| loop: | |||
| for { | |||
| select { | |||
| case <-rewardTimer.C: | |||
| log.Debug("rewardTimer run") | |||
| now := timeutil.TimeStampNow().Add(-2) | |||
| list := reward.GetRewardOperation(rewardThen, now) | |||
| if list != nil { | |||
| log.Debug("GetRewardOperation list=%v", list) | |||
| for _, l := range list { | |||
| m.SendMessage(l.UserId, &Event{ | |||
| Name: "reward-operation", | |||
| Data: l.Msg, | |||
| }) | |||
| } | |||
| } | |||
| rewardThen = now | |||
| } | |||
| select { | |||
| case <-ctx.Done(): | |||
| timer.Stop() | |||
| @@ -44,6 +65,7 @@ loop: | |||
| }) | |||
| } | |||
| then = now | |||
| default: | |||
| } | |||
| } | |||
| m.UnregisterAll() | |||
| @@ -148,8 +148,9 @@ type VersionInfo struct { | |||
| type Flavor struct { | |||
| Info []struct { | |||
| Code string `json:"code"` | |||
| Value string `json:"value"` | |||
| Code string `json:"code"` | |||
| Value string `json:"value"` | |||
| UnitPrice int64 `json:"unitPrice"` | |||
| } `json:"flavor"` | |||
| } | |||
| @@ -5,6 +5,7 @@ | |||
| package action | |||
| import ( | |||
| "code.gitea.io/gitea/modules/auth" | |||
| "encoding/json" | |||
| "fmt" | |||
| "path" | |||
| @@ -345,3 +346,101 @@ func (a *actionNotifier) NotifyOtherTask(doer *models.User, repo *models.Reposit | |||
| log.Error("notifyWatchers: %v", err) | |||
| } | |||
| } | |||
| func (t *actionNotifier) NotifyWechatBind(user *models.User, wechatOpenId string) { | |||
| act := &models.Action{ | |||
| ActUserID: user.ID, | |||
| ActUser: user, | |||
| OpType: models.ActionBindWechat, | |||
| IsPrivate: true, | |||
| Content: wechatOpenId, | |||
| } | |||
| if err := models.NotifyWatchers(act); err != nil { | |||
| log.Error("notifyWatchers: %v", err) | |||
| } | |||
| } | |||
| func (t *actionNotifier) NotifyDatasetRecommend(optUser *models.User, dataset *models.Dataset, action string) { | |||
| switch action { | |||
| case "recommend": | |||
| users, err := models.GetAllDatasetContributorByDatasetId(dataset.ID) | |||
| if err != nil { | |||
| return | |||
| } | |||
| var actions = make([]*models.Action, 0) | |||
| for _, user := range users { | |||
| actions = append(actions, &models.Action{ | |||
| OpType: models.ActionDatasetRecommended, | |||
| ActUserID: user.ID, | |||
| ActUser: user, | |||
| RepoID: dataset.RepoID, | |||
| Repo: dataset.Repo, | |||
| Content: fmt.Sprintf("%d|%s", dataset.ID, dataset.Title), | |||
| }) | |||
| } | |||
| if err := models.NotifyWatchers(actions...); err != nil { | |||
| log.Error("notifyWatchers: %v", err) | |||
| } | |||
| } | |||
| } | |||
| func (t *actionNotifier) NotifyCreateImage(doer *models.User, image models.Image) { | |||
| act := &models.Action{ | |||
| ActUserID: doer.ID, | |||
| ActUser: doer, | |||
| OpType: models.ActionCreateImage, | |||
| IsPrivate: image.IsPrivate, | |||
| Content: fmt.Sprintf("%d|%s", image.ID, image.Tag), | |||
| } | |||
| if err := models.NotifyWatchers(act); err != nil { | |||
| log.Error("notifyWatchers: %v", err) | |||
| } | |||
| } | |||
| func (t *actionNotifier) NotifyImageRecommend(optUser *models.User, image *models.Image, action string) { | |||
| u, err := models.GetUserByID(image.UID) | |||
| if err != nil { | |||
| return | |||
| } | |||
| switch action { | |||
| case "recommend": | |||
| act := &models.Action{ | |||
| ActUserID: u.ID, | |||
| ActUser: u, | |||
| OpType: models.ActionImageRecommend, | |||
| IsPrivate: false, | |||
| Content: fmt.Sprintf("%d|%s", image.ID, image.Tag), | |||
| } | |||
| if err := models.NotifyWatchers(act); err != nil { | |||
| log.Error("notifyWatchers: %v", err) | |||
| } | |||
| } | |||
| } | |||
| func (t *actionNotifier) NotifyChangeUserAvatar(user *models.User, form auth.AvatarForm) { | |||
| act := &models.Action{ | |||
| ActUserID: user.ID, | |||
| ActUser: user, | |||
| OpType: models.ActionChangeUserAvatar, | |||
| IsPrivate: true, | |||
| } | |||
| if err := models.NotifyWatchers(act); err != nil { | |||
| log.Error("notifyWatchers: %v", err) | |||
| } | |||
| } | |||
| func (t *actionNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) { | |||
| act := &models.Action{ | |||
| ActUserID: pusher.ID, | |||
| ActUser: pusher, | |||
| OpType: models.ActionPushCommits, | |||
| RepoID: repo.ID, | |||
| Repo: repo, | |||
| RefName: refName, | |||
| IsPrivate: repo.IsPrivate, | |||
| Content: fmt.Sprintf("%s|%s", oldCommitID, newCommitID), | |||
| } | |||
| if err := models.NotifyWatchers(act); err != nil { | |||
| log.Error("notifyWatchers: %v", err) | |||
| } | |||
| } | |||
| @@ -6,6 +6,7 @@ package base | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/auth" | |||
| "code.gitea.io/gitea/modules/repository" | |||
| ) | |||
| @@ -56,6 +57,11 @@ type Notifier interface { | |||
| NotifySyncDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string) | |||
| NotifyOtherTask(doer *models.User, repo *models.Repository, id string, name string, optype models.ActionType) | |||
| NotifyWechatBind(user *models.User, wechatOpenId string) | |||
| NotifyDatasetRecommend(optUser *models.User, dataset *models.Dataset, action string) | |||
| NotifyCreateImage(doer *models.User, image models.Image) | |||
| NotifyImageRecommend(optUser *models.User, image *models.Image, action string) | |||
| NotifyChangeUserAvatar(user *models.User, form auth.AvatarForm) | |||
| NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) | |||
| } | |||
| @@ -6,6 +6,7 @@ package base | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/auth" | |||
| "code.gitea.io/gitea/modules/repository" | |||
| ) | |||
| @@ -159,6 +160,23 @@ func (*NullNotifier) NotifyOtherTask(doer *models.User, repo *models.Repository, | |||
| } | |||
| func (*NullNotifier) NotifyWechatBind(user *models.User, wechatOpenId string) { | |||
| } | |||
| func (*NullNotifier) NotifyDatasetRecommend(optUser *models.User, dataset *models.Dataset, action string) { | |||
| } | |||
| func (*NullNotifier) NotifyCreateImage(doer *models.User, image models.Image) { | |||
| } | |||
| func (*NullNotifier) NotifyImageRecommend(optUser *models.User, image *models.Image, action string) { | |||
| } | |||
| func (*NullNotifier) NotifyChangeUserAvatar(user *models.User, form auth.AvatarForm) { | |||
| } | |||
| func (*NullNotifier) NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) { | |||
| } | |||
| @@ -6,10 +6,12 @@ package notification | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/auth" | |||
| "code.gitea.io/gitea/modules/notification/action" | |||
| "code.gitea.io/gitea/modules/notification/base" | |||
| "code.gitea.io/gitea/modules/notification/indexer" | |||
| "code.gitea.io/gitea/modules/notification/mail" | |||
| "code.gitea.io/gitea/modules/notification/reward" | |||
| "code.gitea.io/gitea/modules/notification/ui" | |||
| "code.gitea.io/gitea/modules/notification/webhook" | |||
| wechatNotifier "code.gitea.io/gitea/modules/notification/wechat" | |||
| @@ -37,6 +39,7 @@ func NewContext() { | |||
| RegisterNotifier(webhook.NewNotifier()) | |||
| RegisterNotifier(action.NewNotifier()) | |||
| RegisterNotifier(wechatNotifier.NewNotifier()) | |||
| RegisterNotifier(reward.NewNotifier()) | |||
| } | |||
| // NotifyUploadAttachment notifies attachment upload message to notifiers | |||
| @@ -272,6 +275,41 @@ func NotifySyncDeleteRef(pusher *models.User, repo *models.Repository, refType, | |||
| } | |||
| } | |||
| // NotifyWechatBind notifies wechat bind | |||
| func NotifyWechatBind(user *models.User, wechatOpenId string) { | |||
| for _, notifier := range notifiers { | |||
| notifier.NotifyWechatBind(user, wechatOpenId) | |||
| } | |||
| } | |||
| // NotifyDatasetRecommend | |||
| func NotifyDatasetRecommend(optUser *models.User, dataset *models.Dataset, action string) { | |||
| for _, notifier := range notifiers { | |||
| notifier.NotifyDatasetRecommend(optUser, dataset, action) | |||
| } | |||
| } | |||
| // NotifyDatasetRecommend | |||
| func NotifyCreateImage(doer *models.User, image models.Image) { | |||
| for _, notifier := range notifiers { | |||
| notifier.NotifyCreateImage(doer, image) | |||
| } | |||
| } | |||
| // NotifyDatasetRecommend | |||
| func NotifyImageRecommend(optUser *models.User, image *models.Image, action string) { | |||
| for _, notifier := range notifiers { | |||
| notifier.NotifyImageRecommend(optUser, image, action) | |||
| } | |||
| } | |||
| // NotifyDatasetRecommend | |||
| func NotifyChangeUserAvatar(user *models.User, form auth.AvatarForm) { | |||
| for _, notifier := range notifiers { | |||
| notifier.NotifyChangeUserAvatar(user, form) | |||
| } | |||
| } | |||
| // NotifyChangeCloudbrainStatus | |||
| func NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) { | |||
| for _, notifier := range notifiers { | |||
| @@ -0,0 +1,27 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/notification/base" | |||
| ) | |||
| type pointNotifier struct { | |||
| base.NullNotifier | |||
| } | |||
| var ( | |||
| _ base.Notifier = &pointNotifier{} | |||
| ) | |||
| // NewNotifier create a new wechatNotifier notifier | |||
| func NewNotifier() base.Notifier { | |||
| return &pointNotifier{} | |||
| } | |||
| func (*pointNotifier) NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) { | |||
| log.Info("pointNotifier NotifyChangeCloudbrainStatus cloudbrain.id=%d cloudbrain.status=%s oldStatus=%s", cloudbrain.ID, cloudbrain.Status, oldStatus) | |||
| if cloudbrain.IsRunning() || cloudbrain.IsTerminal() { | |||
| models.StatusChangeChan <- cloudbrain | |||
| } | |||
| } | |||
| @@ -76,7 +76,7 @@ func HEXISTS(conn redis.Conn, key string, subKey string) (bool, error) { | |||
| } | |||
| func Expire(conn redis.Conn, key string, seconds int) error { | |||
| func EXPIRE(conn redis.Conn, key string, seconds int) error { | |||
| _, err := conn.Do("EXPIRE", key, seconds) | |||
| return err | |||
| @@ -145,3 +145,85 @@ func TTL(key string) (int, error) { | |||
| return n, nil | |||
| } | |||
| func IncrBy(key string, n int64) (int64, error) { | |||
| redisClient := labelmsg.Get() | |||
| defer redisClient.Close() | |||
| reply, err := redisClient.Do("INCRBY", key, n) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| i, err := strconv.ParseInt(fmt.Sprint(reply), 10, 64) | |||
| return i, nil | |||
| } | |||
| func Expire(key string, expireTime time.Duration) error { | |||
| redisClient := labelmsg.Get() | |||
| defer redisClient.Close() | |||
| _, err := redisClient.Do("EXPIRE", key, int64(expireTime.Seconds())) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| //GetInt64 get redis value by Get(key) | |||
| //and then parse the value to int64 | |||
| //return {isExist(bool)} {value(int64)} {error(error)} | |||
| func GetInt64(key string) (bool, int64, error) { | |||
| str, err := Get(key) | |||
| if err != nil { | |||
| return false, 0, err | |||
| } | |||
| if str == "" { | |||
| return false, 0, nil | |||
| } | |||
| i, err := strconv.ParseInt(str, 10, 64) | |||
| if err != nil { | |||
| return false, 0, err | |||
| } | |||
| return true, i, nil | |||
| } | |||
| func ZAdd(key, value string, score float64) error { | |||
| redisClient := labelmsg.Get() | |||
| defer redisClient.Close() | |||
| _, err := redisClient.Do("ZADD", key, score, value) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| func ZRangeByScore(key string, min, max float64) ([]string, error) { | |||
| redisClient := labelmsg.Get() | |||
| defer redisClient.Close() | |||
| reply, err := redisClient.Do("ZRANGEBYSCORE", key, min, max) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if reply == nil { | |||
| return nil, err | |||
| } | |||
| s, _ := redis.Strings(reply, nil) | |||
| return s, nil | |||
| } | |||
| func ZRemRangeByScore(key string, min, max float64) error { | |||
| redisClient := labelmsg.Get() | |||
| defer redisClient.Close() | |||
| _, err := redisClient.Do("ZREMRANGEBYSCORE", key, min, max) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package redis_key | |||
| import "fmt" | |||
| const ACCOUNT_REDIS_PREFIX = "account" | |||
| func PointAccountOperateLock(userId int64) string { | |||
| return KeyJoin(ACCOUNT_REDIS_PREFIX, fmt.Sprint(userId), "point", "operate", "lock") | |||
| } | |||
| func PointAccountInfo(userId int64) string { | |||
| return KeyJoin(ACCOUNT_REDIS_PREFIX, fmt.Sprint(userId), "info") | |||
| } | |||
| func PointAccountInitLock(userId int64) string { | |||
| return KeyJoin(ACCOUNT_REDIS_PREFIX, fmt.Sprint(userId), "init", "lock") | |||
| } | |||
| @@ -4,6 +4,8 @@ import "strings" | |||
| const KEY_SEPARATE = ":" | |||
| const EMPTY_REDIS_VAL = "Nil" | |||
| func KeyJoin(keys ...string) string { | |||
| var build strings.Builder | |||
| for _, v := range keys { | |||
| @@ -0,0 +1,26 @@ | |||
| package redis_key | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "fmt" | |||
| ) | |||
| const LIMIT_REDIS_PREFIX = "limit" | |||
| func LimitCount(userId int64, limitCode string, limitType string, scope string, period *models.PeriodResult) string { | |||
| if scope == models.LimitScopeAllUsers.Name() { | |||
| if period == nil { | |||
| return KeyJoin(LIMIT_REDIS_PREFIX, limitCode, limitType, "count") | |||
| } | |||
| return KeyJoin(LIMIT_REDIS_PREFIX, limitCode, limitType, fmt.Sprint(period.StartTime.Unix()), fmt.Sprint(period.EndTime.Unix()), "count") | |||
| } | |||
| if period == nil { | |||
| return KeyJoin(LIMIT_REDIS_PREFIX, "uid", fmt.Sprint(userId), limitCode, limitType, "count") | |||
| } | |||
| return KeyJoin(LIMIT_REDIS_PREFIX, "uid", fmt.Sprint(userId), limitCode, limitType, fmt.Sprint(period.StartTime.Unix()), fmt.Sprint(period.EndTime.Unix()), "count") | |||
| } | |||
| func LimitConfig(limitType string) string { | |||
| return KeyJoin(LIMIT_REDIS_PREFIX, limitType, "config") | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| package redis_key | |||
| import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "fmt" | |||
| "strings" | |||
| ) | |||
| const REWARD_REDIS_PREFIX = "reward" | |||
| func RewardOperateLock(requestId string, sourceType string, operateType string) string { | |||
| return KeyJoin(REWARD_REDIS_PREFIX, requestId, sourceType, operateType, "send") | |||
| } | |||
| func RewardOperateNotification() string { | |||
| return KeyJoin(REWARD_REDIS_PREFIX, "operate", strings.ReplaceAll(setting.AppURL, "/", ""), "notification") | |||
| } | |||
| func RewardTaskRunningLock(taskId int64) string { | |||
| return KeyJoin(REWARD_REDIS_PREFIX, "periodic_task", fmt.Sprint(taskId), "lock") | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| package redis_key | |||
| import "time" | |||
| const SERIAL_REDIS_PREFIX = "serial" | |||
| func RewardSerialCounter(now time.Time) string { | |||
| h := now.Format("200601021504") | |||
| return KeyJoin(SERIAL_REDIS_PREFIX, "reward_operate", h, "counter") | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| package redis_key | |||
| const TASK_REDIS_PREFIX = "task" | |||
| func TaskAccomplishLock(sourceId string, taskType string) string { | |||
| return KeyJoin(TASK_REDIS_PREFIX, sourceId, taskType, "accomplish") | |||
| } | |||
| func TaskConfigList() string { | |||
| return KeyJoin(TASK_REDIS_PREFIX, "config", "list") | |||
| } | |||
| func TaskConfigOperateLock(taskCode, rewardType string) string { | |||
| return KeyJoin(TASK_REDIS_PREFIX, "config", "operate", "lock") | |||
| } | |||
| @@ -14,25 +14,32 @@ func NewDistributeLock(lockKey string) *DistributeLock { | |||
| return &DistributeLock{lockKey: lockKey} | |||
| } | |||
| func (lock *DistributeLock) Lock(expireTime time.Duration) bool { | |||
| isOk, _ := redis_client.Setnx(lock.lockKey, "", expireTime) | |||
| return isOk | |||
| func (lock *DistributeLock) Lock(expireTime time.Duration) (bool, error) { | |||
| isOk, err := redis_client.Setnx(lock.lockKey, "", expireTime) | |||
| if err != nil { | |||
| return false, err | |||
| } | |||
| return isOk, nil | |||
| } | |||
| func (lock *DistributeLock) LockWithWait(expireTime time.Duration, waitTime time.Duration) bool { | |||
| func (lock *DistributeLock) LockWithWait(expireTime time.Duration, waitTime time.Duration) (bool, error) { | |||
| start := time.Now().Unix() * 1000 | |||
| duration := waitTime.Milliseconds() | |||
| for { | |||
| isOk, _ := redis_client.Setnx(lock.lockKey, "", expireTime) | |||
| isOk, err := redis_client.Setnx(lock.lockKey, "", expireTime) | |||
| if err != nil { | |||
| return false, err | |||
| } | |||
| if isOk { | |||
| return true | |||
| return true, nil | |||
| } | |||
| if time.Now().Unix()*1000-start > duration { | |||
| return false | |||
| return false, nil | |||
| } | |||
| time.Sleep(50 * time.Millisecond) | |||
| } | |||
| return false | |||
| return false, nil | |||
| } | |||
| func (lock *DistributeLock) UnLock() error { | |||
| @@ -214,10 +214,11 @@ var ( | |||
| UseServiceWorker bool | |||
| Notification struct { | |||
| MinTimeout time.Duration | |||
| TimeoutStep time.Duration | |||
| MaxTimeout time.Duration | |||
| EventSourceUpdateTime time.Duration | |||
| MinTimeout time.Duration | |||
| TimeoutStep time.Duration | |||
| MaxTimeout time.Duration | |||
| EventSourceUpdateTime time.Duration | |||
| RewardNotifyUpdateTime time.Duration | |||
| } `ini:"ui.notification"` | |||
| Admin struct { | |||
| @@ -251,15 +252,17 @@ var ( | |||
| Themes: []string{`gitea`, `arc-green`}, | |||
| Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | |||
| Notification: struct { | |||
| MinTimeout time.Duration | |||
| TimeoutStep time.Duration | |||
| MaxTimeout time.Duration | |||
| EventSourceUpdateTime time.Duration | |||
| MinTimeout time.Duration | |||
| TimeoutStep time.Duration | |||
| MaxTimeout time.Duration | |||
| EventSourceUpdateTime time.Duration | |||
| RewardNotifyUpdateTime time.Duration | |||
| }{ | |||
| MinTimeout: 10 * time.Second, | |||
| TimeoutStep: 10 * time.Second, | |||
| MaxTimeout: 60 * time.Second, | |||
| EventSourceUpdateTime: 10 * time.Second, | |||
| MinTimeout: 10 * time.Second, | |||
| TimeoutStep: 10 * time.Second, | |||
| MaxTimeout: 60 * time.Second, | |||
| EventSourceUpdateTime: 10 * time.Second, | |||
| RewardNotifyUpdateTime: 2 * time.Second, | |||
| }, | |||
| Admin: struct { | |||
| UserPagingNum int | |||
| @@ -610,6 +613,13 @@ var ( | |||
| WechatQRCodeExpireSeconds int | |||
| WechatAuthSwitch bool | |||
| //point config | |||
| CloudBrainPaySwitch bool | |||
| CloudBrainPayDelay time.Duration | |||
| CloudBrainPayInterval time.Duration | |||
| DeductTaskRange time.Duration | |||
| DeductTaskRangeForFirst time.Duration | |||
| //wechat auto reply config | |||
| UserNameOfWechatReply string | |||
| RepoNameOfWechatReply string | |||
| @@ -1481,12 +1491,13 @@ func NewContext() { | |||
| WechatAppId = sec.Key("APP_ID").MustString("wxba77b915a305a57d") | |||
| WechatAppSecret = sec.Key("APP_SECRET").MustString("") | |||
| WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120) | |||
| WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(true) | |||
| WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(false) | |||
| UserNameOfWechatReply = sec.Key("AUTO_REPLY_USER_NAME").MustString("OpenIOSSG") | |||
| RepoNameOfWechatReply = sec.Key("AUTO_REPLY_REPO_NAME").MustString("promote") | |||
| RefNameOfWechatReply = sec.Key("AUTO_REPLY_REF_NAME").MustString("master") | |||
| TreePathOfAutoMsgReply = sec.Key("AUTO_REPLY_TREE_PATH").MustString("wechat/auto_reply.json") | |||
| TreePathOfSubscribe = sec.Key("SUBSCRIBE_TREE_PATH").MustString("wechat/subscribe_reply.json") | |||
| WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(false) | |||
| CloudbrainStartedTemplateId = sec.Key("CLOUDBRAIN_STARTED_TEMPLATE_ID").MustString("") | |||
| CloudbrainStartedNotifyList = strings.Split(sec.Key("CLOUDBRAIN_STARTED_NOTIFY_LIST").MustString("DEBUG"), ",") | |||
| CloudbrainStartedTitle = sec.Key("CLOUDBRAIN_STARTED_TITLE").MustString("您好,您提交的算力资源申请已通过,任务已启动,请您关注运行情况。") | |||
| @@ -1496,6 +1507,12 @@ func NewContext() { | |||
| CloudbrainStoppedTitle = sec.Key("CLOUDBRAIN_STOPPED_TITLE").MustString("您好,您申请的算力资源已结束使用,任务已完成运行,状态为%s,请您关注运行结果") | |||
| CloudbrainStoppedRemark = sec.Key("CLOUDBRAIN_STOPPED_REMARK").MustString("感谢您的耐心等待。") | |||
| sec = Cfg.Section("point") | |||
| CloudBrainPaySwitch = sec.Key("CLOUDBRAIN_PAY_SWITCH").MustBool(false) | |||
| CloudBrainPayDelay = sec.Key("CLOUDBRAIN_PAY_DELAY").MustDuration(30 * time.Minute) | |||
| CloudBrainPayInterval = sec.Key("CLOUDBRAIN_PAY_INTERVAL").MustDuration(60 * time.Minute) | |||
| DeductTaskRange = sec.Key("DEDUCT_TASK_RANGE").MustDuration(30 * time.Minute) | |||
| DeductTaskRangeForFirst = sec.Key("DEDUCT_TASK_RANGE_FOR_FIRST").MustDuration(3 * time.Hour) | |||
| SetRadarMapConfig() | |||
| sec = Cfg.Section("warn_mail") | |||
| @@ -791,7 +791,7 @@ func GetRefName(ref string) string { | |||
| return reg.ReplaceAllString(ref, "") | |||
| } | |||
| func MB2GB(size int64) string { | |||
| func MB2GB(size int) string { | |||
| s := strconv.FormatFloat(float64(size)/float64(1024), 'f', 2, 64) | |||
| for strings.HasSuffix(s, "0") { | |||
| s = strings.TrimSuffix(s, "0") | |||
| @@ -0,0 +1,10 @@ | |||
| package util | |||
| import ( | |||
| gouuid "github.com/satori/go.uuid" | |||
| "strings" | |||
| ) | |||
| func UUID() string { | |||
| return strings.ReplaceAll(gouuid.NewV4().String(), "-", "") | |||
| } | |||
| @@ -23,6 +23,7 @@ signed_in_as = Signed in as | |||
| enable_javascript = This website works better with JavaScript. | |||
| toc = Table of Contents | |||
| return=Back OpenI | |||
| calculation_points = Calculation Points | |||
| username = Username | |||
| email = Email Address | |||
| @@ -1055,7 +1056,7 @@ image_delete_fail=Failed to delete image, please try again later. | |||
| image_overwrite=You had submitted the same name image before, are you sure to overwrite the original image? | |||
| download=Download | |||
| score=Score | |||
| wait_count_start = There are currently | |||
| wait_count_start = There are currently | |||
| wait_count_end = tasks queued | |||
| file_limit_100 = Display up to 100 files or folders in a single directory | |||
| images.name = Image Tag | |||
| @@ -1267,7 +1268,7 @@ model.manage.modellabel=Model label | |||
| model.manage.modeldesc=Model description | |||
| model.manage.baseinfo=Base Information | |||
| modelconvert.notcreate=No model conversion task has been created. | |||
| modelconvert.importfirst1=Please import the | |||
| modelconvert.importfirst1=Please import the | |||
| modelconvert.importfirst2=model | |||
| modelconvert.importfirst3=first, then converts it. | |||
| modelconvert.download=Download | |||
| @@ -3230,4 +3231,14 @@ load_code_failed=Fail to load code, please check if the right branch is selected | |||
| error.dataset_select = dataset select error:the count exceed the limit or has same name | |||
| new_train_gpu_tooltips = The code is storaged in <strong style="color:#010101">%s</strong>, the dataset is storaged in <strong style="color:#010101">%s</strong>, and please put your model into <strong style="color:#010101">%s</strong> then you can download it online | |||
| new_infer_gpu_tooltips = The dataset is stored in <strong style="color:#010101">%s</strong>, the model file is stored in <strong style="color:#010101">%s</strong>, please store the inference output in <strong style="color:#010101">%s</strong> for subsequent downloads. | |||
| new_infer_gpu_tooltips = The dataset is stored in <strong style="color:#010101">%s</strong>, the model file is stored in <strong style="color:#010101">%s</strong>, please store the inference output in <strong style="color:#010101">%s</strong> for subsequent downloads. | |||
| [points] | |||
| points = points | |||
| free = Free | |||
| points_hour = Points/hour | |||
| balance_of_points = Balance of Points: | |||
| hours = Hours | |||
| expected_time = , expected to be available for | |||
| points_acquisition_instructions = Points Acquisition Instructions | |||
| insufficient_points_balance = Insufficient points balance | |||
| @@ -23,6 +23,7 @@ signed_in_as=已登录用户 | |||
| enable_javascript=使用 JavaScript能使本网站更好的工作。 | |||
| toc=目录 | |||
| return=返回OpenI | |||
| calculation_points=算力积分 | |||
| username=用户名 | |||
| email=电子邮件地址 | |||
| @@ -3248,5 +3249,16 @@ load_code_failed=代码加载失败,请确认选择了正确的分支。 | |||
| error.dataset_select = 数据集选择错误:数量超过限制或者有同名数据集 | |||
| [points] | |||
| points = 积分 | |||
| free = 免费 | |||
| points_hour = 积分/每小时 | |||
| balance_of_points = 积分余额: | |||
| hours = 小时 | |||
| expected_time = ,预计可用 | |||
| points_acquisition_instructions = 积分获取说明 | |||
| insufficient_points_balance = 积分余额不足 | |||
| new_train_gpu_tooltips =训练脚本存储在<strong style="color:#010101">%s</strong>中,数据集存储在<strong style="color:#010101">%s</strong>中,训练输出请存储在<strong style="color:#010101">%s</strong>中以供后续下载。 | |||
| new_infer_gpu_tooltips = 数据集存储在<strong style="color:#010101">%s</strong>中,模型文件存储在<strong style="color:#010101">%s</strong>中,推理输出请存储在<strong style="color:#010101">%s</strong>中以供后续下载。 | |||
| new_infer_gpu_tooltips = 数据集存储在<strong style="color:#010101">%s</strong>中,模型文件存储在<strong style="color:#010101">%s</strong>中,推理输出请存储在<strong style="color:#010101">%s</strong>中以供后续下载。 | |||
| @@ -80,4 +80,4 @@ | |||
| "browserslist": [ | |||
| "defaults" | |||
| ] | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| package admin | |||
| import ( | |||
| "code.gitea.io/gitea/modules/notification" | |||
| "net/http" | |||
| "strconv" | |||
| "strings" | |||
| @@ -111,6 +112,8 @@ func DatasetAction(ctx *context.Context) { | |||
| if err != nil { | |||
| ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("repo.star_fail", ctx.Params(":action")))) | |||
| } else { | |||
| d, _ := models.GetDatasetByID(datasetId) | |||
| notification.NotifyDatasetRecommend(ctx.User, d, ctx.Params(":action")) | |||
| ctx.JSON(http.StatusOK, models.BaseOKMessage) | |||
| } | |||
| } | |||
| @@ -31,6 +31,7 @@ func GetQRCode4Bind(ctx *context.Context) { | |||
| r, err := createQRCode4Bind(userId) | |||
| if err != nil { | |||
| log.Error("GetQRCode4Bind failed,error=%v", err) | |||
| ctx.JSON(200, map[string]interface{}{ | |||
| "code": "9999", | |||
| "msg": "Get QR code failed", | |||
| @@ -1,9 +1,9 @@ | |||
| package authentication | |||
| import ( | |||
| "code.gitea.io/gitea/modules/auth/wechat" | |||
| "code.gitea.io/gitea/modules/context" | |||
| "code.gitea.io/gitea/modules/log" | |||
| wechat "code.gitea.io/gitea/services/wechat" | |||
| "encoding/xml" | |||
| "io/ioutil" | |||
| "time" | |||
| @@ -1,6 +1,7 @@ | |||
| package image | |||
| import ( | |||
| "code.gitea.io/gitea/modules/notification" | |||
| "net/http" | |||
| "strconv" | |||
| @@ -25,6 +26,10 @@ func Action(ctx *context.Context) { | |||
| if err != nil { | |||
| ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("repo.star_fail", ctx.Params(":action")))) | |||
| } else { | |||
| image, err := models.GetImageByID(imageId) | |||
| if err == nil { | |||
| notification.NotifyImageRecommend(ctx.User, image, ctx.Params(":action")) | |||
| } | |||
| ctx.JSON(http.StatusOK, models.BaseOKMessage) | |||
| } | |||
| } | |||
| @@ -2,7 +2,6 @@ package repo | |||
| import ( | |||
| "bufio" | |||
| "code.gitea.io/gitea/services/cloudbrain/resource" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| @@ -16,6 +15,9 @@ import ( | |||
| "time" | |||
| "unicode/utf8" | |||
| "code.gitea.io/gitea/services/cloudbrain/resource" | |||
| "code.gitea.io/gitea/services/reward/point/account" | |||
| "code.gitea.io/gitea/modules/notification" | |||
| "code.gitea.io/gitea/modules/grampus" | |||
| @@ -204,9 +206,9 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||
| } | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), jobType, displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tpl, &form) | |||
| return | |||
| @@ -314,6 +316,13 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tpl, &form) | |||
| return | |||
| } | |||
| req := cloudbrain.GenerateCloudBrainTaskReq{ | |||
| Ctx: ctx, | |||
| DisplayJobName: displayJobName, | |||
| @@ -392,9 +401,9 @@ func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBra | |||
| tpl := tplCloudBrainInferenceJobNew | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), jobType, displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tpl, &form) | |||
| return | |||
| @@ -486,6 +495,12 @@ func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBra | |||
| ctx.RenderWithErr("Resource specification not available", tpl, &form) | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tpl, &form) | |||
| return | |||
| } | |||
| req := cloudbrain.GenerateCloudBrainTaskReq{ | |||
| Ctx: ctx, | |||
| DisplayJobName: displayJobName, | |||
| @@ -610,6 +625,13 @@ func CloudBrainRestart(ctx *context.Context) { | |||
| } | |||
| task.Spec = spec | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| resultCode = "-1" | |||
| errorMsg = ctx.Tr("points.insufficient_points_balance") | |||
| break | |||
| } | |||
| count, err := models.GetCloudbrainCountByUserID(ctx.User.ID, string(models.JobTypeDebug)) | |||
| if err != nil { | |||
| log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"]) | |||
| @@ -1026,7 +1048,7 @@ func CloudBrainAdminCommitImage(ctx *context.Context, form auth.CommitAdminImage | |||
| UID: ctx.User.ID, | |||
| Type: models.GetRecommondType(form.IsRecommend), | |||
| Place: form.Place, | |||
| }) | |||
| }, ctx.User) | |||
| if err != nil { | |||
| log.Error("CommitImagefailed") | |||
| if models.IsErrImageTagExist(err) { | |||
| @@ -1073,7 +1095,7 @@ func CloudBrainCommitImage(ctx *context.Context, form auth.CommitImageCloudBrain | |||
| CloudBrainType: form.Type, | |||
| Topics: validTopics, | |||
| UID: ctx.User.ID, | |||
| }) | |||
| }, ctx.User) | |||
| if err != nil { | |||
| log.Error("CommitImage(%s) failed:%v", ctx.Cloudbrain.JobName, err.Error(), ctx.Data["msgID"]) | |||
| if models.IsErrImageTagExist(err) { | |||
| @@ -1087,7 +1109,6 @@ func CloudBrainCommitImage(ctx *context.Context, form auth.CommitImageCloudBrain | |||
| return | |||
| } | |||
| ctx.JSON(200, models.BaseOKMessage) | |||
| } | |||
| @@ -1124,6 +1145,7 @@ func CloudBrainStop(ctx *context.Context) { | |||
| log.Error("the job(%s) has been stopped", task.JobName, ctx.Data["msgID"]) | |||
| resultCode = "-1" | |||
| errorMsg = "cloudbrain.Already_stopped" | |||
| resultCode = task.Status | |||
| break | |||
| } | |||
| @@ -1150,7 +1172,6 @@ func CloudBrainStop(ctx *context.Context) { | |||
| errorMsg = "cloudbrain.Stopped_success_update_status_fail" | |||
| break | |||
| } | |||
| status = task.Status | |||
| break | |||
| } | |||
| @@ -1205,7 +1226,7 @@ func StopJobs(cloudBrains []*models.Cloudbrain) { | |||
| }) | |||
| logErrorAndUpdateJobStatus(err, taskInfo) | |||
| } else { | |||
| } else if taskInfo.Type == models.TypeCloudBrainTwo { | |||
| if taskInfo.JobType == string(models.JobTypeTrain) { | |||
| err := retry(3, time.Second*30, func() error { | |||
| _, err := modelarts.StopTrainJob(taskInfo.JobID, strconv.FormatInt(taskInfo.VersionID, 10)) | |||
| @@ -1222,8 +1243,16 @@ func StopJobs(cloudBrains []*models.Cloudbrain) { | |||
| }) | |||
| logErrorAndUpdateJobStatus(err, taskInfo) | |||
| } | |||
| } | |||
| } else if taskInfo.Type == models.TypeC2Net { | |||
| if taskInfo.JobType == string(models.JobTypeTrain) { | |||
| err := retry(3, time.Second*30, func() error { | |||
| _, err := grampus.StopJob(taskInfo.JobID) | |||
| return err | |||
| }) | |||
| logErrorAndUpdateJobStatus(err, taskInfo) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -2234,9 +2263,9 @@ func BenchMarkAlgorithmCreate(ctx *context.Context, form auth.CreateCloudBrainFo | |||
| ctx.Data["benchmark_child_types_id_hidden"] = benchmarkChildTypeID | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), form.JobType, displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplCloudBrainBenchmarkNew, &form) | |||
| return | |||
| @@ -2284,6 +2313,12 @@ func BenchMarkAlgorithmCreate(ctx *context.Context, form auth.CreateCloudBrainFo | |||
| ctx.RenderWithErr("Resource specification not available", tplCloudBrainBenchmarkNew, &form) | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplCloudBrainBenchmarkNew, &form) | |||
| return | |||
| } | |||
| count, err := models.GetBenchmarkCountByUserID(ctx.User.ID) | |||
| if err != nil { | |||
| @@ -2418,9 +2453,9 @@ func ModelBenchmarkCreate(ctx *context.Context, form auth.CreateCloudBrainForm) | |||
| command := cloudbrain.GetCloudbrainDebugCommand() | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), jobType, displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tpl, &form) | |||
| return | |||
| @@ -2512,6 +2547,13 @@ func ModelBenchmarkCreate(ctx *context.Context, form auth.CreateCloudBrainForm) | |||
| ctx.RenderWithErr("Resource specification not available", tpl, &form) | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tpl, &form) | |||
| return | |||
| } | |||
| log.Info("Command=" + command) | |||
| log.Info("ModelPath=" + storage.GetMinioPath(jobName, cloudbrain.ModelMountPath+"/")) | |||
| req := cloudbrain.GenerateCloudBrainTaskReq{ | |||
| @@ -1,7 +1,6 @@ | |||
| package repo | |||
| import ( | |||
| "code.gitea.io/gitea/services/cloudbrain/resource" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| @@ -13,6 +12,9 @@ import ( | |||
| "strings" | |||
| "time" | |||
| "code.gitea.io/gitea/services/cloudbrain/resource" | |||
| "code.gitea.io/gitea/services/reward/point/account" | |||
| "code.gitea.io/gitea/modules/auth" | |||
| "code.gitea.io/gitea/modules/git" | |||
| "code.gitea.io/gitea/modules/grampus" | |||
| @@ -217,9 +219,9 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain | |||
| image := strings.TrimSpace(form.Image) | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplGrampusTrainJobGPUNew, &form) | |||
| return | |||
| @@ -301,6 +303,13 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplGrampusTrainJobGPUNew, &form) | |||
| return | |||
| } | |||
| //check dataset | |||
| attachment, err := models.GetAttachmentByUUID(uuid) | |||
| if err != nil { | |||
| @@ -429,9 +438,9 @@ func GrampusTrainJobNpuCreate(ctx *context.Context, form auth.CreateGrampusTrain | |||
| engineName := form.EngineName | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplGrampusTrainJobNPUNew, &form) | |||
| return | |||
| @@ -512,6 +521,12 @@ func GrampusTrainJobNpuCreate(ctx *context.Context, form auth.CreateGrampusTrain | |||
| ctx.RenderWithErr("Resource specification not available", tplGrampusTrainJobNPUNew, &form) | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplGrampusTrainJobNPUNew, &form) | |||
| return | |||
| } | |||
| //check dataset | |||
| attachment, err := models.GetAttachmentByUUID(uuid) | |||
| @@ -17,6 +17,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/modelarts_cd" | |||
| "code.gitea.io/gitea/services/cloudbrain/resource" | |||
| "code.gitea.io/gitea/services/reward/point/account" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/auth" | |||
| @@ -213,9 +214,9 @@ func Notebook2Create(ctx *context.Context, form auth.CreateModelArtsNotebookForm | |||
| repo := ctx.Repo.Repository | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeDebug), displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| notebookNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsNotebookNew, &form) | |||
| return | |||
| @@ -267,6 +268,13 @@ func Notebook2Create(ctx *context.Context, form auth.CreateModelArtsNotebookForm | |||
| ctx.RenderWithErr("Resource specification not available", tplModelArtsNotebookNew, &form) | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d ", ctx.User.ID, spec.ID) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplModelArtsNotebookNew, &form) | |||
| return | |||
| } | |||
| if setting.ModelartsCD.Enabled { | |||
| err = modelarts_cd.GenerateNotebook(ctx, displayJobName, jobName, uuid, description, imageId, spec) | |||
| } else { | |||
| @@ -474,7 +482,11 @@ func NotebookRestart(ctx *context.Context) { | |||
| errorMsg = "Resource specification not support any more" | |||
| break | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| errorMsg = ctx.Tr("points.insufficient_points_balance") | |||
| break | |||
| } | |||
| createTime := timeutil.TimeStampNow() | |||
| param := models.NotebookAction{ | |||
| Action: models.ActionStart, | |||
| @@ -1155,9 +1167,9 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) | |||
| } | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| trainJobErrorNewDataPrepare(ctx, form) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsTrainJobNew, &form) | |||
| return | |||
| @@ -1204,6 +1216,13 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) | |||
| ctx.RenderWithErr("Resource specification not available", tplModelArtsTrainJobNew, &form) | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| cloudBrainNewDataPrepare(ctx) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplModelArtsTrainJobNew, &form) | |||
| return | |||
| } | |||
| //Determine whether the task name of the task in the project is duplicated | |||
| tasks, err := models.GetCloudbrainsByDisplayJobName(repo.ID, string(models.JobTypeTrain), displayJobName) | |||
| if err == nil { | |||
| @@ -1530,9 +1549,9 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ | |||
| isLatestVersion := modelarts.IsLatestVersion | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| versionErrorDataPrepare(ctx, form) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsTrainJobVersionNew, &form) | |||
| return | |||
| @@ -1571,6 +1590,12 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ | |||
| ctx.RenderWithErr("Resource specification not available", tplModelArtsTrainJobVersionNew, &form) | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID) | |||
| versionErrorDataPrepare(ctx, form) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplModelArtsTrainJobVersionNew, &form) | |||
| return | |||
| } | |||
| //todo: del the codeLocalPath | |||
| _, err = ioutil.ReadDir(codeLocalPath) | |||
| @@ -2036,7 +2061,6 @@ func TrainJobStop(ctx *context.Context) { | |||
| ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobIndex, nil) | |||
| return | |||
| } | |||
| ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/train-job?listType=" + listType) | |||
| } | |||
| @@ -2146,9 +2170,9 @@ func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInference | |||
| } | |||
| lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeInference), displayJobName)) | |||
| isOk := lock.Lock(models.CloudbrainKeyDuration) | |||
| isOk, err := lock.Lock(models.CloudbrainKeyDuration) | |||
| if !isOk { | |||
| log.Error("The task have been processed", ctx.Data["MsgID"]) | |||
| log.Error("lock processed failed:%v", err, ctx.Data["MsgID"]) | |||
| inferenceJobErrorNewDataPrepare(ctx, form) | |||
| ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsInferenceJobNew, &form) | |||
| return | |||
| @@ -2213,6 +2237,13 @@ func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInference | |||
| ctx.RenderWithErr("Resource specification not available", tplModelArtsInferenceJobNew, &form) | |||
| return | |||
| } | |||
| if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) { | |||
| log.Error("point balance is not enough,userId=%d specId=%d ", ctx.User.ID, spec.ID) | |||
| inferenceJobErrorNewDataPrepare(ctx, form) | |||
| ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplModelArtsInferenceJobNew, &form) | |||
| return | |||
| } | |||
| //todo: del the codeLocalPath | |||
| _, err = ioutil.ReadDir(codeLocalPath) | |||
| if err == nil { | |||
| @@ -0,0 +1,24 @@ | |||
| package point | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/context" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/routers/response" | |||
| "code.gitea.io/gitea/services/reward/point/account" | |||
| "net/http" | |||
| ) | |||
| func SearchPointAccount(ctx *context.Context) { | |||
| q := ctx.Query("q") | |||
| page := ctx.QueryInt("page") | |||
| resopnse, err := account.SearchPointAccount(models.SearchPointAccountOpts{ListOptions: models.ListOptions{Page: page, PageSize: 20}, Keyword: q}) | |||
| if err != nil { | |||
| log.Error("SearchPointAccount error.%v", err) | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(resopnse)) | |||
| return | |||
| } | |||
| @@ -0,0 +1,45 @@ | |||
| package point | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/context" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/routers/response" | |||
| "code.gitea.io/gitea/services/reward/limiter" | |||
| "net/http" | |||
| ) | |||
| func GetSingleDailyPointLimitConfig(ctx *context.Context) { | |||
| r, err := limiter.GetSingleDailyPointLimitConfig() | |||
| if err != nil { | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| resultMap := make(map[string]interface{}, 0) | |||
| if r == nil { | |||
| resultMap["LimitNum"] = "" | |||
| } else { | |||
| resultMap["LimitNum"] = r.LimitNum | |||
| } | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap)) | |||
| } | |||
| func SetSingleDailyPointLimitConfig(ctx *context.Context, config models.LimitConfigVO) { | |||
| err := limiter.SetSingleDailyPointLimitConfig(config.LimitNum, ctx.User) | |||
| if err != nil { | |||
| log.Error("Set single daily point limit config error. %v", err) | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, response.Success()) | |||
| } | |||
| func DeletePointLimitConfig(ctx *context.Context) { | |||
| id := ctx.QueryInt64("id") | |||
| err := limiter.DeleteLimitConfig(id, ctx.User) | |||
| if err != nil { | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, response.Success()) | |||
| } | |||
| @@ -0,0 +1,158 @@ | |||
| package point | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/base" | |||
| "code.gitea.io/gitea/modules/context" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/routers/response" | |||
| "code.gitea.io/gitea/services/reward" | |||
| "code.gitea.io/gitea/services/reward/point/account" | |||
| "errors" | |||
| "net/http" | |||
| ) | |||
| const tplPoint base.TplName = "reward/point" | |||
| const tplPointRule base.TplName = "reward/point/rule" | |||
| type AccountResponse struct { | |||
| Balance int64 | |||
| TotalEarned int64 | |||
| TotalConsumed int64 | |||
| } | |||
| func GetPointAccount(ctx *context.Context) { | |||
| userId := ctx.User.ID | |||
| a, err := account.GetAccount(userId) | |||
| if err != nil { | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| res := &AccountResponse{ | |||
| Balance: a.Balance, | |||
| TotalEarned: a.TotalEarned, | |||
| TotalConsumed: a.TotalConsumed, | |||
| } | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(res)) | |||
| } | |||
| func GetPointRecordList(ctx *context.Context) { | |||
| operateType := ctx.Query("Operate") | |||
| page := ctx.QueryInt("Page") | |||
| var orderBy models.RewardOperateOrderBy | |||
| switch ctx.Query("sort") { | |||
| default: | |||
| orderBy = models.RewardOrderByIDDesc | |||
| } | |||
| t := models.GetRewardOperateTypeInstance(operateType) | |||
| if t == "" { | |||
| ctx.JSON(http.StatusOK, response.ServerError("param error")) | |||
| return | |||
| } | |||
| r, err := reward.GetRewardRecordList(&models.RewardRecordListOpts{ | |||
| ListOptions: models.ListOptions{PageSize: 10, Page: page}, | |||
| UserId: ctx.User.ID, | |||
| OperateType: t, | |||
| RewardType: models.RewardTypePoint, | |||
| OrderBy: orderBy, | |||
| IsAdmin: false, | |||
| UserName: ctx.User.Name, | |||
| }) | |||
| if err != nil { | |||
| log.Error("GetPointRecordList error.%v", err) | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||
| return | |||
| } | |||
| func OperatePointAccountBalance(ctx *context.Context, req models.AdminRewardOperateReq) { | |||
| req.RewardType = models.RewardTypePoint | |||
| if req.OperateType.Name() == "" || req.Remark == "" { | |||
| ctx.JSON(http.StatusOK, "param error") | |||
| return | |||
| } | |||
| err := reward.AdminBalanceOperate(req, ctx.User) | |||
| if err != nil { | |||
| log.Error("OperatePointAccountBalance error.%v", err) | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, response.Success()) | |||
| } | |||
| func GetPointPage(ctx *context.Context) { | |||
| ctx.HTML(200, tplPoint) | |||
| } | |||
| func GetRulePage(ctx *context.Context) { | |||
| ctx.HTML(200, tplPointRule) | |||
| } | |||
| func GetAdminRewardList(ctx *context.Context) { | |||
| opts, err := buildAdminRewardRecordListOpts(ctx) | |||
| if err != nil { | |||
| log.Error("buildAdminRewardRecordListOpts error.%v", err) | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| username := ctx.Query("userName") | |||
| if username != "" { | |||
| user, err := models.GetUserByName(username) | |||
| if err != nil { | |||
| log.Error("GetUserByName error.%v", err) | |||
| if models.IsErrUserNotExist(err) { | |||
| ctx.JSON(http.StatusOK, response.ServerError("user not exist")) | |||
| } else { | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| } | |||
| return | |||
| } | |||
| opts.UserId = user.ID | |||
| opts.UserName = user.Name | |||
| } | |||
| r, err := reward.GetRewardRecordList(opts) | |||
| if err != nil { | |||
| log.Error("GetRewardRecordList error.%v", err) | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||
| } | |||
| func buildAdminRewardRecordListOpts(ctx *context.Context) (*models.RewardRecordListOpts, error) { | |||
| operateType := ctx.Query("operate") | |||
| sourceType := ctx.Query("source") | |||
| actionType := ctx.QueryInt("action") | |||
| serialNo := ctx.Query("serialNo") | |||
| status := ctx.Query("status") | |||
| page := ctx.QueryInt("page") | |||
| var orderBy models.RewardOperateOrderBy | |||
| switch ctx.Query("sort") { | |||
| default: | |||
| orderBy = models.RewardOrderByIDDesc | |||
| } | |||
| t := models.GetRewardOperateTypeInstance(operateType) | |||
| if t == "" { | |||
| return nil, errors.New("param error") | |||
| } | |||
| opts := &models.RewardRecordListOpts{ | |||
| ListOptions: models.ListOptions{PageSize: 10, Page: page}, | |||
| OperateType: t, | |||
| RewardType: models.RewardTypePoint, | |||
| OrderBy: orderBy, | |||
| SourceType: sourceType, | |||
| ActionType: actionType, | |||
| SerialNo: serialNo, | |||
| IsAdmin: true, | |||
| Status: status, | |||
| } | |||
| return opts, nil | |||
| } | |||
| @@ -6,6 +6,9 @@ package routes | |||
| import ( | |||
| "bytes" | |||
| "code.gitea.io/gitea/routers/reward/point" | |||
| "code.gitea.io/gitea/routers/task" | |||
| "code.gitea.io/gitea/services/reward" | |||
| "encoding/gob" | |||
| "net/http" | |||
| "path" | |||
| @@ -328,6 +331,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/", routers.Home) | |||
| m.Get("/dashboard", routers.Dashboard) | |||
| go routers.SocketManager.Run() | |||
| go task.RunTask() | |||
| go reward.AcceptStatusChangeAction() | |||
| m.Get("/action/notification", routers.ActionNotification) | |||
| m.Get("/recommend/home", routers.RecommendHomeInfo) | |||
| //m.Get("/recommend/org", routers.RecommendOrgFromPromote) | |||
| @@ -640,6 +645,20 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Group("/operation", func() { | |||
| m.Get("/config/recommend_org", operation.Organizations) | |||
| m.Post("/config/recommend_org", bindIgnErr(operation.OrgInfos{}), operation.UpdateRecommendOrganizations) | |||
| m.Group("/reward/point", func() { | |||
| m.Combo("/limiter/single-daily").Get(point.GetSingleDailyPointLimitConfig).Post(bindIgnErr(models.LimitConfigVO{}), point.SetSingleDailyPointLimitConfig) | |||
| m.Post("/limiter/delete", point.DeletePointLimitConfig) | |||
| m.Get("/account/search", point.SearchPointAccount) | |||
| m.Post("/account/operate", binding.Bind(models.AdminRewardOperateReq{}), point.OperatePointAccountBalance) | |||
| m.Get("/list", point.GetAdminRewardList) | |||
| }) | |||
| m.Group("/task/config", func() { | |||
| m.Get("/list", task.GetTaskConfigList) | |||
| m.Post("/add/batch", bindIgnErr(models.BatchLimitConfigVO{}), task.BatchAddTaskConfig) | |||
| m.Post("/^:action(new|edit|del)$", bindIgnErr(models.TaskConfigWithLimit{}), task.OperateTaskConfig) | |||
| }) | |||
| }, operationReq) | |||
| // ***** END: Operation ***** | |||
| @@ -1113,7 +1132,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/models", reqRepoCloudBrainReader, repo.CloudBrainShowModels) | |||
| m.Get("/download_model", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDownloadModel) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.CloudBrainNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | |||
| m.Group("/benchmark", func() { | |||
| @@ -1124,7 +1143,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.BenchmarkDel) | |||
| m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainBenchmarkNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.CloudBrainBenchmarkNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainBenchmarkCreate) | |||
| m.Get("/get_child_types", repo.GetChildTypes) | |||
| }) | |||
| @@ -1138,7 +1157,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| //m.Get("/get_log", cloudbrain.AdminOrJobCreaterRightForTrain, repo.GetLogFromModelDir) | |||
| //m.Post("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainTrainJobNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.CloudBrainTrainJobNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | |||
| }) | |||
| m.Group("/inference-job", func() { | |||
| @@ -1148,7 +1167,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/downloadall", repo.DownloadInferenceResultFile) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.InferenceCloudBrainJobNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.InferenceCloudBrainJobNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainInferencForm{}), repo.CloudBrainInferenceJobCreate) | |||
| }) | |||
| }, context.RepoRef()) | |||
| @@ -1161,11 +1180,11 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/model_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ModelDownload) | |||
| }) | |||
| m.Group("/gpu", func() { | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.GrampusTrainJobGPUNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.GrampusTrainJobGPUNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateGrampusTrainJobForm{}), repo.GrampusTrainJobGpuCreate) | |||
| }) | |||
| m.Group("/npu", func() { | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.GrampusTrainJobNPUNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.GrampusTrainJobNPUNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateGrampusTrainJobForm{}), repo.GrampusTrainJobNpuCreate) | |||
| }) | |||
| }) | |||
| @@ -1222,7 +1241,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookStop) | |||
| m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookDel) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.NotebookNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.NotebookNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.Notebook2Create) | |||
| }) | |||
| @@ -1234,10 +1253,10 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRightForTrain, repo.TrainJobDel) | |||
| m.Get("/model_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ModelDownload) | |||
| m.Get("/download_log_file", cloudbrain.AdminOrJobCreaterRightForTrain, repo.TrainJobDownloadLogFile) | |||
| m.Get("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, repo.TrainJobNewVersion) | |||
| m.Get("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, context.PointAccount(), repo.TrainJobNewVersion) | |||
| m.Post("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.TrainJobNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.TrainJobNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreate) | |||
| m.Get("/para-config-list", reqRepoCloudBrainReader, repo.TrainJobGetConfigList) | |||
| @@ -1250,7 +1269,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/result_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ResultDownload) | |||
| m.Get("/downloadall", repo.DownloadMultiResultFile) | |||
| }) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.InferenceJobNew) | |||
| m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.InferenceJobNew) | |||
| m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate) | |||
| }) | |||
| }, context.RepoRef()) | |||
| @@ -1410,6 +1429,13 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/purge", user.NotificationPurgePost) | |||
| }, reqSignIn) | |||
| m.Group("/reward/point", func() { | |||
| m.Get("", point.GetPointPage) | |||
| m.Get("/rule", point.GetRulePage) | |||
| m.Get("/account", point.GetPointAccount) | |||
| m.Get("/record/list", point.GetPointRecordList) | |||
| }, reqSignIn) | |||
| if setting.API.EnableSwagger { | |||
| m.Get("/swagger.v1.json", templates.JSONRenderer(), routers.SwaggerV1Json) | |||
| } | |||
| @@ -0,0 +1,68 @@ | |||
| package task | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/context" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/routers/response" | |||
| "code.gitea.io/gitea/services/task" | |||
| "errors" | |||
| "net/http" | |||
| ) | |||
| func GetTaskConfigList(ctx *context.Context) { | |||
| page := ctx.QueryInt("Page") | |||
| status := ctx.QueryInt("Status") | |||
| action := ctx.QueryInt("Action") | |||
| r, err := task.GetTaskConfigWithLimitList(models.GetTaskConfigOpts{ | |||
| ListOptions: models.ListOptions{PageSize: 20, Page: page}, | |||
| Status: status, | |||
| ActionType: action, | |||
| }) | |||
| if err != nil { | |||
| log.Error("GetTaskConfigList error.%v", err) | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||
| } | |||
| func OperateTaskConfig(ctx *context.Context, config models.TaskConfigWithLimit) { | |||
| action := ctx.Params(":action") | |||
| var err error | |||
| switch action { | |||
| case "edit": | |||
| err = task.EditTaskConfig(config, ctx.User) | |||
| case "new": | |||
| err = task.AddTaskConfig(config, ctx.User) | |||
| case "del": | |||
| err = task.DelTaskConfig(config.ID, ctx.User) | |||
| default: | |||
| err = errors.New("action type error") | |||
| } | |||
| if err != nil { | |||
| log.Error("OperateTaskConfig error ,%v", err) | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, response.Success()) | |||
| } | |||
| func BatchAddTaskConfig(ctx *context.Context, list models.BatchLimitConfigVO) { | |||
| successCount := 0 | |||
| failCount := 0 | |||
| for _, config := range list.ConfigList { | |||
| err := task.AddTaskConfig(config, ctx.User) | |||
| if err != nil { | |||
| failCount++ | |||
| } else { | |||
| successCount++ | |||
| } | |||
| } | |||
| r := make(map[string]int, 2) | |||
| r["successCount"] = successCount | |||
| r["failCount"] = failCount | |||
| log.Debug("BatchAddTaskConfig success.result=%v", r) | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package task | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/services/task" | |||
| ) | |||
| func RunTask() { | |||
| for { | |||
| select { | |||
| case action := <-models.ActionChan4Task: | |||
| task.Accomplish(action) | |||
| } | |||
| } | |||
| } | |||
| @@ -6,6 +6,7 @@ | |||
| package setting | |||
| import ( | |||
| "code.gitea.io/gitea/modules/notification" | |||
| "errors" | |||
| "fmt" | |||
| "io/ioutil" | |||
| @@ -179,6 +180,7 @@ func AvatarPost(ctx *context.Context, form auth.AvatarForm) { | |||
| if err := UpdateAvatarSetting(ctx, form, ctx.User); err != nil { | |||
| ctx.Flash.Error(err.Error()) | |||
| } else { | |||
| notification.NotifyChangeUserAvatar(ctx.User, form) | |||
| ctx.Flash.Success(ctx.Tr("settings.update_avatar_success")) | |||
| } | |||
| @@ -46,7 +46,7 @@ func SendVerifyCode(conn redis.Conn, phoneNumber string) error { | |||
| if err != nil { | |||
| return err | |||
| } | |||
| err = redis_client.Expire(conn, timesKey, getRemainSecondOfDay(time.Now())) | |||
| err = redis_client.EXPIRE(conn, timesKey, getRemainSecondOfDay(time.Now())) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| @@ -0,0 +1,50 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/util" | |||
| ) | |||
| func AdminBalanceOperate(req models.AdminRewardOperateReq, doer *models.User) error { | |||
| logId := util.UUID() | |||
| _, err := models.InsertRewardAdminLog(&models.RewardAdminLog{ | |||
| LogId: logId, | |||
| Amount: req.Amount, | |||
| RewardType: req.RewardType.Name(), | |||
| TargetUserId: req.TargetUserId, | |||
| CreatorId: doer.ID, | |||
| CreatorName: doer.Name, | |||
| Remark: req.Remark, | |||
| Status: models.RewardAdminLogProcessing, | |||
| }) | |||
| if err != nil { | |||
| log.Error("AdminBalanceOperate InsertRewardAdminLog error.%v", err) | |||
| return err | |||
| } | |||
| //reward | |||
| err = Operate(&models.RewardOperateContext{ | |||
| SourceType: models.SourceTypeAdminOperate, | |||
| SourceId: logId, | |||
| Title: "管理员操作", | |||
| Reward: models.Reward{ | |||
| Amount: req.Amount, | |||
| Type: req.RewardType, | |||
| }, | |||
| TargetUserId: req.TargetUserId, | |||
| RequestId: logId, | |||
| OperateType: req.OperateType, | |||
| Remark: req.Remark, | |||
| RejectPolicy: models.JustReject, | |||
| PermittedNegative: true, | |||
| }) | |||
| if err != nil { | |||
| log.Error("AdminBalanceOperate operate error.%v", err) | |||
| models.UpdateRewardAdminLogStatus(logId, models.RewardAdminLogProcessing, models.RewardAdminLogFailed) | |||
| return err | |||
| } | |||
| models.UpdateRewardAdminLogStatus(logId, models.RewardAdminLogProcessing, models.RewardAdminLogSuccess) | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,133 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| var ( | |||
| ResourceSpecs *models.ResourceSpecs | |||
| TrainResourceSpecs *models.ResourceSpecs | |||
| ) | |||
| const RUN_CLOUDBRAIN_TASK_TITTLE = "运行云脑任务" | |||
| func AcceptStatusChangeAction() { | |||
| for { | |||
| select { | |||
| case task := <-models.StatusChangeChan: | |||
| DeductPoint4Cloudbrain(*task, time.Now()) | |||
| } | |||
| } | |||
| } | |||
| func StartAndGetCloudBrainPointDeductTask(task models.Cloudbrain) (*models.RewardPeriodicTask, error) { | |||
| if !setting.CloudBrainPaySwitch { | |||
| return nil, nil | |||
| } | |||
| unitPrice, err := models.GetCloudbrainTaskUnitPrice(task.ID) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if unitPrice == 0 { | |||
| log.Debug("finish StartAndGetCloudBrainPointDeductTask, UnitPrice = 0 task.ID=%d", task.ID) | |||
| return nil, nil | |||
| } | |||
| return StartAndGetPeriodicTask(&models.StartPeriodicTaskOpts{ | |||
| SourceType: models.SourceTypeRunCloudbrainTask, | |||
| SourceId: getCloudBrainPointTaskSourceId(task), | |||
| TargetUserId: task.UserID, | |||
| RequestId: getCloudBrainPointTaskSourceId(task), | |||
| OperateType: models.OperateTypeDecrease, | |||
| Delay: setting.CloudBrainPayDelay, | |||
| Interval: setting.CloudBrainPayInterval, | |||
| UnitAmount: unitPrice, | |||
| RewardType: models.RewardTypePoint, | |||
| StartTime: time.Unix(int64(task.StartTime), 0), | |||
| Title: RUN_CLOUDBRAIN_TASK_TITTLE, | |||
| }) | |||
| } | |||
| func StopCloudBrainPointDeductTask(task models.Cloudbrain) { | |||
| StopPeriodicTask(models.SourceTypeRunCloudbrainTask, getCloudBrainPointTaskSourceId(task), models.OperateTypeDecrease) | |||
| } | |||
| func getCloudBrainPointTaskSourceId(task models.Cloudbrain) string { | |||
| return fmt.Sprint(task.ID) | |||
| } | |||
| var firstTimeFlag = true | |||
| func StartCloudbrainPointDeductTask() { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| log.Debug("try to run CloudbrainPointDeductTask") | |||
| end := time.Now() | |||
| start := end.Add(-1 * setting.DeductTaskRange) | |||
| if firstTimeFlag { | |||
| //When it is executed for the first time, it needs to process the tasks of the last 3 hours. | |||
| //This is done to prevent the application from hanging for a long time | |||
| start = end.Add(-1 * setting.DeductTaskRangeForFirst) | |||
| firstTimeFlag = false | |||
| } | |||
| taskList, err := models.GetStartedCloudbrainTaskByUpdatedUnix(start, end) | |||
| if err != nil { | |||
| log.Error("GetStartedCloudbrainTaskByUpdatedUnix error. %v", err) | |||
| return | |||
| } | |||
| if taskList == nil || len(taskList) == 0 { | |||
| log.Debug("No cloudbrain task need handled") | |||
| return | |||
| } | |||
| for _, t := range taskList { | |||
| DeductPoint4Cloudbrain(t, end) | |||
| } | |||
| log.Debug("CloudbrainPointDeductTask completed") | |||
| } | |||
| func DeductPoint4Cloudbrain(t models.Cloudbrain, now time.Time) error { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| log.Debug("start to deduct point for cloudbrain[%d]", t.ID) | |||
| if t.StartTime == 0 { | |||
| log.Debug("cloudbrain[%d] task not start", t.ID) | |||
| return nil | |||
| } | |||
| task, err := StartAndGetCloudBrainPointDeductTask(t) | |||
| if err != nil { | |||
| log.Error("run cloudbrain point deduct task error,err=%v", err) | |||
| return err | |||
| } | |||
| if task == nil { | |||
| log.Debug("cloudbrain[%d] deduct task is nil") | |||
| return nil | |||
| } | |||
| if task.Status == models.PeriodicTaskStatusFinished { | |||
| log.Info("Periodic task is finished") | |||
| return nil | |||
| } | |||
| if t.EndTime > 0 { | |||
| endTime := time.Unix(int64(t.EndTime), 0) | |||
| RunRewardTask(*task, endTime) | |||
| models.StopPeriodicTask(task.ID, task.OperateSerialNo, endTime) | |||
| } else { | |||
| RunRewardTask(*task, now) | |||
| } | |||
| log.Debug("finished deduct point for cloudbrain[%d]", t.ID) | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,100 @@ | |||
| package limiter | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| ) | |||
| func GetSingleDailyPointLimitConfig() (*models.LimitConfigVO, error) { | |||
| r, err := GetLimitConfigList(models.LimitConfigQueryOpts{ | |||
| RefreshRate: models.PeriodDaily, | |||
| Scope: models.LimitScopeSingleUser, | |||
| LimitCode: models.SourceTypeAccomplishTask.Name(), | |||
| LimitType: models.LimitTypeRewardPoint, | |||
| }) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if r == nil || len(r) == 0 { | |||
| return nil, nil | |||
| } | |||
| return r[0], nil | |||
| } | |||
| func SetSingleDailyPointLimitConfig(limitNum int64, doer *models.User) error { | |||
| l := &models.LimitConfigVO{ | |||
| RefreshRate: models.PeriodDaily, | |||
| Scope: models.LimitScopeSingleUser.Name(), | |||
| LimitCode: models.SourceTypeAccomplishTask.Name(), | |||
| LimitType: models.LimitTypeRewardPoint.Name(), | |||
| LimitNum: limitNum, | |||
| } | |||
| return AddLimitConfig(l, doer) | |||
| } | |||
| func GetLimitConfigList(opts models.LimitConfigQueryOpts) ([]*models.LimitConfigVO, error) { | |||
| r, err := GetLimitersByLimitType(opts.LimitType) | |||
| if err != nil { | |||
| log.Error("GetLimitConfigList error when getting limiters by limit type.err=%v", err) | |||
| return nil, err | |||
| } | |||
| result := make([]*models.LimitConfigVO, 0) | |||
| for _, v := range r { | |||
| if opts.LimitCode != "" && opts.LimitCode != v.LimitCode { | |||
| continue | |||
| } | |||
| if opts.Scope != "" && opts.Scope.Name() != v.Scope { | |||
| continue | |||
| } | |||
| if opts.RefreshRate != "" && opts.RefreshRate != v.RefreshRate { | |||
| continue | |||
| } | |||
| if opts.LimitType != "" && opts.LimitType.Name() != v.LimitType { | |||
| continue | |||
| } | |||
| result = append(result, v.ToLimitConfigVO()) | |||
| } | |||
| return result, nil | |||
| } | |||
| func GetLimitConfigById(id int64) (*models.LimitConfig, error) { | |||
| return models.GetLimitConfigById(id) | |||
| } | |||
| func AddLimitConfig(config *models.LimitConfigVO, doer *models.User) error { | |||
| r := &models.LimitConfig{ | |||
| Title: config.Title, | |||
| RefreshRate: config.RefreshRate, | |||
| Scope: config.Scope, | |||
| LimitNum: config.LimitNum, | |||
| LimitCode: config.LimitCode, | |||
| LimitType: config.LimitType, | |||
| CreatorId: doer.ID, | |||
| CreatorName: doer.Name, | |||
| } | |||
| err := models.AddLimitConfig(r) | |||
| if err != nil { | |||
| log.Error("add limit config error,config:%v err:%v", config, err) | |||
| return err | |||
| } | |||
| redis_client.Del(redis_key.LimitConfig(config.LimitType)) | |||
| return nil | |||
| } | |||
| func DeleteLimitConfig(id int64, doer *models.User) error { | |||
| config, err := GetLimitConfigById(id) | |||
| if err != nil { | |||
| log.Error("GetLimitConfigById err,e=%v", err) | |||
| return err | |||
| } | |||
| err = models.DeleteLimitConfig(*config, doer.ID, doer.Name) | |||
| if err != nil { | |||
| log.Error("add limit config error,config:%v err:%v", config, err) | |||
| return err | |||
| } | |||
| redis_client.Del(redis_key.LimitConfig(config.LimitType)) | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,258 @@ | |||
| package limiter | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "code.gitea.io/gitea/services/task/period" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| type limiterRunner struct { | |||
| limiters []models.LimitConfig | |||
| index int | |||
| userId int64 | |||
| amount int64 | |||
| limitCode string | |||
| limitType models.LimitType | |||
| rejectPolicy models.LimiterRejectPolicy | |||
| resultMap map[int]limitResult | |||
| minRealAmount int64 | |||
| } | |||
| type limitResult struct { | |||
| isLoss bool | |||
| planAmount int64 | |||
| realAmount int64 | |||
| } | |||
| func newLimitResult(isLoss bool, planAmount int64, realAmount int64) limitResult { | |||
| return limitResult{ | |||
| isLoss: isLoss, | |||
| planAmount: planAmount, | |||
| realAmount: realAmount, | |||
| } | |||
| } | |||
| func newLimiterRunner(limitCode string, limitType models.LimitType, userId, amount int64, policy models.LimiterRejectPolicy) *limiterRunner { | |||
| return &limiterRunner{ | |||
| userId: userId, | |||
| amount: amount, | |||
| limitCode: limitCode, | |||
| limitType: limitType, | |||
| index: 0, | |||
| rejectPolicy: policy, | |||
| resultMap: make(map[int]limitResult, 0), | |||
| } | |||
| } | |||
| //Run run all limiters | |||
| //return real used amount(when choose the FillUp reject policy, amount may only be partially used) | |||
| func (l *limiterRunner) Run() error { | |||
| if err := l.LoadLimiters(); err != nil { | |||
| return err | |||
| } | |||
| l.minRealAmount = l.amount | |||
| for l.index < len(l.limiters) { | |||
| err := l.limit(l.limiters[l.index]) | |||
| if err != nil { | |||
| log.Info("limiter check failed,%v", err) | |||
| l.Rollback() | |||
| return err | |||
| } | |||
| result := l.resultMap[l.index] | |||
| if result.isLoss { | |||
| //find the minimum real amount | |||
| if l.minRealAmount > result.realAmount { | |||
| l.minRealAmount = result.realAmount | |||
| } | |||
| } | |||
| l.index += 1 | |||
| } | |||
| //post process | |||
| l.PostProcess() | |||
| return nil | |||
| } | |||
| //Rollback rollback the usedNum from limiters[0] to limiters[index] | |||
| func (l *limiterRunner) Rollback() error { | |||
| for i := l.index - 1; i >= 0; i-- { | |||
| l.rollback(l.limiters[i], l.resultMap[i]) | |||
| } | |||
| return nil | |||
| } | |||
| func (l *limiterRunner) rollback(r models.LimitConfig, result limitResult) error { | |||
| p, err := period.GetPeriod(r.RefreshRate) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| redisKey := redis_key.LimitCount(l.userId, r.LimitCode, r.LimitType, r.Scope, p) | |||
| redis_client.IncrBy(redisKey, -1*result.realAmount) | |||
| return nil | |||
| } | |||
| //PostProcess process loss,if realAmount < planAmount | |||
| func (l *limiterRunner) PostProcess() error { | |||
| for i := l.index - 1; i >= 0; i-- { | |||
| l.postProcess(l.limiters[i], l.resultMap[i]) | |||
| } | |||
| return nil | |||
| } | |||
| func (l *limiterRunner) postProcess(r models.LimitConfig, result limitResult) error { | |||
| if result.realAmount == l.minRealAmount { | |||
| return nil | |||
| } | |||
| p, err := period.GetPeriod(r.RefreshRate) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| diff := result.realAmount - l.minRealAmount | |||
| redisKey := redis_key.LimitCount(l.userId, r.LimitCode, r.LimitType, r.Scope, p) | |||
| redis_client.IncrBy(redisKey, -1*diff) | |||
| return nil | |||
| } | |||
| func (l *limiterRunner) limit(r models.LimitConfig) error { | |||
| p, err := period.GetPeriod(r.RefreshRate) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| redisKey := redis_key.LimitCount(l.userId, r.LimitCode, r.LimitType, r.Scope, p) | |||
| usedNum, err := redis_client.IncrBy(redisKey, l.amount) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| //if usedNum equals amount,it is the first operation in period or redis cache deleted | |||
| //count in database to distinguish the two cases | |||
| if usedNum == l.amount { | |||
| n, err := l.countInPeriod(r, p) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if n > 0 { | |||
| //means redis cache deleted,incr the cache with real value | |||
| usedNum, err = redis_client.IncrBy(redisKey, n) | |||
| } | |||
| if p != nil { | |||
| redis_client.Expire(redisKey, p.LeftTime) | |||
| } else { | |||
| //add default expire time if no period set | |||
| redis_client.Expire(redisKey, 24*time.Hour) | |||
| } | |||
| } | |||
| if usedNum > r.LimitNum { | |||
| if usedNum-r.LimitNum >= l.amount { | |||
| redis_client.IncrBy(redisKey, -1*l.amount) | |||
| return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID)) | |||
| } | |||
| switch l.rejectPolicy { | |||
| case models.FillUp: | |||
| exceed := usedNum - r.LimitNum | |||
| realAmount := l.amount - exceed | |||
| redis_client.IncrBy(redisKey, -1*exceed) | |||
| l.resultMap[l.index] = newLimitResult(true, l.amount, realAmount) | |||
| return nil | |||
| case models.JustReject: | |||
| redis_client.IncrBy(redisKey, -1*l.amount) | |||
| return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID)) | |||
| case models.PermittedOnce: | |||
| l.resultMap[l.index] = newLimitResult(false, l.amount, l.amount) | |||
| return nil | |||
| } | |||
| } | |||
| l.resultMap[l.index] = newLimitResult(false, l.amount, l.amount) | |||
| return nil | |||
| } | |||
| func (l *limiterRunner) LoadLimiters() error { | |||
| limiters, err := GetLimiters(l.limitCode, l.limitType) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if limiters != nil { | |||
| l.limiters = limiters | |||
| } | |||
| return nil | |||
| } | |||
| func (l *limiterRunner) countInPeriod(r models.LimitConfig, p *models.PeriodResult) (int64, error) { | |||
| switch r.LimitType { | |||
| case models.LimitTypeTask.Name(): | |||
| return models.CountTaskAccomplishLogInTaskPeriod(r.LimitCode, l.userId, p) | |||
| case models.LimitTypeRewardPoint.Name(): | |||
| return models.SumRewardAmountInTaskPeriod(models.RewardTypePoint.Name(), r.LimitCode, l.userId, p) | |||
| default: | |||
| return 0, nil | |||
| } | |||
| } | |||
| func CheckLimit(limitCode string, limitType models.LimitType, userId, amount int64, rejectPolicy models.LimiterRejectPolicy) (int64, error) { | |||
| if rejectPolicy == "" { | |||
| rejectPolicy = models.JustReject | |||
| } | |||
| r := newLimiterRunner(limitCode, limitType, userId, amount, rejectPolicy) | |||
| err := r.Run() | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| return r.minRealAmount, nil | |||
| } | |||
| func GetLimiters(limitCode string, limitType models.LimitType) ([]models.LimitConfig, error) { | |||
| limiters, err := GetLimitersByLimitType(limitType) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| result := make([]models.LimitConfig, 0) | |||
| for i, v := range limiters { | |||
| if v.LimitCode == "" || v.LimitCode == limitCode { | |||
| result = append(result, limiters[i]) | |||
| } | |||
| } | |||
| return result, nil | |||
| } | |||
| func GetLimitersByLimitType(limitType models.LimitType) ([]models.LimitConfig, error) { | |||
| redisKey := redis_key.LimitConfig(limitType.Name()) | |||
| val, _ := redis_client.Get(redisKey) | |||
| if val != "" { | |||
| if val == redis_key.EMPTY_REDIS_VAL { | |||
| return nil, nil | |||
| } | |||
| limiters := make([]models.LimitConfig, 0) | |||
| json.Unmarshal([]byte(val), &limiters) | |||
| return limiters, nil | |||
| } | |||
| limiters, err := models.GetLimitConfigByLimitType(limitType) | |||
| if err != nil { | |||
| if models.IsErrRecordNotExist(err) { | |||
| redis_client.Setex(redisKey, redis_key.EMPTY_REDIS_VAL, 5*time.Second) | |||
| return nil, nil | |||
| } | |||
| return nil, err | |||
| } | |||
| jsonStr, _ := json.Marshal(limiters) | |||
| redis_client.Setex(redisKey, string(jsonStr), 30*24*time.Hour) | |||
| return limiters, nil | |||
| } | |||
| func GetLimitersByRelatedIdWithDeleted(limitType models.LimitType) ([]models.LimitConfig, error) { | |||
| limiters, err := models.GetLimitersByRelatedIdWithDeleted(limitType) | |||
| if err != nil { | |||
| if models.IsErrRecordNotExist(err) { | |||
| return nil, nil | |||
| } | |||
| return nil, err | |||
| } | |||
| return limiters, nil | |||
| } | |||
| @@ -0,0 +1,54 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "encoding/json" | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| func NotifyRewardOperation(userId, amount int64, sourceType models.SourceType, rewardType models.RewardType, operateType models.RewardOperateType) { | |||
| switch sourceType { | |||
| case models.SourceTypeRunCloudbrainTask: | |||
| return | |||
| } | |||
| data := &models.UserRewardOperationRedis{ | |||
| UserId: userId, | |||
| Amount: amount, | |||
| RewardType: rewardType, | |||
| OperateType: operateType, | |||
| } | |||
| b, _ := json.Marshal(data) | |||
| redis_client.ZAdd(redis_key.RewardOperateNotification(), string(b), float64(time.Now().Unix())) | |||
| } | |||
| func GetRewardOperation(since, until timeutil.TimeStamp) []models.UserRewardOperation { | |||
| list, err := redis_client.ZRangeByScore(redis_key.RewardOperateNotification(), float64(since), float64(until)) | |||
| if err != nil { | |||
| log.Error("GetRewardOperation ZRangeByScore error. %v", err) | |||
| return nil | |||
| } | |||
| if len(list) == 0 { | |||
| log.Debug("GetRewardOperation list length = 0") | |||
| return nil | |||
| } | |||
| r := make([]models.UserRewardOperation, len(list)) | |||
| for _, v := range list { | |||
| t := models.UserRewardOperationRedis{} | |||
| json.Unmarshal([]byte(v), &t) | |||
| r = append(r, models.UserRewardOperation{ | |||
| UserId: t.UserId, | |||
| Msg: v, | |||
| }) | |||
| } | |||
| redis_client.ZRemRangeByScore(redis_key.RewardOperateNotification(), float64(since), float64(until)) | |||
| return r | |||
| } | |||
| func GetRewardOperateMsg(u models.UserRewardOperationRedis) string { | |||
| return u.OperateType.Show() + fmt.Sprint(u.Amount) + u.RewardType.Show() | |||
| } | |||
| @@ -0,0 +1,280 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "code.gitea.io/gitea/modules/redis/redis_lock" | |||
| "code.gitea.io/gitea/services/reward/point" | |||
| "errors" | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| var RewardOperatorMap = map[string]RewardOperator{ | |||
| fmt.Sprint(models.RewardTypePoint): new(point.PointOperator), | |||
| } | |||
| type RewardOperator interface { | |||
| IsLimited(ctx *models.RewardOperateContext) error | |||
| Operate(ctx *models.RewardOperateContext) error | |||
| } | |||
| func Operate(ctx *models.RewardOperateContext) error { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| if !checkRewardOperationParam(ctx) { | |||
| log.Error("send reward error,param incorrect") | |||
| return errors.New("param incorrect") | |||
| } | |||
| //add lock | |||
| var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardOperateLock(ctx.RequestId, ctx.SourceType.Name(), ctx.OperateType.Name())) | |||
| isOk, err := rewardLock.Lock(3 * time.Second) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if !isOk { | |||
| log.Info("duplicated reward request,targetUserId=%d requestId=%s", ctx.TargetUserId, ctx.RequestId) | |||
| return nil | |||
| } | |||
| defer rewardLock.UnLock() | |||
| //is handled before? | |||
| isHandled, err := isHandled(ctx.SourceType.Name(), ctx.RequestId, ctx.OperateType.Name()) | |||
| if err != nil { | |||
| log.Error("reward is handled error,%v", err) | |||
| return err | |||
| } | |||
| if isHandled { | |||
| log.Info("reward has been handled,ctx=%+v", ctx) | |||
| return nil | |||
| } | |||
| //get operator | |||
| operator := GetOperator(ctx.Reward.Type) | |||
| if operator == nil { | |||
| log.Error("operator of reward type is not exist,ctx=%v", ctx) | |||
| return errors.New("operator of reward type is not exist") | |||
| } | |||
| if ctx.OperateType == models.OperateTypeIncrease { | |||
| //is limited? | |||
| if err := operator.IsLimited(ctx); err != nil { | |||
| log.Info("operator IsLimited, err=%v", err) | |||
| return err | |||
| } | |||
| } | |||
| //new reward operate record | |||
| recordId, err := initRewardOperateRecord(ctx) | |||
| if err != nil { | |||
| log.Error("initRewardOperateRecord error,err=%v", err) | |||
| return err | |||
| } | |||
| ctx.SourceId = recordId | |||
| //operate | |||
| if err := operator.Operate(ctx); err != nil { | |||
| log.Error("operator Operate error,err=%v", err) | |||
| UpdateRewardRecordToFinalStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusFailed) | |||
| return err | |||
| } | |||
| UpdateRewardRecordToFinalStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusSucceeded) | |||
| NotifyRewardOperation(ctx.TargetUserId, ctx.Reward.Amount, ctx.SourceType, ctx.Reward.Type, ctx.OperateType) | |||
| return nil | |||
| } | |||
| func checkRewardOperationParam(ctx *models.RewardOperateContext) bool { | |||
| if ctx.Reward.Type == "" { | |||
| return false | |||
| } | |||
| return true | |||
| } | |||
| func GetOperator(rewardType models.RewardType) RewardOperator { | |||
| return RewardOperatorMap[rewardType.Name()] | |||
| } | |||
| func isHandled(sourceType string, requestId string, operateType string) (bool, error) { | |||
| _, err := models.GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId, operateType) | |||
| if err != nil { | |||
| log.Error("operator isHandled error. %v", err) | |||
| if models.IsErrRecordNotExist(err) { | |||
| return false, nil | |||
| } | |||
| log.Error("GetPointOperateRecordBySourceTypeAndRequestId ZRangeByScore error. %v", err) | |||
| return false, err | |||
| } | |||
| return true, nil | |||
| } | |||
| func initRewardOperateRecord(ctx *models.RewardOperateContext) (string, error) { | |||
| sn, err := generateOperateSerialNo() | |||
| if err != nil { | |||
| log.Error("generateOperateSerialNo error. %v", err) | |||
| return "", err | |||
| } | |||
| record := &models.RewardOperateRecord{ | |||
| UserId: ctx.TargetUserId, | |||
| Amount: ctx.Reward.Amount, | |||
| LossAmount: ctx.LossAmount, | |||
| RewardType: ctx.Reward.Type.Name(), | |||
| SourceType: ctx.SourceType.Name(), | |||
| SourceId: ctx.SourceId, | |||
| SourceTemplateId: ctx.SourceTemplateId, | |||
| RequestId: ctx.RequestId, | |||
| OperateType: ctx.OperateType.Name(), | |||
| Status: models.OperateStatusOperating, | |||
| Remark: ctx.Remark, | |||
| Title: ctx.Title, | |||
| SerialNo: sn, | |||
| } | |||
| _, err = models.InsertRewardOperateRecord(record) | |||
| if err != nil { | |||
| log.Error("InsertRewardOperateRecord error. %v", err) | |||
| return "", err | |||
| } | |||
| return record.SerialNo, nil | |||
| } | |||
| func createPeriodicRewardOperateRecord(ctx *models.StartPeriodicTaskOpts) (string, error) { | |||
| sn, err := generateOperateSerialNo() | |||
| if err != nil { | |||
| log.Error("createPeriodic generateOperateSerialNo error. %v", err) | |||
| return "", err | |||
| } | |||
| record := &models.RewardOperateRecord{ | |||
| UserId: ctx.TargetUserId, | |||
| Amount: 0, | |||
| RewardType: ctx.RewardType.Name(), | |||
| SourceType: ctx.SourceType.Name(), | |||
| SourceId: ctx.SourceId, | |||
| RequestId: ctx.RequestId, | |||
| OperateType: ctx.OperateType.Name(), | |||
| Status: models.OperateStatusOperating, | |||
| Remark: ctx.Remark, | |||
| Title: ctx.Title, | |||
| SerialNo: sn, | |||
| } | |||
| _, err = models.InsertRewardOperateRecord(record) | |||
| if err != nil { | |||
| log.Error("createPeriodic InsertRewardOperateRecord error. %v", err) | |||
| return "", err | |||
| } | |||
| return record.SerialNo, nil | |||
| } | |||
| func UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus string) error { | |||
| _, err := models.UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus) | |||
| if err != nil { | |||
| log.Error("UpdateRewardRecord UpdateRewardRecordToFinalStatus error. %v", err) | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| func StartPeriodicTaskAsyn(opts *models.StartPeriodicTaskOpts) { | |||
| go StartAndGetPeriodicTask(opts) | |||
| } | |||
| func StartAndGetPeriodicTask(opts *models.StartPeriodicTaskOpts) (*models.RewardPeriodicTask, error) { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| //add lock | |||
| var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardOperateLock(opts.RequestId, opts.SourceType.Name(), opts.OperateType.Name())) | |||
| isOk, err := rewardLock.Lock(3 * time.Second) | |||
| if err != nil { | |||
| log.Error("StartAndGetPeriodicTask RewardOperateLock error. %v", err) | |||
| return nil, err | |||
| } | |||
| if !isOk { | |||
| log.Info("duplicated operate request,targetUserId=%d requestId=%s", opts.TargetUserId, opts.RequestId) | |||
| return nil, nil | |||
| } | |||
| defer rewardLock.UnLock() | |||
| _, err = models.GetPointOperateRecordBySourceTypeAndRequestId(opts.SourceType.Name(), opts.RequestId, opts.OperateType.Name()) | |||
| if err == nil { | |||
| task, err := models.GetPeriodicTaskBySourceIdAndType(opts.SourceType, opts.SourceId, opts.OperateType) | |||
| if err != nil { | |||
| log.Error("GetPeriodicTaskBySourceIdAndType error,%v", err) | |||
| return nil, err | |||
| } | |||
| return task, nil | |||
| } | |||
| if err != nil && !models.IsErrRecordNotExist(err) { | |||
| log.Error("operate is handled error,%v", err) | |||
| return nil, err | |||
| } | |||
| //new reward operate record | |||
| recordId, err := createPeriodicRewardOperateRecord(opts) | |||
| if err != nil { | |||
| log.Error("StartAndGetPeriodicTask createPeriodicRewardOperateRecord error. %v", err) | |||
| return nil, err | |||
| } | |||
| if err = NewRewardPeriodicTask(recordId, opts); err != nil { | |||
| log.Error("StartAndGetPeriodicTask NewRewardPeriodicTask error. %v", err) | |||
| UpdateRewardRecordToFinalStatus(opts.SourceType.Name(), opts.RequestId, models.OperateStatusFailed) | |||
| return nil, err | |||
| } | |||
| task, err := models.GetPeriodicTaskBySourceIdAndType(opts.SourceType, opts.SourceId, opts.OperateType) | |||
| if err != nil { | |||
| log.Error("GetPeriodicTaskBySourceIdAndType error,%v", err) | |||
| return nil, err | |||
| } | |||
| return task, nil | |||
| } | |||
| func StopPeriodicTaskAsyn(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) { | |||
| go StopPeriodicTask(sourceType, sourceId, operateType) | |||
| } | |||
| func StopPeriodicTask(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) error { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| task, err := models.GetPeriodicTaskBySourceIdAndType(sourceType, sourceId, operateType) | |||
| if err != nil { | |||
| log.Error("StopPeriodicTask. GetPeriodicTaskBySourceIdAndType error. %v", err) | |||
| return err | |||
| } | |||
| if task == nil { | |||
| log.Info("Periodic task is not exist") | |||
| return nil | |||
| } | |||
| if task.Status == models.PeriodicTaskStatusFinished { | |||
| log.Info("Periodic task is finished") | |||
| return nil | |||
| } | |||
| now := time.Now() | |||
| RunRewardTask(*task, now) | |||
| return models.StopPeriodicTask(task.ID, task.OperateSerialNo, now) | |||
| } | |||
| func generateOperateSerialNo() (string, error) { | |||
| s, err := GetSerialNoByRedis() | |||
| if err != nil { | |||
| log.Error("generateOperateSerialNo error. %v", err) | |||
| return "", err | |||
| } | |||
| return s, nil | |||
| } | |||
| @@ -0,0 +1,131 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "code.gitea.io/gitea/modules/redis/redis_lock" | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "code.gitea.io/gitea/routers/repo" | |||
| "errors" | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| func NewRewardPeriodicTask(operateRecordId string, opts *models.StartPeriodicTaskOpts) error { | |||
| task := &models.RewardPeriodicTask{} | |||
| task.DelaySeconds = int64(opts.Delay.Seconds()) | |||
| task.IntervalSeconds = int64(opts.Interval.Seconds()) | |||
| task.Amount = int64(opts.UnitAmount) | |||
| task.OperateSerialNo = operateRecordId | |||
| task.Status = models.PeriodicTaskStatusRunning | |||
| task.NextExecuteTime = timeutil.TimeStamp(opts.StartTime.Add(opts.Delay).Unix()) | |||
| _, err := models.InsertPeriodicTask(task) | |||
| return err | |||
| } | |||
| func StartRewardTask() { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| log.Debug("try to run reward tasks") | |||
| now := time.Now() | |||
| taskList, err := models.GetRunningRewardTask(now) | |||
| if err != nil { | |||
| log.Error("GetRunningRewardTask error. %v", err) | |||
| return | |||
| } | |||
| if taskList == nil || len(taskList) == 0 { | |||
| log.Debug("No GetRunningRewardTask need handled") | |||
| return | |||
| } | |||
| for _, t := range taskList { | |||
| RunRewardTask(t, now) | |||
| } | |||
| } | |||
| func RunRewardTask(t models.RewardPeriodicTask, now time.Time) error { | |||
| lock := redis_lock.NewDistributeLock(redis_key.RewardTaskRunningLock(t.ID)) | |||
| isOk, _ := lock.LockWithWait(5*time.Second, 5*time.Second) | |||
| if !isOk { | |||
| log.Error("get RewardTaskRunningLock failed,t=%+v", t) | |||
| return errors.New("get RewardTaskRunningLock failed") | |||
| } | |||
| defer lock.UnLock() | |||
| record, err := models.GetPointOperateRecordBySerialNo(t.OperateSerialNo) | |||
| if err != nil { | |||
| log.Error("RunRewardTask. GetPointOperateRecordBySerialNo error. %v", err) | |||
| return errors.New("GetPointOperateRecordBySerialNo error") | |||
| } | |||
| if record.Status != models.OperateStatusOperating { | |||
| log.Info("RunRewardTask. operate record is finished,record=%+v", record) | |||
| return nil | |||
| } | |||
| n, _ := countExecuteTimes(t, now) | |||
| if n == 0 { | |||
| log.Info("countExecuteTimes result is 0") | |||
| return nil | |||
| } | |||
| //get operator | |||
| operator := GetOperator(models.GetRewardTypeInstance(record.RewardType)) | |||
| if operator == nil { | |||
| log.Error("RunRewardTask. operator of reward type is not exist") | |||
| return errors.New("operator of reward type is not exist") | |||
| } | |||
| nextTime := timeutil.TimeStamp(int64(t.NextExecuteTime) + t.IntervalSeconds) | |||
| log.Debug("RunRewardTask n=%d", n) | |||
| for i := 1; int64(i) <= n; i++ { | |||
| log.Debug("operator.Operate i=%d n=%d", i, n) | |||
| err = operator.Operate(&models.RewardOperateContext{ | |||
| SourceType: models.SourceTypeRunCloudbrainTask, | |||
| SourceId: t.OperateSerialNo, | |||
| Reward: models.Reward{ | |||
| Amount: t.Amount, | |||
| Type: models.GetRewardTypeInstance(record.RewardType), | |||
| }, | |||
| TargetUserId: record.UserId, | |||
| OperateType: models.GetRewardOperateTypeInstance(record.OperateType), | |||
| }) | |||
| if err != nil { | |||
| log.Error("RunRewardTask.operator operate error.%v", err) | |||
| if models.IsErrInsufficientPointsBalance(err) { | |||
| task, err := models.GetCloudbrainByID(record.SourceId) | |||
| if err != nil { | |||
| log.Error("RunRewardTask GetCloudbrainByID error. %v", err) | |||
| return err | |||
| } | |||
| repo.StopJobs([]*models.Cloudbrain{task}) | |||
| models.StopPeriodicTask(task.ID, t.OperateSerialNo, time.Now()) | |||
| return nil | |||
| } | |||
| return nil | |||
| } | |||
| models.IncrRewardTaskSuccessCount(t, 1, nextTime) | |||
| nextTime = timeutil.TimeStamp(int64(nextTime) + t.IntervalSeconds) | |||
| } | |||
| return nil | |||
| } | |||
| func countExecuteTimes(t models.RewardPeriodicTask, now time.Time) (int64, timeutil.TimeStamp) { | |||
| interval := t.IntervalSeconds | |||
| nextTime := int64(t.NextExecuteTime) | |||
| if nextTime > now.Unix() { | |||
| return 0, 0 | |||
| } | |||
| diff := now.Unix() - nextTime | |||
| var n int64 | |||
| if diff%interval == 0 { | |||
| n = diff / interval | |||
| } else { | |||
| n = diff/interval + 1 | |||
| } | |||
| newNextTime := timeutil.TimeStamp(nextTime + n*interval) | |||
| return n, newNextTime | |||
| } | |||
| @@ -0,0 +1,150 @@ | |||
| package account | |||
| import ( | |||
| "bytes" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "code.gitea.io/gitea/modules/redis/redis_lock" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/modules/util" | |||
| "encoding/json" | |||
| "strings" | |||
| "time" | |||
| ) | |||
| func GetAccount(userId int64) (*models.PointAccount, error) { | |||
| redisKey := redis_key.PointAccountInfo(userId) | |||
| val, _ := redis_client.Get(redisKey) | |||
| if val != "" { | |||
| account := &models.PointAccount{} | |||
| json.Unmarshal([]byte(val), account) | |||
| return account, nil | |||
| } | |||
| account, err := models.GetAccountByUserId(userId) | |||
| if err != nil { | |||
| if models.IsErrRecordNotExist(err) { | |||
| a, err := InitAccount(userId) | |||
| if err != nil { | |||
| log.Error("InitAccount error,err=%v", err) | |||
| return nil, err | |||
| } | |||
| return a, nil | |||
| } | |||
| log.Error("GetAccountByUserId error,err=%v", err) | |||
| return nil, err | |||
| } | |||
| jsonStr, _ := json.Marshal(account) | |||
| redis_client.Setex(redisKey, string(jsonStr), 24*time.Hour) | |||
| return account, nil | |||
| } | |||
| func InitAccount(userId int64) (*models.PointAccount, error) { | |||
| lock := redis_lock.NewDistributeLock(redis_key.PointAccountInitLock(userId)) | |||
| isOk, err := lock.LockWithWait(3*time.Second, 3*time.Second) | |||
| if err != nil { | |||
| log.Error("PointAccountInitLock error,err=%v", err) | |||
| return nil, err | |||
| } | |||
| if isOk { | |||
| defer lock.UnLock() | |||
| account, _ := models.GetAccountByUserId(userId) | |||
| if account == nil { | |||
| models.InsertAccount(&models.PointAccount{ | |||
| Balance: 0, | |||
| TotalEarned: 0, | |||
| TotalConsumed: 0, | |||
| UserId: userId, | |||
| Status: models.PointAccountNormal, | |||
| Version: 0, | |||
| AccountCode: util.UUID(), | |||
| }) | |||
| return models.GetAccountByUserId(userId) | |||
| } | |||
| return account, nil | |||
| } | |||
| return nil, nil | |||
| } | |||
| //IsPointBalanceEnough check whether the user's point balance is bigger than task unit price | |||
| func IsPointBalanceEnough(targetUserId int64, unitPrice int) bool { | |||
| if !setting.CloudBrainPaySwitch { | |||
| return true | |||
| } | |||
| if unitPrice == 0 { | |||
| return true | |||
| } | |||
| a, err := GetAccount(targetUserId) | |||
| if err != nil { | |||
| log.Error("IsPointBalanceEnough GetAccount error,err=%v", err) | |||
| return false | |||
| } | |||
| return a.Balance >= int64(unitPrice) | |||
| } | |||
| func SearchPointAccount(opt models.SearchPointAccountOpts) (*models.SearchPointAccountResponse, error) { | |||
| var result = &models.SearchPointAccountResponse{ | |||
| Records: make([]*models.UserPointAccount, 0), | |||
| PageSize: opt.PageSize, | |||
| Page: opt.Page, | |||
| Total: 0, | |||
| } | |||
| userSearch := &models.SearchUserOptions{ | |||
| Type: models.UserTypeIndividual, | |||
| ListOptions: models.ListOptions{ | |||
| PageSize: 20, | |||
| }, | |||
| SearchByEmail: true, | |||
| OrderBy: models.SearchOrderByAlphabetically, | |||
| } | |||
| userSearch.Page = opt.Page | |||
| if userSearch.Page <= 0 { | |||
| userSearch.Page = 1 | |||
| } | |||
| userSearch.Keyword = strings.Trim(opt.Keyword, " ") | |||
| if len(userSearch.Keyword) == 0 || isKeywordValid(userSearch.Keyword) { | |||
| users, count, err := models.SearchUsers(userSearch) | |||
| if err != nil { | |||
| log.Error("SearchPointAccount SearchUsers error.%v", err) | |||
| return nil, err | |||
| } | |||
| userIds := make([]int64, 0) | |||
| for _, v := range users { | |||
| userIds = append(userIds, v.ID) | |||
| } | |||
| accountMap, err := models.GetPointAccountMapByUserIds(userIds) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| records := make([]*models.UserPointAccount, 0) | |||
| for _, v := range users { | |||
| upa := &models.UserPointAccount{ | |||
| UserId: v.ID, | |||
| UserName: v.Name, | |||
| Email: v.Email, | |||
| Balance: 0, | |||
| TotalEarned: 0, | |||
| TotalConsumed: 0, | |||
| } | |||
| a := accountMap[v.ID] | |||
| if a != nil { | |||
| upa.Balance = a.Balance | |||
| upa.TotalConsumed = a.TotalConsumed | |||
| upa.TotalEarned = a.TotalEarned | |||
| } | |||
| records = append(records, upa) | |||
| } | |||
| result.Records = records | |||
| result.Total = count | |||
| } | |||
| return result, nil | |||
| } | |||
| func isKeywordValid(keyword string) bool { | |||
| return !bytes.Contains([]byte(keyword), []byte{0x00}) | |||
| } | |||
| @@ -0,0 +1,65 @@ | |||
| package point | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "code.gitea.io/gitea/modules/redis/redis_lock" | |||
| "code.gitea.io/gitea/services/reward/limiter" | |||
| "code.gitea.io/gitea/services/reward/point/account" | |||
| "errors" | |||
| "time" | |||
| ) | |||
| type PointOperator struct { | |||
| } | |||
| func (operator *PointOperator) IsLimited(ctx *models.RewardOperateContext) error { | |||
| realAmount, err := limiter.CheckLimit(ctx.SourceType.Name(), models.LimitTypeRewardPoint, ctx.TargetUserId, ctx.Reward.Amount, ctx.RejectPolicy) | |||
| if err != nil { | |||
| log.Error("PointOperator IsLimited error,err=%v", err) | |||
| return err | |||
| } | |||
| if realAmount < ctx.Reward.Amount { | |||
| ctx.LossAmount = ctx.Reward.Amount - realAmount | |||
| ctx.Reward.Amount = realAmount | |||
| } | |||
| return nil | |||
| } | |||
| func (operator *PointOperator) Operate(ctx *models.RewardOperateContext) error { | |||
| lock := redis_lock.NewDistributeLock(redis_key.PointAccountOperateLock(ctx.TargetUserId)) | |||
| isOk, err := lock.LockWithWait(3*time.Second, 3*time.Second) | |||
| if err != nil { | |||
| log.Error("Get PointAccountOperateLock error,err=%v", err) | |||
| return err | |||
| } | |||
| if isOk { | |||
| defer lock.UnLock() | |||
| na, err := account.GetAccount(ctx.TargetUserId) | |||
| if err != nil || na == nil { | |||
| log.Error("operator get account error error,err=%v", err) | |||
| return errors.New("get account error") | |||
| } | |||
| if ctx.OperateType == models.OperateTypeIncrease { | |||
| err = na.Increase(ctx.Reward.Amount, ctx.SourceId) | |||
| } else if ctx.OperateType == models.OperateTypeDecrease { | |||
| if !ctx.PermittedNegative && na.Balance < ctx.Reward.Amount { | |||
| log.Info("account balance is not enough,ctx=%v", ctx) | |||
| return models.ErrInsufficientPointsBalance{} | |||
| } | |||
| err = na.Decrease(ctx.Reward.Amount, ctx.SourceId) | |||
| } | |||
| if err != nil { | |||
| log.Error("operate account balance error,err=%v", err) | |||
| return err | |||
| } | |||
| redis_client.Del(redis_key.PointAccountInfo(ctx.TargetUserId)) | |||
| } else { | |||
| log.Error("Get account operate lock failed,ctx=%v", ctx) | |||
| return errors.New("Get account operate lock failed") | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| ) | |||
| type RecordResponse struct { | |||
| Records []*models.RewardOperateRecordShow | |||
| Total int64 | |||
| PageSize int | |||
| Page int | |||
| } | |||
| func GetRewardRecordList(opts *models.RewardRecordListOpts) (*RecordResponse, error) { | |||
| var l models.RewardRecordShowList | |||
| var n int64 | |||
| var err error | |||
| if opts.IsAdmin { | |||
| l, n, err = models.GetAdminRewardRecordShowList(opts) | |||
| } else { | |||
| l, n, err = models.GetRewardRecordShowList(opts) | |||
| } | |||
| if err != nil { | |||
| log.Error("GetRewardRecordList error. %v", err) | |||
| return nil, err | |||
| } | |||
| if len(l) == 0 { | |||
| return &RecordResponse{Records: make([]*models.RewardOperateRecordShow, 0), Total: n, Page: opts.Page, PageSize: opts.PageSize}, nil | |||
| } | |||
| return &RecordResponse{Records: l, Total: n, Page: opts.Page, PageSize: opts.PageSize}, nil | |||
| } | |||
| func handleRecordResponse(opts *models.RewardRecordListOpts, list models.RewardRecordShowList) { | |||
| if opts.IsAdmin { | |||
| for _, v := range list { | |||
| v.UserName = opts.UserName | |||
| } | |||
| } else { | |||
| for _, v := range list { | |||
| if v.Cloudbrain != nil { | |||
| v.Cloudbrain.AiCenter = "" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| package reward | |||
| import ( | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "fmt" | |||
| "math/rand" | |||
| "time" | |||
| ) | |||
| func GetSerialNoByRedis() (string, error) { | |||
| now := time.Now() | |||
| r := int64(rand.Intn(3)) + 1 | |||
| n, err := redis_client.IncrBy(redis_key.RewardSerialCounter(now), r) | |||
| if err != nil { | |||
| log.Error("GetSerialNoByRedis RewardSerialCounter error. %v", err) | |||
| return "", err | |||
| } | |||
| if n == r { | |||
| redis_client.Expire(redis_key.RewardSerialCounter(now), 2*time.Minute) | |||
| } | |||
| //when the counter n exceeds 1000, the length of the serial number will become longer | |||
| if n >= 1000 { | |||
| return now.Format("200601021504") + fmt.Sprintf("%d", n) + fmt.Sprint(rand.Intn(10)), nil | |||
| } | |||
| return now.Format("200601021504") + fmt.Sprintf("%03d", n) + fmt.Sprint(rand.Intn(10)), nil | |||
| } | |||
| @@ -0,0 +1,50 @@ | |||
| package period | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "errors" | |||
| "time" | |||
| ) | |||
| var PeriodHandlerMap = map[string]PeriodHandler{ | |||
| models.PeriodNotCycle: new(NoCycleHandler), | |||
| models.PeriodDaily: new(DailyHandler), | |||
| } | |||
| type PeriodHandler interface { | |||
| GetCurrentPeriod() *models.PeriodResult | |||
| } | |||
| type NoCycleHandler struct { | |||
| } | |||
| func (l *NoCycleHandler) GetCurrentPeriod() *models.PeriodResult { | |||
| return nil | |||
| } | |||
| type DailyHandler struct { | |||
| } | |||
| func (l *DailyHandler) GetCurrentPeriod() *models.PeriodResult { | |||
| t := time.Now() | |||
| startTime := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) | |||
| endTime := startTime.Add(24 * time.Hour) | |||
| leftTime := endTime.Sub(t) | |||
| return &models.PeriodResult{ | |||
| StartTime: startTime, | |||
| EndTime: endTime, | |||
| LeftTime: leftTime, | |||
| } | |||
| } | |||
| func getPeriodHandler(refreshRateype string) PeriodHandler { | |||
| return PeriodHandlerMap[refreshRateype] | |||
| } | |||
| func GetPeriod(refreshRate string) (*models.PeriodResult, error) { | |||
| handler := getPeriodHandler(refreshRate) | |||
| if handler == nil { | |||
| return nil, errors.New("task config incorrect") | |||
| } | |||
| return handler.GetCurrentPeriod(), nil | |||
| } | |||
| @@ -0,0 +1,111 @@ | |||
| package task | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/services/reward" | |||
| "code.gitea.io/gitea/services/reward/limiter" | |||
| "fmt" | |||
| ) | |||
| func Accomplish(action models.Action) { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| action.OpType = models.GetTaskOptType(action) | |||
| switch action.OpType { | |||
| //only creating public repo can be rewarded | |||
| case models.ActionCreateRepo: | |||
| if action.Repo.IsPrivate { | |||
| return | |||
| } | |||
| //only creating public image can be rewarded | |||
| case models.ActionCreateImage: | |||
| if action.IsPrivate { | |||
| return | |||
| } | |||
| case models.ActionBindWechat: | |||
| n, err := models.CountWechatBindLog(action.Content, models.WECHAT_BIND) | |||
| if err != nil { | |||
| log.Error("CountWechatBindLog error when accomplish task,err=%v", err) | |||
| return | |||
| } | |||
| //if wechatOpenId has been bound before,the action can not get reward | |||
| if n > 1 { | |||
| log.Debug("the wechat account has been bound before,wechatOpenId = %s", action.Content) | |||
| return | |||
| } | |||
| } | |||
| go accomplish(action) | |||
| } | |||
| func accomplish(action models.Action) error { | |||
| defer func() { | |||
| if err := recover(); err != nil { | |||
| combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
| log.Error("PANIC:%v", combinedErr) | |||
| } | |||
| }() | |||
| userId := action.ActUserID | |||
| taskType := fmt.Sprint(action.OpType) | |||
| //get task config | |||
| config, err := GetTaskConfig(taskType) | |||
| if err != nil { | |||
| log.Error("GetTaskConfig error,%v", err) | |||
| return err | |||
| } | |||
| if config == nil { | |||
| log.Info("task config not exist,userId=%d taskType=%s", userId, taskType) | |||
| return nil | |||
| } | |||
| //is limited? | |||
| if isLimited(userId, config, models.JustReject) { | |||
| log.Info("task accomplish maximum times are reached,userId=%d taskType=%s", userId, taskType) | |||
| return nil | |||
| } | |||
| //add log | |||
| _, err = models.InsertTaskAccomplishLog(&models.TaskAccomplishLog{ | |||
| ConfigId: config.ID, | |||
| TaskCode: config.TaskCode, | |||
| UserId: userId, | |||
| ActionId: action.ID, | |||
| }) | |||
| if err != nil { | |||
| log.Error("InsertTaskAccomplishLog error,%v", err) | |||
| return err | |||
| } | |||
| //reward | |||
| reward.Operate(&models.RewardOperateContext{ | |||
| SourceType: models.SourceTypeAccomplishTask, | |||
| SourceId: fmt.Sprint(action.ID), | |||
| SourceTemplateId: fmt.Sprint(action.OpType), | |||
| Title: config.Title, | |||
| Reward: models.Reward{ | |||
| Amount: config.AwardAmount, | |||
| Type: models.GetRewardTypeInstance(config.AwardType), | |||
| }, | |||
| TargetUserId: userId, | |||
| RequestId: fmt.Sprint(action.ID), | |||
| OperateType: models.OperateTypeIncrease, | |||
| RejectPolicy: models.FillUp, | |||
| }) | |||
| log.Debug("accomplish success,action=%v", action) | |||
| return nil | |||
| } | |||
| func isLimited(userId int64, config *models.TaskConfig, rejectPolicy models.LimiterRejectPolicy) bool { | |||
| if _, err := limiter.CheckLimit(config.TaskCode, models.LimitTypeTask, userId, 1, rejectPolicy); err != nil { | |||
| log.Error(" isLimited CheckLimit error. %v", err) | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| @@ -0,0 +1,183 @@ | |||
| package task | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "code.gitea.io/gitea/modules/redis/redis_lock" | |||
| "encoding/json" | |||
| "errors" | |||
| "time" | |||
| ) | |||
| //GetTaskConfig get task config from redis cache first | |||
| // if not exist in redis, find in db and refresh the redis key | |||
| func GetTaskConfig(taskType string) (*models.TaskConfig, error) { | |||
| list, err := GetTaskConfigList() | |||
| if err != nil { | |||
| log.Error(" GetTaskConfigList error. %v", err) | |||
| return nil, err | |||
| } | |||
| for _, v := range list { | |||
| if v.TaskCode == taskType { | |||
| return v, nil | |||
| } | |||
| } | |||
| return nil, nil | |||
| } | |||
| func GetTaskConfigList() ([]*models.TaskConfig, error) { | |||
| redisKey := redis_key.TaskConfigList() | |||
| configStr, _ := redis_client.Get(redisKey) | |||
| if configStr != "" { | |||
| if configStr == redis_key.EMPTY_REDIS_VAL { | |||
| return nil, nil | |||
| } | |||
| config := make([]*models.TaskConfig, 0) | |||
| json.Unmarshal([]byte(configStr), &config) | |||
| return config, nil | |||
| } | |||
| config, err := models.GetTaskConfigList() | |||
| if err != nil { | |||
| log.Error(" GetTaskConfigList from model error. %v", err) | |||
| if models.IsErrRecordNotExist(err) { | |||
| redis_client.Setex(redisKey, redis_key.EMPTY_REDIS_VAL, 5*time.Second) | |||
| return nil, nil | |||
| } | |||
| return nil, err | |||
| } | |||
| jsonStr, _ := json.Marshal(config) | |||
| redis_client.Setex(redisKey, string(jsonStr), 30*24*time.Hour) | |||
| return config, nil | |||
| } | |||
| func GetTaskConfigPageWithDeleted(opt models.GetTaskConfigOpts) ([]*models.TaskAndLimiterConfig, int64, error) { | |||
| config, count, err := models.GetTaskConfigPageWithDeleted(opt) | |||
| if err != nil { | |||
| log.Error(" GetTaskConfigPageWithDeleted from model error. %v", err) | |||
| if models.IsErrRecordNotExist(err) { | |||
| return nil, 0, nil | |||
| } | |||
| return nil, 0, err | |||
| } | |||
| return config, count, nil | |||
| } | |||
| func GetTaskConfigWithLimitList(opt models.GetTaskConfigOpts) (*models.TaskConfigWithLimitResponse, error) { | |||
| list, n, err := GetTaskConfigPageWithDeleted(opt) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if len(list) == 0 { | |||
| return nil, nil | |||
| } | |||
| r := make([]*models.TaskConfigWithSingleLimit, 0) | |||
| for i := 0; i < len(list); i++ { | |||
| li := list[i] | |||
| t := &models.TaskConfigWithSingleLimit{ | |||
| ID: li.TaskConfig.ID, | |||
| TaskCode: li.TaskConfig.TaskCode, | |||
| AwardType: li.TaskConfig.AwardType, | |||
| AwardAmount: li.TaskConfig.AwardAmount, | |||
| Creator: li.TaskConfig.CreatorName, | |||
| CreatedUnix: li.TaskConfig.CreatedUnix, | |||
| IsDeleted: li.TaskConfig.DeletedAt > 0, | |||
| DeleteAt: li.TaskConfig.DeletedAt, | |||
| LimitNum: li.LimitConfig.LimitNum, | |||
| RefreshRate: li.LimitConfig.RefreshRate, | |||
| } | |||
| r = append(r, t) | |||
| } | |||
| return &models.TaskConfigWithLimitResponse{ | |||
| Records: r, | |||
| Page: opt.Page, | |||
| PageSize: opt.PageSize, | |||
| Total: n, | |||
| }, nil | |||
| } | |||
| func AddTaskConfig(config models.TaskConfigWithLimit, doer *models.User) error { | |||
| if config.TaskCode == "" || config.AwardType == "" { | |||
| log.Error(" EditTaskConfig param error") | |||
| return errors.New("param error") | |||
| } | |||
| var lock = redis_lock.NewDistributeLock(redis_key.TaskConfigOperateLock(config.TaskCode, config.AwardType)) | |||
| isOk, _ := lock.LockWithWait(3*time.Second, 3*time.Second) | |||
| if !isOk { | |||
| return errors.New("Get lock failed") | |||
| } | |||
| defer lock.UnLock() | |||
| t, err := models.GetTaskConfigByTaskCode(config.TaskCode) | |||
| if err != nil && !models.IsErrRecordNotExist(err) { | |||
| return err | |||
| } | |||
| if t != nil { | |||
| return errors.New("task config is exist") | |||
| } | |||
| for i, l := range config.Limiters { | |||
| if l.Scope == "" { | |||
| config.Limiters[i].Scope = models.LimitScopeSingleUser.Name() | |||
| } | |||
| } | |||
| err = models.NewTaskConfig(config, doer) | |||
| if err != nil { | |||
| log.Error("add task config error,config:%v err:%v", config, err) | |||
| return err | |||
| } | |||
| redis_client.Del(redis_key.LimitConfig(models.LimitTypeTask.Name())) | |||
| redis_client.Del(redis_key.TaskConfigList()) | |||
| return nil | |||
| } | |||
| func EditTaskConfig(config models.TaskConfigWithLimit, doer *models.User) error { | |||
| if config.TaskCode == "" || config.AwardType == "" || config.ID <= 0 { | |||
| log.Error(" EditTaskConfig param error") | |||
| return errors.New("param error") | |||
| } | |||
| var lock = redis_lock.NewDistributeLock(redis_key.TaskConfigOperateLock(config.TaskCode, config.AwardType)) | |||
| isOk, _ := lock.LockWithWait(3*time.Second, 3*time.Second) | |||
| if !isOk { | |||
| return errors.New("Get lock failed") | |||
| } | |||
| defer lock.UnLock() | |||
| t, err := models.GetTaskConfigByID(config.ID) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if t == nil { | |||
| return errors.New("task config is not exist") | |||
| } | |||
| for i, l := range config.Limiters { | |||
| if l.Scope == "" { | |||
| config.Limiters[i].Scope = models.LimitScopeSingleUser.Name() | |||
| } | |||
| } | |||
| err = models.EditTaskConfig(config, doer) | |||
| if err != nil { | |||
| log.Error("add task config error,config:%v err:%v", config, err) | |||
| return err | |||
| } | |||
| redis_client.Del(redis_key.LimitConfig(models.LimitTypeTask.Name())) | |||
| redis_client.Del(redis_key.TaskConfigList()) | |||
| return nil | |||
| } | |||
| func DelTaskConfig(id int64, doer *models.User) error { | |||
| if id == 0 { | |||
| log.Error(" EditTaskConfig param error") | |||
| return errors.New("param error") | |||
| } | |||
| err := models.DelTaskConfig(id, doer) | |||
| if err != nil { | |||
| log.Error("del task config error,err:%v", err) | |||
| return err | |||
| } | |||
| redis_client.Del(redis_key.LimitConfig(models.LimitTypeTask.Name())) | |||
| redis_client.Del(redis_key.TaskConfigList()) | |||
| return nil | |||
| } | |||
| @@ -1,6 +1,9 @@ | |||
| package wechat | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/auth/wechat" | |||
| "code.gitea.io/gitea/modules/notification" | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "encoding/json" | |||
| @@ -142,22 +145,26 @@ func HandleScanEvent(we WechatMsg) string { | |||
| if val == "" { | |||
| return "" | |||
| } | |||
| qrCache := new(QRCode4BindCache) | |||
| qrCache := new(wechat.QRCode4BindCache) | |||
| json.Unmarshal([]byte(val), qrCache) | |||
| if qrCache.Status == BIND_STATUS_UNBIND { | |||
| err := BindWechat(qrCache.UserId, we.FromUserName) | |||
| if qrCache.Status == wechat.BIND_STATUS_UNBIND { | |||
| err := wechat.BindWechat(qrCache.UserId, we.FromUserName) | |||
| if err != nil { | |||
| if err, ok := err.(WechatBindError); ok { | |||
| if err, ok := err.(wechat.WechatBindError); ok { | |||
| return err.Reply | |||
| } | |||
| return BIND_REPLY_FAILED_DEFAULT | |||
| return wechat.BIND_REPLY_FAILED_DEFAULT | |||
| } | |||
| qrCache.Status = BIND_STATUS_BOUND | |||
| qrCache.Status = wechat.BIND_STATUS_BOUND | |||
| jsonStr, _ := json.Marshal(qrCache) | |||
| redis_client.Setex(redis_key.WechatBindingUserIdKey(sceneStr), string(jsonStr), 60*time.Second) | |||
| } | |||
| u, err := models.GetUserByID(qrCache.UserId) | |||
| if err == nil { | |||
| notification.NotifyWechatBind(u, we.FromUserName) | |||
| } | |||
| return BIND_REPLY_SUCCESS | |||
| return wechat.BIND_REPLY_SUCCESS | |||
| } | |||
| func HandleSubscribeEvent(we WechatMsg) *WechatReplyContent { | |||
| @@ -20,7 +20,7 @@ | |||
| <div class="column ui vertical text menu"> | |||
| <div class="header item">{{.i18n.Tr "custom.foot.help"}}</div> | |||
| <div class="ui language bottom floating slide up dropdown link item"> | |||
| <i class="world icon"></i> | |||
| <i class="globe icon"></i> | |||
| <div class="text">{{.LangName}}</div> | |||
| <div class="menu"> | |||
| {{range .AllLangs}} | |||
| @@ -29,12 +29,12 @@ | |||
| </div> | |||
| </div> | |||
| <a href="https://git.openi.org.cn/zeizei/OpenI_Learning" class=" item a_margin" target="_blank"><i class="ri-creative-commons-by-line footer_icon" ></i><p class="footer_icon">{{.i18n.Tr "custom.Platform_Tutorial"}}</p> </a> | |||
| {{if .EnableSwagger}}<a href="/api/swagger" class=" item a_margin"><i class="ri-exchange-line footer_icon" > </i><p class="footer_icon">API</p> </a>{{end}} | |||
| <a href="https://git.openi.org.cn/zeizei/OpenI_Learning" class="item" target="_blank"><i class="compass icon" ></i> {{.i18n.Tr "custom.Platform_Tutorial"}}</a> | |||
| {{if .EnableSwagger}}<a href="/api/swagger" class="item"><i class="plug icon"></i> API</a>{{end}} | |||
| {{if .IsSigned}} | |||
| <a href="https://git.openi.org.cn/zeizei/OpenI_Learning/issues/new" class=" item a_margin" target="_blank"><i class="ri-mail-send-line footer_icon"></i><p class="footer_icon">{{.i18n.Tr "custom.foot.advice_feedback"}}</p></a> | |||
| <a href="https://git.openi.org.cn/zeizei/OpenI_Learning/issues/new" class="item" target="_blank"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a> | |||
| {{else}} | |||
| <a href="{{AppSubUrl}}/user/login" class=" item a_margin" ><i class="ri-mail-send-line footer_icon" ></i><p class="footer_icon">{{.i18n.Tr "custom.foot.advice_feedback"}}</p></a> | |||
| <a href="{{AppSubUrl}}/user/login" class="item"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a> | |||
| {{end}} | |||
| {{template "custom/extra_links_footer" .}} | |||
| @@ -18,7 +18,7 @@ | |||
| <div class="column ui vertical text menu"> | |||
| <div class="header item">{{.i18n.Tr "custom.foot.help"}}</div> | |||
| <div class="ui language bottom floating slide up dropdown link item"> | |||
| <i class="world icon"></i> | |||
| <i class="globe icon"></i> | |||
| <div class="text">{{.LangName}}</div> | |||
| <div class="menu"> | |||
| {{range .AllLangs}} | |||
| @@ -26,12 +26,12 @@ | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <a href="https://git.openi.org.cn/zeizei/OpenI_Learning" class=" item a_margin" target="_blank"><i class="ri-creative-commons-by-line footer_icon" ></i><p class="footer_icon">{{.i18n.Tr "custom.Platform_Tutorial"}}</p> </a> | |||
| {{if .EnableSwagger}}<a href="/api/swagger" class=" item a_margin"><i class="ri-exchange-line footer_icon" > </i><p class="footer_icon">API</p> </a>{{end}} | |||
| <a href="https://git.openi.org.cn/zeizei/OpenI_Learning" class="item" target="_blank"><i class="compass icon"></i> {{.i18n.Tr "custom.Platform_Tutorial"}} </a> | |||
| {{if .EnableSwagger}}<a href="/api/swagger" class="item"><i class="plug icon" ></i> API</a>{{end}} | |||
| {{if .IsSigned}} | |||
| <a href="https://git.openi.org.cn/zeizei/OpenI_Learning/issues/new" class=" item a_margin" target="_blank"><i class="ri-mail-send-line footer_icon"></i><p class="footer_icon">{{.i18n.Tr "custom.foot.advice_feedback"}}</p></a> | |||
| <a href="https://git.openi.org.cn/zeizei/OpenI_Learning/issues/new" class="item" target="_blank"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a> | |||
| {{else}} | |||
| <a href="{{AppSubUrl}}/user/login" class=" item a_margin" ><i class="ri-mail-send-line footer_icon" ></i><p class="footer_icon footer_icon">{{.i18n.Tr "custom.foot.advice_feedback"}}</p></a> | |||
| <a href="{{AppSubUrl}}/user/login" class="item"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a> | |||
| {{end}} | |||
| {{template "custom/extra_links_footer" .}} | |||
| </div> | |||
| @@ -27,7 +27,8 @@ | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> | |||
| <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -59,7 +60,8 @@ | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a> | |||
| <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -28,6 +28,7 @@ | |||
| <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -59,6 +60,7 @@ | |||
| <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -19,7 +19,8 @@ | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> | |||
| <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -50,7 +51,8 @@ | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a> | |||
| <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -28,7 +28,8 @@ | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> | |||
| <a style="border: none;color: #000;white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -60,7 +61,8 @@ | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a> | |||
| <a style="border: none;color: #000;white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> | |||
| <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -131,8 +131,18 @@ | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label> | |||
| <select id="__specs__" class="ui search dropdown width48" | |||
| placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' ovalue="{{.spec_id}}" | |||
| {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}} | |||
| name="spec_id"> | |||
| </select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <div class="inline min_title field"> | |||
| <label class="label-fix-width" style="font-weight: normal;"></label> | |||
| @@ -192,7 +202,7 @@ | |||
| <div class="required unite inline min_title fields" style="width: 90%;margin-left: 5.7rem;"> | |||
| <div class="required eight wide field"> | |||
| <label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.cloudbrain.benchmark.evaluate_type"}}</label> | |||
| <select class="ui fluid selection search dropdown" id="benchmark_types_id" | |||
| name="benchmark_types_id"> | |||
| {{range .benchmark_types}} | |||
| @@ -231,8 +241,18 @@ | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label> | |||
| <select id="__specs__" class="ui search dropdown width48" | |||
| placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' ovalue="{{.spec_id}}" | |||
| {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}} | |||
| name="spec_id"> | |||
| </select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <div class="inline min_title field required"> | |||
| @@ -373,8 +393,8 @@ | |||
| ;(function() { | |||
| var SPECS = {{ .benchmark_specs }}; | |||
| var showPoint = true; | |||
| renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| var showPoint = {{ .CloudBrainPaySwitch }}; | |||
| window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, | |||
| @@ -382,4 +402,4 @@ | |||
| shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, | |||
| }); | |||
| })(); | |||
| </script> | |||
| </script> | |||
| @@ -455,9 +455,7 @@ | |||
| </td> | |||
| <td class="ti-text-form-content resorce_type"> | |||
| <div class="text-span text-span-w"> | |||
| {{$.resource_type}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| <tr class="ti-no-ng-animate"> | |||
| @@ -466,9 +464,7 @@ | |||
| </td> | |||
| <td class="ti-text-form-content spec"> | |||
| <div class="text-span text-span-w"> | |||
| {{$.resource_spec}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| <tr class="ti-no-ng-animate"> | |||
| @@ -599,7 +595,7 @@ | |||
| <script> | |||
| ;(function() { | |||
| var SPEC = {{ $.Spec }}; | |||
| var showPoint = true; | |||
| var showPoint = false; | |||
| var specStr = window.renderSpecStr(SPEC, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| @@ -608,7 +604,7 @@ | |||
| shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, | |||
| }); | |||
| $('td.ti-text-form-content.spec div').text(specStr); | |||
| $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| SPEC && $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| })(); | |||
| </script> | |||
| <script> | |||
| @@ -254,8 +254,17 @@ | |||
| </div>--> | |||
| <div class="required min_title inline field"> | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label> | |||
| <select id="__specs__" class="ui search dropdown width48" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" ovalue="{{.spec_id}}" name="spec_id"> | |||
| <select id="__specs__" class="ui search dropdown width48" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" ovalue="{{.spec_id}}" name="spec_id" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}> | |||
| </select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <!-- 表单操作 --> | |||
| <div class="inline min_title field"> | |||
| @@ -443,7 +452,7 @@ | |||
| msg = JSON.stringify(msg) | |||
| $('#store_run_para').val(msg) | |||
| } | |||
| var isValidate = false; | |||
| function validate(){ | |||
| $('.ui.form') | |||
| @@ -526,8 +535,8 @@ | |||
| }) | |||
| ;(function() { | |||
| var SPECS = {{ .inference_specs }}; | |||
| var showPoint = true; | |||
| renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| var showPoint = {{ .CloudBrainPaySwitch }}; | |||
| window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, | |||
| @@ -342,9 +342,7 @@ | |||
| {{$.i18n.Tr "repo.modelarts.train_job.resource_type"}} | |||
| </td> | |||
| <td class="ti-text-form-content resorce_type"> | |||
| <div class="text-span text-span-w"> | |||
| {{$.resource_type}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| @@ -484,9 +482,7 @@ | |||
| </td> | |||
| <td class="ti-text-form-content spec"> | |||
| <div class="text-span text-span-w"> | |||
| {{$.i18n.Tr "cloudbrain.gpu_num"}}:{{$.GpuNum}},{{$.i18n.Tr "cloudbrain.cpu_num"}}:{{$.CpuNum}},{{$.i18n.Tr "cloudbrain.memory"}}(MB):{{$.MemMiB}},{{$.i18n.Tr "cloudbrain.shared_memory"}}(MB):{{$.ShareMemMiB}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| @@ -662,7 +658,7 @@ | |||
| ;(function() { | |||
| var SPEC = {{ .Spec }}; | |||
| var showPoint = true; | |||
| var showPoint = false; | |||
| var specStr = window.renderSpecStr(SPEC, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| @@ -671,6 +667,6 @@ | |||
| shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, | |||
| }); | |||
| $('td.ti-text-form-content.spec div').text(specStr); | |||
| $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| SPEC && $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| })(); | |||
| </script> | |||
| @@ -1,6 +1,6 @@ | |||
| {{template "base/head" .}} | |||
| <style> | |||
| /* 遮罩层css效果图 */ | |||
| .inline.required.field.cloudbrain_benchmark { | |||
| display: none; | |||
| @@ -13,7 +13,7 @@ | |||
| .inline.required.field.cloudbrain_brainscore { | |||
| display: none; | |||
| } | |||
| </style> | |||
| @@ -55,8 +55,8 @@ | |||
| d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z" /> | |||
| </svg> | |||
| Ascend NPU</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="inline field"> | |||
| <label></label> | |||
| {{template "custom/task_wait_count" .}} | |||
| @@ -111,7 +111,7 @@ | |||
| {{end}} | |||
| {{end}} | |||
| </select> | |||
| </div> | |||
| </div> | |||
| <!--<div class="inline required field"> | |||
| <label>{{.i18n.Tr "cloudbrain.gpu_type"}}</label> | |||
| <select id="cloudbrain_gpu_type" class="ui search dropdown gpu-type" placeholder="选择GPU类型" | |||
| @@ -128,7 +128,7 @@ | |||
| <div id="select-multi-dataset"> | |||
| </div> | |||
| <!--<div class="inline required field"> | |||
| <label>{{.i18n.Tr "cloudbrain.resource_specification"}}</label> | |||
| <select id="cloudbrain_resource_spec" class="ui search dropdown" | |||
| @@ -141,13 +141,23 @@ | |||
| {{end}} | |||
| </select> | |||
| </div>--> | |||
| <div class="inline required field"> | |||
| <label>{{.i18n.Tr "cloudbrain.resource_specification"}}</label> | |||
| <select id="__specs__" class="ui search dropdown" | |||
| placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' ovalue="{{.spec_id}}" | |||
| {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}} | |||
| name="spec_id"> | |||
| </select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip" style="padding:0 5px;margin:6px 0;margin-left:265px;font-size:12px;width: 50%!important;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <div class="inline required field"> | |||
| @@ -212,7 +222,7 @@ | |||
| function validate(){ | |||
| $('.ui.form').form({ | |||
| on: 'blur', | |||
| fields: { | |||
| fields: { | |||
| display_job_name:{ | |||
| identifier : 'display_job_name', | |||
| rules: [ | |||
| @@ -226,7 +236,7 @@ | |||
| rules: [{ type: 'empty' }] | |||
| } | |||
| }, | |||
| onSuccess: function(){ | |||
| onSuccess: function(){ | |||
| isValidate = true; | |||
| }, | |||
| onFailure: function(e){ | |||
| @@ -262,7 +272,7 @@ | |||
| if (document.readyState === "complete") { | |||
| document.getElementById("mask").style.display = "none" | |||
| } | |||
| } | |||
| $('#cloudbrain_benchmark_category') | |||
| @@ -299,7 +309,7 @@ | |||
| } | |||
| }) | |||
| }) | |||
| $('.ui.green.button').click(function () { | |||
| if (!$('input[name="isBranches"]').val()) { | |||
| return false | |||
| @@ -311,13 +321,13 @@ | |||
| ;(function() { | |||
| var SPECS = {{ .debug_specs }}; | |||
| var showPoint = true; | |||
| var showPoint = {{ .CloudBrainPaySwitch }}; | |||
| window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, | |||
| memory: {{$.i18n.Tr "cloudbrain.memory"}}, | |||
| shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, | |||
| }); | |||
| }); | |||
| })(); | |||
| </script> | |||
| </script> | |||
| @@ -346,9 +346,7 @@ | |||
| </td> | |||
| <td class="ti-text-form-content resorce_type"> | |||
| <div class="text-span text-span-w"> | |||
| {{$.resource_type}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| <tr class="ti-no-ng-animate"> | |||
| @@ -601,7 +599,7 @@ | |||
| } | |||
| ;(function() { | |||
| var SPEC = {{ .Spec }}; | |||
| var showPoint = true; | |||
| var showPoint = false; | |||
| var specStr = window.renderSpecStr(SPEC, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| @@ -610,6 +608,6 @@ | |||
| shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, | |||
| }); | |||
| $('td.ti-text-form-content.spec div').text(specStr); | |||
| $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| SPEC && $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| })(); | |||
| </script> | |||
| @@ -259,8 +259,18 @@ | |||
| <div class="required min_title inline field"> | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label> | |||
| <select id="__specs__" class="ui dropdown width48" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" ovalue="{{.spec_id}}" | |||
| {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}} | |||
| name="spec_id"> | |||
| </select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <div class="inline field" style="padding: 1rem 0;"> | |||
| @@ -354,7 +364,7 @@ | |||
| $('select.dropdown') | |||
| .dropdown(); | |||
| var isValidate = false; | |||
| function validate() { | |||
| $('.ui.form') | |||
| @@ -442,8 +452,8 @@ | |||
| }) | |||
| ;(function() { | |||
| var SPECS = {{ .train_specs }}; | |||
| var showPoint = true; | |||
| renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| var showPoint = {{ .CloudBrainPaySwitch }}; | |||
| window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, | |||
| @@ -360,9 +360,7 @@ | |||
| </td> | |||
| <td class="ti-text-form-content resorce_type"> | |||
| <div class="text-span text-span-w"> | |||
| {{$.resource_type}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| <tr class="ti-no-ng-animate"> | |||
| @@ -371,9 +369,7 @@ | |||
| </td> | |||
| <td class="ti-text-form-content spec"> | |||
| <div class="text-span text-span-w"> | |||
| {{$.i18n.Tr "cloudbrain.gpu_num"}}:{{$.GpuNum}},{{$.i18n.Tr "cloudbrain.cpu_num"}}:{{$.CpuNum}},{{$.i18n.Tr "cloudbrain.memory"}}(MB):{{$.MemMiB}},{{$.i18n.Tr "cloudbrain.shared_memory"}}(MB):{{$.ShareMemMiB}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| @@ -990,7 +986,7 @@ | |||
| ;(function() { | |||
| var SPEC = {{ .Spec }}; | |||
| var showPoint = true; | |||
| var showPoint = false; | |||
| var specStr = window.renderSpecStr(SPEC, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| @@ -999,6 +995,6 @@ | |||
| shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, | |||
| }); | |||
| $('td.ti-text-form-content.spec div').text(specStr); | |||
| $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| SPEC && $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| })(); | |||
| </script> | |||
| @@ -106,7 +106,7 @@ | |||
| <path d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/> | |||
| </svg> | |||
| Ascend NPU</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="min_title inline field" style="margin-top:-10px;"> | |||
| <label class="label-fix-width" style="font-weight: normal;"></label> | |||
| @@ -206,7 +206,16 @@ | |||
| </div>--> | |||
| <div class="required min_title inline field" id="flavor_name"> | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> | |||
| <select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}"></select> | |||
| <select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <div class="inline min_title field"> | |||
| @@ -343,7 +352,7 @@ | |||
| type : 'integer[1..25]', | |||
| } | |||
| ] | |||
| }, | |||
| }, | |||
| spec_id: { | |||
| identifier: 'spec_id', | |||
| rules: [{ type: 'empty' }] | |||
| @@ -393,8 +402,8 @@ | |||
| ;(function() { | |||
| var SPECS = {{ .Specs }}; | |||
| var showPoint = true; | |||
| renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| var showPoint = {{ .CloudBrainPaySwitch }}; | |||
| window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, | |||
| @@ -218,7 +218,16 @@ | |||
| </div>--> | |||
| <div class="required min_title inline field" id="flavor_name"> | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> | |||
| <select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}"></select> | |||
| <select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <div class="inline required min_title field"> | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label> | |||
| @@ -414,8 +423,8 @@ | |||
| ;(function() { | |||
| var SPECS = {{ .Specs }}; | |||
| var showPoint = true; | |||
| renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| var showPoint = {{ .CloudBrainPaySwitch }}; | |||
| window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, | |||
| @@ -358,9 +358,7 @@ | |||
| </td> | |||
| <td class="ti-text-form-content spec"> | |||
| <div class="text-span text-span-w"> | |||
| {{.FlavorName}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| <tr class="ti-no-ng-animate"> | |||
| @@ -638,7 +636,7 @@ | |||
| <script> | |||
| ;(function() { | |||
| var SPEC = {{ .Spec }}; | |||
| var showPoint = true; | |||
| var showPoint = false; | |||
| var specStr = window.renderSpecStr(SPEC, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| @@ -275,7 +275,16 @@ | |||
| </div>--> | |||
| <div class="required min_title inline field" id="flaver_name"> | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> | |||
| <select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}"></select> | |||
| <select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <!-- 计算节点 --> | |||
| <div class="inline required min_title field"> | |||
| @@ -550,8 +559,8 @@ | |||
| ;(function() { | |||
| var SPECS = {{ .Specs }}; | |||
| var showPoint = true; | |||
| renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| var showPoint = {{ .CloudBrainPaySwitch }}; | |||
| window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, | |||
| @@ -424,9 +424,7 @@ td, th { | |||
| </td> | |||
| <td class="ti-text-form-content spec"> | |||
| <div class="text-span text-span-w"> | |||
| {{.FlavorName}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| @@ -541,7 +539,7 @@ $(document).ready(function(){ | |||
| }) | |||
| ;(function() { | |||
| var SPEC = {{ .Spec }}; | |||
| var showPoint = true; | |||
| var showPoint = false; | |||
| var specStr = window.renderSpecStr(SPEC, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| @@ -550,6 +548,5 @@ $(document).ready(function(){ | |||
| shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, | |||
| }); | |||
| $('td.ti-text-form-content.spec div').text(specStr); | |||
| // $('td.ti-text-form-content.resorce_type').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| })(); | |||
| </script> | |||
| @@ -79,7 +79,18 @@ | |||
| </div>--> | |||
| <div class="inline required field"> | |||
| <label>{{.i18n.Tr "cloudbrain.specification"}}</label> | |||
| <select id="__specs__" class="ui search dropdown" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' name="spec_id" ovalue="{{.spec_id}}"></select> | |||
| <select id="__specs__" class="ui search dropdown" ovalue="{{.spec_id}}" | |||
| {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}} | |||
| placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' name="spec_id"></select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip" style="padding:0 5px;margin:6px 0;margin-left:265px;font-size:12px;width: 50%!important;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <!--<div class="inline required field"> | |||
| <label>数据集存放路径</label> | |||
| @@ -113,7 +124,7 @@ | |||
| function validate(){ | |||
| $('.ui.form').form({ | |||
| on: 'blur', | |||
| fields: { | |||
| fields: { | |||
| display_job_name:{ | |||
| identifier : 'display_job_name', | |||
| rules: [ | |||
| @@ -189,8 +200,8 @@ | |||
| ;(function() { | |||
| var SPECS = {{ .Specs }}; | |||
| var showPoint = true; | |||
| renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| var showPoint = {{ .CloudBrainPaySwitch }}; | |||
| window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, | |||
| @@ -368,9 +368,7 @@ | |||
| </td> | |||
| <td class="ti-text-form-content spec"> | |||
| <div class="text-span text-span-w"> | |||
| {{$.resource_spec}} | |||
| </div> | |||
| <div class="text-span text-span-w"></div> | |||
| </td> | |||
| </tr> | |||
| <tr class="ti-no-ng-animate"> | |||
| @@ -502,7 +500,7 @@ | |||
| ;(function() { | |||
| var SPEC = {{ .Spec }}; | |||
| var showPoint = true; | |||
| var showPoint = false; | |||
| var specStr = window.renderSpecStr(SPEC, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| @@ -511,6 +509,5 @@ | |||
| shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, | |||
| }); | |||
| $('td.ti-text-form-content.spec div').text(specStr); | |||
| $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType)); | |||
| })(); | |||
| </script> | |||
| @@ -109,7 +109,7 @@ | |||
| d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z" /> | |||
| </svg> | |||
| Ascend NPU</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="min_title inline field" style="margin-top:-10px;"> | |||
| <label class="label-fix-width" style="font-weight: normal;"></label> | |||
| @@ -283,7 +283,16 @@ | |||
| </div>--> | |||
| <div class="required inline min_title field" id="flaver_name"> | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> | |||
| <select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}"></select> | |||
| <select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select> | |||
| {{if .CloudBrainPaySwitch}} | |||
| <div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;"> | |||
| <span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span> | |||
| <span style="float:right;"> | |||
| <i class="question circle icon link" data-position="right center" data-variation="mini"></i> | |||
| <a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a> | |||
| </span> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| <div class="inline required min_title field"> | |||
| <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label> | |||
| @@ -556,8 +565,8 @@ | |||
| ;(function() { | |||
| var SPECS = {{ .Specs }}; | |||
| var showPoint = true; | |||
| renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| var showPoint = {{ .CloudBrainPaySwitch }}; | |||
| window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { | |||
| gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, | |||
| free: {{$.i18n.Tr "cloudbrain.free"}}, | |||
| point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, | |||