|
|
|
@ -3,10 +3,16 @@ package questionBank |
|
|
|
import ( |
|
|
|
v1 "Knowledge_Test_Go/api/v1" |
|
|
|
"Knowledge_Test_Go/internal/service" |
|
|
|
"Knowledge_Test_Go/utility/response" |
|
|
|
"context" |
|
|
|
"fmt" |
|
|
|
"strconv" |
|
|
|
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror" |
|
|
|
"github.com/gogf/gf/v2/frame/g" |
|
|
|
"github.com/gogf/gf/v2/net/ghttp" |
|
|
|
|
|
|
|
"github.com/xuri/excelize/v2" |
|
|
|
) |
|
|
|
|
|
|
|
type sQuestionBank struct{} |
|
|
|
@ -19,7 +25,7 @@ func init() { |
|
|
|
func (s *sQuestionBank) GetQuestions(ctx context.Context, req *v1.QuestionOutputReq) (res []*v1.QuestionOutputRes, total int, err error) { |
|
|
|
db := g.Model("question_bank a"). |
|
|
|
LeftJoin("question_type b", "a.question_type_id = b.id"). |
|
|
|
LeftJoin("course_recommend c", "a.course_recommendation_id = c.id"). |
|
|
|
LeftJoin("course_recommend c", "a.course_recommendation_id = c.id").Where("a.isdel = 0"). |
|
|
|
Fields("a.id", "a.stem", "a.a", "a.b", "a.c", "a.d", "a.correct_answer", |
|
|
|
"b.question_type_name", "c.cr_name", "a.error_count", "a.citation_count") |
|
|
|
|
|
|
|
@ -115,3 +121,147 @@ func (s *sQuestionBank) QuestionDel(ctx context.Context, req *v1.QuestionDelReq) |
|
|
|
} |
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// UserScoreOutput 获取用户成绩列表
|
|
|
|
func (s *sQuestionBank) UserScoreOutput(ctx context.Context, req *v1.UserScoreOutputReq) (res []*v1.UserScoreOutput, total int, err error) { |
|
|
|
db := g.Model("user a"). |
|
|
|
RightJoin("total_score b", "a.jwcode=b.jwcode"). |
|
|
|
Fields("a.id", "a.user_name", "a.user_identity", "a.jwcode", "b.created_at", "b.score") |
|
|
|
err = db.Page(req.Page, req.PageSize).ScanAndCount(&res, &total, false) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// ErrorOutPutUser 错题统计出错用户
|
|
|
|
func (s *sQuestionBank) ErrorOutPutUser(ctx context.Context, req *v1.ErrorOutPutUserReq) (res []*v1.ErrorOutPutUserRes, total int, err error) { |
|
|
|
// 参数验证
|
|
|
|
if req.Id == 0 { |
|
|
|
return nil, 0, gerror.New("题目ID不能为空") |
|
|
|
} |
|
|
|
|
|
|
|
// 直接在数据库层面按 jwcode 分组统计
|
|
|
|
err = g.Model("question_record r"). |
|
|
|
LeftJoin("user u", "r.jwcode = u.jwcode"). |
|
|
|
Where("r.question_bank_id = ?", req.Id). |
|
|
|
Fields("r.jwcode", "u.user_name", "u.user_identity", "COUNT(*) as error_count"). |
|
|
|
Group("r.jwcode", "u.user_name", "u.user_identity"). |
|
|
|
OrderDesc("error_count"). |
|
|
|
Scan(&res) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
return nil, 0, gerror.Wrap(err, "查询错题用户统计失败") |
|
|
|
} |
|
|
|
|
|
|
|
total = len(res) |
|
|
|
return res, total, nil |
|
|
|
} |
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// ExportQuestion 导出题目数据到 Excel
|
|
|
|
func (s *sQuestionBank) ExportQuestion(r *ghttp.Request, ctx context.Context, req *v1.QuestionOutputReq) { |
|
|
|
// === 1. 数据查询和验证 ===
|
|
|
|
var result []*v1.QuestionOutputRes |
|
|
|
|
|
|
|
// 查询总数
|
|
|
|
_, total, err := service.QuestionBank().GetQuestions(ctx, req) |
|
|
|
if err != nil { |
|
|
|
response.JsonExit(r, 400, "查询失败", err.Error()) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 数据量验证
|
|
|
|
if total == 0 { |
|
|
|
response.JsonExit(r, 400, "查询结果为空") |
|
|
|
return |
|
|
|
} |
|
|
|
if total > 20000 { |
|
|
|
response.JsonExit(r, 400, "导出数据过多,请添加筛选条件后再导出") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// === 2. 分批查询数据 ===
|
|
|
|
var num int |
|
|
|
req.PageSize = 1000 |
|
|
|
for { |
|
|
|
res, total, _ := s.GetQuestions(ctx, req) |
|
|
|
result = append(result, res[:]...) |
|
|
|
num += 1000 |
|
|
|
if num > total { |
|
|
|
break |
|
|
|
} |
|
|
|
req.Page++ |
|
|
|
} |
|
|
|
|
|
|
|
// === 3. 创建 Excel 文件 ===
|
|
|
|
excelFile := excelize.NewFile() |
|
|
|
sheetName := "Sheet1" |
|
|
|
fileName := "题目数据导出.xlsx" |
|
|
|
|
|
|
|
// === 4. 设置表头 ===
|
|
|
|
headers := []string{"ID", "题干", "选项A", "选项B", "选项C", "选项D", "正确答案", "题目类型", "推荐课程", "引用次数", "错误次数", "错误率"} |
|
|
|
for i, header := range headers { |
|
|
|
col := string('A' + i) |
|
|
|
excelFile.SetCellValue(sheetName, col+"1", header) |
|
|
|
} |
|
|
|
|
|
|
|
// === 5. 写入数据 ===
|
|
|
|
rowIndex := 2 |
|
|
|
for _, question := range result { |
|
|
|
excelFile.SetCellValue(sheetName, "A"+strconv.Itoa(rowIndex), rowIndex-1) |
|
|
|
excelFile.SetCellValue(sheetName, "B"+strconv.Itoa(rowIndex), question.Id) |
|
|
|
excelFile.SetCellValue(sheetName, "C"+strconv.Itoa(rowIndex), question.Stem) |
|
|
|
excelFile.SetCellValue(sheetName, "D"+strconv.Itoa(rowIndex), question.A) |
|
|
|
excelFile.SetCellValue(sheetName, "E"+strconv.Itoa(rowIndex), question.B) |
|
|
|
excelFile.SetCellValue(sheetName, "F"+strconv.Itoa(rowIndex), question.C) |
|
|
|
excelFile.SetCellValue(sheetName, "G"+strconv.Itoa(rowIndex), question.D) |
|
|
|
excelFile.SetCellValue(sheetName, "H"+strconv.Itoa(rowIndex), question.CorrectAnswer) |
|
|
|
excelFile.SetCellValue(sheetName, "I"+strconv.Itoa(rowIndex), question.QuestionTypeName) |
|
|
|
excelFile.SetCellValue(sheetName, "J"+strconv.Itoa(rowIndex), question.CrName) |
|
|
|
excelFile.SetCellValue(sheetName, "K"+strconv.Itoa(rowIndex), question.CitationCount) |
|
|
|
excelFile.SetCellValue(sheetName, "L"+strconv.Itoa(rowIndex), question.ErrorCount) |
|
|
|
excelFile.SetCellValue(sheetName, "M"+strconv.Itoa(rowIndex), strconv.Itoa(question.ErrorRate)+"%") |
|
|
|
|
|
|
|
rowIndex++ |
|
|
|
} |
|
|
|
|
|
|
|
// === 6. 样式设置 ===
|
|
|
|
lastRow := rowIndex - 1 |
|
|
|
|
|
|
|
// 创建样式
|
|
|
|
style, err := excelFile.NewStyle(&excelize.Style{ |
|
|
|
Alignment: &excelize.Alignment{ |
|
|
|
Horizontal: "center", |
|
|
|
Vertical: "center", |
|
|
|
}, |
|
|
|
}) |
|
|
|
if err != nil { |
|
|
|
response.JsonExit(r, 400, "创建样式失败", err.Error()) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 应用样式
|
|
|
|
excelFile.SetCellStyle(sheetName, "A1", "L"+strconv.Itoa(lastRow), style) |
|
|
|
|
|
|
|
// 设置列宽
|
|
|
|
excelFile.SetColWidth(sheetName, "A", "A", 10) // ID
|
|
|
|
excelFile.SetColWidth(sheetName, "B", "B", 40) // 题干
|
|
|
|
excelFile.SetColWidth(sheetName, "C", "F", 20) // 选项A-D
|
|
|
|
excelFile.SetColWidth(sheetName, "G", "G", 12) // 正确答案
|
|
|
|
excelFile.SetColWidth(sheetName, "H", "H", 15) // 题目类型
|
|
|
|
excelFile.SetColWidth(sheetName, "I", "I", 20) // 推荐课程
|
|
|
|
excelFile.SetColWidth(sheetName, "J", "L", 12) // 统计信息
|
|
|
|
|
|
|
|
// === 7. 输出 Excel 文件 ===
|
|
|
|
buffer, err := excelFile.WriteToBuffer() |
|
|
|
if err != nil { |
|
|
|
response.JsonExit(r, 400, "生成Excel文件失败", err.Error()) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 设置响应头
|
|
|
|
r.Response.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") |
|
|
|
r.Response.Header().Set("Content-Disposition", "attachment; filename="+fileName) |
|
|
|
r.Response.Write(buffer.Bytes()) |
|
|
|
} |