sunsheng 8 months ago
parent
commit
536e83afa5
11 changed files with 506 additions and 94 deletions
  1. 7 25
      src/api/api.go
  2. 0 38
      src/api/controller.go
  3. 2 13
      src/api/jwt.go
  4. 126 0
      src/api/learn.go
  5. 9 0
      src/api/router.go
  6. 289 0
      src/api/user.go
  7. 32 0
      src/db/model/learnLog.go
  8. 24 0
      src/db/model/user.go
  9. 4 1
      src/db/repo/repo.go
  10. 0 17
      src/go.sum
  11. 13 0
      src/utils/utils.go

+ 7 - 25
src/api/api.go

@@ -13,15 +13,12 @@ import (
 )
 
 type Service struct {
-	Gin            *gin.Engine
-	Mongo          *db.MongoDB
-	Redis          *redis.Client
-	Port           int32
-	DebugUserId    string
-	DebugUserPhone string
-	DebugUserRole  string
-	JWT            *UtilsJwt
-	Conf           *conf.AppConf
+	Gin   *gin.Engine
+	Mongo *db.MongoDB
+	Redis *redis.Client
+	Port  int32
+	JWT   *UtilsJwt
+	Conf  *conf.AppConf
 }
 
 func (svc *Service) Run() {
@@ -55,7 +52,7 @@ func NewHttpService(app *conf.AppConf, dbMongo *db.MongoDB, redisClient *redis.C
 
 	jwt := NewUitlsJwt(app)
 
-	s := &Service{Conf: app, Redis: redisClient, JWT: jwt, Gin: engine, Mongo: dbMongo, Port: app.Port, DebugUserId: app.Debug.UserId, DebugUserPhone: app.Debug.UserPhone, DebugUserRole: app.Debug.UserRole}
+	s := &Service{Conf: app, Redis: redisClient, JWT: jwt, Gin: engine, Mongo: dbMongo, Port: app.Port}
 
 	RegRouters(s)
 
@@ -93,11 +90,6 @@ func (g GinRouter) GETJWT(path string, httpHandler JWTHander) {
 	g.group.GET(path, g.svc.JWT.MiddleFunc(), ResultJWTWrapper(httpHandler, g.svc))
 }
 
-// GETJWTTest http Get 请求
-func (g GinRouter) GETJWTTest(path string, httpHandler JWTHander) {
-	g.group.GET(path, ResultJWTTestWrapper(httpHandler, g.svc))
-}
-
 // POSTJWT http POST 请求
 func (g GinRouter) POSTJWT(path string, httpHandler JWTHander) {
 	g.group.POST(path, g.svc.JWT.MiddleFunc(), ResultJWTWrapper(httpHandler, g.svc))
@@ -108,16 +100,6 @@ func (g GinRouter) DeleteJWT(path string, httpHandler JWTHander) {
 	g.group.DELETE(path, g.svc.JWT.MiddleFunc(), ResultJWTWrapper(httpHandler, g.svc))
 }
 
-// DeleteJWT http POST 请求
-func (g GinRouter) DeleteJWTTEST(path string, httpHandler JWTHander) {
-	g.group.DELETE(path, ResultJWTTestWrapper(httpHandler, g.svc))
-}
-
-// POSTJWTTest 测试
-func (g GinRouter) POSTJWTTest(path string, httpHandler JWTHander) {
-	g.group.POST(path, ResultJWTTestWrapper(httpHandler, g.svc))
-}
-
 // 代参数判断权限
 func (g GinRouter) GETJWTKEY(path string, httpHandler JWTHander, keys ...string) {
 	g.group.GET(path, g.svc.JWT.MiddleFunc(), ResultJWTWrapperKey(httpHandler, g.svc, keys))

+ 0 - 38
src/api/controller.go

@@ -111,44 +111,6 @@ func ResultJWTWrapper(handle JWTHander, svc *Service) gin.HandlerFunc {
 	}
 }
 
-// ResultJWTTestWrapper test 默认一个测试用户 JWT授权处理handler
-func ResultJWTTestWrapper(handle JWTHander, svc *Service) gin.HandlerFunc {
-
-	return func(c *gin.Context) {
-
-		defer func() {
-			if r := recover(); r != nil {
-
-				fmt.Println("recover success.")
-				fmt.Println(r)
-
-				buf := make([]byte, 1<<16)
-				runtime.Stack(buf, true)
-				fmt.Println("buf", string(buf))
-
-				c.JSON(http.StatusOK, NewFailResultWithData("error", r))
-			}
-		}()
-
-		var usr *JWTUser = &JWTUser{ID: svc.DebugUserId, Phone: svc.DebugUserPhone, Parent: svc.DebugUserId, Role: svc.DebugUserRole}
-
-		data, err := handle(c, &ApiSession{Svc: svc, User: usr})
-
-		if err != nil {
-			fmt.Println(err)
-			httpErr, ok := err.(HTTPError)
-			if ok {
-				c.JSON(http.StatusOK, NewFailResultWithCode(httpErr.Error(), httpErr.Code))
-				return
-			}
-
-			c.JSON(http.StatusOK, NewFailResult(err.Error()))
-			return
-		}
-		c.JSON(http.StatusOK, NewOkResult(data))
-	}
-}
-
 // ResultJWTWrapper JWT授权处理handler
 func ResultJWTWrapperKey(handle JWTHander, svc *Service, keys []string) gin.HandlerFunc {
 

+ 2 - 13
src/api/jwt.go

@@ -41,13 +41,7 @@ func NewUitlsJwt(app *conf.AppConf) *UtilsJwt {
 		PayloadFunc: func(data interface{}) jwt.MapClaims {
 			if v, ok := data.(*JWTUser); ok {
 				return jwt.MapClaims{
-					"id":       v.ID,
-					"name":     v.Name,
-					"phone":    v.Phone,
-					"parent":   v.Parent,
-					"state":    v.State,
-					"key":      v.Key,
-					"userType": v.UserType,
+					"id": v.ID,
 				}
 			}
 			return jwt.MapClaims{}
@@ -57,12 +51,7 @@ func NewUitlsJwt(app *conf.AppConf) *UtilsJwt {
 			// lg.Debug().Msgf("token: %v\n", claims)
 
 			u := &JWTUser{
-				ID:     claims["id"].(string), //uint32(claims["id"].(float64)),
-				Phone:  claims["phone"].(string),
-				Parent: claims["parent"].(string),
-			}
-			if claims["state"] != nil {
-				u.State = int32(claims["state"].(float64))
+				ID: claims["id"].(string), //uint32(claims["id"].(float64)),
 			}
 
 			return u

+ 126 - 0
src/api/learn.go

@@ -0,0 +1,126 @@
+package api
+
+// // 具体数据库中创建
+// // 每个用户每个模块有多条记录,每次学习生成一条记录
+// func CreateLearnLog(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+// 	db := c.Param("scope")
+// 	if len(db) == 0 {
+// 		return nil, errors.New("scope不能为空")
+// 	}
+// 	learnLog := &model.LearnLog{}
+// 	err := c.ShouldBindJSON(learnLog)
+// 	if err != nil {
+// 		log.Error(err)
+// 		return nil, err
+// 	}
+// 	if len(learnLog.Cid) < 1 {
+// 		return nil, errors.New("模块id不能为空")
+// 	}
+
+// 	// 没找到数据,创建
+// 	learnLog.Uid = apictx.User.ID
+// 	zero := 0
+// 	learnLog.LearnTime = &zero
+// 	learnLog.CreateTime = time.Now()
+// 	learnLog.UpdateTime = time.Now()
+// 	return repo.RepoAddDbDoc(apictx.CreateRepoCtx(), db, repo.CollectionLearnLog, learnLog)
+// }
+
+// // 每分钟记录下学习时长
+// // sync/time/:id/:scope
+// func SyncLearnTime(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+// 	id := c.Param("id")
+// 	db := c.Param("scope")
+// 	objId, _ := primitive.ObjectIDFromHex(id)
+// 	if objId.IsZero() {
+// 		return nil, errors.New("id错误")
+// 	}
+
+// 	// 写入统计数据,每个人每个模块的总学习时长
+// 	learnLog := &model.LearnLog{}
+// 	found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+// 		Db:          db,
+// 		CollectName: repo.CollectionLearnLog,
+// 		Query:       repo.Map{"_id": objId},
+// 		Project:     []string{"cid", "uid"},
+// 	}, learnLog)
+// 	if err != nil {
+// 		log.Error(err)
+// 		return nil, err
+// 	}
+// 	if !found {
+// 		return nil, errors.New("未找到记录")
+// 	}
+
+// 	update := bson.M{"$inc": bson.M{"learnTime": 1}, "$set": bson.M{"updateTime": time.Now()}}
+
+// 	_, err = repo.RepoUpdateSetDbDocs(apictx.CreateRepoCtx(), db, repo.CollectionLearnLogStatistics, bson.M{"cid": learnLog.Cid, "uid": learnLog.Uid}, update)
+// 	if err != nil {
+// 		log.Error(err)
+// 		fmt.Println(err)
+// 	}
+// 	return repo.RepoUpdateSetDbDocProps(apictx.CreateRepoCtx(), db, repo.CollectionLearnLog, id, update)
+// }
+
+// func LearnLogStatistics(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+// 	db := c.Param("scope")
+// 	if len(db) == 0 {
+// 		return nil, errors.New("scope不能为空")
+// 	}
+// 	// 学习模块进度
+// 	uid := c.Query("uid")
+// 	if len(uid) == 0 {
+// 		uid = apictx.User.ID
+// 	}
+// 	_, a, err := getLearnLogStatistics(apictx, db, uid)
+// 	return a, err
+// }
+
+// func getLearnLogStatistics(apictx *ApiSession, db string, uid string) (int, float64, error) {
+// 	learnLogStatistics := make([]*model.LearnLogStatistics, 0)
+// 	err := repo.RepoSeachDocs(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+// 		Db:          db,
+// 		CollectName: repo.CollectionLearnLogStatistics,
+// 		Query:       repo.Map{"uid": uid},
+// 		Project:     []string{"cid", "learnTime"},
+// 	}, &learnLogStatistics)
+// 	if err != nil {
+// 		log.Error(err)
+// 		return 0, 0.00, err
+// 	}
+// 	completeRate := 0.00
+// 	total := len(LearnLogSettings)
+// 	totalLearnTime := 0
+// 	if len(learnLogStatistics) > 0 {
+// 		for _, v := range learnLogStatistics {
+// 			totalLearnTime += v.LearnTime
+// 			limit, ok := LearnLogSettings[v.Cid]
+// 			if !ok {
+// 				limit = 10
+// 			}
+// 			rate := float64(v.LearnTime) / float64(limit)
+// 			if rate > 1 {
+// 				rate = 1
+// 			}
+// 			fmt.Println("pre_rate:", rate)
+// 			completeRate += rate / float64(total)
+// 		}
+// 	}
+// 	fmt.Println("completeRate:", completeRate)
+// 	fmt.Println("uid_totalLearnTime:", totalLearnTime)
+// 	fmt.Println("----------------------------------------")
+// 	return totalLearnTime, completeRate, nil
+
+// }
+
+// var LearnLogSettings = map[string]int{
+// 	"65a8edf6a7ef2b18346edd8c": 10,
+// 	"65a8ee02a7ef2b18346edd8d": 10,
+// 	"65a8ee0da7ef2b18346edd8e": 10,
+// 	"65a8ee18a7ef2b18346edd8f": 10,
+// 	"65a8ee21a7ef2b18346edd90": 10,
+// 	"65a8ee2ba7ef2b18346edd91": 60,
+// 	"65a8ee39a7ef2b18346edd92": 10,
+// 	"65a8ee39a7ef2b18346edd93": 10,
+// 	"65a8ee39a7ef2b18346edd94": 60,
+// }

+ 9 - 0
src/api/router.go

@@ -11,6 +11,15 @@ func RegRouters(svc *Service) {
 
 	crrouter := svc.NewGinRouter("/" + svc.Conf.Name)
 	crrouter.group.Use(Logger())
+	// 用户登录
+	crrouter.POST("/user/login/password", UserLoginPassword)
+	// 用户管理
+	crrouter.POSTJWT("/admin/user/create", CreateUser)
+	crrouter.POSTJWT("/admin/user/delete/:id", DeleteUser)
+	crrouter.POSTJWT("/admin/user/delete/batch", BatchDeleteUser)
+	crrouter.GETJWT("/admin/user/list", UserList)
+	crrouter.GETJWT("/admin/user/detail/:id", UserDetail)
+	crrouter.POSTJWT("/admin/user/update", UpdateUser)
 
 	Upload(crrouter)
 	Version(crrouter)

+ 289 - 0
src/api/user.go

@@ -0,0 +1,289 @@
+package api
+
+import (
+	"cr-svc/log"
+	"errors"
+	"time"
+
+	"cr-svc/db/model"
+	"cr-svc/db/repo"
+	"cr-svc/utils"
+
+	"github.com/gin-gonic/gin"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type UserLoginPasswordReq struct {
+	LoginName string `json:"loginName"`
+	Password  string `json:"password"`
+	Role      string `json:"role"`
+}
+
+func UserLoginPassword(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	var form UserLoginPasswordReq
+	err := c.ShouldBindJSON(&form)
+	if err != nil {
+		return nil, err
+	}
+
+	// 查找用户:根据longinName/password/role是否在roles中
+	user := &model.User{}
+	found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+		CollectName: repo.CollectionUser,
+		Query: repo.Map{
+			"loginName": form.LoginName,
+			"password":  utils.UtilMd5(form.Password),
+			"roles":     bson.M{"$elemMatch": bson.M{"$eq": form.Role}},
+		},
+	}, user)
+	if err != nil {
+		return nil, err
+	}
+	if !found {
+		return nil, errors.New("账号/密码/角色不正确")
+	}
+
+	jwtU := &JWTUser{ID: user.GetID()}
+	token, _, err := apictx.Svc.JWT.JwtCreateToken(jwtU)
+	if err != nil {
+		return nil, err
+	}
+
+	// 前端返回处理
+	user.Password = ""
+	out := map[string]interface{}{
+		"token": token,
+		"user":  user,
+	}
+	return out, nil
+}
+
+// 用户唯一
+// db.users.createIndex({ nid: 1 }, { unique: true })
+func CreateUser(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	// 验证是否为管理员
+	isAdmin, err := IsAdmin(c, apictx)
+	if err != nil {
+		return nil, err
+	}
+	if !isAdmin {
+		return nil, errors.New("没有权限")
+	}
+
+	user := &model.User{}
+	err = c.ShouldBindJSON(&user)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+
+	// 验证登录名是否存在
+	found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+		CollectName: repo.CollectionUser,
+		Query:       repo.Map{"loginName": user.LoginName},
+	}, user)
+	if err != nil {
+		return nil, err
+	}
+	if found {
+		return nil, errors.New("该账号已存在")
+	}
+
+	// student,teacher,admin
+	if len(user.Roles) < 1 {
+		user.Roles = []string{"student"}
+	}
+	user.Password = UtilMd5(user.Password)
+	user.CreateTime = time.Now()
+	user.UpdateTime = time.Now()
+	return repo.RepoAddDoc(apictx.CreateRepoCtx(), repo.CollectionUser, &user)
+}
+
+func DeleteUser(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	// 验证是否为管理员
+	isAdmin, err := IsAdmin(c, apictx)
+	if err != nil {
+		return nil, err
+	}
+	if !isAdmin {
+		return nil, errors.New("没有权限")
+	}
+	_id := c.Param("id")
+	id, _ := primitive.ObjectIDFromHex(_id)
+	if id.IsZero() {
+		return nil, errors.New("id错误")
+	}
+	return repo.RepoDeleteDoc(apictx.CreateRepoCtx(), repo.CollectionUser, _id)
+}
+
+type BatchDeleteUserReq struct {
+	Ids []string `json:"ids"`
+}
+
+func BatchDeleteUser(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	// 验证是否为管理员
+	isAdmin, err := IsAdmin(c, apictx)
+	if err != nil {
+		return nil, err
+	}
+	if !isAdmin {
+		return nil, errors.New("没有权限")
+	}
+	var form BatchDeleteUserReq
+	err = c.ShouldBindJSON(&form)
+	if err != nil {
+		return nil, errors.New("参数错误")
+	}
+	if len(form.Ids) > 0 {
+		for _, id := range form.Ids {
+			repo.RepoDeleteDoc(apictx.CreateRepoCtx(), repo.CollectionUser, id)
+		}
+	}
+	return true, nil
+}
+
+// 用户列表
+// /user/list?role=student&name=xxx&nid=xxx&page=1&size=10
+func UserList(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	// 验证是否为管理员
+	isStudent, err := IsStudent(c, apictx)
+	if err != nil {
+		return nil, err
+	}
+	if isStudent {
+		return nil, errors.New("没有权限")
+	}
+	page, size, query := UtilQueryPageSize(c)
+	role := c.Query("role")
+	name := c.Query("name")
+
+	if len(role) > 0 {
+		query["roles"] = bson.M{"$elemMatch": bson.M{"$eq": role}}
+	}
+	if len(name) > 0 {
+		query["name"] = bson.M{"$regex": name, "$options": "$i"}
+	}
+
+	return repo.RepoPageSearch(apictx.CreateRepoCtx(), &repo.PageSearchOptions{
+		CollectName: repo.CollectionUser,
+		Page:        page,
+		Size:        size,
+		Query:       query,
+		Sort:        bson.D{bson.E{Key: "createTime", Value: -1}, bson.E{Key: "_id", Value: -1}},
+		Project:     []string{"name", "loginName", "avatar", "roles", "createTime", "updateTime"},
+	})
+
+}
+
+func UserDetail(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	// 验证是否为管理员
+	isAdmin, err := IsAdmin(c, apictx)
+	if err != nil {
+		return nil, err
+	}
+	if !isAdmin {
+		return nil, errors.New("没有权限")
+	}
+	_id := c.Param("id")
+	id, _ := primitive.ObjectIDFromHex(_id)
+	if id.IsZero() {
+		return nil, errors.New("id错误")
+	}
+	return GetUserById(apictx, _id)
+}
+
+func UpdateUser(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	// 验证是否为管理员
+	isAdmin, err := IsAdmin(c, apictx)
+	if err != nil {
+		return nil, err
+	}
+	if !isAdmin {
+		return nil, errors.New("没有权限")
+	}
+	user := &model.User{}
+	err = c.ShouldBindJSON(&user)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+	if user.Id.IsZero() {
+		return nil, errors.New("id错误")
+	}
+
+	if len(user.Password) > 0 {
+		user.Password = UtilMd5(user.Password)
+	}
+	result, err := repo.RepoUpdateSetDoc(apictx.CreateRepoCtx(), repo.CollectionUser, user.Id.Hex(), user)
+	if err != nil {
+		return nil, errors.New("更新失败,请检查编码是否重复")
+	}
+	return result, err
+}
+
+// 获取自己的信息
+func UserProfile(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	return GetUserById(apictx, apictx.User.ID)
+}
+
+// 根据id获取用户信息
+func GetUserById(apictx *ApiSession, id string) (*model.User, error) {
+	user := &model.User{}
+	found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+		CollectName: repo.CollectionUser,
+		Query:       repo.Map{"_id": id},
+	}, user)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+
+	if !found {
+		return nil, errors.New("未找到该数据")
+	}
+	user.Password = ""
+	return user, nil
+}
+
+// 是否是管理员
+func IsAdmin(c *gin.Context, apictx *ApiSession) (bool, error) {
+	user, err := GetUserById(apictx, apictx.User.ID)
+	if err != nil {
+		return false, err
+	}
+	for _, v := range user.Roles {
+		if v == "admin" {
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
+// 是否是老师
+func IsTeacher(c *gin.Context, apictx *ApiSession) (bool, error) {
+	user, err := GetUserById(apictx, apictx.User.ID)
+	if err != nil {
+		return false, err
+	}
+	for _, v := range user.Roles {
+		if v == "teacher" {
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
+// 是否是学生
+func IsStudent(c *gin.Context, apictx *ApiSession) (bool, error) {
+	user, err := GetUserById(apictx, apictx.User.ID)
+	if err != nil {
+		return false, err
+	}
+	for _, v := range user.Roles {
+		if v == "student" {
+			return true, nil
+		}
+	}
+	return false, nil
+}

+ 32 - 0
src/db/model/learnLog.go

@@ -0,0 +1,32 @@
+package model
+
+import (
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+// 学习记录: 哪个用户 学习了哪个内容:怎么标识这个内容 学习了多少时间:每分钟请求接口同步一次
+// 当前登录用户 cid对应的内容,category 中type为'course' learnTime +1
+
+type LearnLog struct {
+	Id  primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
+	Uid string             `bson:"uid,omitempty" json:"uid"` // 用户id
+	Cid string             `bson:"cid,omitempty" json:"cid"` // 分类配置id
+	// Sid        string             `bson:"sid,omitempty" json:"sid"`             //subject id 学习主题id // 客户端用于标识题目的(因为服务端不存储题目)
+	Type       string    `bson:"type,omitempty" json:"type"`           // 学习类型: 理论/实操
+	LearnTime  *int      `bson:"learnTime,omitempty" json:"learnTime"` // 学习时长
+	CreateTime time.Time `bson:"createTime,omitempty" json:"createTime"`
+	UpdateTime time.Time `bson:"updateTime,omitempty" json:"updateTime"`
+}
+
+// 学习记录统计
+type LearnLogStatistics struct {
+	Id  primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
+	Uid string             `bson:"uid,omitempty" json:"uid"` // 用户id
+	Cid string             `bson:"cid,omitempty" json:"cid"` // 分类id
+	// CateName   string             `bson:"cateName,omitempty" json:"cateName"`   // 分类名称
+	LearnTime  int       `bson:"learnTime,omitempty" json:"learnTime"` // 学习时长
+	CreateTime time.Time `bson:"createTime,omitempty" json:"createTime"`
+	UpdateTime time.Time `bson:"updateTime,omitempty" json:"updateTime"`
+}

+ 24 - 0
src/db/model/user.go

@@ -0,0 +1,24 @@
+package model
+
+import (
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+type User struct {
+	Id primitive.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
+	// Nid       string             `bson:"nid,omitempty" json:"nid,omitempty"` // 编号
+	// Name      string `bson:"name,omitempty" json:"name,omitempty"`
+	LoginName string `bson:"loginName,omitempty" json:"loginName,omitempty"`
+	Password  string `bson:"password,omitempty" json:"password,omitempty"`
+	Avatar    string `bson:"avatar,omitempty" json:"avatar,omitempty"`
+	// student,teacher,admin
+	Roles      []string  `bson:"roles,omitempty" json:"roles,omitempty"`
+	CreateTime time.Time `bson:"createTime,omitempty" json:"createTime,omitempty"`
+	UpdateTime time.Time `bson:"updateTime,omitempty" json:"updateTime,omitempty"`
+}
+
+func (m *User) GetID() string {
+	return m.Id.Hex()
+}

+ 4 - 1
src/db/repo/repo.go

@@ -18,7 +18,10 @@ type RepoSession struct {
 }
 
 const (
-	CollectionVersions = "versions"
+	CollectionVersions           = "versions"
+	CollectionUser               = "users"
+	CollectionLearnLog           = "LearnLogs"
+	CollectionLearnLogStatistics = "LearnLogStatistics"
 )
 
 type Map map[string]interface{}

+ 0 - 17
src/go.sum

@@ -174,10 +174,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
 github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
-github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
-github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
-github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
 github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
 github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
@@ -430,14 +427,11 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
 github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
-github.com/gin-contrib/sessions v0.0.3 h1:PoBXki+44XdJdlgDqDrY5nDVe3Wk7wDV/UCOuLP6fBI=
-github.com/gin-contrib/sessions v0.0.3/go.mod h1:8C/J6cad3Il1mWYYgtw0w+hqasmpvy25mPkXdOgeB9I=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
 github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
 github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
-github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
 github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
 github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
@@ -583,7 +577,6 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
@@ -640,18 +633,11 @@ github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3i
 github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
 github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
-github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
-github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
-github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
-github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU=
-github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -793,7 +779,6 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALr
 github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
 github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
-github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -873,7 +858,6 @@ github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
-github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
 github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
@@ -1054,7 +1038,6 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
 github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=

+ 13 - 0
src/utils/utils.go

@@ -0,0 +1,13 @@
+package utils
+
+import (
+	"crypto/md5"
+	"fmt"
+)
+
+// UtilMd5 结算md5的值
+func UtilMd5(s string) string {
+	data := []byte(s)
+	has := md5.Sum(data)
+	return fmt.Sprintf("%x", has)
+}