From 940b2618b3104af5253e0559cdd5d2986751858f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Tue, 25 Apr 2023 00:36:46 +0800 Subject: [PATCH] =?UTF-8?q?finish=20=E8=AF=95=E5=8D=B7=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/generate.go | 21 ++++ backend/global/file.go | 2 +- backend/global/generate.go | 55 ++++++--- frontend/vben/src/api/page/index.ts | 26 ++++- .../vben/src/api/page/model/fileListModel.ts | 8 ++ .../vben/src/views/page/genfile/Step1.vue | 7 +- .../vben/src/views/page/genfile/Step2.vue | 110 ++++++++++++++---- .../vben/src/views/page/genfile/Step3.vue | 84 ++++++++++--- frontend/vben/src/views/page/genfile/data.tsx | 21 ++-- .../vben/src/views/page/genfile/index.vue | 24 ++-- 10 files changed, 280 insertions(+), 78 deletions(-) diff --git a/backend/generate.go b/backend/generate.go index b56a9e3..a34ed7b 100644 --- a/backend/generate.go +++ b/backend/generate.go @@ -4,10 +4,16 @@ import ( "encoding/json" "io" "net/http" + "os" + "time" + "github.com/FloatTech/ttl" + "github.com/fumiama/go-docx" "github.com/fumiama/paper-manager/backend/global" ) +var genfilecache = ttl.NewCache[int, *docx.Docx](time.Minute * 10) + func init() { apimap["/api/genFile"] = &apihandler{"POST", func(w http.ResponseWriter, r *http.Request) { user := usertokens.Get(r.Header.Get("Authorization")) @@ -27,6 +33,21 @@ func init() { writeresult(w, codeError, nil, err.Error(), typeError) return } + genfilecache.Set(*user.ID, docf) + writeresult(w, codeSuccess, "请在10分钟内下载, 且不要在下载完成前关闭页面, 云端不会保存", messageOk, typeSuccess) + }} + + apimap["/api/dlGen"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { + user := usertokens.Get(r.Header.Get("Authorization")) + if user == nil { + writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) + return + } + docf := genfilecache.Get(*user.ID) + if docf == nil { + writeresult(w, codeError, nil, os.ErrNotExist.Error(), typeError) + return + } _, _ = io.Copy(w, docf) }} } diff --git a/backend/global/file.go b/backend/global/file.go index eb85ccf..65df284 100644 --- a/backend/global/file.go +++ b/backend/global/file.go @@ -53,7 +53,7 @@ func init() { panic(err) } err = FileDB.db.Create(FileTableQuestion, &Question{}, - "FOREIGN KEY(FileID) REFERENCES "+FileTableFile+"(ID)", + "FOREIGN KEY(ListID) REFERENCES "+FileTableList+"(ID)", ) if err != nil { panic(err) diff --git a/backend/global/generate.go b/backend/global/generate.go index 0c05492..d6dea1a 100644 --- a/backend/global/generate.go +++ b/backend/global/generate.go @@ -7,11 +7,13 @@ import ( sql "github.com/FloatTech/sqlite" "github.com/fumiama/go-docx" + "github.com/sirupsen/logrus" ) var ( ErrInvalidGenerateConfig = errors.New("invalid generate config") ErrMajorTooLarge = errors.New("major too large") + ErrNoSuchMajor = errors.New("no such major") ErrNoEnoughQuestionToMatchRequire = errors.New("no enough question to match require") ErrRateLimitExceeded = errors.New("rate limit exceeded") ) @@ -26,21 +28,31 @@ type GenerateConfig struct { } // GenerateFile 用一些限定条件生成新试卷, 云端不保存 -func (f *FileDatabase) GenerateFile(config *GenerateConfig) (*docx.Docx, error) { +func (f *FileDatabase) GenerateFile(config *GenerateConfig) (docf *docx.Docx, err 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() + mm := map[string]struct{}{} + for _, m := range f.GetMajors() { + mm[m] = struct{}{} + } + for n := range config.Distribution { + if _, ok := mm[n]; !ok { + return nil, ErrNoSuchMajor + } + } + docf = docx.NewA4() f.mu.RLock() defer f.mu.RUnlock() + i := 0 for n, c := range config.Distribution { - if c == 0 { + if c <= 0 { continue } - docf.AddParagraph().AddText(string([]rune("一二三四五六七八九十")[c]) + "、" + n).Size("44").Bold() + docf.AddParagraph().AddText(string([]rune("一二三四五六七八九十")[i]) + "、" + n).Size("30").Bold() cond := " WHERE" hasfront := false if config.YearStart > 0 { @@ -51,20 +63,32 @@ func (f *FileDatabase) GenerateFile(config *GenerateConfig) (*docx.Docx, error) if hasfront { cond += " AND" } - cond += " Year<=" + strconv.Itoa(int(config.YearStart)) + cond += " Year<=" + strconv.Itoa(int(config.YearEnd)) hasfront = true } - if hasfront { - cond += " AND" + if config.TypeMask > 0 { + if hasfront { + cond += " AND" + } + typmsk := strconv.Itoa(int(config.TypeMask)) + cond += " (Type&" + typmsk + ")==" + typmsk + hasfront = true + } + var ques []*Question + q := "" + if hasfront { + q = "SELECT * FROM " + FileTableQuestion + + " WHERE Major='" + n + "' AND ListID IN (SELECT DISTINCT ListID FROM " + + FileTableFile + cond + + ") ORDER BY RANDOM() limit " + strconv.Itoa(int(c)) + ";" + ques, err = sql.QueryAll[Question](&f.db, q) + } else { + q = "SELECT * FROM " + FileTableQuestion + + " WHERE Major='" + n + "' ORDER BY RANDOM() limit " + strconv.Itoa(int(c)) + ";" + ques, err = sql.QueryAll[Question](&f.db, q) } - 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 { + logrus.Warnln(err, q) return nil, err } if len(ques) != int(c) { @@ -79,7 +103,7 @@ func (f *FileDatabase) GenerateFile(config *GenerateConfig) (*docx.Docx, error) return nil, ErrRateLimitExceeded } for i, q := range ques { - lst, err := sql.Find[List](&f.db, FileTableFile, "WHERE ID="+strconv.Itoa(q.ListID)) + lst, err := sql.Find[List](&f.db, FileTableList, "WHERE ID="+strconv.Itoa(q.ListID)) if err != nil { return nil, err } @@ -98,6 +122,7 @@ func (f *FileDatabase) GenerateFile(config *GenerateConfig) (*docx.Docx, error) docf.AddParagraph().AddText(strconv.Itoa(i+1) + ". (" + lst.Desc + ")") docf.AppendFile(docq) } + i++ } return docf, nil } diff --git a/frontend/vben/src/api/page/index.ts b/frontend/vben/src/api/page/index.ts index ab3309e..500967d 100644 --- a/frontend/vben/src/api/page/index.ts +++ b/frontend/vben/src/api/page/index.ts @@ -1,5 +1,10 @@ import { defHttp, paperHttp } from '/@/utils/http/axios' -import { getFileListModel, AnalyzeFile, FileListGroupItem } from './model/fileListModel' +import { + getFileListModel, + AnalyzeFile, + FileListGroupItem, + GenerateConfig, +} from './model/fileListModel' import { DownloadFile, FileStatus } from './model/fileModel' enum Api { @@ -11,6 +16,8 @@ enum Api { DlFile = '/dlFile', GetFileStatus = '/getFileStatus', GetMajors = '/getMajors', + GenFile = '/genFile', + DlGen = '/dlGen', } /** @@ -81,3 +88,20 @@ export const getFileStatus = (id: number) => { export const getMajors = () => { return defHttp.get({ url: Api.GetMajors }) } + +/** + * @description: Generate File + */ +export const generateFile = (config: GenerateConfig) => { + return defHttp.post({ url: Api.GenFile, params: config }, { errorMessageMode: 'none' }) +} + +/** + * @description: Download generated file + */ +export const dlGeneratedFile = () => { + return paperHttp.get( + { url: '/api' + Api.DlGen, responseType: 'blob' }, + { errorMessageMode: 'none' }, + ) +} diff --git a/frontend/vben/src/api/page/model/fileListModel.ts b/frontend/vben/src/api/page/model/fileListModel.ts index af85355..237b28f 100644 --- a/frontend/vben/src/api/page/model/fileListModel.ts +++ b/frontend/vben/src/api/page/model/fileListModel.ts @@ -18,3 +18,11 @@ export interface AnalyzeFile { code: number msg: string } + +export interface GenerateConfig { + Distribution: { [x: string]: any } // Distribution is map[majorname]subcount + RateLimit: number // RateLimit 重复率上限 + YearStart: number // YearStart 起始年份(空则直到最旧) + YearEnd: number // YearEnd 截止年份(空则直到最新) + TypeMask: number // TypeMask & File.Type != 0 则匹配 +} diff --git a/frontend/vben/src/views/page/genfile/Step1.vue b/frontend/vben/src/views/page/genfile/Step1.vue index e63381f..25e022d 100644 --- a/frontend/vben/src/views/page/genfile/Step1.vue +++ b/frontend/vben/src/views/page/genfile/Step1.vue @@ -95,7 +95,12 @@ async function customSubmitFunc() { try { const values = await validate() - emit('next', values) + const data = getDataSource() + if (data.length == 0) { + createMessage.error('必须指定至少一种题型!') + return + } + emit('next', { values, data }) } catch (error) {} } diff --git a/frontend/vben/src/views/page/genfile/Step2.vue b/frontend/vben/src/views/page/genfile/Step2.vue index 9ce31b6..0cc33ef 100644 --- a/frontend/vben/src/views/page/genfile/Step2.vue +++ b/frontend/vben/src/views/page/genfile/Step2.vue @@ -1,11 +1,36 @@