package services import ( "errors" "time" "server/models" "github.com/beego/beego/v2/client/orm" ) type QuestionListParams struct { TenantId int Keyword string QuestionType *int8 BankId int64 Page int PageSize int } // CountQuestionsInBank 统计指定题库下的题目数量(仅统计未删除且启用的题目) func CountQuestionsInBank(tenantId int, bankId int64) (int64, error) { o := orm.NewOrm() qs := o.QueryTable(new(models.ExamQuestion)).Filter("delete_time__isnull", true).Filter("status", 1).Filter("bank_id", bankId) if tenantId > 0 { qs = qs.Filter("tenant_id", tenantId) } return qs.Count() } type QuestionWithMeta struct { Question *models.ExamQuestion Options []*models.ExamQuestionOption Answer *models.ExamQuestionAnswer } type BankListParams struct { TenantId int Keyword string Page int PageSize int } func GetExamQuestions(params QuestionListParams) ([]*models.ExamQuestion, int64, error) { o := orm.NewOrm() qs := o.QueryTable(new(models.ExamQuestion)).Filter("delete_time__isnull", true).Filter("status", 1) if params.TenantId > 0 { qs = qs.Filter("tenant_id", params.TenantId) } if params.Keyword != "" { qs = qs.Filter("question_title__icontains", params.Keyword) } if params.QuestionType != nil { qs = qs.Filter("question_type", *params.QuestionType) } if params.BankId > 0 { qs = qs.Filter("bank_id", params.BankId) } total, err := qs.Count() if err != nil { return nil, 0, err } if params.Page <= 0 { params.Page = 1 } if params.PageSize <= 0 { params.PageSize = 10 } offset := (params.Page - 1) * params.PageSize var list []*models.ExamQuestion _, err = qs.OrderBy("-id").Limit(params.PageSize, offset).All(&list, "Id", "TenantId", "QuestionType", "QuestionTitle", "Score") return list, total, err } func GetExamQuestionDetail(tenantId int, id int64) (*QuestionWithMeta, error) { o := orm.NewOrm() q := new(models.ExamQuestion) if err := o.QueryTable(q).Filter("id", id).Filter("delete_time__isnull", true).One(q); err != nil { return nil, err } if tenantId > 0 && q.TenantId != tenantId { return nil, errors.New("not found") } var opts []*models.ExamQuestionOption _, _ = o.QueryTable(new(models.ExamQuestionOption)).Filter("question_id", q.Id).Filter("delete_time__isnull", true).All(&opts) a := new(models.ExamQuestionAnswer) _ = o.QueryTable(a).Filter("question_id", q.Id).Filter("delete_time__isnull", true).One(a) return &QuestionWithMeta{Question: q, Options: opts, Answer: a}, nil } // FindExamQuestionByTitle 在指定租户下根据题目标题精确查找试题 // 用于批量导入时判断是否存在完全相同题目,以便执行覆盖更新 func FindExamQuestionByTitle(tenantId int, title string) (*models.ExamQuestion, error) { o := orm.NewOrm() q := new(models.ExamQuestion) qs := o.QueryTable(q).Filter("delete_time__isnull", true).Filter("question_title", title) if tenantId > 0 { qs = qs.Filter("tenant_id", tenantId) } if err := qs.One(q); err != nil { return nil, err } return q, nil } func CreateExamQuestion(tenantId int, q *models.ExamQuestion, options []models.ExamQuestionOption, answerContent string) (int64, error) { o := orm.NewOrm() if tenantId > 0 { q.TenantId = tenantId } q.Status = 1 if _, err := o.Insert(q); err != nil { return 0, err } for i := range options { options[i].TenantId = q.TenantId options[i].QuestionId = q.Id _, _ = o.Insert(&options[i]) } ans := &models.ExamQuestionAnswer{TenantId: q.TenantId, QuestionId: q.Id, AnswerContent: answerContent} _, _ = o.Insert(ans) return q.Id, nil } func UpdateExamQuestion(tenantId int, id int64, q *models.ExamQuestion, options []models.ExamQuestionOption, answerContent string) error { o := orm.NewOrm() existing := models.ExamQuestion{Id: id} if err := o.Read(&existing); err != nil { return err } if tenantId > 0 && existing.TenantId != tenantId { return errors.New("not found") } existing.QuestionType = q.QuestionType existing.QuestionTitle = q.QuestionTitle existing.QuestionAnalysis = q.QuestionAnalysis existing.Score = q.Score // 可选更新题库归属 fields := []string{"QuestionType", "QuestionTitle", "QuestionAnalysis", "Score"} if q.BankId > 0 { existing.BankId = q.BankId fields = append(fields, "BankId") } if _, err := o.Update(&existing, fields...); err != nil { return err } // soft delete old options and answer now := time.Now() o.QueryTable(new(models.ExamQuestionOption)).Filter("question_id", id).Filter("delete_time__isnull", true).Update(orm.Params{"delete_time": now}) o.QueryTable(new(models.ExamQuestionAnswer)).Filter("question_id", id).Filter("delete_time__isnull", true).Update(orm.Params{"delete_time": now}) for i := range options { options[i].TenantId = existing.TenantId options[i].QuestionId = id _, _ = o.Insert(&options[i]) } ans := &models.ExamQuestionAnswer{TenantId: existing.TenantId, QuestionId: id, AnswerContent: answerContent} _, _ = o.Insert(ans) return nil } func DeleteExamQuestion(tenantId int, id int64) error { o := orm.NewOrm() existing := models.ExamQuestion{Id: id} if err := o.Read(&existing); err != nil { return err } if tenantId > 0 && existing.TenantId != tenantId { return errors.New("not found") } now := time.Now() if _, err := o.QueryTable(new(models.ExamQuestion)).Filter("id", id).Update(orm.Params{"delete_time": now}); err != nil { return err } o.QueryTable(new(models.ExamQuestionOption)).Filter("question_id", id).Filter("delete_time__isnull", true).Update(orm.Params{"delete_time": now}) o.QueryTable(new(models.ExamQuestionAnswer)).Filter("question_id", id).Filter("delete_time__isnull", true).Update(orm.Params{"delete_time": now}) return nil } func GetExamQuestionBanks(params BankListParams) ([]*models.ExamQuestionBank, int64, error) { o := orm.NewOrm() qs := o.QueryTable(new(models.ExamQuestionBank)).Filter("delete_time__isnull", true).Filter("status", 1) if params.TenantId > 0 { qs = qs.Filter("tenant_id", params.TenantId) } if params.Keyword != "" { qs = qs.Filter("bank_name__icontains", params.Keyword) } total, err := qs.Count() if err != nil { return nil, 0, err } if params.Page <= 0 { params.Page = 1 } if params.PageSize <= 0 { params.PageSize = 10 } offset := (params.Page - 1) * params.PageSize var list []*models.ExamQuestionBank _, err = qs.OrderBy("-id").Limit(params.PageSize, offset).All(&list, "Id", "TenantId", "BankName", "BankDesc") return list, total, err } func GetExamQuestionBankDetail(tenantId int, id int64) (*models.ExamQuestionBank, error) { o := orm.NewOrm() bank := new(models.ExamQuestionBank) if err := o.QueryTable(bank).Filter("id", id).Filter("delete_time__isnull", true).One(bank); err != nil { return nil, err } if tenantId > 0 && bank.TenantId != tenantId { return nil, errors.New("not found") } return bank, nil } func CreateExamQuestionBank(tenantId int, bank *models.ExamQuestionBank) (int64, error) { o := orm.NewOrm() if tenantId > 0 { bank.TenantId = tenantId } bank.Status = 1 if _, err := o.Insert(bank); err != nil { return 0, err } return bank.Id, nil } func UpdateExamQuestionBank(tenantId int, id int64, bank *models.ExamQuestionBank) error { o := orm.NewOrm() existing := models.ExamQuestionBank{Id: id} if err := o.Read(&existing); err != nil { return err } if tenantId > 0 && existing.TenantId != tenantId { return errors.New("not found") } existing.BankName = bank.BankName existing.BankDesc = bank.BankDesc if _, err := o.Update(&existing, "BankName", "BankDesc"); err != nil { return err } return nil } func DeleteExamQuestionBank(tenantId int, id int64) error { o := orm.NewOrm() existing := models.ExamQuestionBank{Id: id} if err := o.Read(&existing); err != nil { return err } if tenantId > 0 && existing.TenantId != tenantId { return errors.New("not found") } now := time.Now() if _, err := o.QueryTable(new(models.ExamQuestionBank)).Filter("id", id).Update(orm.Params{"delete_time": now}); err != nil { return err } return nil }