From 0d98890831032fdce5d56e301181585eacfa6caa Mon Sep 17 00:00:00 2001 From: zouap Date: Mon, 29 Aug 2022 11:00:13 +0800 Subject: [PATCH 01/77] =?UTF-8?q?0918=E5=88=86=E6=94=AF=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++ routers/routes/routes.go | 1 + routers/user/Invitation.go | 36 +++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 models/user_invitation.go create mode 100644 routers/user/Invitation.go diff --git a/models/user_invitation.go b/models/user_invitation.go new file mode 100644 index 000000000..db004b7e1 --- /dev/null +++ b/models/user_invitation.go @@ -0,0 +1,50 @@ +package models + +import ( + "fmt" + + "code.gitea.io/gitea/modules/timeutil" +) + +// Follow represents relations of user and his/her followers. +type Invitation struct { + ID int64 `xorm:"pk autoincr"` + SrcUserID int64 `xorm:"NOT NULL DEFAULT 0"` + UserID int64 `xorm:"NOT NULL DEFAULT 0"` + Phone string `xorm:"NULL"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` +} + +func QueryInvitaion(start int64, end int64) ([]*Invitation, int) { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + cond := "created_unix >=" + fmt.Sprint(start) + " and created_unix <=" + fmt.Sprint(end) + + userList := make([]*Invitation, 0) + + if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). + Find(&userList); err != nil { + return nil, 0 + } + return userList, len(userList) +} + +func InsertInvitaion(invitationUser *Invitation) { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + statictisSess.Insert(invitationUser) +} + +func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + cond := "src_user_id =" + fmt.Sprint(srcUserId) + + userList := make([]*Invitation, 0) + + if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). + Find(&userList); err != nil { + return nil, 0 + } + return userList, len(userList) +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 9d83594fa..4982edd93 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -504,6 +504,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/forgot_password", user.ForgotPasswd) m.Post("/forgot_password", user.ForgotPasswdPost) m.Post("/logout", user.SignOut) + m.Get("/invitation_code", user.GetInvitaionCode) }) // ***** END: User ***** diff --git a/routers/user/Invitation.go b/routers/user/Invitation.go new file mode 100644 index 000000000..12050d928 --- /dev/null +++ b/routers/user/Invitation.go @@ -0,0 +1,36 @@ +package user + +import ( + "strings" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/repository" +) + +func GetInvitaionCode(ctx *context.Context) { + + url := setting.RecommentRepoAddr + "invitaion_page" + result, err := repository.RecommendFromPromote(url) + resultJsonMap := make(map[string]interface{}, 0) + if err == nil { + for _, strLine := range result { + tmpIndex := strings.Index(strLine, "=") + if tmpIndex != -1 { + key := strLine[0:tmpIndex] + value := strLine[tmpIndex+1:] + resultJsonMap[key] = value + } + } + } + + if ctx.IsSigned { + resultJsonMap["invitaion_code"] = ctx.User.Name + + } + ctx.JSON(200, resultJsonMap) +} + +func RegisteUserByInvitaionCode(ctx *context.Context) { + +} From a51f73654381f289eeee98c353c092636e7dea1d Mon Sep 17 00:00:00 2001 From: zouap Date: Tue, 30 Aug 2022 17:39:04 +0800 Subject: [PATCH 02/77] =?UTF-8?q?=E8=80=81=E6=8B=89=E6=96=B0=E9=9C=80?= =?UTF-8?q?=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 32 +++++++++++++++++++++++--------- modules/auth/user_form.go | 3 ++- routers/routes/routes.go | 1 + routers/user/Invitation.go | 41 ++++++++++++++++++++++++++++++++++++++--- routers/user/auth.go | 46 ++++++++++++++++++++++++---------------------- 5 files changed, 88 insertions(+), 35 deletions(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index db004b7e1..816cacdaf 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -11,28 +11,42 @@ type Invitation struct { ID int64 `xorm:"pk autoincr"` SrcUserID int64 `xorm:"NOT NULL DEFAULT 0"` UserID int64 `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"INDEX"` CreatedUnix timeutil.TimeStamp `xorm:"created"` } +func QueryInvitaionByPhone(phone string) []*Invitation { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + cond := "phone ='" + phone + "'" + invitationList := make([]*Invitation, 0) + if err := statictisSess.Table(new(Invitation)).Where(cond). + Find(&invitationList); err != nil { + return nil + } else { + return invitationList + } +} + func QueryInvitaion(start int64, end int64) ([]*Invitation, int) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() cond := "created_unix >=" + fmt.Sprint(start) + " and created_unix <=" + fmt.Sprint(end) - userList := make([]*Invitation, 0) + invitationList := make([]*Invitation, 0) if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). - Find(&userList); err != nil { + Find(&invitationList); err != nil { return nil, 0 } - return userList, len(userList) + return invitationList, len(invitationList) } -func InsertInvitaion(invitationUser *Invitation) { +func InsertInvitaion(invitationUser *Invitation) error { statictisSess := xStatistic.NewSession() defer statictisSess.Close() - statictisSess.Insert(invitationUser) + _, err := statictisSess.Insert(invitationUser) + return err } func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { @@ -40,11 +54,11 @@ func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { defer statictisSess.Close() cond := "src_user_id =" + fmt.Sprint(srcUserId) - userList := make([]*Invitation, 0) + invitationList := make([]*Invitation, 0) if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). - Find(&userList); err != nil { + Find(&invitationList); err != nil { return nil, 0 } - return userList, len(userList) + return invitationList, len(invitationList) } diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 130586a5a..885e3efbc 100755 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -86,6 +86,7 @@ type RegisterForm struct { Retype string GRecaptchaResponse string `form:"g-recaptcha-response"` Agree bool + InvitaionCode string } // Validate valideates the fields @@ -372,7 +373,7 @@ func (f *U2FDeleteForm) Validate(ctx *macaron.Context, errs binding.Errors) bind type PhoneNumberForm struct { PhoneNumber string `binding:"Required;MaxSize(20)"` - Mode int `binding:"Required"` + Mode int `binding:"Required"` SlideID string `binding:"Required;MaxSize(100)"` } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 4982edd93..3f927ea79 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -376,6 +376,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/login/cloud_brain", bindIgnErr(auth.SignInForm{}), user.SignInCloudBrainPost) m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) + m.Get("/invitaion", user.GetInvitaionCode) m.Get("/login/phone", user.SignInPhone) m.Post("/login/phone", bindIgnErr(auth.PhoneNumberCodeForm{}), user.SignInPhonePost) m.Group("", func() { diff --git a/routers/user/Invitation.go b/routers/user/Invitation.go index 12050d928..fc0a03f45 100644 --- a/routers/user/Invitation.go +++ b/routers/user/Invitation.go @@ -1,9 +1,13 @@ package user import ( + "errors" "strings" + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/repository" ) @@ -25,12 +29,43 @@ func GetInvitaionCode(ctx *context.Context) { } if ctx.IsSigned { - resultJsonMap["invitaion_code"] = ctx.User.Name - + resultJsonMap["invitaion_code"] = getInvitaionCode(ctx) } ctx.JSON(200, resultJsonMap) } -func RegisteUserByInvitaionCode(ctx *context.Context) { +func RegisteUserByInvitaionCode(form auth.RegisterForm, newUserId int64) error { + invitationcode := form.InvitaionCode + + re := models.QueryInvitaionByPhone(form.PhoneNumber) + if re != nil { + if len(re) > 0 { + log.Info("The phone has been invitated. so ingore it.") + return errors.New("The phone has been invitated.") + } + } + + user := parseInvitaionCode(invitationcode) + invitation := &models.Invitation{ + SrcUserID: user.ID, + UserID: newUserId, + Phone: form.PhoneNumber, + } + err := models.InsertInvitaion(invitation) + if err != nil { + log.Info("insert error," + err.Error()) + } + return err +} + +func getInvitaionCode(ctx *context.Context) string { + return ctx.User.Name +} +func parseInvitaionCode(invitationcode string) *models.User { + user, err := models.GetUserByName(invitationcode) + if err == nil { + return user + } + return nil } diff --git a/routers/user/auth.go b/routers/user/auth.go index 7cd6472ff..a95d0e3c4 100755 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -8,11 +8,12 @@ package user import ( "errors" "fmt" - "github.com/gomodule/redigo/redis" "net/http" "strconv" "strings" + "github.com/gomodule/redigo/redis" + "code.gitea.io/gitea/modules/slideimage" phoneService "code.gitea.io/gitea/services/phone" @@ -352,18 +353,17 @@ func SignInPostCommon(ctx *context.Context, form auth.SignInForm) { ctx.Redirect(setting.AppSubURL + "/user/two_factor") } - func SignInCloudBrainPost(ctx *context.Context, form auth.SignInForm) { ctx.Data["PageIsCloudBrainLogin"] = true ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login/cloud_brain" - SignInPostCommon(ctx,form) + SignInPostCommon(ctx, form) } // SignInPost response for sign in request func SignInPost(ctx *context.Context, form auth.SignInForm) { ctx.Data["PageIsLogin"] = true ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" - SignInPostCommon(ctx,form) + SignInPostCommon(ctx, form) } // TwoFactor shows the user a two-factor authentication page. @@ -1337,6 +1337,9 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.RenderWithErr(ctx.Tr("sign_up_agree_tips"), tplSignUp, &form) return } + if form.InvitaionCode != "" { + RegisteUserByInvitaionCode(ctx, form) + } u := &models.User{ Name: form.UserName, @@ -1919,7 +1922,7 @@ func SendVerifyCode(ctx *context.Context, slideImage *slideimage.SlideImage, for return } - if form.Mode==0 { //注册 + if form.Mode == 0 { //注册 if has { ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) @@ -1935,32 +1938,31 @@ func SendVerifyCode(ctx *context.Context, slideImage *slideimage.SlideImage, for } else { //修改手机号 mode=2 绑定手机 - u, err := models.GetUserByPhoneNumber(phoneNumber) - if err != nil && !models.IsErrUserNotExist(err) { - log.Warn("sql err", err) - ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) - return - } - - if u != nil { + u, err := models.GetUserByPhoneNumber(phoneNumber) + if err != nil && !models.IsErrUserNotExist(err) { + log.Warn("sql err", err) + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) + return + } - if u.ID == ctx.User.ID { //没有修改手机号 - ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.not_modify"))) - return - } else { //修改的手机已经被别的用户注册 - ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) - return - } + if u != nil { + if u.ID == ctx.User.ID { //没有修改手机号 + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.not_modify"))) + return + } else { //修改的手机已经被别的用户注册 + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) + return } - } + } + } redisConn := labelmsg.Get() defer redisConn.Close() sendTimes, err := phoneService.GetPhoneNumberSendTimes(redisConn, phoneNumber) - if err != nil && err!=redis.ErrNil { + if err != nil && err != redis.ErrNil { log.Warn("redis err", err) ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) return From 2eeb3d53b6173622143124c2e85c24a615f7f63d Mon Sep 17 00:00:00 2001 From: zouap Date: Mon, 5 Sep 2022 10:47:35 +0800 Subject: [PATCH 03/77] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E8=80=81=E6=8B=89=E6=96=B0=E9=9C=80=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 6 +++--- modules/grampus/resty.go | 11 ++++++----- routers/api/v1/api.go | 1 + routers/repo/cloudbrain.go | 2 +- routers/repo/grampus.go | 30 ++++++++++++++++++++++++++++++ routers/routes/routes.go | 1 + routers/user/Invitation.go | 13 ++++++++++++- 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index 816cacdaf..56de43d01 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -49,7 +49,7 @@ func InsertInvitaion(invitationUser *Invitation) error { return err } -func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { +func QueryInvitaionBySrcUserId(srcUserId int64) []*Invitation { statictisSess := xStatistic.NewSession() defer statictisSess.Close() cond := "src_user_id =" + fmt.Sprint(srcUserId) @@ -58,7 +58,7 @@ func QueryInvitaionBySrcUserId(srcUserId int64) ([]*Invitation, int) { if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). Find(&invitationList); err != nil { - return nil, 0 + return nil } - return invitationList, len(invitationList) + return invitationList } diff --git a/modules/grampus/resty.go b/modules/grampus/resty.go index 5e8722b4b..593abccbb 100755 --- a/modules/grampus/resty.go +++ b/modules/grampus/resty.go @@ -1,14 +1,15 @@ package grampus import ( - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "crypto/tls" "encoding/json" "fmt" - "github.com/go-resty/resty/v2" "net/http" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "github.com/go-resty/resty/v2" ) var ( @@ -235,7 +236,7 @@ func GetTrainJobLog(jobID string) (string, error) { return logContent, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error()) } log.Error("GetTrainJobLog failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) - return logContent, fmt.Errorf("GetTrainJobLog failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) + return logContent, fmt.Errorf("GetTrainJobLog failed(%d):%d(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) } logContent = res.String() diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 0b941b400..3e588d942 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -969,6 +969,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("", repo.GetModelArtsTrainJobVersion) m.Post("/stop_version", cloudbrain.AdminOrOwnerOrJobCreaterRightForTrain, repo_ext.GrampusStopJob) m.Get("/log", repo_ext.GrampusGetLog) + m.Get("/download_log", repo_ext.GrampusDownloadLog) }) }) }, reqRepoReader(models.UnitTypeCloudBrain)) diff --git a/routers/repo/cloudbrain.go b/routers/repo/cloudbrain.go index c1e89dde5..457f275ed 100755 --- a/routers/repo/cloudbrain.go +++ b/routers/repo/cloudbrain.go @@ -2718,7 +2718,7 @@ func getTrainJobCommand(form auth.CreateCloudBrainForm) (string, error) { } } - command += "python /code/" + bootFile + param + " | tee " + cloudbrain.ModelMountPath + "/" + form.DisplayJobName + "-" + cloudbrain.LogFile + command += "python /code/" + bootFile + param + " > " + cloudbrain.ModelMountPath + "/" + form.DisplayJobName + "-" + cloudbrain.LogFile return command, nil } diff --git a/routers/repo/grampus.go b/routers/repo/grampus.go index 33e111df2..4b9ef621c 100755 --- a/routers/repo/grampus.go +++ b/routers/repo/grampus.go @@ -725,6 +725,36 @@ func GrampusTrainJobShow(ctx *context.Context) { ctx.HTML(http.StatusOK, tplGrampusTrainJobShow) } +func GrampusDownloadLog(ctx *context.Context) { + jobID := ctx.Params(":jobid") + job, err := models.GetCloudbrainByJobID(jobID) + if err != nil { + log.Error("GetCloudbrainByJobID failed: %v", err, ctx.Data["MsgID"]) + ctx.ServerError(err.Error(), err) + return + } + + content, err := grampus.GetTrainJobLog(job.JobID) + if err != nil { + log.Error("GetTrainJobLog failed: %v", err, ctx.Data["MsgID"]) + ctx.ServerError(err.Error(), err) + return + } + fileName := job.JobName + "-log.txt" + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+fileName) + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + var b []byte = []byte(content) + + ctx.Resp.Write(b) + + // ctx.JSON(http.StatusOK, map[string]interface{}{ + // "JobName": job.JobName, + // "Content": content, + // }) + + //return +} + func GrampusGetLog(ctx *context.Context) { jobID := ctx.Params(":jobid") job, err := models.GetCloudbrainByJobID(jobID) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 3f927ea79..bfa0552ac 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -506,6 +506,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/forgot_password", user.ForgotPasswdPost) m.Post("/logout", user.SignOut) m.Get("/invitation_code", user.GetInvitaionCode) + m.Get("/invitation_tpl", user.InviationTpl) }) // ***** END: User ***** diff --git a/routers/user/Invitation.go b/routers/user/Invitation.go index fc0a03f45..78718b33f 100644 --- a/routers/user/Invitation.go +++ b/routers/user/Invitation.go @@ -6,12 +6,17 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/repository" ) +const ( + tplInvitation base.TplName = "user/settings/invite" +) + func GetInvitaionCode(ctx *context.Context) { url := setting.RecommentRepoAddr + "invitaion_page" @@ -29,11 +34,17 @@ func GetInvitaionCode(ctx *context.Context) { } if ctx.IsSigned { - resultJsonMap["invitaion_code"] = getInvitaionCode(ctx) + resultJsonMap["invitation_code"] = getInvitaionCode(ctx) + resultJsonMap["invitation_users"] = models.QueryInvitaionBySrcUserId(ctx.User.ID) } + ctx.JSON(200, resultJsonMap) } +func InviationTpl(ctx *context.Context) { + ctx.HTML(200, tplInvitation) +} + func RegisteUserByInvitaionCode(form auth.RegisterForm, newUserId int64) error { invitationcode := form.InvitaionCode From a84152653a4bfe421d7bd4da3c786129c9267a54 Mon Sep 17 00:00:00 2001 From: zouap Date: Mon, 5 Sep 2022 11:37:08 +0800 Subject: [PATCH 04/77] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- routers/routes/routes.go | 7 +++---- routers/user/auth.go | 7 ++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 142fde739..af139b7a9 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -375,8 +375,8 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/login/cloud_brain", user.SignInCloudBrain) m.Post("/login/cloud_brain", bindIgnErr(auth.SignInForm{}), user.SignInCloudBrainPost) m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) - - m.Get("/invitaion", user.GetInvitaionCode) + m.Get("/invitation_code", user.GetInvitaionCode) + m.Get("/invitation_tpl", user.InviationTpl) m.Get("/login/phone", user.SignInPhone) m.Post("/login/phone", bindIgnErr(auth.PhoneNumberCodeForm{}), user.SignInPhonePost) m.Group("", func() { @@ -505,8 +505,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/forgot_password", user.ForgotPasswd) m.Post("/forgot_password", user.ForgotPasswdPost) m.Post("/logout", user.SignOut) - m.Get("/invitation_code", user.GetInvitaionCode) - m.Get("/invitation_tpl", user.InviationTpl) + }) // ***** END: User ***** diff --git a/routers/user/auth.go b/routers/user/auth.go index a95d0e3c4..3c80af4e1 100755 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -1337,9 +1337,6 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo ctx.RenderWithErr(ctx.Tr("sign_up_agree_tips"), tplSignUp, &form) return } - if form.InvitaionCode != "" { - RegisteUserByInvitaionCode(ctx, form) - } u := &models.User{ Name: form.UserName, @@ -1369,6 +1366,10 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo } log.Trace("Account created: %s", u.Name, ctx.Data["MsgID"]) + if form.InvitaionCode != "" { + RegisteUserByInvitaionCode(form, u.ID) + } + err := models.AddEmailAddress(&models.EmailAddress{ UID: u.ID, Email: form.Email, From 608859e5ce1d066eb6f9107ce52f304120c3285a Mon Sep 17 00:00:00 2001 From: chenshihai Date: Mon, 5 Sep 2022 14:08:38 +0800 Subject: [PATCH 05/77] Invite Friends --- options/locale/locale_en-US.ini | 1 + options/locale/locale_zh-CN.ini | 1 + public/img/ad/ad01.png | Bin 0 -> 13638 bytes public/img/ad/ad02.png | Bin 0 -> 32334 bytes public/img/ad/ad03.jpg | Bin 0 -> 36620 bytes templates/base/head_navbar.tmpl | 4 + templates/base/head_navbar_fluid.tmpl | 4 + templates/base/head_navbar_home.tmpl | 4 + templates/base/head_navbar_pro.tmpl | 4 + templates/user/auth/signup_inner.tmpl | 13 + templates/user/dashboard/repolist.tmpl | 3 + templates/user/profile.tmpl | 6 + templates/user/settings/invite.tmpl | 7 + web_src/js/features/ad.js | 87 +++++++ web_src/js/index.js | 1 + web_src/js/standalone/phoneverify.js | 20 +- web_src/less/standalone/_phoneverify.less | 3 +- web_src/vuepages/apis/modules/userinvite.js | 11 + web_src/vuepages/pages/user/invite/index.vue | 279 +++++++++++++++++++++ .../vuepages/pages/user/invite/vp-user-invite.js | 17 ++ 20 files changed, 459 insertions(+), 6 deletions(-) create mode 100644 public/img/ad/ad01.png create mode 100644 public/img/ad/ad02.png create mode 100644 public/img/ad/ad03.jpg create mode 100644 templates/user/settings/invite.tmpl create mode 100644 web_src/js/features/ad.js create mode 100644 web_src/vuepages/apis/modules/userinvite.js create mode 100644 web_src/vuepages/pages/user/invite/index.vue create mode 100644 web_src/vuepages/pages/user/invite/vp-user-invite.js diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 3453344f7..30c3c3b5b 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -69,6 +69,7 @@ your_dashboard = Dashboard your_profile = Profile your_starred = Starred your_settings = Settings +invite_friends = Invite Friends all = All sources = Sources diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index d527218d3..e1354bdfd 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -69,6 +69,7 @@ your_dashboard=个人中心 your_profile=个人信息 your_starred=已点赞 your_settings=设置 +invite_friends=邀请好友 all=所有 sources=自建 diff --git a/public/img/ad/ad01.png b/public/img/ad/ad01.png new file mode 100644 index 0000000000000000000000000000000000000000..379c39bd155af3eb2ff359338f78a1713844ea34 GIT binary patch literal 13638 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 AR{#J2 literal 0 KcmV+b0RR6000031 diff --git a/public/img/ad/ad03.jpg b/public/img/ad/ad03.jpg new file mode 100644 index 0000000000000000000000000000000000000000..247b40d3e88c48c9f06545c330804b88be0cc301 GIT binary patch literal 36620 zcmV(mkMnT<*#F`TSV==eUrX>M-<5C8yeZgg@|M?xT2Sy~_m z&Hw@c2>=EFF#rHzV{>p#Q$|z(0000PI3P%6cWh;JbS+R}V{2h@WFSLtZE#_3c>n-p zWpiTy00000000tmQb$4{Nkv08F*!CiEix`K00000000000000Ib5ch_AW20-HZeIi zHZ3wPF#rGn0000000000000000000000000000000000000000000000000000000 z00013Sy~_f000000P|4*0RR910T#?ySy~_f00000000000000000013Sy~_f00000 z0B@oI066sk00WR%Sy~_f000000AiT{0Jntz02ta>Sy~_f0000003@IQ01t!!0JhI$ zWpiTy00000000(AMMEHHbaZe!FE4j@cP?pVV=iN800000000000000MNku~-XmoUN zIxjDGcXuvnWn(U5XaE2J000000000000000000000000000000000000000000000 z000000001FWpiTy00000001sYMMEGqF*!CiEix`KAVg(mVRdYDAW}y{AY*TAZ*_7Y zb8ul}Wgsmeb5ch_00000000000000kNku~-HZeIiHZ3wPF(5=`W?^+~bRbekLLg&r zY;SdPAaihGV`U&MAahbjLI3~&000000000000000000000001FWpiTy00000001mf zWoBh^Wo~0-AXaH*cWG{CAVY6%WNCD1Z*CxIZXiiTLpCuvHa0CXE-?TA0000000000 z04!2vW@U0^ZewL2R%vB-X>MmALvL@6CZXjuHAW20-HZeIiHZ3wPF#rGn00000 z000000000000000000000001XX=Qf+000006QuqC6kjd?5YH3<1MSQJ1QQDY16-Z} z00044Sy~_f00000ObJ#1Pyhe`S0CqXWnpsw000000003100000000000000000000 z00NHy0007WX=fk+0000(QdA&gb#it90000000aO4000F53IGoP6aX0j9snZ%C;%-0 zG5|LKI{-icMF2_wPXJT^SpZ%DV*qFXZ2)orcL00nLmp8%u)sQ|73 zvH-ULya2%f#sJFz&;Zo{+W_DIhTq`T_$2 z3<4Aa9Rel-F9J9MK>|wxR03TBX995odIE$3jslnhq5`Y}wgSNd%L3H`-~#Ic^#cF{ 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 zWo|wSARr(hARr(hARr(hARr(hARs(*bVYV_I&*Jkba!ELWkF|UZgf6DWN%_+AW3X& zb#rubVRUbDATl;CGax8eX>Me1cXKH`FLQK7c62&(Z)S9NVRB_bXJu}5J_;ZpARr(h zARr(hARr(hARr(hJacqKc62&pXkl(=Wn?}tJTG%}MRs&LV`yP+XJuqQ3LqdLARr(h zARr(hARr(hJTG!&W;$$XJ_;ZpARr(hARr(hARs(1a%5&YQ)O{J3LqdLARr(hARs(1 zcx`Y^O*%+vb98TVc|HmtARr(hARr(hJZWrfb#rubVRUbDI#YCEa&&cYP;zf(X>4Ua zP;zN*bUZI0a&KgHV`Xwa3LqdLARr(-FLGpNIz(l2V{&P5bZKvHJ_;Zp zAUrQ}WM(>2L`FUeJTG`Ucx`ZPWprUa3LqdLARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hAPOKLARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr1LARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(LARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(h3LqdLARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hAPOKLARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr1LARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(LARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h z3LqdLARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hAPOKL zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr1LARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(LARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h3LqdLARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hAPOKLARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr1LARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(LARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(h3LqdLARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hAPOKLARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr1LARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(LARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(h3LqdLARr(hARr(hARr(hARr(hARr(hARr(h3Oqk}aA9L> zWpp5AZe%?ocOpMN|KI=+N=8XW009C407w8x0095(04z{wZ**^SXm4;JGcGUyI6_HH z1ML6+0000G07w7;0096207w7;00962|JwkB00II60s;a80s;a90s;a91OoyB0|W&G z1Ox;G1qKBL1qB5K1_lQQ2nPoS2?+`c2?-1g3=9km3=9km3=9km3;_cJ0|f*H2?hoU z4GRei4G#_u4h|0w3=9km3=a00aPJ|Ly<|L1b@YWdLNr00001|G)qd2mnw6AOipq009vJ0ucfK!~voJ0RRI4 z0s#d800000000001q22M0SE&C0tpHW0RRF600RL40{{R30000000jmE1PB2F2LK5Q z3lIPS0s{dA1qcm&00000000330{{dOAq5gKK@%cTa41ml$Dm3n3Lo_va__cwzs&sy1~N3#Kp$P$jQpl)6~`0 z*Vx(G;^XAy=I7|?>hSUM^Yr!h_xSnx5dZ-K0s{mE3k87y00000009630}%uvF%mLS zaT7B^VX*}UB2r?3k)grS@CQP2;qeFw3KSI<7Z@2DBP1mzCnzZ@G&MFiI5|2)L`6nN zNJ&alR8>}2SXo+QWMyV&XlZJ5bai%jczJq)gM@{Khlq)al9QB`mY0~BnxdnmrKYE- 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 {{.i18n.Tr "custom.Platform_Tutorial"}} + + + {{.i18n.Tr "invite_friends"}} + {{if .IsAdmin}}
diff --git a/templates/base/head_navbar_fluid.tmpl b/templates/base/head_navbar_fluid.tmpl index 84781db11..9e31cc2db 100644 --- a/templates/base/head_navbar_fluid.tmpl +++ b/templates/base/head_navbar_fluid.tmpl @@ -177,6 +177,10 @@ {{.i18n.Tr "custom.Platform_Tutorial"}} + + + + {{.i18n.Tr "invite_friends"}} {{if .IsAdmin}}
diff --git a/templates/base/head_navbar_home.tmpl b/templates/base/head_navbar_home.tmpl index 64e04b4c3..471540f64 100644 --- a/templates/base/head_navbar_home.tmpl +++ b/templates/base/head_navbar_home.tmpl @@ -160,6 +160,10 @@ {{.i18n.Tr "custom.Platform_Tutorial"}} + + + {{.i18n.Tr "invite_friends"}} + {{if .IsAdmin}}
diff --git a/templates/base/head_navbar_pro.tmpl b/templates/base/head_navbar_pro.tmpl index e9f662bbe..0b5babf6e 100644 --- a/templates/base/head_navbar_pro.tmpl +++ b/templates/base/head_navbar_pro.tmpl @@ -181,6 +181,10 @@ {{.i18n.Tr "custom.Platform_Tutorial"}} + + + {{.i18n.Tr "invite_friends"}} + {{if .IsAdmin}}
diff --git a/templates/user/auth/signup_inner.tmpl b/templates/user/auth/signup_inner.tmpl index 6ba10e8e7..43667f6f5 100644 --- a/templates/user/auth/signup_inner.tmpl +++ b/templates/user/auth/signup_inner.tmpl @@ -35,6 +35,9 @@ {{if .DisableRegistration}}

