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
+}