用Golang写个健康打卡模板,这事儿我琢磨了好几天

说实话,一开始我觉得健康打卡这事儿特简单,不就是填个表嘛,但真让我在Golang里搞一个通用的模板,才发现里头门道挺多的,咱们今天就边想...

说实话,一开始我觉得健康打卡这事儿特简单,不就是填个表嘛,但真让我在Golang里搞一个通用的模板,才发现里头门道挺多的,咱们今天就边想边写,把我踩过的坑和一些小心得一起聊聊。

为什么非得用Golang写健康打卡?

先说说背景,前阵子公司要求每天上报健康状况,HR小姐姐天天在群里喊:“别忘了打卡啊!”结果总有人忘,或者格式不统一,有的写“良好”,有的写“正常”,搞得统计起来特别费劲,我就想,能不能写个小程序,每天自动提醒,还能统一格式?

选Golang的原因很简单:编译快、部署方便、并发性能好,你不知道,那些打卡高峰时段(比如早上九点),服务器压力真挺大的,Go的goroutine天然适合处理这种并发请求。

先搭个数据结构

万事开头难,我先把打卡数据的样子想清楚,一个标准的健康打卡记录,至少得有这些信息:

用Golang写个健康打卡模板,这事儿我琢磨了好几天

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()函数统一处理。

实际部署踩的坑

说几个真实遇到的坑,希望对你有用:

  1. 时区问题:服务器默认UTC,打卡时间是东八区,我一开始没注意,结果统计全是错的,解决方案是设置全局时区:

    os.Setenv("TZ", "Asia/Shanghai")
  2. 并发写入:多个员工同时打卡时,SQLite会报database is locked,解决办法是加个写入锁,或者干脆用PostgreSQL。

  3. 模板缓存:每次生成模板都解析太慢了,我做了个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

(15)

文章推荐

发表回复

本站作者才能评论

评论列表(4条)

  • kyadmin
    kyadmin 2026-07-01

    我是ac米兰官网的签约作者“kyadmin”!

  • kyadmin
    kyadmin 2026-07-01

    希望本篇文章《用Golang写个健康打卡模板,这事儿我琢磨了好几天》能对你有所帮助!

  • kyadmin
    kyadmin 2026-07-01

    本站[ac米兰官网]内容主要涵盖:AC米兰,ac米兰中文,AC米兰官网

  • kyadmin
    kyadmin 2026-07-01

    本文概览:说实话,一开始我觉得健康打卡这事儿特简单,不就是填个表嘛,但真让我在Golang里搞一个通用的模板,才发现里头门道挺多的,咱们今天就边想...

    联系我们

    工作时间:周一至周五,9:30-18:30,节假日休息

    关注我们