question.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. package learning
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "lims_adapter/dao/learning"
  9. "lims_adapter/model/learning"
  10. "net/http"
  11. "strconv"
  12. "strings"
  13. "dashoo.cn/micro_libary/micro_srv"
  14. "dashoo.cn/micro_libary/myerrors"
  15. "dashoo.cn/micro_libary/request"
  16. "github.com/gogf/gf/os/gtime"
  17. "github.com/gogf/gf/util/gvalid"
  18. "github.com/xuri/excelize/v2"
  19. )
  20. type LearningQuestionService struct {
  21. Dao *dao.LearningQuestionDao
  22. Tenant string
  23. userInfo request.UserInfo
  24. }
  25. func NewLearningQuestionService(ctx context.Context) (*LearningQuestionService, error) {
  26. tenant, err := micro_srv.GetTenant(ctx)
  27. if err != nil {
  28. return nil, fmt.Errorf("获取组合码异常:%s", err.Error())
  29. }
  30. // 获取用户信息
  31. userInfo, err := micro_srv.GetUserInfo(ctx)
  32. if err != nil {
  33. return nil, fmt.Errorf("获取用户信息异常:%s", err.Error())
  34. }
  35. return &LearningQuestionService{
  36. Dao: dao.NewLearningQuestionDao(tenant),
  37. Tenant: tenant,
  38. userInfo: userInfo,
  39. }, nil
  40. }
  41. func (s LearningQuestionService) Get(ctx context.Context, req *learning.LearningQuestionGetReq) (ent *learning.LearningQuestionGetRsp, err error) {
  42. validErr := gvalid.CheckStruct(ctx, req, nil)
  43. if validErr != nil {
  44. return nil, myerrors.NewMsgError(nil, validErr.Current().Error())
  45. }
  46. q, err := s.Dao.Where("Id = ?", req.Id).One()
  47. if err != nil {
  48. return nil, err
  49. }
  50. if q == nil {
  51. return nil, myerrors.NewMsgError(nil, "题目不存在")
  52. }
  53. content := []learning.LearningQuestionOption{}
  54. err = json.Unmarshal([]byte(q.Content), &content)
  55. return &learning.LearningQuestionGetRsp{
  56. LearningQuestion: *q,
  57. Content: content,
  58. }, err
  59. }
  60. func (s LearningQuestionService) List(ctx context.Context, req *learning.LearningQuestionListReq) (int, []*learning.LearningQuestionGetRsp, error) {
  61. dao := &s.Dao.LearningQuestionDao
  62. if req.Name != "" {
  63. dao = dao.Where("Name LIKE ?", fmt.Sprintf("%%%s%%", req.Name))
  64. }
  65. if req.SkillId != 0 {
  66. dao = dao.Where("SkillId = ?", req.SkillId)
  67. }
  68. total, err := dao.Count()
  69. if err != nil {
  70. return 0, nil, err
  71. }
  72. if req.Page != nil {
  73. if req.Page.Current == 0 {
  74. req.Page.Current = 1
  75. }
  76. if req.Page.Size == 0 {
  77. req.Page.Size = 10
  78. }
  79. dao = dao.Page(req.Page.Current, req.Page.Size)
  80. }
  81. if req.OrderBy != nil && req.OrderBy.Value != "" {
  82. order := "asc"
  83. if req.OrderBy.Type == "desc" {
  84. order = "desc"
  85. }
  86. dao = dao.Order(req.OrderBy.Value, order)
  87. }
  88. ent, err := dao.All()
  89. if err != nil {
  90. return 0, nil, err
  91. }
  92. questions, err := ConvLearningQuestionGetRsp(ent)
  93. if err != nil {
  94. return 0, nil, err
  95. }
  96. return total, questions, err
  97. }
  98. func ConvLearningQuestionGetRsp(ent []*learning.LearningQuestion) ([]*learning.LearningQuestionGetRsp, error) {
  99. var questions []*learning.LearningQuestionGetRsp
  100. for _, q := range ent {
  101. content := []learning.LearningQuestionOption{}
  102. err := json.Unmarshal([]byte(q.Content), &content)
  103. if err != nil {
  104. return nil, err
  105. }
  106. answer := []string{}
  107. for _, item := range content {
  108. if item.IsCorrect {
  109. answer = append(answer, item.Name)
  110. }
  111. }
  112. questions = append(questions, &learning.LearningQuestionGetRsp{
  113. LearningQuestion: *q,
  114. Answer: strings.Join(answer, " "),
  115. Content: content,
  116. })
  117. }
  118. return questions, nil
  119. }
  120. func (s LearningQuestionService) Add(ctx context.Context, req *learning.LearningQuestionAddReq) (int, error) {
  121. validErr := gvalid.CheckStruct(ctx, req, nil)
  122. if validErr != nil {
  123. return 0, myerrors.NewMsgError(nil, validErr.Current().Error())
  124. }
  125. if req.Name == "" && req.NameImage == "" {
  126. return 0, myerrors.NewMsgError(nil, "请输入题目或题目图片")
  127. }
  128. if len(req.Content) < 2 {
  129. return 0, myerrors.NewMsgError(nil, "至少需要两个选项")
  130. }
  131. correctCount := 0
  132. for _, o := range req.Content {
  133. if o.IsCorrect {
  134. correctCount += 1
  135. }
  136. }
  137. if correctCount < 1 {
  138. return 0, myerrors.NewMsgError(nil, "正确答案未设置")
  139. }
  140. if (req.Type == 1 || req.Type == 3) && correctCount != 1 {
  141. return 0, myerrors.NewMsgError(nil, "正确答案只能设置一个")
  142. }
  143. content, err := json.Marshal(req.Content)
  144. if err != nil {
  145. return 0, err
  146. }
  147. id, err := s.Dao.InsertAndGetId(learning.LearningQuestion{
  148. SkillId: req.SkillId,
  149. Name: req.Name,
  150. NameImage: req.NameImage,
  151. Type: req.Type,
  152. Enable: req.Enable,
  153. Content: string(content),
  154. Explanation: req.Explanation,
  155. ExplanationImage: req.ExplanationImage,
  156. OperateBy: s.userInfo.RealName,
  157. CreatedAt: gtime.Now(),
  158. UpdatedAt: gtime.Now(),
  159. })
  160. if err != nil {
  161. return 0, err
  162. }
  163. return int(id), err
  164. }
  165. func (s LearningQuestionService) Update(ctx context.Context, req *learning.LearningQuestionUpdateReq) error {
  166. validErr := gvalid.CheckStruct(ctx, req, nil)
  167. if validErr != nil {
  168. return myerrors.NewMsgError(nil, validErr.Current().Error())
  169. }
  170. if len(req.Content) != 0 {
  171. if req.Type == nil {
  172. return myerrors.NewMsgError(nil, "请输入题型")
  173. }
  174. questionType := *req.Type
  175. if len(req.Content) < 2 {
  176. return myerrors.NewMsgError(nil, "至少需要两个选项")
  177. }
  178. correctCount := 0
  179. for _, o := range req.Content {
  180. if o.IsCorrect {
  181. correctCount += 1
  182. }
  183. }
  184. if correctCount < 1 {
  185. return myerrors.NewMsgError(nil, "正确答案未设置")
  186. }
  187. if (questionType == 1 || questionType == 3) && correctCount != 1 {
  188. return myerrors.NewMsgError(nil, "正确答案只能设置一个")
  189. }
  190. }
  191. q, err := s.Dao.Where("Id = ?", req.Id).One()
  192. if err != nil {
  193. return err
  194. }
  195. if q == nil {
  196. return myerrors.NewMsgError(nil, fmt.Sprintf("题目不存在: %d", req.Id))
  197. }
  198. if req.SkillId != 0 {
  199. r, err := s.Dao.DB.Table("learning_skill").Where("Id", req.SkillId).One()
  200. if err != nil {
  201. return err
  202. }
  203. if r.IsEmpty() {
  204. return myerrors.NewMsgError(nil, fmt.Sprintf("技能不存在: %d", req.SkillId))
  205. }
  206. }
  207. dao := &s.Dao.LearningQuestionDao
  208. toupdate := map[string]interface{}{}
  209. if req.SkillId != 0 {
  210. toupdate["SkillId"] = req.SkillId
  211. }
  212. if req.Name != nil {
  213. toupdate["Name"] = req.Name
  214. }
  215. if req.NameImage != nil {
  216. toupdate["NameImage"] = req.NameImage
  217. }
  218. if req.Type != nil {
  219. toupdate["Type"] = req.Type
  220. }
  221. if req.Enable != nil {
  222. toupdate["Enable"] = req.Enable
  223. }
  224. if req.Content != nil {
  225. content, err := json.Marshal(req.Content)
  226. if err != nil {
  227. return err
  228. }
  229. toupdate["Content"] = content
  230. }
  231. if req.Explanation != nil {
  232. toupdate["Explanation"] = req.Explanation
  233. }
  234. if req.ExplanationImage != nil {
  235. toupdate["ExplanationImage"] = req.ExplanationImage
  236. }
  237. if len(toupdate) == 0 {
  238. return nil
  239. }
  240. toupdate["OperateBy"] = s.userInfo.RealName
  241. _, err = dao.Where("Id", req.Id).Data(toupdate).Update()
  242. return err
  243. }
  244. func (s LearningQuestionService) Delete(ctx context.Context, id []int) error {
  245. _, err := s.Dao.Where("Id IN (?)", id).Delete()
  246. return err
  247. }
  248. func (s LearningQuestionService) BatchUpload(ctx context.Context, req *learning.LearningQuestionBatchUploadReq) error {
  249. r, err := s.Dao.DB.Table("learning_skill").Where("Id", req.SkillId).One()
  250. if err != nil {
  251. return err
  252. }
  253. if r.IsEmpty() {
  254. return myerrors.NewMsgError(nil, fmt.Sprintf("技能不存在: %d", req.SkillId))
  255. }
  256. b, err := DownFile(req.ExcelUrl)
  257. if err != nil {
  258. return myerrors.NewMsgError(nil, fmt.Sprintf("下载 excel 异常 %s", err.Error()))
  259. }
  260. question, err := ParseQuestionExcel(req.SkillId, s.userInfo.RealName, b)
  261. if err != nil {
  262. return myerrors.NewMsgError(nil, fmt.Sprintf("解析 excel 异常 %s", err.Error()))
  263. }
  264. _, err = s.Dao.Insert(question)
  265. return err
  266. }
  267. func DownFile(url string) ([]byte, error) {
  268. r, err := http.Get(url)
  269. if err != nil {
  270. return nil, err
  271. }
  272. if r.StatusCode != http.StatusOK {
  273. return nil, fmt.Errorf("DownFile from %s StatusCode %d", url, r.StatusCode)
  274. }
  275. defer r.Body.Close()
  276. return ioutil.ReadAll(r.Body)
  277. }
  278. var allowAnswer = []string{"A", "B", "C", "D"}
  279. func ParseQuestionExcel(skillId int, operateBy string, b []byte) ([]learning.LearningQuestion, error) {
  280. f, err := excelize.OpenReader(bytes.NewBuffer(b))
  281. if err != nil {
  282. return nil, err
  283. }
  284. sheet := "Sheet1"
  285. rows, err := f.GetRows(sheet)
  286. if err != nil {
  287. return nil, err
  288. }
  289. questions := []learning.LearningQuestion{}
  290. for rown, row := range rows[1:] {
  291. rown += 1
  292. if len(row) < 9 {
  293. return nil, fmt.Errorf("excel 格式错误:列数小于9列")
  294. }
  295. name := strings.TrimSpace(row[2])
  296. typeStr := strings.TrimSpace(row[1])
  297. explanation := strings.TrimSpace(row[8])
  298. a := strings.TrimSpace(row[3])
  299. b := strings.TrimSpace(row[4])
  300. c := strings.TrimSpace(row[5])
  301. d := strings.TrimSpace(row[6])
  302. var qtype int
  303. switch typeStr {
  304. case "单选题":
  305. qtype = 1
  306. case "多选题":
  307. qtype = 2
  308. case "判断题":
  309. qtype = 3
  310. default:
  311. return nil, fmt.Errorf("excel 格式错误:不合法的题型 '%s' %d", typeStr, rown)
  312. }
  313. answerStr := strings.TrimSpace(row[7])
  314. answer := strings.Split(answerStr, " ")
  315. for i := range answer {
  316. pass := false
  317. for _, allow := range allowAnswer {
  318. if answer[i] == allow {
  319. pass = true
  320. break
  321. }
  322. }
  323. if !pass {
  324. return nil, fmt.Errorf("excel 格式错误:不合法的答案:'%s' %d", answer[i], rown)
  325. }
  326. }
  327. options := []learning.LearningQuestionOption{
  328. {
  329. Name: "A",
  330. Content: a,
  331. IsCorrect: strings.Contains(answerStr, "A"),
  332. },
  333. {
  334. Name: "B",
  335. Content: b,
  336. IsCorrect: strings.Contains(answerStr, "B"),
  337. },
  338. {
  339. Name: "C",
  340. Content: c,
  341. IsCorrect: strings.Contains(answerStr, "C"),
  342. },
  343. {
  344. Name: "D",
  345. Content: d,
  346. IsCorrect: strings.Contains(answerStr, "D"),
  347. },
  348. }
  349. correctCount := 0
  350. for _, o := range options {
  351. if o.IsCorrect {
  352. correctCount += 1
  353. }
  354. }
  355. if correctCount < 1 {
  356. return nil, fmt.Errorf("excel 格式错误:含有未设置正确答案的题目 %d", rown)
  357. }
  358. if (qtype == 1 || qtype == 3) && correctCount != 1 {
  359. return nil, fmt.Errorf("excel 格式错误:含有设置正确答案和题型不符的题目 %d", rown)
  360. }
  361. content, _ := json.Marshal(options)
  362. q := learning.LearningQuestion{
  363. SkillId: skillId,
  364. Name: name,
  365. Type: qtype,
  366. Enable: 1,
  367. Content: string(content),
  368. Explanation: explanation,
  369. OperateBy: operateBy,
  370. CreatedAt: gtime.New(),
  371. UpdatedAt: gtime.New(),
  372. }
  373. questions = append(questions, q)
  374. }
  375. return questions, nil
  376. }
  377. func QuestionTemplate() (*excelize.File, error) {
  378. f := excelize.NewFile()
  379. sheet := "Sheet1"
  380. header := []string{
  381. "序号", "题型", "题目", "选项A", "选项B", "选项C", "选项D", "正确答案", "解析",
  382. }
  383. colWidth := []float64{
  384. 12, 12, 40, 20, 20, 20, 20, 12, 20,
  385. }
  386. tempData := [][]string{
  387. {"1", "单选题", "凝胶成像系统不能对以下哪些进行图像采集分析?", "蛋白凝胶", "培养皿", "DNA凝胶", "96孔细胞板", "B", "无"},
  388. {"2", "多选题", "凝胶成像系统有哪几种紫外光源可以选择?", "254mm为中心的宽波长紫外光源", "302mm为中心的宽波长紫外光源", "365mm为中心的宽波长紫外光源", "380mm为中心的宽波长紫外光源", "A B C", "无"},
  389. {"3", "判断题", "凝胶成像系统对紫外线光源有保护功能,如暗室门未关紧,系统将自动切断紫外,是否正确?", "正确", "错误", "", "", "A", "无"},
  390. }
  391. colStyle, err := f.NewStyle(&excelize.Style{
  392. Alignment: &excelize.Alignment{
  393. Horizontal: "center",
  394. Vertical: "center",
  395. WrapText: true,
  396. },
  397. Font: &excelize.Font{
  398. Size: 11,
  399. Family: "宋体",
  400. },
  401. })
  402. if err != nil {
  403. return nil, err
  404. }
  405. headerStyle, err := f.NewStyle(&excelize.Style{
  406. Alignment: &excelize.Alignment{
  407. Horizontal: "center",
  408. },
  409. Fill: excelize.Fill{
  410. Type: "pattern",
  411. Color: []string{"#a6a6a6"},
  412. Pattern: 1,
  413. },
  414. Border: []excelize.Border{
  415. {Type: "left", Color: "#000000", Style: 1},
  416. {Type: "top", Color: "#000000", Style: 1},
  417. {Type: "bottom", Color: "#000000", Style: 1},
  418. {Type: "right", Color: "#000000", Style: 1},
  419. },
  420. })
  421. if err != nil {
  422. return nil, err
  423. }
  424. err = f.SetColStyle(sheet, "A:I", colStyle)
  425. if err != nil {
  426. return nil, err
  427. }
  428. err = f.SetCellStyle(sheet, "A1", "I1", headerStyle)
  429. if err != nil {
  430. return nil, err
  431. }
  432. for i := range header {
  433. n, err := excelize.ColumnNumberToName(i + 1)
  434. if err != nil {
  435. return nil, err
  436. }
  437. f.SetCellValue(sheet, n+"1", header[i])
  438. }
  439. for i, w := range colWidth {
  440. n, err := excelize.ColumnNumberToName(i + 1)
  441. if err != nil {
  442. return nil, err
  443. }
  444. err = f.SetColWidth(sheet, n, n, w)
  445. if err != nil {
  446. return nil, err
  447. }
  448. }
  449. for row, item := range tempData {
  450. for col, v := range item {
  451. colName, err := excelize.ColumnNumberToName(col + 1)
  452. if err != nil {
  453. return nil, err
  454. }
  455. rowName := strconv.Itoa(row + 2)
  456. f.SetCellValue(sheet, colName+rowName, v)
  457. }
  458. }
  459. index := f.NewSheet(sheet)
  460. f.SetActiveSheet(index)
  461. return f, nil
  462. }