package learning import ( "bytes" "context" "encoding/json" "fmt" "io/ioutil" "lims_adapter/dao/learning" "lims_adapter/model/learning" "net/http" "strconv" "strings" "dashoo.cn/micro_libary/micro_srv" "dashoo.cn/micro_libary/myerrors" "dashoo.cn/micro_libary/request" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/util/gvalid" "github.com/xuri/excelize/v2" ) type LearningQuestionService struct { Dao *dao.LearningQuestionDao Tenant string userInfo request.UserInfo } func NewLearningQuestionService(ctx context.Context) (*LearningQuestionService, error) { tenant, err := micro_srv.GetTenant(ctx) if err != nil { return nil, fmt.Errorf("获取组合码异常:%s", err.Error()) } // 获取用户信息 userInfo, err := micro_srv.GetUserInfo(ctx) if err != nil { return nil, fmt.Errorf("获取用户信息异常:%s", err.Error()) } return &LearningQuestionService{ Dao: dao.NewLearningQuestionDao(tenant), Tenant: tenant, userInfo: userInfo, }, nil } func (s LearningQuestionService) Get(ctx context.Context, req *learning.LearningQuestionGetReq) (ent *learning.LearningQuestionGetRsp, err error) { validErr := gvalid.CheckStruct(ctx, req, nil) if validErr != nil { return nil, myerrors.NewMsgError(nil, validErr.Current().Error()) } q, err := s.Dao.Where("Id = ?", req.Id).One() if err != nil { return nil, err } if q == nil { return nil, myerrors.NewMsgError(nil, "题目不存在") } content := []learning.LearningQuestionOption{} err = json.Unmarshal([]byte(q.Content), &content) return &learning.LearningQuestionGetRsp{ LearningQuestion: *q, Content: content, }, err } func (s LearningQuestionService) List(ctx context.Context, req *learning.LearningQuestionListReq) (int, []*learning.LearningQuestionGetRsp, error) { dao := &s.Dao.LearningQuestionDao if req.Name != "" { dao = dao.Where("Name LIKE ?", fmt.Sprintf("%%%s%%", req.Name)) } if req.SkillId != 0 { dao = dao.Where("SkillId = ?", req.SkillId) } total, err := dao.Count() if err != nil { return 0, nil, err } if req.Page != nil { if req.Page.Current == 0 { req.Page.Current = 1 } if req.Page.Size == 0 { req.Page.Size = 10 } dao = dao.Page(req.Page.Current, req.Page.Size) } if req.OrderBy != nil && req.OrderBy.Value != "" { order := "asc" if req.OrderBy.Type == "desc" { order = "desc" } dao = dao.Order(req.OrderBy.Value, order) } ent, err := dao.All() if err != nil { return 0, nil, err } questions, err := ConvLearningQuestionGetRsp(ent) if err != nil { return 0, nil, err } return total, questions, err } func ConvLearningQuestionGetRsp(ent []*learning.LearningQuestion) ([]*learning.LearningQuestionGetRsp, error) { var questions []*learning.LearningQuestionGetRsp for _, q := range ent { content := []learning.LearningQuestionOption{} err := json.Unmarshal([]byte(q.Content), &content) if err != nil { return nil, err } answer := []string{} for _, item := range content { if item.IsCorrect { answer = append(answer, item.Name) } } questions = append(questions, &learning.LearningQuestionGetRsp{ LearningQuestion: *q, Answer: strings.Join(answer, " "), Content: content, }) } return questions, nil } func (s LearningQuestionService) Add(ctx context.Context, req *learning.LearningQuestionAddReq) (int, error) { validErr := gvalid.CheckStruct(ctx, req, nil) if validErr != nil { return 0, myerrors.NewMsgError(nil, validErr.Current().Error()) } if req.Name == "" && req.NameImage == "" { return 0, myerrors.NewMsgError(nil, "请输入题目或题目图片") } if len(req.Content) < 2 { return 0, myerrors.NewMsgError(nil, "至少需要两个选项") } correctCount := 0 for _, o := range req.Content { if o.IsCorrect { correctCount += 1 } } if correctCount < 1 { return 0, myerrors.NewMsgError(nil, "正确答案未设置") } if (req.Type == 1 || req.Type == 3) && correctCount != 1 { return 0, myerrors.NewMsgError(nil, "正确答案只能设置一个") } content, err := json.Marshal(req.Content) if err != nil { return 0, err } id, err := s.Dao.InsertAndGetId(learning.LearningQuestion{ SkillId: req.SkillId, Name: req.Name, NameImage: req.NameImage, Type: req.Type, Enable: req.Enable, Content: string(content), Explanation: req.Explanation, ExplanationImage: req.ExplanationImage, OperateBy: s.userInfo.RealName, CreatedAt: gtime.Now(), UpdatedAt: gtime.Now(), }) if err != nil { return 0, err } return int(id), err } func (s LearningQuestionService) Update(ctx context.Context, req *learning.LearningQuestionUpdateReq) error { validErr := gvalid.CheckStruct(ctx, req, nil) if validErr != nil { return myerrors.NewMsgError(nil, validErr.Current().Error()) } if len(req.Content) != 0 { if req.Type == nil { return myerrors.NewMsgError(nil, "请输入题型") } questionType := *req.Type if len(req.Content) < 2 { return myerrors.NewMsgError(nil, "至少需要两个选项") } correctCount := 0 for _, o := range req.Content { if o.IsCorrect { correctCount += 1 } } if correctCount < 1 { return myerrors.NewMsgError(nil, "正确答案未设置") } if (questionType == 1 || questionType == 3) && correctCount != 1 { return myerrors.NewMsgError(nil, "正确答案只能设置一个") } } q, err := s.Dao.Where("Id = ?", req.Id).One() if err != nil { return err } if q == nil { return myerrors.NewMsgError(nil, fmt.Sprintf("题目不存在: %d", req.Id)) } if req.SkillId != 0 { r, err := s.Dao.DB.Table("learning_skill").Where("Id", req.SkillId).One() if err != nil { return err } if r.IsEmpty() { return myerrors.NewMsgError(nil, fmt.Sprintf("技能不存在: %d", req.SkillId)) } } dao := &s.Dao.LearningQuestionDao toupdate := map[string]interface{}{} if req.SkillId != 0 { toupdate["SkillId"] = req.SkillId } if req.Name != nil { toupdate["Name"] = req.Name } if req.NameImage != nil { toupdate["NameImage"] = req.NameImage } if req.Type != nil { toupdate["Type"] = req.Type } if req.Enable != nil { toupdate["Enable"] = req.Enable } if req.Content != nil { content, err := json.Marshal(req.Content) if err != nil { return err } toupdate["Content"] = content } if req.Explanation != nil { toupdate["Explanation"] = req.Explanation } if req.ExplanationImage != nil { toupdate["ExplanationImage"] = req.ExplanationImage } if len(toupdate) == 0 { return nil } toupdate["OperateBy"] = s.userInfo.RealName _, err = dao.Where("Id", req.Id).Data(toupdate).Update() return err } func (s LearningQuestionService) Delete(ctx context.Context, id []int) error { _, err := s.Dao.Where("Id IN (?)", id).Delete() return err } func (s LearningQuestionService) BatchUpload(ctx context.Context, req *learning.LearningQuestionBatchUploadReq) error { r, err := s.Dao.DB.Table("learning_skill").Where("Id", req.SkillId).One() if err != nil { return err } if r.IsEmpty() { return myerrors.NewMsgError(nil, fmt.Sprintf("技能不存在: %d", req.SkillId)) } b, err := DownFile(req.ExcelUrl) if err != nil { return myerrors.NewMsgError(nil, fmt.Sprintf("下载 excel 异常 %s", err.Error())) } question, err := ParseQuestionExcel(req.SkillId, s.userInfo.RealName, b) if err != nil { return myerrors.NewMsgError(nil, fmt.Sprintf("解析 excel 异常 %s", err.Error())) } _, err = s.Dao.Insert(question) return err } func DownFile(url string) ([]byte, error) { r, err := http.Get(url) if err != nil { return nil, err } if r.StatusCode != http.StatusOK { return nil, fmt.Errorf("DownFile from %s StatusCode %d", url, r.StatusCode) } defer r.Body.Close() return ioutil.ReadAll(r.Body) } var allowAnswer = []string{"A", "B", "C", "D"} func ParseQuestionExcel(skillId int, operateBy string, b []byte) ([]learning.LearningQuestion, error) { f, err := excelize.OpenReader(bytes.NewBuffer(b)) if err != nil { return nil, err } sheet := "Sheet1" rows, err := f.GetRows(sheet) if err != nil { return nil, err } questions := []learning.LearningQuestion{} for rown, row := range rows[1:] { rown += 1 if len(row) < 9 { return nil, fmt.Errorf("excel 格式错误:列数小于9列") } name := strings.TrimSpace(row[2]) typeStr := strings.TrimSpace(row[1]) explanation := strings.TrimSpace(row[8]) a := strings.TrimSpace(row[3]) b := strings.TrimSpace(row[4]) c := strings.TrimSpace(row[5]) d := strings.TrimSpace(row[6]) var qtype int switch typeStr { case "单选题": qtype = 1 case "多选题": qtype = 2 case "判断题": qtype = 3 default: return nil, fmt.Errorf("excel 格式错误:不合法的题型 '%s' %d", typeStr, rown) } answerStr := strings.TrimSpace(row[7]) answer := strings.Split(answerStr, " ") for i := range answer { pass := false for _, allow := range allowAnswer { if answer[i] == allow { pass = true break } } if !pass { return nil, fmt.Errorf("excel 格式错误:不合法的答案:'%s' %d", answer[i], rown) } } options := []learning.LearningQuestionOption{ { Name: "A", Content: a, IsCorrect: strings.Contains(answerStr, "A"), }, { Name: "B", Content: b, IsCorrect: strings.Contains(answerStr, "B"), }, { Name: "C", Content: c, IsCorrect: strings.Contains(answerStr, "C"), }, { Name: "D", Content: d, IsCorrect: strings.Contains(answerStr, "D"), }, } correctCount := 0 for _, o := range options { if o.IsCorrect { correctCount += 1 } } if correctCount < 1 { return nil, fmt.Errorf("excel 格式错误:含有未设置正确答案的题目 %d", rown) } if (qtype == 1 || qtype == 3) && correctCount != 1 { return nil, fmt.Errorf("excel 格式错误:含有设置正确答案和题型不符的题目 %d", rown) } content, _ := json.Marshal(options) q := learning.LearningQuestion{ SkillId: skillId, Name: name, Type: qtype, Enable: 1, Content: string(content), Explanation: explanation, OperateBy: operateBy, CreatedAt: gtime.New(), UpdatedAt: gtime.New(), } questions = append(questions, q) } return questions, nil } func QuestionTemplate() (*excelize.File, error) { f := excelize.NewFile() sheet := "Sheet1" header := []string{ "序号", "题型", "题目", "选项A", "选项B", "选项C", "选项D", "正确答案", "解析", } colWidth := []float64{ 12, 12, 40, 20, 20, 20, 20, 12, 20, } tempData := [][]string{ {"1", "单选题", "凝胶成像系统不能对以下哪些进行图像采集分析?", "蛋白凝胶", "培养皿", "DNA凝胶", "96孔细胞板", "B", "无"}, {"2", "多选题", "凝胶成像系统有哪几种紫外光源可以选择?", "254mm为中心的宽波长紫外光源", "302mm为中心的宽波长紫外光源", "365mm为中心的宽波长紫外光源", "380mm为中心的宽波长紫外光源", "A B C", "无"}, {"3", "判断题", "凝胶成像系统对紫外线光源有保护功能,如暗室门未关紧,系统将自动切断紫外,是否正确?", "正确", "错误", "", "", "A", "无"}, } colStyle, err := f.NewStyle(&excelize.Style{ Alignment: &excelize.Alignment{ Horizontal: "center", Vertical: "center", WrapText: true, }, Font: &excelize.Font{ Size: 11, Family: "宋体", }, }) if err != nil { return nil, err } headerStyle, err := f.NewStyle(&excelize.Style{ Alignment: &excelize.Alignment{ Horizontal: "center", }, Fill: excelize.Fill{ Type: "pattern", Color: []string{"#a6a6a6"}, Pattern: 1, }, Border: []excelize.Border{ {Type: "left", Color: "#000000", Style: 1}, {Type: "top", Color: "#000000", Style: 1}, {Type: "bottom", Color: "#000000", Style: 1}, {Type: "right", Color: "#000000", Style: 1}, }, }) if err != nil { return nil, err } err = f.SetColStyle(sheet, "A:I", colStyle) if err != nil { return nil, err } err = f.SetCellStyle(sheet, "A1", "I1", headerStyle) if err != nil { return nil, err } for i := range header { n, err := excelize.ColumnNumberToName(i + 1) if err != nil { return nil, err } f.SetCellValue(sheet, n+"1", header[i]) } for i, w := range colWidth { n, err := excelize.ColumnNumberToName(i + 1) if err != nil { return nil, err } err = f.SetColWidth(sheet, n, n, w) if err != nil { return nil, err } } for row, item := range tempData { for col, v := range item { colName, err := excelize.ColumnNumberToName(col + 1) if err != nil { return nil, err } rowName := strconv.Itoa(row + 2) f.SetCellValue(sheet, colName+rowName, v) } } index := f.NewSheet(sheet) f.SetActiveSheet(index) return f, nil }