sunsheng 1 tahun lalu
induk
melakukan
25fc19b4b5
10 mengubah file dengan 426 tambahan dan 25 penghapusan
  1. 30 11
      src/api/category.go
  2. 96 0
      src/api/learnLog.go
  3. 20 0
      src/api/router.go
  4. 140 0
      src/api/test.go
  5. TEMPAT SAMPAH
      src/copter-train.exe
  6. 9 13
      src/db/model/category.go
  7. 20 0
      src/db/model/learnLog.go
  8. 20 0
      src/db/model/test.go
  9. 90 0
      src/db/repo/repo.go
  10. 1 1
      src/main.go

+ 30 - 11
src/api/category.go

@@ -12,18 +12,17 @@ import (
 	"go.mongodb.org/mongo-driver/bson/primitive"
 )
 
-func Category(r *GinRouter) {
-	r.POSTJWT("/category/create", CreateCategory)
-	r.POSTJWT("/category/delete/:id", DeleteCategory)
-	r.GET("/category/list", CategoryList)
-	r.GET("/category/detail/:id", CategoryDetail)
-	r.POSTJWT("/category/update", UpdateCategory)
-
-}
-
 func CreateCategory(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 category model.Category
-	err := c.ShouldBindJSON(&category)
+	err = c.ShouldBindJSON(&category)
 	if err != nil {
 		log.Error(err)
 		return nil, err
@@ -31,12 +30,24 @@ func CreateCategory(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 	if len(category.Pid) == 0 {
 		category.Pid = "top"
 	}
+	if category.Sort == nil {
+		zero := 0
+		category.Sort = &zero
+	}
 	category.CreateTime = time.Now()
 	category.UpdateTime = time.Now()
 	return repo.RepoAddDoc(apictx.CreateRepoCtx(), repo.CollectionCategory, &category)
 }
 
 func DeleteCategory(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() {
@@ -79,8 +90,16 @@ func CategoryDetail(c *gin.Context, apictx *ApiSession) (interface{}, error) {
 }
 
 func UpdateCategory(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 cate model.Category
-	err := c.ShouldBindJSON(&cate)
+	err = c.ShouldBindJSON(&cate)
 	if err != nil {
 		log.Error(err)
 		return nil, err

+ 96 - 0
src/api/learnLog.go

@@ -0,0 +1,96 @@
+package api
+
+import (
+	"copter-train/db/model"
+	"copter-train/db/repo"
+	"copter-train/log"
+	"errors"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+// 具体数据库中创建
+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
+	}
+	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错误")
+	}
+	update := bson.M{"$inc": bson.M{"learnTime": 1}, "updateTime": time.Now()}
+	return repo.RepoUpdateDbSetDoc(apictx.CreateRepoCtx(), db, repo.CollectionLearnLog, id, update)
+}
+
+// func DeleteCategory(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.CollectionCategory, _id)
+// }
+
+// func CategoryList(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+// 	page, size, query := UtilQueryPageSize(c)
+// 	return repo.RepoPageSearch(apictx.CreateRepoCtx(), &repo.PageSearchOptions{
+// 		CollectName: repo.CollectionCategory,
+// 		Page:        page,
+// 		Size:        size,
+// 		Query:       query,
+// 		Sort:        bson.D{bson.E{Key: "sort", Value: 1}, bson.E{Key: "createTime", Value: 1}},
+// 	})
+// }
+
+// func CategoryDetail(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+// 	_id := c.Param("id")
+// 	id, _ := primitive.ObjectIDFromHex(_id)
+// 	if id.IsZero() {
+// 		return nil, errors.New("id错误")
+// 	}
+// 	cate := &model.Category{}
+// 	found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+// 		CollectName: repo.CollectionCategory,
+// 		Query:       repo.Map{"_id": id},
+// 	}, cate)
+// 	if err != nil {
+// 		log.Error(err)
+// 		return nil, err
+// 	}
+
+// 	if !found {
+// 		return nil, errors.New("未找到该数据")
+// 	}
+// 	return cate, nil
+// }

+ 20 - 0
src/api/router.go

@@ -23,6 +23,26 @@ func RegRouters(svc *Service) {
 	// 获取自己的详情信息
 	root.GETJWT("/user/profile", UserProfile)
 
+	// 分类管理
+	root.POSTJWT("/category/create", CreateCategory)
+	root.POSTJWT("/category/delete/:id", DeleteCategory)
+	root.GET("/category/list", CategoryList)
+	// r.GET("/category/detail/:id", CategoryDetail)
+	root.POSTJWT("/category/update", UpdateCategory)
+
+	// 学习记录
+	// 学习记录: 哪个用户 学习了哪个内容:怎么标识这个内容 学习了多少时间:每分钟请求接口同步一次
+	// 当前登录用户 cid对应的内容,category 中type为'course' learnTime +1
+	root.POSTJWT("/learnLog/create/:scope", CreateLearnLog)
+	root.POSTJWT("/learnLog/sync/time/:id/:scope", SyncLearnTime)
+
+	// 考核试题管理
+	// 单选/判断
+
+	// todo
+
+	// 考核记录
+
 }
 
 func Logger() gin.HandlerFunc {

+ 140 - 0
src/api/test.go

@@ -0,0 +1,140 @@
+package api
+
+import (
+	"copter-train/db/model"
+	"copter-train/db/repo"
+	"copter-train/log"
+	"errors"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+// 新增试题
+// /admin/test/create/:scope
+func CreateTest(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	// 验证是否为管理员
+	isAdmin, err := IsAdmin(c, apictx)
+	if err != nil {
+		return nil, err
+	}
+	if !isAdmin {
+		return nil, errors.New("没有权限")
+	}
+	db := c.Param("scope")
+	if len(db) == 0 {
+		return nil, errors.New("scope不能为空")
+	}
+	test := &model.Test{}
+	err = c.ShouldBindJSON(test)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+	if test.Socre == nil {
+		zero := 0
+		test.Socre = &zero
+	}
+	test.Uid = apictx.User.ID
+	test.CreateTime = time.Now()
+	test.UpdateTime = time.Now()
+	return repo.RepoAddDbDoc(apictx.CreateRepoCtx(), db, repo.CollectionTest, test)
+}
+
+// 删除试题
+// /admin/test/delete/:id/:scope
+func DeleteTest(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	// 验证是否为管理员
+	isAdmin, err := IsAdmin(c, apictx)
+	if err != nil {
+		return nil, err
+	}
+	if !isAdmin {
+		return nil, errors.New("没有权限")
+	}
+	db := c.Param("scope")
+	if len(db) == 0 {
+		return nil, errors.New("scope不能为空")
+	}
+	_id := c.Param("id")
+	id, _ := primitive.ObjectIDFromHex(_id)
+	if id.IsZero() {
+		return nil, errors.New("id错误")
+	}
+	return repo.RepoDeleteDbDoc(apictx.CreateRepoCtx(), db, repo.CollectionTest, _id)
+}
+
+// 试题列表
+// /admin/test/list/:scope
+func TestList(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	db := c.Param("scope")
+	if len(db) == 0 {
+		return nil, errors.New("scope不能为空")
+	}
+	page, size, query := UtilQueryPageSize(c)
+	return repo.RepoDbPageSearch(apictx.CreateRepoCtx(), &repo.PageSearchOptions{
+		Db:          db,
+		CollectName: repo.CollectionTest,
+		Page:        page,
+		Size:        size,
+		Query:       query,
+		// Sort:        bson.D{bson.E{Key: "sort", Value: 1}, bson.E{Key: "createTime", Value: 1}},
+	})
+}
+
+// 试题详情
+// /admin/test/detail/:id/:scope
+func TestDetail(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	db := c.Param("scope")
+	if len(db) == 0 {
+		return nil, errors.New("scope不能为空")
+	}
+	_id := c.Param("id")
+	id, _ := primitive.ObjectIDFromHex(_id)
+	if id.IsZero() {
+		return nil, errors.New("id错误")
+	}
+	test := &model.Test{}
+	found, err := repo.RepoSeachDoc(apictx.CreateRepoCtx(), &repo.DocSearchOptions{
+		Db:          db,
+		CollectName: repo.CollectionTest,
+		Query:       repo.Map{"_id": id},
+	}, test)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+
+	if !found {
+		return nil, errors.New("未找到该数据")
+	}
+	return test, nil
+}
+
+// 更新试题
+// /admin/test/update/:scope
+func UpdateTest(c *gin.Context, apictx *ApiSession) (interface{}, error) {
+	// 验证是否为管理员
+	isAdmin, err := IsAdmin(c, apictx)
+	if err != nil {
+		return nil, err
+	}
+	if !isAdmin {
+		return nil, errors.New("没有权限")
+	}
+	db := c.Param("scope")
+	if len(db) == 0 {
+		return nil, errors.New("scope不能为空")
+	}
+	test := &model.Test{}
+	err = c.ShouldBindJSON(test)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+	if test.Id.IsZero() {
+		return nil, errors.New("id错误")
+	}
+	return repo.RepoUpdateDbSetDoc(apictx.CreateRepoCtx(), db, repo.CollectionTest, test.Id.Hex(), test)
+}

TEMPAT SAMPAH
src/copter-train.exe


+ 9 - 13
src/db/model/category.go

@@ -6,18 +6,14 @@ import (
 	"go.mongodb.org/mongo-driver/bson/primitive"
 )
 
-// db.getCollection(category).createIndexes([{pid: 1}, {isHome: 1}])
+// db.getCollection(category).createIndexes([{pid: 1}])
 type Category struct {
-	Id      primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
-	Pid     string             `bson:"pid,omitempty" json:"pid"` // 分类的上层id // 默认为top
-	Name    string             `bson:"name,omitempty" json:"name"`
-	SubName string             `bson:"subName,omitempty" json:"subName"` // 副标题
-	Cover   string             `bson:"cover,omitempty" json:"cover"`
-	Sort    *int               `bson:"sort,omitempty" json:"sort"`     // 排序,使用创建时间联合排序。排序按升序排,默认为0
-	Type    string             `bson:"type,omitempty" json:"type"`     // 分类类型 list,detail,download
-	Lang    string             `bson:"lang,omitempty" json:"lang"`     // cn en
-	IsHome  *bool              `bson:"isHome,omitempty" json:"isHome"` // 是否展示到首页
-	// IsNav      *bool              `bson:"isNav,omitempty" json:"isNav"`   // 是否是导航页
-	CreateTime time.Time `bson:"createTime,omitempty" json:"createTime"`
-	UpdateTime time.Time `bson:"updateTime,omitempty" json:"updateTime"`
+	Id         primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
+	Pid        string             `bson:"pid,omitempty" json:"pid"` // 分类的上层id // 默认为top
+	Name       string             `bson:"name,omitempty" json:"name"`
+	Sort       *int               `bson:"sort,omitempty" json:"sort"` // 排序,使用创建时间联合排序。排序按升序排,默认为0
+	Type       string             `bson:"type,omitempty" json:"type"` // 分类类型,系列/课程/章节/知识点
+	Db         string             `bson:"db,omitempty" json:"db"`     // 分类所属的数据库
+	CreateTime time.Time          `bson:"createTime,omitempty" json:"createTime"`
+	UpdateTime time.Time          `bson:"updateTime,omitempty" json:"updateTime"`
 }

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

@@ -0,0 +1,20 @@
+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:"pid,omitempty" json:"pid"`             // 分类配置id
+	Sid        string             `bson:"sid,omitempty" json:"sid"`             //subject id 学习主题id // 客户端用于标识题目的(因为服务端不存储题目)
+	LearnTime  *int               `bson:"learnTime,omitempty" json:"learnTime"` // 学习时长
+	CreateTime time.Time          `bson:"createTime,omitempty" json:"createTime"`
+	UpdateTime time.Time          `bson:"updateTime,omitempty" json:"updateTime"`
+}

+ 20 - 0
src/db/model/test.go

@@ -0,0 +1,20 @@
+package model
+
+import (
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+// 试题
+type Test struct {
+	Id         primitive.ObjectID `bson:"_id,omitempty" json:"_id"`
+	Uid        string             `bson:"uid,omitempty" json:"uid"`         // 创建人的id
+	Type       string             `bson:"type,omitempty" json:"type"`       // 试题类型: 单选/判断
+	Socre      *int               `bson:"socre,omitempty" json:"socre"`     // 分值
+	Content    string             `bson:"content,omitempty" json:"content"` // 题目内容
+	Answer     string             `bson:"answer,omitempty" json:"answer"`   // 答案 A/B/C/D/T/F
+	Options    map[string]string  `bson:"options,omitempty" json:"options"` // 选项 {"A":"xxx","B":"xxx","C":"xxx","D":"xxx"} / {"T":"xxx","F":"xxx"}
+	CreateTime time.Time          `bson:"createTime,omitempty" json:"createTime"`
+	UpdateTime time.Time          `bson:"updateTime,omitempty" json:"updateTime"`
+}

+ 90 - 0
src/db/repo/repo.go

@@ -20,6 +20,8 @@ type RepoSession struct {
 const (
 	CollectionCategory = "category"
 	CollectionUser     = "users"
+	CollectionLearnLog = "learn_logs"
+	CollectionTest     = "tests"
 )
 
 type Map map[string]interface{}
@@ -32,6 +34,7 @@ type PageResult struct {
 }
 
 type PageSearchOptions struct {
+	Db          string
 	CollectName string
 	Page        int64
 	Size        int64
@@ -64,6 +67,15 @@ func RepoAddDoc(ctx *RepoSession, collectName string, doc interface{}) (string,
 	return result.InsertedID.(primitive.ObjectID).Hex(), nil
 }
 
+func RepoAddDbDoc(ctx *RepoSession, Db string, collectName string, doc interface{}) (string, error) {
+	coll := ctx.Client.GetDbCollection(Db, collectName)
+	result, err := coll.InsertOne(ctx.Ctx, doc)
+	if err != nil {
+		return "", err
+	}
+	return result.InsertedID.(primitive.ObjectID).Hex(), nil
+}
+
 func RepoDeleteDoc(ctx *RepoSession, collectName string, id string) (interface{}, error) {
 
 	uid, _ := primitive.ObjectIDFromHex(id)
@@ -73,6 +85,15 @@ func RepoDeleteDoc(ctx *RepoSession, collectName string, id string) (interface{}
 	return colls.DeleteOne(ctx.Ctx, &bson.M{"_id": uid})
 }
 
+func RepoDeleteDbDoc(ctx *RepoSession, Db string, collectName string, id string) (interface{}, error) {
+
+	uid, _ := primitive.ObjectIDFromHex(id)
+
+	colls := ctx.Client.GetDbCollection(Db, collectName)
+
+	return colls.DeleteOne(ctx.Ctx, &bson.M{"_id": uid})
+}
+
 func RepoDeleteDocs(ctx *RepoSession, collectName string, query interface{}) (interface{}, error) {
 	colls := ctx.Client.GetCollection(collectName)
 
@@ -99,6 +120,17 @@ func RepoUpdateSetDoc(ctx *RepoSession, collectName string, idstr string, model
 	return colls.UpdateByID(ctx.Ctx, uid, update)
 }
 
+func RepoUpdateDbSetDoc(ctx *RepoSession, Db string, collectName string, idstr string, model interface{}) (*mongo.UpdateResult, error) {
+
+	colls := ctx.Client.GetDbCollection(Db, collectName)
+
+	update := bson.M{"$set": model}
+
+	uid, _ := primitive.ObjectIDFromHex(idstr)
+
+	return colls.UpdateByID(ctx.Ctx, uid, update)
+}
+
 func RepoUpsertSetDoc(ctx *RepoSession, collectName string, filter interface{}, model interface{}) (*mongo.UpdateResult, error) {
 
 	coll := ctx.Client.GetCollection(collectName)
@@ -291,6 +323,64 @@ func RepoPageSearch(ctx *RepoSession, para *PageSearchOptions) (out *PageResult,
 	return
 }
 
+func RepoDbPageSearch(ctx *RepoSession, para *PageSearchOptions) (out *PageResult, err error) {
+
+	colls := ctx.Client.GetDbCollection(para.Db, para.CollectName)
+
+	findoptions := &options.FindOptions{}
+
+	if para.Size > 0 {
+		findoptions.SetLimit(para.Size)
+		findoptions.SetSkip(para.Size * (para.Page - 1))
+	}
+	if para.Sort != nil {
+		findoptions.SetSort(para.Sort)
+	}
+
+	if len(para.Project) > 0 {
+		prj := bson.M{}
+		for _, v := range para.Project {
+			prj[v] = 1
+		}
+		findoptions.SetProjection(prj)
+	}
+
+	filter := bson.M{}
+	if len(para.Query) > 0 {
+		for k, v := range para.Query {
+
+			if value, ok := v.(string); ok {
+				if len(value) > 0 {
+					filter[k] = v
+					continue
+				}
+			} else if v != nil {
+				filter[k] = v
+			}
+		}
+	}
+
+	cur, err := colls.Find(ctx.Ctx, filter, findoptions)
+
+	out = &PageResult{
+		List:  []map[string]interface{}{},
+		Total: 0,
+		Page:  para.Page,
+		Size:  para.Size,
+	}
+
+	if err != nil {
+		return out, err
+	}
+
+	defer cur.Close(ctx.Ctx)
+
+	err = cur.All(ctx.Ctx, &out.List)
+
+	out.Total, _ = colls.CountDocuments(ctx.Ctx, filter)
+	return
+}
+
 // PageSearch 单表分页查询
 func RepoCountDoc(ctx *RepoSession, collectionName string, Query Map) (int64, error) {
 

+ 1 - 1
src/main.go

@@ -32,7 +32,7 @@ func BuildApp() *dig.Container {
 func main() {
 	flag.Parse()
 	app := BuildApp()
-	comm.AppMongoMiration()
+	// comm.AppMongoMiration()
 
 	err := app.Invoke(func(svc *api.Service, bus *comm.NatsBus) error {
 		go bus.Run(nil)