From 93975b26bc8799f1973e1557577f610491595037 Mon Sep 17 00:00:00 2001 From: govolokatliai Date: Thu, 17 Jul 2025 16:14:06 +0800 Subject: [PATCH] 1 --- Dockerfile | 26 ++++++++ FrontEnd/assignment.html | 129 ++++++++++++++++++++------------------- FrontEnd/renderTable.js | 111 ++++++++++++++++----------------- database/database.go | 5 +- doc/排班算法.md | 4 ++ go.mod | 4 +- handler/assignments.go | 77 +++++++---------------- handler/init.go | 1 + handler/tweaks.go | 1 + handler/unit/tweaks.go | 46 -------------- main.go | 18 ++++++ model/member.go | 2 + model/tweak.go | 38 ++++++++++++ 13 files changed, 234 insertions(+), 228 deletions(-) create mode 100644 Dockerfile create mode 100644 handler/init.go create mode 100644 handler/tweaks.go delete mode 100644 handler/unit/tweaks.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..66a16b1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# 构建阶段 +FROM golang:1.23 AS builder + +WORKDIR /app + +# 复制源码 +COPY . . + +# 构建二进制文件 +RUN go build -o /app/scheduler + +# 运行阶段 +FROM scratch + +WORKDIR /app + +# 复制构建好的二进制文件 +COPY --from=builder ./scheduler ./scheduler + +RUN mkdir -p config + +EXPOSE 25005 + +USER nonroot:nonroot + +CMD ["./scheduler","--config","config/config.yaml"] diff --git a/FrontEnd/assignment.html b/FrontEnd/assignment.html index 5b1875f..47859dd 100644 --- a/FrontEnd/assignment.html +++ b/FrontEnd/assignment.html @@ -1,65 +1,68 @@ - - - - -
-

今日值班表

-
- -
-
- - - + + + + +
+

今日值班表

