diff --git a/backend/global/analyze.go b/backend/global/analyze.go index 6689164..b9895e8 100644 --- a/backend/global/analyze.go +++ b/backend/global/analyze.go @@ -236,6 +236,7 @@ func (f *FileDatabase) AddFile(lstid int, reg *Regex, istemp bool, progress func que := &Question{ ID: int64(binary.LittleEndian.Uint64(m[:8])), FileID: file.ID, + Major: majorq.Name, Plain: base14.BytesToString(sb.Bytes()), Images: func() []byte { m := make(map[string]string) diff --git a/backend/global/generate.go b/backend/global/generate.go index 3fe5bbf..7d6f193 100644 --- a/backend/global/generate.go +++ b/backend/global/generate.go @@ -1,9 +1,18 @@ package global -import "errors" +import ( + "errors" + "strconv" + + sql "github.com/FloatTech/sqlite" + "github.com/fumiama/go-docx" +) var ( - ErrInvalidGenerateConfig = errors.New("invalid generate config") + ErrInvalidGenerateConfig = errors.New("invalid generate config") + ErrMajorTooLarge = errors.New("major too large") + ErrNoEnoughQuestionToMatchRequire = errors.New("no enough question to match require") + ErrRateLimitExceeded = errors.New("rate limit exceeded") ) // GenerateConfig 试卷生成配置 @@ -16,9 +25,59 @@ type GenerateConfig struct { } // GenerateFile 用一些限定条件生成新试卷, 云端不保存 -func (f *FileDatabase) GenerateFile(config *GenerateConfig) ([]byte, error) { - if config == nil || config.Distribution == nil { +func (f *FileDatabase) GenerateFile(config *GenerateConfig) (*docx.Docx, error) { + if config == nil || config.Distribution == nil || len(config.Distribution) == 0 { return nil, ErrInvalidGenerateConfig } + if len(config.Distribution) > 10 { + return nil, ErrMajorTooLarge + } + docf := docx.NewA4() + f.mu.RLock() + defer f.mu.RUnlock() + for n, c := range config.Distribution { + if c == 0 { + continue + } + docf.AddParagraph().AddText(string([]rune("一二三四五六七八九十")[c]) + "、" + n).Size("44").Bold() + cond := " WHERE" + hasfront := false + if config.YearStart > 0 { + cond += " Year>=" + strconv.Itoa(int(config.YearStart)) + hasfront = true + } + if config.YearEnd > 0 { + if hasfront { + cond += " AND" + } + cond += " Year<=" + strconv.Itoa(int(config.YearStart)) + hasfront = true + } + if hasfront { + cond += " AND" + } + cond += " (Type&" + strconv.Itoa(int(config.TypeMask)) + ")!=0" + ques, err := sql.QueryAll[Question](&f.db, + "SELECT * FROM "+FileTableQuestion+ + " WHERE FileID IN (SELECT FileID FROM "+ + FileTableFile+cond+ + ") ORDER BY RANDOM() limit "+strconv.Itoa(int(c))+";", + ) + if err != nil { + return nil, err + } + if len(ques) != int(c) { + return nil, ErrNoEnoughQuestionToMatchRequire + } + rate := 0.0 + for _, q := range ques { + rate += q.MaxDuplicateRate() + } + rate /= float64(len(ques)) + if rate > config.RateLimit { + return nil, ErrRateLimitExceeded + } + // TODO: 写入question到docf + } return nil, nil } diff --git a/backend/global/question.go b/backend/global/question.go index 4ffe5c2..91852aa 100644 --- a/backend/global/question.go +++ b/backend/global/question.go @@ -81,6 +81,7 @@ func (f *FileDatabase) DelQuestion(id int64, istemp bool) error { type Question struct { ID int64 // ID is the first 8 bytes of the Plain's md5 FileID int64 // FileID is fk to File(ID) + Major string // Major is sub's major name Path string // Path is the question's docx position Plain string // Plain is the plain text of the question (like markdown format) Images []byte // Images is json of the image dhash in XML, ex. ['rId1': '1234567890abcdef', ...] @@ -110,6 +111,21 @@ func (f *FileDatabase) GetQuestionHex(hexid string, istemp bool) (q Question, er return f.GetQuestion(int64(binary.LittleEndian.Uint64(idb)), istemp) } +// GetMajors ... +func (f *FileDatabase) GetMajors() (majors []string) { + type majorq struct { + Major string + } + var maj majorq + f.mu.RLock() + defer f.mu.RUnlock() + f.db.QueryFor("SELECT DISTINCT Major FROM question;", &maj, func() error { + majors = append(majors, maj.Major) + return nil + }) + return +} + // MaxDuplicateRate parse q.Dup and get the max rate func (q *Question) MaxDuplicateRate() float64 { dupmap := make(map[string]float64, 64)