说实话,一开始我觉得健康打卡这事儿特简单,不就是填个表嘛,但真让我在Golang里搞一个通用的模板,才发现里头门道挺多的,咱们今天就边想边写,把我踩过的坑和一些小心得一起聊聊。
为什么非得用Golang写健康打卡?
先说说背景,前阵子公司要求每天上报健康状况,HR小姐姐天天在群里喊:“别忘了打卡啊!”结果总有人忘,或者格式不统一,有的写“良好”,有的写“正常”,搞得统计起来特别费劲,我就想,能不能写个小程序,每天自动提醒,还能统一格式?
选Golang的原因很简单:编译快、部署方便、并发性能好,你不知道,那些打卡高峰时段(比如早上九点),服务器压力真挺大的,Go的goroutine天然适合处理这种并发请求。
先搭个数据结构
万事开头难,我先把打卡数据的样子想清楚,一个标准的健康打卡记录,至少得有这些信息:

type HealthCheck struct {
UserID string // 工号
UserName string // 姓名
Department string // 部门
Temperature float64 // 体温
IsHealthy bool // 是否健康
Symptoms []string // 症状列表(咳嗽、发烧等)
CheckTime time.Time // 打卡时间
Location string // 打卡地点
Remark string // 备注
}
这里有个小细节我吃了亏——体温用float64存的时候,数据库里查出来可能会有0.0000001的误差,后来改成存整数的“毫度”,比如36.5度存成36500,计算和比较都准确多了。
核心功能的实现
打卡模板生成器
关键是要生成一个灵活的模板,不能写死,我用的是text/template包,它可以动态渲染内容:
import (
"text/template"
"strings"
)
type HealthTemplate struct {
CompanyName string
Fields []TemplateField
}
type TemplateField struct {
Label string // 字段中文名
Key string // 字段英文键
Required bool // 是否必填
Type string // 字段类型:text/radio/checkbox
}
func GenerateTemplate(company string) string {
tmplName := "health_check_template"
tmplStr := `{{.CompanyName}} - 健康打卡
{{range .Fields}}{{if .Required}}*{{end}}{{.Label}}:
{{if eq .Type "radio"}} ○ 是 ○ 否
{{else if eq .Type "checkbox"}} □ 选项1 □ 选项2
{{else}} [填写]
{{end}}
{{end}}`
tmpl, _ := template.New(tmplName).Parse(tmplStr)
var buf strings.Builder
tmpl.Execute(&buf, HealthTemplate{
CompanyName: company,
Fields: []TemplateField{
{Label: "体温", Key: "temperature", Required: true, Type: "text"},
{Label: "有无咳嗽", Key: "cough", Required: true, Type: "radio"},
{Label: "近期接触史", Key: "contact", Required: false, Type: "checkbox"},
},
})
return buf.String()
}
这个生成器的好处是,不同部门可以定义自己的模板,比如生产车间可能要多加“是否接触化学物品”,而办公室可能关注“是否离开本地”——模板一变,程序不用动。
打卡记录持久化
我用的是最轻量的SQLite(通过mattn/go-sqlite3),因为小团队用不上MySQL,一个文件搞定,建表语句长这样:
CREATE TABLE IF NOT EXISTS health_checks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
user_name TEXT NOT NULL,
department TEXT,
temperature REAL,
is_healthy INTEGER DEFAULT 1,
symptoms TEXT, -- JSON数组存症状
check_time DATETIME DEFAULT CURRENT_TIMESTAMP,
location TEXT,
remark TEXT,
UNIQUE(user_id, DATE(check_time)) -- 每人每天只能打一次
);
注意那个UNIQUE约束,防止同一个人重复打卡,虽然前端可以判断,但数据库层面也得做,这叫安全冗余。
自动提醒功能
我知道有些人老是忘打卡,就用Go的time.Timer写了个提醒服务:
type ReminderService struct {
db *sql.DB
clients map[string]chan string // 用channel做通知
}
func (r *ReminderService) Start() {
ticker := time.NewTicker(30 * time.Minute) // 每半小时检查一次
go func() {
for range ticker.C {
now := time.Now()
if now.Hour() == 8 { // 早上8点
// 查今天没打卡的人
rows, _ := r.db.Query(`
SELECT user_id, user_name FROM users
WHERE user_id NOT IN (
SELECT user_id FROM health_checks
WHERE DATE(check_time) = DATE('now')
)
`)
// 发送提醒...
}
}
}()
}
这个代码在实际跑的时候,我发现一个问题:如果程序重启,所有定时器就重置了,后来改成把待提醒名单存到数据库,启动时重新加载。
数据统计展示
打卡不是目的,管理才是,所以我用Go写了个简单的统计接口:
| 部门 | 应打卡 | 已打卡 | 未打卡 | 异常人数 | 打卡率 |
|---|---|---|---|---|---|
| 研发部 | 50 | 48 | 2 | 0 | 96% |
| 市场部 | 30 | 25 | 5 | 1 | 83% |
| 行政部 | 15 | 15 | 0 | 0 | 100% |
这个表格是动态生成的,关键代码思路是:
type DeptStat struct {
DeptName string
Total int
Checked int
Abnormal int
}
func GetDeptStats(db *sql.DB, date string) []DeptStat {
rows, _ := db.Query(`
SELECT
d.department,
COUNT(DISTINCT u.user_id) as total,
COUNT(DISTINCT hc.user_id) as checked,
SUM(CASE WHEN hc.is_healthy = 0 THEN 1 ELSE 0 END) as abnormal
FROM users u
LEFT JOIN health_checks hc ON u.user_id = hc.user_id
AND DATE(hc.check_time) = ?
GROUP BY d.department
`, date)
// 解析结果...
}
统计这块我折腾最久,因为LEFT JOIN和日期比较的细节容易出错,比如跨天问题——如果打卡时间是2027-11-20 03:00,但统计数据按自然日算,得用DATE()函数统一处理。
实际部署踩的坑
说几个真实遇到的坑,希望对你有用:
-
时区问题:服务器默认UTC,打卡时间是东八区,我一开始没注意,结果统计全是错的,解决方案是设置全局时区:
os.Setenv("TZ", "Asia/Shanghai") -
并发写入:多个员工同时打卡时,SQLite会报
database is locked,解决办法是加个写入锁,或者干脆用PostgreSQL。 -
模板缓存:每次生成模板都解析太慢了,我做了个
sync.Map来缓存编译好的模板:var templateCache sync.Map func GetCompiledTemplate(name, content string) *template.Template { if tmpl, ok := templateCache.Load(name); ok { return tmpl.(*template.Template) } tmpl := template.Must(template.New(name).Parse(content)) templateCache.Store(name, tmpl) return tmpl }
更灵活的扩展思路
这个模板不止能做健康打卡,换个数据字段,它就是:
- 出差申请单
- 日报填写表
- 设备巡检记录
- 客户反馈表
核心思想是一样的:定义数据结构 -> 生成模板 -> 收集数据 -> 统计分析,Go的强类型和编译特性保证了数据不会乱,而模板引擎给了业务人员配置的自由。
去年给一个300人的公司装了这个系统,跑了一年多没出过大问题,唯一的小故障是有人凌晨三点打卡,统计报表里显示的是前一天——那时区的问题,后来加了+8:00偏移就解决了。
所以你看,一个看似简单的健康打卡,真要认真做起来,要考虑的东西还挺多,但用Golang一点点搭起来的感觉,就像拼积木一样,越来越顺手,如果你也打算自己写一个,别怕代码不完美,先跑起来再说——跑起来才能迭代。
有个同事说:“你这模板比我女朋友还体贴,每天定时提醒我打卡。” 我笑了笑没说话,心里想的是,下个版本再加个“多语言支持”功能,毕竟公司有不少外籍同事。
本文来自作者[kyadmin]投稿,不代表ac米兰官网立场,如若转载,请注明出处:http://www.milanatour.com/jiankang/858.html
评论列表(4条)
我是ac米兰官网的签约作者“kyadmin”!
希望本篇文章《用Golang写个健康打卡模板,这事儿我琢磨了好几天》能对你有所帮助!
本站[ac米兰官网]内容主要涵盖:AC米兰,ac米兰中文,AC米兰官网
本文概览:说实话,一开始我觉得健康打卡这事儿特简单,不就是填个表嘛,但真让我在Golang里搞一个通用的模板,才发现里头门道挺多的,咱们今天就边想...