+
+ +
+
+ + + diff --git a/FrontEnd/renderTable.js b/FrontEnd/renderTable.js index 47c709b..b2e306a 100644 --- a/FrontEnd/renderTable.js +++ b/FrontEnd/renderTable.js @@ -1,83 +1,76 @@ -document.getElementById('getAssignment').addEventListener('click', function () { - dateInput = document.getElementById('calendar').value; +document.getElementById("getAssignment").addEventListener("click", function () { + dateInput = document.getElementById("calendar").value; - if (!dateInput) { - dateInput = getToday() - } + if (!dateInput) { + dateInput = getToday(); + } - const url = `/api/getAssignment?date=${dateInput}`; + const url = `/api/getAssignment?date=${dateInput}`; - fetch(url) - .then(response => { - if (!response.ok) { - throw new Error('网络响应失败'); - } - return response.json(); - }) - .then(data => { - const responseDiv = document.getElementById('response'); - responseDiv.innerHTML = ''; // 清除旧内容 + fetch(url) + .then((response) => { + if (!response.ok) { + throw new Error("网络响应失败"); + } + return response.json(); + }) + .then((data) => { + const responseDiv = document.getElementById("response"); + responseDiv.innerHTML = ""; // 清除旧内容 - const table = document.createElement('table'); + const table = document.createElement("table"); - data.forEach(subArray => { - const row = document.createElement('tr'); + data.forEach((subArray) => { + const row = document.createElement("tr"); - subArray.forEach(item => { - const cell = document.createElement('td'); - cell.textContent = item.Name || item.ID; + subArray.forEach((item) => { + const cell = document.createElement("td"); + cell.textContent = item.Name || item.ID; - // 优先判断 Access 条件 - if (item.Access <=3) { - cell.classList.add('cell_Moderator'); - } else if (item.Note === 1) { - cell.classList.add('cell_SwitchOrRepay'); - } else if (item.Note === 2) { - cell.classList.add('cell_Volunteering'); - } + // 优先判断 Access 条件 + if (item.Access <= 3) { + cell.classList.add("cell_Moderator"); + } else if (item.Note === 1) { + cell.classList.add("cell_SwitchOrRepay"); + } else if (item.Note === 2) { + cell.classList.add("cell_Volunteering"); + } - row.appendChild(cell); - }); + row.appendChild(cell); + }); - table.appendChild(row); - }); - const title =`
${dateInput}网维值班表
` - const titleContainer = document.createElement('div'); - titleContainer.innerHTML = title - responseDiv.appendChild(titleContainer) - // 插入表格 - responseDiv.appendChild(table); + table.appendChild(row); + }); + const title = `
${dateInput}网维值班表
`; + const titleContainer = document.createElement("div"); + titleContainer.innerHTML = title; + responseDiv.appendChild(titleContainer); + // 插入表格 + responseDiv.appendChild(table); - // 添加图例说明 - const legendHTML = ` + // 添加图例说明 + const legendHTML = ` 片区负责人
管理层
换班/补班
蹭班
`; - const legendContainer = document.createElement('div'); - legendContainer.innerHTML = legendHTML; - responseDiv.appendChild(legendContainer); - }) - .catch(error => { - console.error('请求失败:', error); - document.getElementById('response').innerHTML = '获取任务失败,请重试。'; - }); + const legendContainer = document.createElement("div"); + legendContainer.innerHTML = legendHTML; + responseDiv.appendChild(legendContainer); + }) + .catch((error) => { + console.error("请求失败:", error); + document.getElementById("response").innerHTML = "获取任务失败,请重试。"; + }); }); function getToday() { const today = new Date(); const year = today.getFullYear(); - const month = String(today.getMonth() + 1).padStart(2, '0'); // 月份从0开始,需要+1 - const day = String(today.getDate()).padStart(2, '0'); + const month = String(today.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需要+1 + const day = String(today.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } - - - - - - - diff --git a/database/database.go b/database/database.go index 9e001c6..0e1bde5 100644 --- a/database/database.go +++ b/database/database.go @@ -9,7 +9,6 @@ import ( "gorm.io/driver/sqlite" "gorm.io/gorm" "zsxyww.com/scheduler/config" - "zsxyww.com/scheduler/model" ) var err error @@ -35,7 +34,7 @@ func connectSQLite() { os.Exit(1) } if config.InitDB == true { - Main.AutoMigrate(&model.Member{}, &model.Tweak{}) + //Main.AutoMigrate(&model.Member{}, &model.Tweak{}) } } @@ -45,6 +44,6 @@ func connectPGSQL() { panic(err) } if config.InitDB == true { - Main.AutoMigrate(&model.Member{}, &model.Tweak{}) + //Main.AutoMigrate(&model.Member{}, &model.Tweak{}) } } diff --git a/doc/排班算法.md b/doc/排班算法.md index 2ea4fa3..76d6638 100644 --- a/doc/排班算法.md +++ b/doc/排班算法.md @@ -8,3 +8,7 @@ 为了让实习成员熟悉每一个片区,每两周都会轮换值班每位成员负责值班的的片区,片区的轮换遵守上面的规则,也就是说,女生只会在女生片区内轮换,男生则会在全部的片区内轮换。 一个学期值班的周数不确定,但是一般不会少于12周,即可以轮换到每个成员。 +## 问题 +排班算法现在是试图平均分配成员,可能会优化为根据片区的单子数量加权分配 +对于每两周换片区的问题,我简单地对当值成员列表进行循环移位来解决,好像和目前的算法有点冲突,未来准备写一个测试来覆盖 + diff --git a/go.mod b/go.mod index ca2aef7..3310f52 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/pgx/v5 v5.7.5 github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -34,7 +34,7 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/pflag v1.0.6 github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect diff --git a/handler/assignments.go b/handler/assignments.go index 81c2aca..ede9d57 100644 --- a/handler/assignments.go +++ b/handler/assignments.go @@ -3,21 +3,17 @@ package handler import ( //"fmt" "errors" - "github.com/gocarina/gocsv" + //"github.com/gocarina/gocsv" "github.com/golang-module/carbon/v2" "github.com/labstack/echo/v4" "net/http" - "os" - "sync" + //"os" + //"sync" "zsxyww.com/scheduler/config" "zsxyww.com/scheduler/model" - "zsxyww.com/scheduler/signals" + //"zsxyww.com/scheduler/signals" ) -var data *[7][]*model.Member -var mutex sync.RWMutex //lock for data -var err error - // /api/getAssignment GET 获取当日值班表 // 接受参数date,是需要生成值班表的日期 func GetAssignment(i echo.Context) error { @@ -29,51 +25,40 @@ func GetAssignment(i echo.Context) error { arg = carbon.Parse(date) } - if (carbon.Now().ToDateString() != signals.Table.GetLastUpdated().ToDateString()) || signals.Table.IsNeedUpdate() == true { + data, err := generateTable(arg) - mutex.Lock() - data, err = generateTable(arg) - mutex.Unlock() - - if err != nil { - i.String(http.StatusInternalServerError, err.Error()) - return echo.ErrInternalServerError - } - - //signals.Table.SetUpdated(carbon.Now()) - //测试时注释掉上面的状态更新方便调试 + if err != nil { + i.String(http.StatusInternalServerError, err.Error()) + return echo.ErrInternalServerError } - mutex.RLock() - i.JSON(200, data) - mutex.RUnlock() + i.JSON(200, data) return nil } // 根据指定的时间来生成对应的值班表 func generateTable(time carbon.Carbon) (*[7][]*model.Member, error) { + table := [7][]*model.Member{} //结果放入这里 members := []*model.Member{} //包含所有成员信息的切片 today := []*model.Member{} //今天值班的人 female := []*model.Member{} //今天的女生 male := []*model.Member{} //今天的男生 week, dayOfWeek := getWorkDay(time) + //检查传入时间有没有问题 //TODO:这里好像有bug(对日期是否在值班时间内的判断部分),不过不怎么影响使用 if (week < 0) || (week > config.Default.Business.Week) { return nil, errors.New("日期错误,日期需要在本学期的值班日期内并且格式正确") } - // 为了实现更换值班的片区,写的一个闭包切片访问器 + // 切片访问函数,用来实现自动更换值班片区的功能 iter := func(array []*model.Member, i int) *model.Member { return array[(i+week)%len(array)] } - //读取csv文件 - err := readTableData(&members) - if err != nil { - return nil, err - } + members = model.MemberList + //添加标题 table[0] = append(table[0], &model.Member{Name: "凤翔", Access: 7}) table[1] = append(table[1], &model.Member{Name: "朝晖", Access: 7}) @@ -98,32 +83,32 @@ func generateTable(time carbon.Carbon) (*[7][]*model.Member, error) { } } - //为女生分配负责人 - for i := 0; i < len(female); i++ { + //将所有正式女生分配到女生片区 + for i := range female { if a := iter(female, i); a.Access < model.FRESH { //是正式成员 table[i%4] = append(table[i%4], a) //轮流分配到女生片区 a.Arranged = true } } - //为剩下的片区分配负责人 - for i := 0; i < len(male); i++ { + //将所有正式男生分配到所有片区(优先分配人少的片区) + for i := range male { if a := iter(male, i); a.Access < model.FRESH { //是正式成员 table[fewest(table)] = append(table[fewest(table)], a) a.Arranged = true } } - //分配剩下的所有女生到女生片区 - for i := 0; i < len(female); i++ { + //分配剩下的所有女生到女生片区(优先分配人少的片区) + for i := range female { if a := iter(female, i); a.Arranged != true { //还没有安排 table[fewestF(table)] = append(table[fewestF(table)], a) a.Arranged = true } } - //分配剩下的所有男生 - for i := 0; i < len(male); i++ { + //分配剩下的所有男生(优先分配人少的片区) + for i := range male { if a := iter(male, i); a.Arranged == false { //还没有安排 table[fewest(table)] = append(table[fewest(table)], a) a.Arranged = true @@ -133,24 +118,6 @@ func generateTable(time carbon.Carbon) (*[7][]*model.Member, error) { return &table, nil } -// 读取csv文件 -func readTableData(m *[]*model.Member) error { - data, err := os.OpenFile(config.Default.App.File, os.O_RDWR|os.O_CREATE, os.ModePerm) - if err != nil { - return err - } - defer data.Close() - - err = gocsv.UnmarshalFile(data, m) - if err != nil { - return err - } - //for index, member := range *m { - // fmt.Printf("%v:%v\n", index, member) // for debug concerns - //} - return nil -} - // 找出人数最少的片区 func fewest(a [7][]*model.Member) int { b := min(len(a[0]), len(a[1]), len(a[2]), len(a[3]), len(a[4]), len(a[5]), len(a[6])) diff --git a/handler/init.go b/handler/init.go new file mode 100644 index 0000000..abeebd1 --- /dev/null +++ b/handler/init.go @@ -0,0 +1 @@ +package handler diff --git a/handler/tweaks.go b/handler/tweaks.go new file mode 100644 index 0000000..abeebd1 --- /dev/null +++ b/handler/tweaks.go @@ -0,0 +1 @@ +package handler diff --git a/handler/unit/tweaks.go b/handler/unit/tweaks.go deleted file mode 100644 index a669f06..0000000 --- a/handler/unit/tweaks.go +++ /dev/null @@ -1,46 +0,0 @@ -// CRUD的基础操作 -package uo - -import ( - "zsxyww.com/scheduler/database" - "zsxyww.com/scheduler/model" -) - -// 增加一项tweak -func AddTweak(in *model.Tweak) error { - result := db.Main.Create(in) - return result.Error - -} - -// 删除一项tweak -func DeleteTweak(in *model.Tweak) error { - if db.Main.Error != nil { - return db.Main.Error - } - return nil -} - -// 查询一些tweak,通过IssueID -func GetTweakByIssueID(in *model.Tweak) (result []*model.Tweak, err error) { - if db.Main.Error != nil { - return nil, db.Main.Error - } - return nil, nil -} - -// 查询一些tweak,通过一个日期 -func GetTweakByTime(in *model.Tweak) (result []*model.Tweak, err error) { - if db.Main.Error != nil { - return nil, db.Main.Error - } - return nil, nil -} - -// 查询一些tweak,通过一个工号 -func GetTweakByID(in *model.Tweak) (result []*model.Tweak, err error) { - if db.Main.Error != nil { - return nil, db.Main.Error - } - return nil, nil -} diff --git a/main.go b/main.go index c5ee737..72edeee 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,13 @@ package main import ( "fmt" + "github.com/gocarina/gocsv" "github.com/labstack/echo/v4" "html/template" + "os" "zsxyww.com/scheduler/config" "zsxyww.com/scheduler/database" + "zsxyww.com/scheduler/model" "zsxyww.com/scheduler/route" "zsxyww.com/scheduler/templates" ) @@ -17,6 +20,7 @@ func main() { app := echo.New() register(app) + csv() listenAddress := fmt.Sprintf(":%d", config.Default.App.ListenPort) @@ -31,3 +35,17 @@ func register(app *echo.Echo) { app.Renderer = renderer } + +// 读取csv文件 +func csv() { + data, err := os.OpenFile(config.Default.App.File, os.O_RDWR|os.O_CREATE, os.ModePerm) + if err != nil { + panic(err) + } + defer data.Close() + + err = gocsv.UnmarshalFile(data, &model.MemberList) + if err != nil { + panic(err) + } +} diff --git a/model/member.go b/model/member.go index f4dcc8e..147fd6f 100644 --- a/model/member.go +++ b/model/member.go @@ -20,3 +20,5 @@ const GROUP = 3 //组长 const FORMAL = 4 //正式成员 const FRESH = 5 //实习成员 const PRE = 6 //前成员 + +var MemberList []*Member diff --git a/model/tweak.go b/model/tweak.go index cc16e28..68b5ebe 100644 --- a/model/tweak.go +++ b/model/tweak.go @@ -3,6 +3,7 @@ package model import ( "gorm.io/gorm" "time" + "zsxyww.com/scheduler/database" ) // 这个结构体是供数据库使用的表结构,换班补班蹭班的记录都会以这种方式储存 @@ -23,3 +24,40 @@ const ( OP_ADMIN_ADD = 4 OP_ADMIN_SUB = 5 ) + +// 增加一项tweak +func (t *Tweak) addTweak() error { + result := db.Main.Create(t) + return result.Error + +} + +// 删除一项tweak +func (t *Tweak) deleteTweak() error { + result := db.Main.Delete(t) + return result.Error +} + +// 查询一些tweak,通过IssueID +func (t *Tweak) getTweakByIssueID() (result []*Tweak, err error) { + if db.Main.Error != nil { + return nil, db.Main.Error + } + return nil, nil +} + +// 查询一些tweak,通过一个日期 +func (t *Tweak) getTweakByTime() (result []*Tweak, err error) { + if db.Main.Error != nil { + return nil, db.Main.Error + } + return nil, nil +} + +// 查询一些tweak,通过一个工号 +func (t *Tweak) getTweakByID() (result []*Tweak, err error) { + if db.Main.Error != nil { + return nil, db.Main.Error + } + return nil, nil +}