diff --git a/backend/file.go b/backend/file.go index 5ac3e86..017ee97 100644 --- a/backend/file.go +++ b/backend/file.go @@ -1,13 +1,283 @@ package backend import ( + "errors" "net/http" + "strconv" + "strings" + "time" + sql "github.com/FloatTech/sqlite" "github.com/fumiama/paper-manager/backend/global" "github.com/fumiama/paper-manager/backend/utils" "github.com/sirupsen/logrus" ) +var ( + errNoDeletePermission = errors.New("no delete permission") + errExtractUIDError = errors.New("extract uid error") + errParseFilePath = errors.New("parse filepath error") + errNoDownloadPermission = errors.New("no download permission") +) + +type filelist struct { + ID int `json:"id"` + Title string `json:"title"` + Desc string `json:"description"` + Size float64 `json:"size"` + Ques int `json:"questions"` + Auth string `json:"author"` + Date string `json:"datetime"` + Per uint `json:"percent"` +} + +func getFileList(count int, istemp bool) ([]filelist, error) { + lst, err := global.FileDB.ListUploadedFile(istemp) + if err != nil && err != sql.ErrNullResult { + return nil, err + } + if count > 0 && len(lst) > count { + lst = lst[:count] + } + result := make([]filelist, len(lst)) + for i, v := range lst { + result[i].ID = *v.ID + j := strings.LastIndex(v.Path, "/") + if j <= 0 { + result[i].Title = v.Path + } else { + result[i].Title = v.Path[j+1:] + } + result[i].Desc = v.Desc + result[i].Size = float64(v.Size) / 1024 / 1024 // MB + result[i].Ques = v.QuesC + result[i].Auth = v.UpName + result[i].Date = time.Unix(v.UpTime, 0).Format(chineseYYMMDDLayout) + if !v.HasntAnalyzed { + result[i].Per = 100 + } else { + result[i].Per = analyzeper.Get(*v.ID) + } + } + return result, nil +} + +func getFileInfo(lstid int) (*filelist, error) { + lst, err := global.FileDB.ListFileByID(lstid) + if err != nil && err != sql.ErrNullResult { + return nil, err + } + result := filelist{ + ID: lstid, + Desc: lst.Desc, + Size: float64(lst.Size) / 1024 / 1024, // MB + Ques: lst.QuesC, + Auth: lst.UpName, + Date: time.Unix(lst.UpTime, 0).Format(chineseYYMMDDLayout), + } + j := strings.LastIndex(lst.Path, "/") + if j <= 0 { + result.Title = lst.Path + } else { + result.Title = lst.Path[j+1:] + } + if !lst.HasntAnalyzed { + result.Per = 100 + } else { + result.Per = analyzeper.Get(lstid) + } + return &result, nil +} + +func dlFile(lstid int, user *global.User) (string, error) { + lst, err := global.FileDB.ListFileByID(lstid) + if err != nil { + return "", err + } + if strings.HasPrefix(lst.Path, global.PaperFolder+"tmp/") { + uidstr := lst.Path[17:] + i := strings.Index(uidstr, "/") + if i <= 0 { + return "", errExtractUIDError + } + uid, err := strconv.Atoi(uidstr[:i]) + if err != nil { + return "", err + } + if uid != *user.ID { + return "", errNoDownloadPermission + } + return lst.Path[6:], nil + } + if strings.HasPrefix(lst.Path, global.PaperFolder) { + return lst.Path[6:], nil + } + return "", errParseFilePath +} + +type filestatus struct { + Name string `json:"name"` + Size float64 `json:"size"` + Rate float64 `json:"rate"` + Questions []question `json:"questions"` + Duplications []duplication `json:"duplications"` // Duplications length == 10 +} + +func getFileStatus(lstid int, user *global.User) (*filestatus, error) { + file, sz, istemp, err := global.FileDB.GetFile(lstid, *user.ID) + if err != nil { + return nil, err + } + qs, ds, filerate, err := parseFileQuestions(file.Questions, istemp) + if err != nil { + return nil, err + } + return &filestatus{ + Name: file.Class + ".docx", + Size: float64(sz) / 1024 / 1024, // MB + Rate: filerate * 100, + Questions: qs, + Duplications: ds, + }, nil +} + +func init() { + apimap["/api/getFileList"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + user := usertokens.Get(token) + if user == nil { + writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) + return + } + istemp := r.URL.Query().Get("permanent") != "true" + count := -1 + var err error + countstr := r.URL.Query().Get("count") + if countstr != "" { + count, err = strconv.Atoi(countstr) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + } + lst, err := getFileList(count, istemp) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + writeresult(w, codeSuccess, &lst, messageOk, typeSuccess) + }} + + apimap["/api/getFileInfo"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + user := usertokens.Get(token) + if user == nil { + writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) + return + } + var err error + idstr := r.URL.Query().Get("id") + if idstr == "" { + writeresult(w, codeError, nil, "empty id", typeError) + return + } + id, err := strconv.Atoi(idstr) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + inf, err := getFileInfo(id) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + writeresult(w, codeSuccess, inf, messageOk, typeSuccess) + }} + + apimap["/api/delFile"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + user := usertokens.Get(token) + if user == nil { + writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) + return + } + istemp := r.URL.Query().Get("permanent") != "true" + if !user.IsSuper() && !istemp { + writeresult(w, codeError, nil, errNoDeletePermission.Error(), typeError) + return + } + idstr := r.URL.Query().Get("id") + if idstr == "" { + writeresult(w, codeError, nil, "empty id", typeError) + return + } + id, err := strconv.Atoi(idstr) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + err = global.FileDB.DelFile(id, *user.ID, istemp) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + writeresult(w, codeSuccess, "删除成功", messageOk, typeSuccess) + }} + + apimap["/api/dlFile"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + user := usertokens.Get(token) + if user == nil { + writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) + return + } + idstr := r.URL.Query().Get("id") + if idstr == "" { + writeresult(w, codeError, nil, "empty id", typeError) + return + } + id, err := strconv.Atoi(idstr) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + type message struct { + URL string `json:"url"` + } + u, err := dlFile(id, user) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + writeresult(w, codeSuccess, &message{URL: u}, messageOk, typeSuccess) + }} + + apimap["/api/getFileStatus"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + user := usertokens.Get(token) + if user == nil { + writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) + return + } + idstr := r.URL.Query().Get("id") + if idstr == "" { + writeresult(w, codeError, nil, "empty id", typeError) + return + } + id, err := strconv.Atoi(idstr) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + fstat, err := getFileStatus(id, user) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + writeresult(w, codeSuccess, fstat, messageOk, typeSuccess) + }} +} + // FileHandler serves contents in global.FileFolder func FileHandler(w http.ResponseWriter, r *http.Request) { if !utils.IsMethod("GET", w, r) { diff --git a/backend/global/regex.go b/backend/global/regex.go index 074e73c..3f408c0 100644 --- a/backend/global/regex.go +++ b/backend/global/regex.go @@ -21,7 +21,7 @@ type Regex struct { Sub string // Sub default `(\d+)、` } -func newRegex() (reg Regex) { +func GetDefaultRegex() (reg Regex) { reg.Title = `.*(\d{4})\s*-.*学年.*(\d).*([中末]).*([AB])\s*卷` reg.Class = `(考试科目|课程名称):\s*(\S+)\s*` reg.OpenCl = `考试形式:\s*(\S+)\s*` @@ -52,7 +52,7 @@ func (u *UserDatabase) SetUserRegex(id int, name, re string) error { if err != nil { return err } - reg := newRegex() + reg := GetDefaultRegex() rreg := reflect.ValueOf(®).Elem() f := rreg.FieldByName(name) if !f.IsValid() { @@ -80,7 +80,7 @@ func (u *UserDatabase) GetUserRegex(id int) (*Regex, error) { u.mu.RUnlock() reg.ID = *user.ID rf := reflect.ValueOf(reg) - defaultrf := reflect.ValueOf(newRegex()) + defaultrf := reflect.ValueOf(GetDefaultRegex()) for i := 0; i < rf.NumField(); i++ { if rf.Field(i).IsZero() { rf.Field(i).Set(defaultrf.Field(i)) diff --git a/backend/login.go b/backend/login.go index d16dd2e..7e87050 100644 --- a/backend/login.go +++ b/backend/login.go @@ -98,7 +98,7 @@ type loginResult struct { var ( usertokens = ttl.NewCacheOn(time.Hour, [4]func(string, *global.User){ - nil, nil, func(t string, _ *global.User) { loginstatus.Delete(t) }, nil, + nil, nil, func(_ string, user *global.User) { loginstatus.Delete(user.Name) }, nil, }) ) diff --git a/backend/paper.go b/backend/paper.go index ececb3d..ea024d5 100644 --- a/backend/paper.go +++ b/backend/paper.go @@ -7,7 +7,6 @@ import ( "strings" "time" - sql "github.com/FloatTech/sqlite" "github.com/FloatTech/ttl" "github.com/fumiama/paper-manager/backend/global" "github.com/fumiama/paper-manager/backend/utils" @@ -22,123 +21,10 @@ const ( var analyzeper = ttl.NewCache[int, uint](time.Hour) var ( - errNoAnalyzePermission = errors.New("no analyze permission") - errNoDeletePermission = errors.New("no delete permission") - errNoDownloadPermission = errors.New("no download permission") + errNoAnalyzePermission = errors.New("no analyze permission") ) -type filelist struct { - ID int `json:"id"` - Title string `json:"title"` - Desc string `json:"description"` - Size float64 `json:"size"` - Ques int `json:"questions"` - Auth string `json:"author"` - Date string `json:"datetime"` - Per uint `json:"percent"` -} - -type filestatus struct { - Name string `json:"name"` - Size float64 `json:"size"` - Rate float64 `json:"rate"` - Questions []question `json:"questions"` - Duplications []duplication `json:"duplications"` // Duplications length == 10 -} - func init() { - apimap["/api/getFileList"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("Authorization") - user := usertokens.Get(token) - if user == nil { - writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) - return - } - istemp := r.URL.Query().Get("permanent") != "true" - count := -1 - var err error - countstr := r.URL.Query().Get("count") - if countstr != "" { - count, err = strconv.Atoi(countstr) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - } - lst, err := global.FileDB.ListUploadedFile(istemp) - if err != nil && err != sql.ErrNullResult { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - if count > 0 && len(lst) > count { - lst = lst[:count] - } - result := make([]filelist, len(lst)) - for i, v := range lst { - result[i].ID = *v.ID - j := strings.LastIndex(v.Path, "/") - if j <= 0 { - result[i].Title = v.Path - } else { - result[i].Title = v.Path[j+1:] - } - result[i].Desc = v.Desc - result[i].Size = float64(v.Size) / 1024 / 1024 // MB - result[i].Ques = v.QuesC - result[i].Auth = v.UpName - result[i].Date = time.Unix(v.UpTime, 0).Format(chineseYYMMDDLayout) - if !v.HasntAnalyzed { - result[i].Per = 100 - } else { - result[i].Per = analyzeper.Get(*v.ID) - } - } - writeresult(w, codeSuccess, &result, messageOk, typeSuccess) - }} - apimap["/api/getFileInfo"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("Authorization") - user := usertokens.Get(token) - if user == nil { - writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) - return - } - var err error - idstr := r.URL.Query().Get("id") - if idstr == "" { - writeresult(w, codeError, nil, "empty id", typeError) - return - } - id, err := strconv.Atoi(idstr) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - lst, err := global.FileDB.ListFileByID(id) - if err != nil && err != sql.ErrNullResult { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - result := filelist{ - ID: id, - Desc: lst.Desc, - Size: float64(lst.Size) / 1024 / 1024, // MB - Ques: lst.QuesC, - Auth: lst.UpName, - Date: time.Unix(lst.UpTime, 0).Format(chineseYYMMDDLayout), - } - j := strings.LastIndex(lst.Path, "/") - if j <= 0 { - result.Title = lst.Path - } else { - result.Title = lst.Path[j+1:] - } - if !lst.HasntAnalyzed { - result.Per = 100 - } else { - result.Per = analyzeper.Get(id) - } - writeresult(w, codeSuccess, &result, messageOk, typeSuccess) - }} apimap["/api/getFilePercent"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") user := usertokens.Get(token) @@ -158,6 +44,7 @@ func init() { } writeresult(w, codeSuccess, analyzeper.Get(id), messageOk, typeSuccess) }} + apimap["/api/analyzeFile"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") user := usertokens.Get(token) @@ -211,120 +98,6 @@ func init() { writeresult(w, codeSuccess, &message{C: 0, M: "分析完成"}, messageOk, typeSuccess) } }} - apimap["/api/delFile"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("Authorization") - user := usertokens.Get(token) - if user == nil { - writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) - return - } - istemp := r.URL.Query().Get("permanent") != "true" - if !user.IsSuper() && !istemp { - writeresult(w, codeError, nil, errNoDeletePermission.Error(), typeError) - return - } - idstr := r.URL.Query().Get("id") - if idstr == "" { - writeresult(w, codeError, nil, "empty id", typeError) - return - } - id, err := strconv.Atoi(idstr) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - err = global.FileDB.DelFile(id, *user.ID, istemp) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - writeresult(w, codeSuccess, "删除成功", messageOk, typeSuccess) - }} - apimap["/api/dlFile"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("Authorization") - user := usertokens.Get(token) - if user == nil { - writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) - return - } - idstr := r.URL.Query().Get("id") - if idstr == "" { - writeresult(w, codeError, nil, "empty id", typeError) - return - } - id, err := strconv.Atoi(idstr) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - lst, err := global.FileDB.ListFileByID(id) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - type message struct { - URL string `json:"url"` - } - if strings.HasPrefix(lst.Path, global.PaperFolder+"tmp/") { - uidstr := lst.Path[17:] - i := strings.Index(uidstr, "/") - if i <= 0 { - writeresult(w, codeError, nil, "extract uid error", typeError) - return - } - uid, err := strconv.Atoi(uidstr[:i]) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - if uid != *user.ID { - writeresult(w, codeError, nil, errNoDownloadPermission.Error(), typeError) - return - } - writeresult(w, codeSuccess, &message{URL: lst.Path[6:]}, messageOk, typeSuccess) - return - } - if strings.HasPrefix(lst.Path, global.PaperFolder) { - writeresult(w, codeSuccess, &message{URL: lst.Path[6:]}, messageOk, typeSuccess) - return - } - writeresult(w, codeError, nil, "parse filepath error", typeError) - }} - apimap["/api/getFileStatus"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("Authorization") - user := usertokens.Get(token) - if user == nil { - writeresult(w, codeError, nil, errInvalidToken.Error(), typeError) - return - } - idstr := r.URL.Query().Get("id") - if idstr == "" { - writeresult(w, codeError, nil, "empty id", typeError) - return - } - id, err := strconv.Atoi(idstr) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - file, sz, istemp, err := global.FileDB.GetFile(id, *user.ID) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - qs, ds, filerate, err := parseFileQuestions(file.Questions, istemp) - if err != nil { - writeresult(w, codeError, nil, err.Error(), typeError) - return - } - writeresult(w, codeSuccess, &filestatus{ - Name: file.Class + ".docx", - Size: float64(sz) / 1024 / 1024, // MB - Rate: filerate * 100, - Questions: qs, - Duplications: ds, - }, messageOk, typeSuccess) - }} } // PaperHandler serves protected contents in global.PaperFolder diff --git a/backend/regex.go b/backend/regex.go new file mode 100644 index 0000000..6328819 --- /dev/null +++ b/backend/regex.go @@ -0,0 +1,26 @@ +package backend + +import ( + "net/http" + + "github.com/fumiama/paper-manager/backend/global" +) + +func getUserRegex(token string) (*global.Regex, error) { + user := usertokens.Get(token) + if user == nil { + return nil, errInvalidToken + } + return global.UserDB.GetUserRegex(*user.ID) +} + +func init() { + apimap["/api/getUserRegex"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) { + reg, err := getUserRegex(r.Header.Get("Authorization")) + if err != nil { + writeresult(w, codeError, nil, err.Error(), typeError) + return + } + writeresult(w, codeSuccess, reg, messageOk, typeSuccess) + }} +} diff --git a/frontend/vben/src/locales/lang/zh-CN/routes/dashboard.ts b/frontend/vben/src/locales/lang/zh-CN/routes/dashboard.ts index b00c166..31cefe4 100644 --- a/frontend/vben/src/locales/lang/zh-CN/routes/dashboard.ts +++ b/frontend/vben/src/locales/lang/zh-CN/routes/dashboard.ts @@ -4,4 +4,5 @@ export default { workbench: '工作台', analysis: '分析页', account: '用户管理', + regex: '试卷解析配置', } diff --git a/frontend/vben/src/router/menus/modules/dashboard.ts b/frontend/vben/src/router/menus/modules/dashboard.ts index dfc2c79..0b19145 100644 --- a/frontend/vben/src/router/menus/modules/dashboard.ts +++ b/frontend/vben/src/router/menus/modules/dashboard.ts @@ -19,6 +19,10 @@ const menu: MenuModule = { path: 'account', name: t('routes.dashboard.account'), }, + { + path: 'regex', + name: t('routes.dashboard.regex'), + }, ], }, } diff --git a/frontend/vben/src/router/routes/modules/dashboard.ts b/frontend/vben/src/router/routes/modules/dashboard.ts index f40afc6..bfec5c0 100644 --- a/frontend/vben/src/router/routes/modules/dashboard.ts +++ b/frontend/vben/src/router/routes/modules/dashboard.ts @@ -43,6 +43,15 @@ const dashboard: AppRouteModule = { roles: [RoleEnum.SUPER], }, }, + { + path: 'regex', + name: 'Regex', + component: () => import('/@/views/dashboard/regex/index.vue'), + meta: { + // affix: true, + title: t('routes.dashboard.regex'), + }, + }, ], } diff --git a/frontend/vben/src/views/dashboard/regex/data.ts b/frontend/vben/src/views/dashboard/regex/data.ts index 903b655..e7d2000 100644 --- a/frontend/vben/src/views/dashboard/regex/data.ts +++ b/frontend/vben/src/views/dashboard/regex/data.ts @@ -1,6 +1,6 @@ import { FormSchema } from '/@/components/Form' const colProps = { - span: 8, + span: 24, } export const schemas: FormSchema[] = [ @@ -8,80 +8,56 @@ export const schemas: FormSchema[] = [ field: 'title', component: 'Input', colProps, - label: '标题', - helpMessage: '目标的服务对象', - componentProps: { - placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号', - }, + label: '试卷标题', + defaultValue: 'more 吗', }, { field: 'class', component: 'Input', colProps, - label: '客户', - helpMessage: '目标的服务对象', - componentProps: { - placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号', - }, + label: '课程名称', + defaultValue: 'more 吗', }, { field: 'opencl', component: 'Input', colProps, - label: '客户', - helpMessage: '目标的服务对象', - componentProps: { - placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号', - }, + label: '开/闭卷', + defaultValue: 'more 吗', }, { field: 'date', component: 'Input', colProps, - label: '客户', - helpMessage: '目标的服务对象', - componentProps: { - placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号', - }, + label: '考试日期', + defaultValue: 'more 吗', }, { field: 'time', component: 'Input', colProps, - label: '客户', - helpMessage: '目标的服务对象', - componentProps: { - placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号', - }, + label: '考试时长', + defaultValue: 'more 吗', }, { field: 'rate', component: 'Input', colProps, - label: '客户', - helpMessage: '目标的服务对象', - componentProps: { - placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号', - }, + label: '成绩占比', + defaultValue: 'more 吗', }, { field: 'major', component: 'Input', colProps, - label: '客户', - helpMessage: '目标的服务对象', - componentProps: { - placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号', - }, + label: '大题题号', + defaultValue: 'more 吗', }, { field: 'sub', component: 'Input', colProps, - label: '客户', - helpMessage: '目标的服务对象', - componentProps: { - placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号', - }, + label: '小题题号', + defaultValue: 'more 吗', }, ] diff --git a/frontend/vben/src/views/dashboard/regex/index.vue b/frontend/vben/src/views/dashboard/regex/index.vue index 42c89ed..1f56e9e 100644 --- a/frontend/vben/src/views/dashboard/regex/index.vue +++ b/frontend/vben/src/views/dashboard/regex/index.vue @@ -1,6 +1,6 @@