{{.i18n.Tr "auth.disable_register_prompt"}}

{{else}} +
+ 您的好友 Itx003 邀请你加入启智社区AI协作平台,畅享充沛的免费算力资源! +
@@ -71,6 +74,16 @@ {{template "user/auth/phone_verify" .}} {{end}} + +
+
+
+ 推荐人 +
+ +
+
+
diff --git a/templates/user/dashboard/repolist.tmpl b/templates/user/dashboard/repolist.tmpl index ff85d72d4..b89fdbf1f 100644 --- a/templates/user/dashboard/repolist.tmpl +++ b/templates/user/dashboard/repolist.tmpl @@ -18,6 +18,9 @@ v-cloak >
+
+ +
@@ -107,3 +107,29 @@
+ diff --git a/web_src/vuepages/pages/user/invite/index.vue b/web_src/vuepages/pages/user/invite/index.vue index 86cdc7a54..b49db76cb 100644 --- a/web_src/vuepages/pages/user/invite/index.vue +++ b/web_src/vuepages/pages/user/invite/index.vue @@ -6,25 +6,25 @@
- -
邀请好友来启智,用免费算力还能赚奖金!
+ +
{{ bannerTitle }}
- 新一期的开源打榜活动,每邀请一名好友注册并激活,就可以获得5打榜积分。快快邀请更多好友帮你冲击榜单吧~ - 点击查看活动详情 + {{ pageLinkDesc }} + 点击查看活动详情
- 启智AI协作平台是启智社区面向AI开发者提供的一站式AI开发协作平台,提供了代码托管、数据集管理、基于异构计算资源的模型调试与训练等功能。目前已经与鹏城云脑、中国算力网(C²NET)一期打通,免费提供丰富算力资源,支撑大家完成AI开发任务。 + {{ pageOpeniDesc }}
-
{{ sharedLink }}
-
推荐人:{{ sharedUser }}
+
{{ invitationLink + invitationCode }}
+
推荐人:{{ invitationCode }}
复制注册邀请链接
- +
@@ -43,9 +43,7 @@ {{ scope.row.statusStr }} - - - + {{end}}
  • {{svg "octicon-clock" 16}} {{.i18n.Tr "user.join_on"}} {{.Owner.CreatedUnix.FormatShort}}
  • diff --git a/web_src/js/features/ad.js b/web_src/js/features/ad.js index 55036c1d9..7e555e749 100644 --- a/web_src/js/features/ad.js +++ b/web_src/js/features/ad.js @@ -1,43 +1,71 @@ ; (function () { - const adList = [{ - id: 1, - pos: { - left: 50, - bottom: 50, + /*const adList = [ + { + "width": 144, + "height": 108, + "pos": { + "left": 50, + "bottom": 50 }, - src: '/img/ad/ad01.png', - url: '/user/invitation_tpl', - width: 144, - height: 108, - }/*, { - id: 2, - pos: { - right: 50, - bottom: 50, + "src": "https://git.openi.org.cn/OpenIOSSG/promote/raw/branch/master/imgs/invitation/pic-01.png", + "url": "/user/invitation_tpl", + "show": true + }, + { + "width": 144, + "height": 108, + "pos": { + "right": 50, + "bottom": 50 }, - src: '/img/ad/ad01.png', - url: '/user/invitation_tpl', - width: 144, - height: 108, - }*/]; + "src": "https://git.openi.org.cn/OpenIOSSG/promote/raw/branch/master/imgs/invitation/pic-01.png", + "url": "/user/invitation_tpl", + "show": false + } +];*/ + const exceptPages = [ + // '/user/invitation_tpl' + ]; + + function initAd() { + $.ajax({ + type: "GET", + url: "/dashboard/invitation", + dataType: "json", + data: { filename: 'ad-pop-up.json' }, + success: function (res) { + try { + var data = JSON.parse(res); + createAd(data); + } catch (err) { + console.log(err); + } + }, + error: function (err) { + console.log(err); + } + }); + } function createAd(adList) { const adInfoStr = window.localStorage.getItem('ads') || '{}'; let adInfoObj = JSON.parse(adInfoStr); const today = new Date(); - const timeEnd = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1).getTime(); + const timeTodayEnd = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1).getTime(); const now = Date.now(); + const expTime = now + 4 * 60 * 60 * 1000; if (!adInfoObj.expires || adInfoObj.expires <= now) { adInfoObj = { - expires: timeEnd, + expires: Math.min(timeTodayEnd, expTime), }; } for (var i = 0, iLen = adList.length; i < iLen; i++) { var adI = adList[i]; - var showOr = adInfoObj[adI.id] === false ? false : true; - adInfoObj[adI.id] = showOr; + if (adI.show === false) continue; + var showOr = adInfoObj[i] === false ? false : true; + adInfoObj[i] = showOr; if (!showOr) continue; - var adEl = $(`
    `); + adEl.data('data', adI); $('body').append(adEl); } window.localStorage.setItem('ads', JSON.stringify(adInfoObj)); @@ -73,15 +102,39 @@ var offSet = scrollTop - scrollTopOld; scrollTopOld = scrollTop; timeHandler && clearTimeout(timeHandler); - $('.__ad_c__').animate({ bottom: 50 + offSet + 'px' }, 0); + $('.__ad_c__').each(function (_, item) { + var self = $(item); + var adData = self.data('data'); + if (adData.pos.bottom !== undefined) { + self.animate({ bottom: adData.pos.bottom + offSet + 'px' }, 0); + } + if (adData.pos.top !== undefined) { + self.animate({ top: adData.pos.top - offSet + 'px' }, 0); + } + }) timeHandler = setTimeout(function () { - $('.__ad_c__').animate({ bottom: 50 + 'px' }, 0); + $('.__ad_c__').each(function (_, item) { + var self = $(item); + var adData = self.data('data'); + if (adData.pos.bottom !== undefined) { + self.animate({ bottom: adData.pos.bottom + 'px' }, 0); + } + if (adData.pos.top !== undefined) { + self.animate({ top: adData.pos.top + 'px' }, 0); + } + }) }, 20); }); } setTimeout(function () { - createAd(adList); + if (!$('meta[name="_uid"]').length) { // 未登录,不显示 + window.localStorage.removeItem('ads'); + return; + } + var pathName = window.location.pathname; + if (exceptPages.indexOf(pathName) > -1) return; // 排除页,不显示 + initAd(); initAdEvent(); }, 0); })(); From 65afb3bb73d4eb3d02418a64dc35486490ca7b79 Mon Sep 17 00:00:00 2001 From: zouap Date: Wed, 14 Sep 2022 16:59:05 +0800 Subject: [PATCH 31/77] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=BF=90=E8=90=A5?= =?UTF-8?q?=E5=88=86=E6=9E=90=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 14 +++--- routers/api/v1/api.go | 10 +++++ routers/repo/user_invitation.go | 98 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 routers/repo/user_invitation.go diff --git a/models/user_invitation.go b/models/user_invitation.go index 4831e27da..5d9105fa7 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -32,18 +32,22 @@ func QueryInvitaionByPhone(phone string) []*Invitation { } } -func QueryInvitaion(start int64, end int64) ([]*Invitation, int) { +func QueryInvitaionPage(startTime int64, endTime int64, start int, pageSize int) ([]*Invitation, int64) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() - cond := "created_unix >=" + fmt.Sprint(start) + " and created_unix <=" + fmt.Sprint(end) + cond := "created_unix >=" + fmt.Sprint(startTime) + " and created_unix <=" + fmt.Sprint(endTime) + allCount, err := statictisSess.Where(cond).Count(new(Invitation)) + if err != nil { + log.Info("query error." + err.Error()) + return nil, 0 + } invitationList := make([]*Invitation, 0) - - if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc"). + if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc").Limit(pageSize, start). Find(&invitationList); err != nil { return nil, 0 } - return invitationList, len(invitationList) + return invitationList, allCount } func InsertInvitaion(invitationUser *Invitation) error { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 3e588d942..36ba44ce5 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -572,6 +572,16 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/query_user_all", operationReq, repo_ext.QueryUserStaticAll) m.Get("/query_user_activity", operationReq, repo_ext.QueryUserActivity) m.Get("/query_user_login", operationReq, repo_ext.QueryUserLoginInfo) + + m.Get("/query_invitation_current_month", operationReq, repo_ext.QueryInvitationCurrentMonth) + m.Get("/query_invitation_current_week", operationReq, repo_ext.QueryInvitationCurrentWeek) + m.Get("/query_invitation_last_week", operationReq, repo_ext.QueryInvitationLastWeek) + m.Get("/query_invitation_current_year", operationReq, repo_ext.QueryInvitationCurrentYear) + m.Get("/query_invitation_last30_day", operationReq, repo_ext.QueryInvitationLast30Day) + m.Get("/query_invitation_last_month", operationReq, repo_ext.QueryInvitationLastMonth) + m.Get("/query_invitation_yesterday", operationReq, repo_ext.QueryInvitationYesterday) + m.Get("/query_invitation_all", operationReq, repo_ext.QueryInvitationAll) + //cloudbrain board m.Group("/cloudbrainboard", func() { m.Get("/downloadAll", repo.DownloadCloudBrainBoard) diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go new file mode 100644 index 000000000..462fe6b4a --- /dev/null +++ b/routers/repo/user_invitation.go @@ -0,0 +1,98 @@ +package repo + +import ( + "net/http" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" +) + +func QueryInvitationCurrentMonth(ctx *context.Context) { + + currentTimeNow := time.Now() + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) + + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationCurrentWeek(ctx *context.Context) { + currentTimeNow := time.Now() + offset := int(time.Monday - currentTimeNow.Weekday()) + if offset > 0 { + offset = -6 + } + pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationLastWeek(ctx *context.Context) { + currentTimeNow := time.Now() + offset := int(time.Monday - currentTimeNow.Weekday()) + if offset > 0 { + offset = -6 + } + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) + pageStartTime := pageEndTime.AddDate(0, 0, -7) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationCurrentYear(ctx *context.Context) { + currentTimeNow := time.Now() + pageStartTime := time.Date(currentTimeNow.Year(), 1, 1, 0, 0, 0, 0, currentTimeNow.Location()) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationLast30Day(ctx *context.Context) { + currentTimeNow := time.Now() + pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, -30) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationLastMonth(ctx *context.Context) { + currentTimeNow := time.Now() + thisMonth := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) + pageStartTime := thisMonth.AddDate(0, -1, 0) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 23, 59, 59, 0, currentTimeNow.Location()).AddDate(0, 0, -1) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationYesterday(ctx *context.Context) { + currentTimeNow := time.Now().AddDate(0, 0, -1) + pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 23, 59, 59, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func QueryInvitationAll(ctx *context.Context) { + currentTimeNow := time.Now() + pageStartTime := time.Date(2022, 8, 5, 0, 0, 0, 0, currentTimeNow.Location()) + pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) +} + +func queryData(ctx *context.Context, startTime int64, endTime int64) { + page, pageSize := getPageInfo(ctx) + result, count := models.QueryInvitaionPage(startTime, endTime, (page-1)*pageSize, pageSize) + mapInterface := make(map[string]interface{}) + mapInterface["data"] = result + mapInterface["count"] = count + ctx.JSON(http.StatusOK, mapInterface) +} + +func getPageInfo(ctx *context.Context) (int, int) { + page := ctx.QueryInt("page") + if page <= 0 { + page = 1 + } + pageSize := ctx.QueryInt("pageSize") + if pageSize <= 0 { + pageSize = setting.UI.IssuePagingNum + } + return page, pageSize +} From 74a904064ddf30033c03f7a39e12cd4012d2279a Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 10:57:03 +0800 Subject: [PATCH 32/77] =?UTF-8?q?=E8=80=81=E6=8B=89=E6=96=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=BB=9F=E8=AE=A1=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_business_analysis.go | 77 +++++++++++++++++++++++-- models/user_business_struct.go | 21 ++++--- models/user_invitation.go | 17 +++--- routers/repo/user_invitation.go | 119 +++++++++++++++++++++++++-------------- 4 files changed, 172 insertions(+), 62 deletions(-) diff --git a/models/user_business_analysis.go b/models/user_business_analysis.go index 0c67a569a..e99927e18 100644 --- a/models/user_business_analysis.go +++ b/models/user_business_analysis.go @@ -106,7 +106,8 @@ type UserBusinessAnalysisAll struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysis struct { @@ -193,7 +194,8 @@ type UserBusinessAnalysis struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisQueryOptions struct { @@ -354,6 +356,33 @@ func QueryRankList(key string, tableName string, limit int) ([]*UserBusinessAnal return userBusinessAnalysisAllList, int64(len(userBusinessAnalysisAllList)) } +func QueryUserInvitationDataByTableName(start int, pageSize int, tableName string, queryObj interface{}, userName string, invitationNum int) ([]*UserBusinessAnalysisAll, int64) { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + var cond = builder.NewCond() + if len(userName) > 0 { + cond = cond.And( + builder.Like{"lower(name)", strings.ToLower(userName)}, + ) + } + cond = cond.And( + builder.Gte{"invitation_user_num": invitationNum}, + ) + + allCount, err := statictisSess.Where(cond).Count(queryObj) + if err != nil { + log.Info("query error." + err.Error()) + return nil, 0 + } + log.Info("query return total:" + fmt.Sprint(allCount)) + userBusinessAnalysisAllList := make([]*UserBusinessAnalysisAll, 0) + if err := statictisSess.Table(tableName).Where(cond).OrderBy("invitation_user_num desc,id asc").Limit(pageSize, start). + Find(&userBusinessAnalysisAllList); err != nil { + return nil, 0 + } + return userBusinessAnalysisAllList, allCount +} + func QueryUserStaticDataByTableName(start int, pageSize int, tableName string, queryObj interface{}, userName string) ([]*UserBusinessAnalysisAll, int64) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() @@ -363,6 +392,7 @@ func QueryUserStaticDataByTableName(start int, pageSize int, tableName string, q builder.Like{"lower(name)", strings.ToLower(userName)}, ) } + allCount, err := statictisSess.Where(cond).Count(queryObj) if err != nil { log.Info("query error." + err.Error()) @@ -752,6 +782,8 @@ func refreshUserStaticTable(wikiCountMap map[string]int, tableName string, pageS CollectImage, CollectedImage := queryImageStars(start_unix, end_unix) RecommendImage := queryRecommedImage(start_unix, end_unix) + InvitationMap := queryUserInvitationCount(start_unix, end_unix) + DataDate := currentTimeNow.Format("2006-01-02") + " 00:01" cond := "type != 1 and is_active=true" @@ -825,7 +857,7 @@ func refreshUserStaticTable(wikiCountMap map[string]int, tableName string, pageS dateRecordAll.CollectImage = getMapValue(dateRecordAll.ID, CollectImage) dateRecordAll.CollectedImage = getMapValue(dateRecordAll.ID, CollectedImage) dateRecordAll.RecommendImage = getMapValue(dateRecordAll.ID, RecommendImage) - + dateRecordAll.InvitationUserNum = getMapValue(dateRecordAll.ID, InvitationMap) dateRecordAll.UserIndexPrimitive = getUserIndexFromAnalysisAll(dateRecordAll, ParaWeight) userIndexMap[dateRecordAll.ID] = dateRecordAll.UserIndexPrimitive if maxUserIndex < dateRecordAll.UserIndexPrimitive { @@ -888,7 +920,7 @@ func insertTable(dateRecords []UserBusinessAnalysisAll, tableName string, static insertBatchSql := "INSERT INTO public." + tableName + "(id, count_date, code_merge_count, commit_count, issue_count, comment_count, focus_repo_count, star_repo_count, watched_count, gitea_age_month, commit_code_size, commit_dataset_size, " + - "commit_model_count, solve_issue_count, encyclopedias_count, regist_date, create_repo_count, login_count, open_i_index, email, name, data_date,cloud_brain_task_num,gpu_debug_job,npu_debug_job,gpu_train_job,npu_train_job,npu_inference_job,gpu_bench_mark_job,cloud_brain_run_time,commit_dataset_num,user_index,user_location,focus_other_user,collect_dataset,collected_dataset,recommend_dataset,collect_image,collected_image,recommend_image,user_index_primitive,phone) " + + "commit_model_count, solve_issue_count, encyclopedias_count, regist_date, create_repo_count, login_count, open_i_index, email, name, data_date,cloud_brain_task_num,gpu_debug_job,npu_debug_job,gpu_train_job,npu_train_job,npu_inference_job,gpu_bench_mark_job,cloud_brain_run_time,commit_dataset_num,user_index,user_location,focus_other_user,collect_dataset,collected_dataset,recommend_dataset,collect_image,collected_image,recommend_image,user_index_primitive,phone,invitation_user_num) " + "VALUES" for i, record := range dateRecords { @@ -897,7 +929,7 @@ func insertTable(dateRecords []UserBusinessAnalysisAll, tableName string, static ", " + fmt.Sprint(record.WatchedCount) + ", " + fmt.Sprint(record.GiteaAgeMonth) + ", " + fmt.Sprint(record.CommitCodeSize) + ", " + fmt.Sprint(record.CommitDatasetSize) + ", " + fmt.Sprint(record.CommitModelCount) + ", " + fmt.Sprint(record.SolveIssueCount) + ", " + fmt.Sprint(record.EncyclopediasCount) + ", " + fmt.Sprint(record.RegistDate) + ", " + fmt.Sprint(record.CreateRepoCount) + ", " + fmt.Sprint(record.LoginCount) + ", " + fmt.Sprint(record.OpenIIndex) + ", '" + record.Email + "', '" + record.Name + "', '" + record.DataDate + "'," + fmt.Sprint(record.CloudBrainTaskNum) + "," + fmt.Sprint(record.GpuDebugJob) + "," + fmt.Sprint(record.NpuDebugJob) + "," + fmt.Sprint(record.GpuTrainJob) + "," + fmt.Sprint(record.NpuTrainJob) + "," + fmt.Sprint(record.NpuInferenceJob) + "," + fmt.Sprint(record.GpuBenchMarkJob) + "," + fmt.Sprint(record.CloudBrainRunTime) + "," + fmt.Sprint(record.CommitDatasetNum) + "," + fmt.Sprint(record.UserIndex) + ",'" + record.UserLocation + "'," + - fmt.Sprint(record.FocusOtherUser) + "," + fmt.Sprint(record.CollectDataset) + "," + fmt.Sprint(record.CollectedDataset) + "," + fmt.Sprint(record.RecommendDataset) + "," + fmt.Sprint(record.CollectImage) + "," + fmt.Sprint(record.CollectedImage) + "," + fmt.Sprint(record.RecommendImage) + "," + fmt.Sprint(record.UserIndexPrimitive) + ",'" + record.Phone + "')" + fmt.Sprint(record.FocusOtherUser) + "," + fmt.Sprint(record.CollectDataset) + "," + fmt.Sprint(record.CollectedDataset) + "," + fmt.Sprint(record.RecommendDataset) + "," + fmt.Sprint(record.CollectImage) + "," + fmt.Sprint(record.CollectedImage) + "," + fmt.Sprint(record.RecommendImage) + "," + fmt.Sprint(record.UserIndexPrimitive) + ",'" + record.Phone + "'" + "," + fmt.Sprint(record.InvitationUserNum) + ")" if i < (len(dateRecords) - 1) { insertBatchSql += "," } @@ -2173,6 +2205,41 @@ func queryCloudBrainTask(start_unix int64, end_unix int64) (map[int64]int, map[s return resultMap, resultItemMap } + +func queryUserInvitationCount(start_unix int64, end_unix int64) map[int64]int { + statictisSess := xStatistic.NewSession() + defer statictisSess.Close() + + resultMap := make(map[int64]int) + cond := "created_unix>=" + fmt.Sprint(start_unix) + " and created_unix<=" + fmt.Sprint(end_unix) + count, err := statictisSess.Where(cond).Count(new(Invitation)) + if err != nil { + log.Info("query queryUserInvitationCount error. return.") + return resultMap + } + var indexTotal int64 + indexTotal = 0 + for { + statictisSess.Select("id,src_user_id,user_id").Table("invitation").Where(cond).OrderBy("id asc").Limit(PAGE_SIZE, int(indexTotal)) + invitationList := make([]*Invitation, 0) + statictisSess.Find(&invitationList) + log.Info("query invitationList size=" + fmt.Sprint(len(invitationList))) + for _, invitationRecord := range invitationList { + if _, ok := resultMap[invitationRecord.SrcUserID]; !ok { + resultMap[invitationRecord.SrcUserID] = 1 + } else { + resultMap[invitationRecord.SrcUserID] += 1 + } + } + indexTotal += PAGE_SIZE + if indexTotal >= count { + break + } + } + log.Info("invitationList size=" + fmt.Sprint(len(resultMap))) + return resultMap +} + func setMapKey(key string, userId int64, value int, resultItemMap map[string]int) { newKey := fmt.Sprint(userId) + "_" + key if _, ok := resultItemMap[newKey]; !ok { diff --git a/models/user_business_struct.go b/models/user_business_struct.go index 36ef077e2..fe98be760 100644 --- a/models/user_business_struct.go +++ b/models/user_business_struct.go @@ -66,7 +66,8 @@ type UserBusinessAnalysisCurrentYear struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisLast30Day struct { @@ -133,7 +134,8 @@ type UserBusinessAnalysisLast30Day struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisLastMonth struct { @@ -200,7 +202,8 @@ type UserBusinessAnalysisLastMonth struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisCurrentMonth struct { @@ -267,7 +270,8 @@ type UserBusinessAnalysisCurrentMonth struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisCurrentWeek struct { @@ -335,7 +339,8 @@ type UserBusinessAnalysisCurrentWeek struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisYesterday struct { @@ -403,7 +408,8 @@ type UserBusinessAnalysisYesterday struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserBusinessAnalysisLastWeek struct { @@ -471,7 +477,8 @@ type UserBusinessAnalysisLastWeek struct { CollectedImage int `xorm:"NOT NULL DEFAULT 0"` RecommendImage int `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"NULL"` + Phone string `xorm:"NULL"` + InvitationUserNum int `xorm:"NOT NULL DEFAULT 0"` } type UserAnalysisPara struct { diff --git a/models/user_invitation.go b/models/user_invitation.go index 5d9105fa7..f0e99b1a7 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -9,14 +9,15 @@ import ( // Follow represents relations of user and his/her followers. type Invitation struct { - ID int64 `xorm:"pk autoincr"` - SrcUserID int64 `xorm:"NOT NULL DEFAULT 0"` - UserID int64 `xorm:"NOT NULL DEFAULT 0"` - Phone string `xorm:"INDEX"` - Avatar string `xorm:"-"` - Name string `xorm:"-"` - IsActive bool `xorm:"-"` - CreatedUnix timeutil.TimeStamp `xorm:"created"` + ID int64 `xorm:"pk autoincr"` + SrcUserID int64 `xorm:"NOT NULL DEFAULT 0"` + UserID int64 `xorm:"NOT NULL DEFAULT 0"` + Phone string `xorm:"INDEX"` + Avatar string `xorm:"-"` + Name string `xorm:"-"` + InvitationUserNum int `xorm:"-"` + IsActive bool `xorm:"-"` + CreatedUnix timeutil.TimeStamp `xorm:"created"` } func QueryInvitaionByPhone(phone string) []*Invitation { diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go index 462fe6b4a..222fc1c75 100644 --- a/routers/repo/user_invitation.go +++ b/routers/repo/user_invitation.go @@ -2,7 +2,6 @@ package repo import ( "net/http" - "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -10,70 +9,106 @@ import ( ) func QueryInvitationCurrentMonth(ctx *context.Context) { + // userName := ctx.Query("userName") + // currentTimeNow := time.Now() + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) - currentTimeNow := time.Now() - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) + // queryUserDataPage(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) + //_, count := models.QueryUserStaticDataByTableName(1, 1, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth), userName, 1) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + queryDataFromStaticTable(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) } -func QueryInvitationCurrentWeek(ctx *context.Context) { - currentTimeNow := time.Now() - offset := int(time.Monday - currentTimeNow.Weekday()) - if offset > 0 { - offset = -6 +func queryDataFromStaticTable(ctx *context.Context, tableName string, queryObj interface{}) { + page, pageSize := getPageInfo(ctx) + userName := ctx.Query("userName") + resultRecord, count := models.QueryUserInvitationDataByTableName((page-1)*pageSize, pageSize, tableName, queryObj, userName, 1) + result := make([]models.Invitation, 0) + for _, record := range resultRecord { + invi := models.Invitation{ + SrcUserID: record.ID, + Name: record.Name, + InvitationUserNum: record.InvitationUserNum, + Phone: record.Phone, + } + result = append(result, invi) } - pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + mapInterface := make(map[string]interface{}) + mapInterface["data"] = result + mapInterface["count"] = count + ctx.JSON(http.StatusOK, mapInterface) +} + +func QueryInvitationCurrentWeek(ctx *context.Context) { + // currentTimeNow := time.Now() + // offset := int(time.Monday - currentTimeNow.Weekday()) + // if offset > 0 { + // offset = -6 + // } + // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + queryDataFromStaticTable(ctx, "public.user_business_analysis_current_week", new(models.UserBusinessAnalysisCurrentWeek)) } func QueryInvitationLastWeek(ctx *context.Context) { - currentTimeNow := time.Now() - offset := int(time.Monday - currentTimeNow.Weekday()) - if offset > 0 { - offset = -6 - } - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) - pageStartTime := pageEndTime.AddDate(0, 0, -7) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // offset := int(time.Monday - currentTimeNow.Weekday()) + // if offset > 0 { + // offset = -6 + // } + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset) + // pageStartTime := pageEndTime.AddDate(0, 0, -7) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_last_week", new(models.UserBusinessAnalysisLastWeek)) } func QueryInvitationCurrentYear(ctx *context.Context) { - currentTimeNow := time.Now() - pageStartTime := time.Date(currentTimeNow.Year(), 1, 1, 0, 0, 0, 0, currentTimeNow.Location()) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // pageStartTime := time.Date(currentTimeNow.Year(), 1, 1, 0, 0, 0, 0, currentTimeNow.Location()) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_current_year", new(models.UserBusinessAnalysisCurrentYear)) } func QueryInvitationLast30Day(ctx *context.Context) { - currentTimeNow := time.Now() - pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, -30) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, -30) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_last30_day", new(models.UserBusinessAnalysisLast30Day)) } func QueryInvitationLastMonth(ctx *context.Context) { - currentTimeNow := time.Now() - thisMonth := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) - pageStartTime := thisMonth.AddDate(0, -1, 0) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 23, 59, 59, 0, currentTimeNow.Location()).AddDate(0, 0, -1) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // thisMonth := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) + // pageStartTime := thisMonth.AddDate(0, -1, 0) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 23, 59, 59, 0, currentTimeNow.Location()).AddDate(0, 0, -1) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_last_month", new(models.UserBusinessAnalysisLastMonth)) } func QueryInvitationYesterday(ctx *context.Context) { - currentTimeNow := time.Now().AddDate(0, 0, -1) - pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 23, 59, 59, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now().AddDate(0, 0, -1) + // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, time.Local) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 23, 59, 59, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_yesterday", new(models.UserBusinessAnalysisYesterday)) } func QueryInvitationAll(ctx *context.Context) { - currentTimeNow := time.Now() - pageStartTime := time.Date(2022, 8, 5, 0, 0, 0, 0, currentTimeNow.Location()) - pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) - queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + // currentTimeNow := time.Now() + // pageStartTime := time.Date(2022, 8, 5, 0, 0, 0, 0, currentTimeNow.Location()) + // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) + // queryData(ctx, pageStartTime.Unix(), pageEndTime.Unix()) + + queryDataFromStaticTable(ctx, "public.user_business_analysis_all", new(models.UserBusinessAnalysisAll)) } func queryData(ctx *context.Context, startTime int64, endTime int64) { From 784c9efd8dc8f92da7e6a7d573e031866aed917c Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 15:05:26 +0800 Subject: [PATCH 33/77] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 8 +-- options/locale/locale_en-US.ini | 2 + options/locale/locale_zh-CN.ini | 2 + routers/api/v1/api.go | 1 + routers/repo/user_invitation.go | 115 +++++++++++++++++++++++++++++++++++----- 5 files changed, 110 insertions(+), 18 deletions(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index f0e99b1a7..34d6b239f 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -33,18 +33,18 @@ func QueryInvitaionByPhone(phone string) []*Invitation { } } -func QueryInvitaionPage(startTime int64, endTime int64, start int, pageSize int) ([]*Invitation, int64) { +func QueryInvitaionPage(start int, pageSize int) ([]*Invitation, int64) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() - cond := "created_unix >=" + fmt.Sprint(startTime) + " and created_unix <=" + fmt.Sprint(endTime) + //cond := "created_unix >=" + fmt.Sprint(startTime) + " and created_unix <=" + fmt.Sprint(endTime) - allCount, err := statictisSess.Where(cond).Count(new(Invitation)) + allCount, err := statictisSess.Count(new(Invitation)) if err != nil { log.Info("query error." + err.Error()) return nil, 0 } invitationList := make([]*Invitation, 0) - if err := statictisSess.Table(new(Invitation)).Where(cond).OrderBy("created_unix desc").Limit(pageSize, start). + if err := statictisSess.Table(new(Invitation)).OrderBy("created_unix desc").Limit(pageSize, start). Find(&invitationList); err != nil { return nil, 0 } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 70df4ad90..b8584c785 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -536,6 +536,8 @@ form.name_reserved = The username '%s' is reserved. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. form.name_chars_not_allowed = User name '%s' contains invalid characters. +user.static.invitationNum=User Invitation Count +static.invitationsheetname=User Invitation static.sheetname=User Analysis static.id=ID static.name=User Name diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index e6128a859..51f150cee 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -541,7 +541,9 @@ form.name_reserved='%s' 用户名被保留。 form.name_pattern_not_allowed=用户名中不允许使用 "%s"。 form.name_chars_not_allowed=用户名 '%s' 包含无效字符。 +user.static.invitationNum=邀请用户数 static.sheetname=用户分析 +static.invitationsheetname=用户邀请分析 static.id=ID static.name=用户名 static.codemergecount=PR数 diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 36ba44ce5..ff5474914 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -581,6 +581,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/query_invitation_last_month", operationReq, repo_ext.QueryInvitationLastMonth) m.Get("/query_invitation_yesterday", operationReq, repo_ext.QueryInvitationYesterday) m.Get("/query_invitation_all", operationReq, repo_ext.QueryInvitationAll) + m.Get("/download_invitation_detail", operationReq, repo_ext.DownloadInvitationDetail) //cloudbrain board m.Group("/cloudbrainboard", func() { diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go index 222fc1c75..1a63ec1bc 100644 --- a/routers/repo/user_invitation.go +++ b/routers/repo/user_invitation.go @@ -1,11 +1,15 @@ package repo import ( + "fmt" "net/http" + "net/url" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "github.com/360EntSecGroup-Skylar/excelize/v2" ) func QueryInvitationCurrentMonth(ctx *context.Context) { @@ -14,30 +18,113 @@ func QueryInvitationCurrentMonth(ctx *context.Context) { // pageEndTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), currentTimeNow.Day(), 0, 0, 0, 0, currentTimeNow.Location()) // pageStartTime := time.Date(currentTimeNow.Year(), currentTimeNow.Month(), 1, 0, 0, 0, 0, currentTimeNow.Location()) - // queryUserDataPage(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) + //queryUserDataPage(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) //_, count := models.QueryUserStaticDataByTableName(1, 1, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth), userName, 1) queryDataFromStaticTable(ctx, "public.user_business_analysis_current_month", new(models.UserBusinessAnalysisCurrentMonth)) } +func getInvitationExcelHeader(ctx *context.Context) map[string]string { + excelHeader := make([]string, 0) + excelHeader = append(excelHeader, ctx.Tr("user.static.id")) + excelHeader = append(excelHeader, ctx.Tr("user.static.name")) + excelHeader = append(excelHeader, ctx.Tr("user.static.invitationNum")) + excelHeader = append(excelHeader, ctx.Tr("user.static.phone")) + excelHeader = append(excelHeader, ctx.Tr("user.static.registdate")) + + excelHeaderMap := make(map[string]string, 0) + var i byte + i = 0 + for _, value := range excelHeader { + excelColumn := getColumn(i) + fmt.Sprint(1) + excelHeaderMap[excelColumn] = value + i++ + } + return excelHeaderMap +} + +func writeInvitationExcel(row int, xlsx *excelize.File, sheetName string, userRecord *models.UserBusinessAnalysisAll) { + rows := fmt.Sprint(row) + var tmp byte + tmp = 0 + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.ID) + tmp = tmp + 1 + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Name) + tmp = tmp + 1 + + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.InvitationUserNum) + tmp = tmp + 1 + + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Phone) + tmp = tmp + 1 + + formatTime := userRecord.RegistDate.Format("2006-01-02 15:04:05") + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, formatTime[0:len(formatTime)-3]) + +} + +func DownloadInvitationDetail(ctx *context.Context) { + +} + func queryDataFromStaticTable(ctx *context.Context, tableName string, queryObj interface{}) { page, pageSize := getPageInfo(ctx) userName := ctx.Query("userName") - resultRecord, count := models.QueryUserInvitationDataByTableName((page-1)*pageSize, pageSize, tableName, queryObj, userName, 1) - result := make([]models.Invitation, 0) - for _, record := range resultRecord { - invi := models.Invitation{ - SrcUserID: record.ID, - Name: record.Name, - InvitationUserNum: record.InvitationUserNum, - Phone: record.Phone, + IsReturnFile := ctx.QueryBool("IsReturnFile") + + if IsReturnFile { + //writer exec file. + xlsx := excelize.NewFile() + sheetName := ctx.Tr("user.static.invitationsheetname") + index := xlsx.NewSheet(sheetName) + xlsx.DeleteSheet("Sheet1") + excelHeader := getInvitationExcelHeader(ctx) + for k, v := range excelHeader { + //设置单元格的值 + xlsx.SetCellValue(sheetName, k, v) + } + _, count := models.QueryUserInvitationDataByTableName(1, 1, tableName, queryObj, "", 1) + var indexTotal int64 + indexTotal = 0 + row := 1 + for { + re, _ := models.QueryUserInvitationDataByTableName(int(indexTotal), PAGE_SIZE, tableName, queryObj, "", 1) + log.Info("return count=" + fmt.Sprint(count)) + for _, userRecord := range re { + row++ + writeInvitationExcel(row, xlsx, sheetName, userRecord) + } + indexTotal += PAGE_SIZE + if indexTotal >= count { + break + } + } + //设置默认打开的表单 + xlsx.SetActiveSheet(index) + filename := sheetName + "_" + ctx.Tr("user.static."+tableName) + ".xlsx" + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(filename)) + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + if _, err := xlsx.WriteTo(ctx.Resp); err != nil { + log.Info("writer exel error." + err.Error()) + } + } else { + resultRecord, count := models.QueryUserInvitationDataByTableName((page-1)*pageSize, pageSize, tableName, queryObj, userName, 1) + result := make([]models.Invitation, 0) + for _, record := range resultRecord { + invi := models.Invitation{ + SrcUserID: record.ID, + Name: record.Name, + InvitationUserNum: record.InvitationUserNum, + Phone: record.Phone, + CreatedUnix: record.RegistDate, + } + result = append(result, invi) } - result = append(result, invi) + mapInterface := make(map[string]interface{}) + mapInterface["data"] = result + mapInterface["count"] = count + ctx.JSON(http.StatusOK, mapInterface) } - mapInterface := make(map[string]interface{}) - mapInterface["data"] = result - mapInterface["count"] = count - ctx.JSON(http.StatusOK, mapInterface) } func QueryInvitationCurrentWeek(ctx *context.Context) { From 0bb98eb80d07f9576cf04cc6b2dec367c27d53e8 Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 15:40:40 +0800 Subject: [PATCH 34/77] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- options/locale/locale_en-US.ini | 4 +- options/locale/locale_zh-CN.ini | 4 +- routers/repo/user_invitation.go | 88 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b8584c785..b48c0fa6c 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -536,8 +536,10 @@ form.name_reserved = The username '%s' is reserved. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. form.name_chars_not_allowed = User name '%s' contains invalid characters. -user.static.invitationNum=User Invitation Count +static.invitationdetailsheetname=User Invitation Detail +static.invitationNum=User Invitation Count static.invitationsheetname=User Invitation +static.srcUserId=Recommended User ID static.sheetname=User Analysis static.id=ID static.name=User Name diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 51f150cee..434a73986 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -541,8 +541,10 @@ form.name_reserved='%s' 用户名被保留。 form.name_pattern_not_allowed=用户名中不允许使用 "%s"。 form.name_chars_not_allowed=用户名 '%s' 包含无效字符。 -user.static.invitationNum=邀请用户数 +static.invitationdetailsheetname=用户邀请详细数据 +static.invitationNum=邀请用户数 static.sheetname=用户分析 +static.srcUserId=推荐用户ID static.invitationsheetname=用户邀请分析 static.id=ID static.name=用户名 diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go index 1a63ec1bc..47eb1a3e7 100644 --- a/routers/repo/user_invitation.go +++ b/routers/repo/user_invitation.go @@ -43,6 +43,25 @@ func getInvitationExcelHeader(ctx *context.Context) map[string]string { return excelHeaderMap } +func getInvitationDetailExcelHeader(ctx *context.Context) map[string]string { + excelHeader := make([]string, 0) + excelHeader = append(excelHeader, ctx.Tr("user.static.id")) + excelHeader = append(excelHeader, ctx.Tr("user.static.name")) + excelHeader = append(excelHeader, ctx.Tr("user.static.srcUserId")) + excelHeader = append(excelHeader, ctx.Tr("user.static.phone")) + excelHeader = append(excelHeader, ctx.Tr("user.static.registdate")) + + excelHeaderMap := make(map[string]string, 0) + var i byte + i = 0 + for _, value := range excelHeader { + excelColumn := getColumn(i) + fmt.Sprint(1) + excelHeaderMap[excelColumn] = value + i++ + } + return excelHeaderMap +} + func writeInvitationExcel(row int, xlsx *excelize.File, sheetName string, userRecord *models.UserBusinessAnalysisAll) { rows := fmt.Sprint(row) var tmp byte @@ -63,8 +82,61 @@ func writeInvitationExcel(row int, xlsx *excelize.File, sheetName string, userRe } +func writeInvitationDetailExcel(row int, xlsx *excelize.File, sheetName string, userRecord *models.Invitation) { + rows := fmt.Sprint(row) + var tmp byte + tmp = 0 + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.ID) + tmp = tmp + 1 + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Name) + tmp = tmp + 1 + + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.SrcUserID) + tmp = tmp + 1 + + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Phone) + tmp = tmp + 1 + + formatTime := userRecord.CreatedUnix.Format("2006-01-02 15:04:05") + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, formatTime[0:len(formatTime)-3]) + +} + func DownloadInvitationDetail(ctx *context.Context) { + xlsx := excelize.NewFile() + sheetName := ctx.Tr("user.static.invitationdetailsheetname") + index := xlsx.NewSheet(sheetName) + xlsx.DeleteSheet("Sheet1") + excelHeader := getInvitationDetailExcelHeader(ctx) + for k, v := range excelHeader { + //设置单元格的值 + xlsx.SetCellValue(sheetName, k, v) + } + _, count := models.QueryInvitaionPage(1, 1) + var indexTotal int64 + indexTotal = 0 + row := 1 + for { + re, _ := models.QueryInvitaionPage(int(indexTotal), PAGE_SIZE) + log.Info("return count=" + fmt.Sprint(count)) + for _, userRecord := range re { + row++ + writeInvitationDetailExcel(row, xlsx, sheetName, userRecord) + } + indexTotal += PAGE_SIZE + if indexTotal >= count { + break + } + } + //设置默认打开的表单 + xlsx.SetActiveSheet(index) + filename := sheetName + ".xlsx" + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(filename)) + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + if _, err := xlsx.WriteTo(ctx.Resp); err != nil { + log.Info("writer exel error." + err.Error()) + } } func queryDataFromStaticTable(ctx *context.Context, tableName string, queryObj interface{}) { @@ -198,14 +270,14 @@ func QueryInvitationAll(ctx *context.Context) { queryDataFromStaticTable(ctx, "public.user_business_analysis_all", new(models.UserBusinessAnalysisAll)) } -func queryData(ctx *context.Context, startTime int64, endTime int64) { - page, pageSize := getPageInfo(ctx) - result, count := models.QueryInvitaionPage(startTime, endTime, (page-1)*pageSize, pageSize) - mapInterface := make(map[string]interface{}) - mapInterface["data"] = result - mapInterface["count"] = count - ctx.JSON(http.StatusOK, mapInterface) -} +// func queryData(ctx *context.Context, startTime int64, endTime int64) { +// page, pageSize := getPageInfo(ctx) +// result, count := models.QueryInvitaionPage(startTime, endTime, (page-1)*pageSize, pageSize) +// mapInterface := make(map[string]interface{}) +// mapInterface["data"] = result +// mapInterface["count"] = count +// ctx.JSON(http.StatusOK, mapInterface) +// } func getPageInfo(ctx *context.Context) (int, int) { page := ctx.QueryInt("page") From 97d7cd6d5b5fc06430556b3791a18658d728d13f Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 15:55:20 +0800 Subject: [PATCH 35/77] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 13 +++++++++++++ routers/repo/user_invitation.go | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index 34d6b239f..a462ee74a 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -33,6 +33,19 @@ func QueryInvitaionByPhone(phone string) []*Invitation { } } +func GetAllUserName() map[int64]string { + sess := x.NewSession() + defer sess.Close() + sess.Select("`user`.id,`user`.name,").Table("user") + userList := make([]*User, 0) + reMap := make(map[int64]string) + sess.Find(&userList) + for _, user := range userList { + reMap[user.ID] = user.Name + } + return reMap +} + func QueryInvitaionPage(start int, pageSize int) ([]*Invitation, int64) { statictisSess := xStatistic.NewSession() defer statictisSess.Close() diff --git a/routers/repo/user_invitation.go b/routers/repo/user_invitation.go index 47eb1a3e7..b0aac4eed 100644 --- a/routers/repo/user_invitation.go +++ b/routers/repo/user_invitation.go @@ -86,7 +86,7 @@ func writeInvitationDetailExcel(row int, xlsx *excelize.File, sheetName string, rows := fmt.Sprint(row) var tmp byte tmp = 0 - xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.ID) + xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.UserID) tmp = tmp + 1 xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.Name) tmp = tmp + 1 @@ -112,7 +112,7 @@ func DownloadInvitationDetail(ctx *context.Context) { //设置单元格的值 xlsx.SetCellValue(sheetName, k, v) } - + userNameMap := models.GetAllUserName() _, count := models.QueryInvitaionPage(1, 1) var indexTotal int64 indexTotal = 0 @@ -122,6 +122,7 @@ func DownloadInvitationDetail(ctx *context.Context) { log.Info("return count=" + fmt.Sprint(count)) for _, userRecord := range re { row++ + userRecord.Name = userNameMap[userRecord.UserID] writeInvitationDetailExcel(row, xlsx, sheetName, userRecord) } indexTotal += PAGE_SIZE From b3f59f381985bd7acc5c53fb3eeeddafae76db03 Mon Sep 17 00:00:00 2001 From: zouap Date: Thu, 15 Sep 2022 15:59:30 +0800 Subject: [PATCH 36/77] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/user_invitation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user_invitation.go b/models/user_invitation.go index a462ee74a..7b0ca1cf9 100644 --- a/models/user_invitation.go +++ b/models/user_invitation.go @@ -36,7 +36,7 @@ func QueryInvitaionByPhone(phone string) []*Invitation { func GetAllUserName() map[int64]string { sess := x.NewSession() defer sess.Close() - sess.Select("`user`.id,`user`.name,").Table("user") + sess.Select("id,name").Table("user") userList := make([]*User, 0) reMap := make(map[int64]string) sess.Find(&userList) From 0fb745427b958e62003546b44dabaed4d5b3f405 Mon Sep 17 00:00:00 2001 From: zouap Date: Fri, 16 Sep 2022 11:18:18 +0800 Subject: [PATCH 37/77] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zouap --- models/ai_model_manage.go | 16 ++++++++++++ routers/repo/ai_model_manage.go | 58 +++++++++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/models/ai_model_manage.go b/models/ai_model_manage.go index 0ea01d6e5..97cae95a0 100644 --- a/models/ai_model_manage.go +++ b/models/ai_model_manage.go @@ -286,6 +286,22 @@ func ModifyModelDescription(id string, description string) error { return nil } +func ModifyModelStatus(id string, modelSize int64, status int, modelPath string) error { + var sess *xorm.Session + sess = x.ID(id) + defer sess.Close() + re, err := sess.Cols("size", "status", "path").Update(&AiModelManage{ + Size: modelSize, + Status: status, + Path: modelPath, + }) + if err != nil { + return err + } + log.Info("success to update ModelStatus from db.re=" + fmt.Sprint((re))) + return nil +} + func ModifyModelNewProperty(id string, new int, versioncount int) error { var sess *xorm.Session sess = x.ID(id) diff --git a/routers/repo/ai_model_manage.go b/routers/repo/ai_model_manage.go index d01539a75..1b295660a 100644 --- a/routers/repo/ai_model_manage.go +++ b/routers/repo/ai_model_manage.go @@ -27,6 +27,9 @@ const ( MODEL_LATEST = 1 MODEL_NOT_LATEST = 0 MODEL_MAX_SIZE = 1024 * 1024 * 1024 + STATUS_COPY_MODEL = 1 + STATUS_FINISHED = 0 + STATUS_ERROR = 2 ) func saveModelByParameters(jobId string, versionName string, name string, version string, label string, description string, engine int, ctx *context.Context) error { @@ -62,13 +65,9 @@ func saveModelByParameters(jobId string, versionName string, name string, versio modelSelectedFile := ctx.Query("modelSelectedFile") //download model zip //train type if aiTask.ComputeResource == models.NPUResource { - modelPath, modelSize, err = downloadModelFromCloudBrainTwo(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) - if err != nil { - log.Info("download model from CloudBrainTwo faild." + err.Error()) - return err - } cloudType = models.TypeCloudBrainTwo } else if aiTask.ComputeResource == models.GPUResource { + cloudType = models.TypeCloudBrainOne var ResourceSpecs *models.ResourceSpecs json.Unmarshal([]byte(setting.ResourceSpecs), &ResourceSpecs) for _, tmp := range ResourceSpecs.ResourceSpec { @@ -77,24 +76,8 @@ func saveModelByParameters(jobId string, versionName string, name string, versio aiTask.FlavorName = flaverName } } - modelPath, modelSize, err = downloadModelFromCloudBrainOne(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) - if err != nil { - log.Info("download model from CloudBrainOne faild." + err.Error()) - return err - } - cloudType = models.TypeCloudBrainOne } - // else if cloudType == models.TypeC2Net { - // if aiTask.ComputeResource == models.NPUResource { - // modelPath, modelSize, err = downloadModelFromCloudBrainTwo(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) - // if err != nil { - // log.Info("download model from CloudBrainTwo faild." + err.Error()) - // return err - // } - // } else if aiTask.ComputeResource == models.GPUResource { - - // } - // } + accuracy := make(map[string]string) accuracy["F1"] = "" accuracy["Recall"] = "" @@ -123,6 +106,7 @@ func saveModelByParameters(jobId string, versionName string, name string, versio Engine: int64(engine), TrainTaskInfo: string(aiTaskJson), Accuracy: string(accuracyJson), + Status: STATUS_COPY_MODEL, } err = models.SaveModelToDb(model) @@ -146,11 +130,41 @@ func saveModelByParameters(jobId string, versionName string, name string, versio models.UpdateRepositoryUnits(ctx.Repo.Repository, units, deleteUnitTypes) + go asyncToCopyModel(aiTask, id, modelSelectedFile) + log.Info("save model end.") notification.NotifyOtherTask(ctx.User, ctx.Repo.Repository, id, name, models.ActionCreateNewModelTask) return nil } +func asyncToCopyModel(aiTask *models.Cloudbrain, id string, modelSelectedFile string) { + if aiTask.ComputeResource == models.NPUResource { + modelPath, modelSize, err := downloadModelFromCloudBrainTwo(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) + if err != nil { + updateStatus(id, 0, STATUS_ERROR, modelPath) + log.Info("download model from CloudBrainTwo faild." + err.Error()) + } else { + updateStatus(id, modelSize, STATUS_FINISHED, modelPath) + } + } else if aiTask.ComputeResource == models.GPUResource { + + modelPath, modelSize, err := downloadModelFromCloudBrainOne(id, aiTask.JobName, "", aiTask.TrainUrl, modelSelectedFile) + if err != nil { + updateStatus(id, 0, STATUS_ERROR, modelPath) + log.Info("download model from CloudBrainOne faild." + err.Error()) + } else { + updateStatus(id, modelSize, STATUS_FINISHED, modelPath) + } + } +} + +func updateStatus(id string, modelSize int64, status int, modelPath string) { + err := models.ModifyModelStatus(id, modelSize, STATUS_FINISHED, modelPath) + if err != nil { + log.Info("update status error." + err.Error()) + } +} + func SaveNewNameModel(ctx *context.Context) { if !ctx.Repo.CanWrite(models.UnitTypeModelManage) { ctx.Error(403, ctx.Tr("repo.model_noright")) From 928a7b8d215cc76664e12cd2c291bf654632aa2c Mon Sep 17 00:00:00 2001 From: zhoupzh Date: Fri, 16 Sep 2022 16:07:39 +0800 Subject: [PATCH 38/77] fix issue --- templates/repo/modelmanage/index.tmpl | 4 +-- web_src/js/components/Model.vue | 57 ++++++++++++++++++++++++++++------- web_src/js/features/i18nVue.js | 8 +++++ 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/templates/repo/modelmanage/index.tmpl b/templates/repo/modelmanage/index.tmpl index 3a5240768..b2994f0c2 100644 --- a/templates/repo/modelmanage/index.tmpl +++ b/templates/repo/modelmanage/index.tmpl @@ -46,9 +46,9 @@
    {{template "repo/header" .}} -
    +
    {{template "base/alert" .}} -
    +