目录
  • 练习要求:
  • 考察点:
  • 编码:
  • README.md
  • 附:使用Golang导出CSV数据并解决数据乱码问题
    • CSV 格式
    • 实现方式
    • golang实现csv数据写文件
    • golang实现web导出csv数据
  • 总结

    练习要求:

    写一个小程序解析data.csv,要求实现如下功能:

    1. 接收姓名作为参数。
    2. 根据姓名查找出对应员工的工时信息,并将 日期、上班、下班、工时 打印到标准输出。
    3. 将上一条输出的内容保存到json文件,使用姓名.json作为文件名
    4. 根据上条中生成的json文件,计算出该员工的月总工时、每周的平均工时。

    考察点:

    1. 结构体定义
    2. 字符串拼接
    3. 类型转换
    4. 编码转换
    5. 命令行参数解析
    6. 文件读取
    7. json库使用

    编码:

    package main
     
    import (
        "bufio"
        "encoding/json"
        "errors"
        "flag"
        "fmt"
        "io"
        "os"
        "strconv"
        "strings"
     
        "github.com/axgle/mahonia"
    )
     
    //给 fmt.Println 起一个短的别名。
    var p = fmt.Println
     
    //定义一个全局变量 一个月上班加休息总天数
    var gAllDays float64 = 0
     
    //定义一个全局变量 考勤异常的天数
    var gAbnormalDays int = 0
     
    //上班信息
    type WorkInfo struct {
        WorkDate  string //上班日期
        StartTime string //上班打卡时间
        EndTime   string //下班打卡时间
        LaborHour string //当天工时
    }
     
    //考勤异常信息
    type WorkAbnormalInfo struct {
        WorkDate   string //上班日期
        NormalInfo string //异常信息
    }
     
    /**
     * @brief  把当前字符串按照指定方式进行编码
     * @param[in]       src                   待进行转码的字符串
     * @param[in]       srcCode               字符串当前编码
     * @param[in]       tagCode               要转换的编码
     * @return   进行转换后的字符串
     */
    func ConvertToString(src string, srcCode string, tagCode string) (string, error) {
        if len(src) == 0 || len(srcCode) == 0 || len(tagCode) == 0 {
            return "", errors.New("input arguments error")
        }
        srcCoder := mahonia.NewDecoder(srcCode)
        srcResult := srcCoder.ConvertString(src)
        tagCoder := mahonia.NewDecoder(tagCode)
        _, cdata, _ := tagCoder.Translate([]byte(srcResult), true)
        result := string(cdata)
     
        return result, nil
    }
     
    /**
     * @brief            写入数据到指定名字的文件中
     * @param[in]       buf                    待写入的数据内容
     * @param[in]       name                文件名字
     * @return   成功返回nil 失败返回error    错误信息
     */
    func WriteFile(name string, buf string) error {
        if len(name) == 0 || len(buf) == 0 {
            return errors.New("input arguments error")
        }
     
        fout, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0666)
        defer fout.Close()
        if err != nil {
            return err
        }
     
        //写入到本地文件中
        fout.WriteString(buf)
     
        return nil
    }
     
    /**
     * @brief  读取文件
     * @param[in]       name            文件名(可以加路径)
     * @return   成功返回 文件内容,失败返回error    错误信息
     */
    func ReadFile(name string) ([]byte, error) {
        if len(name) == 0 {
            return nil, errors.New("input arguments error")
        }
     
        //打开本地文件 读取出全部数据
        fin, err := os.Open(name)
        defer fin.Close()
        if err != nil {
            return nil, errors.New("Close error")
        }
     
        buf_len, _ := fin.Seek(0, os.SEEK_END)
        fin.Seek(0, os.SEEK_SET)
     
        buf := make([]byte, buf_len)
        fin.Read(buf)
     
        return buf, nil
    }
     
    /**
     * @brief  读取csv文件并打印指定员工信息
     * @param[in]       csvName            csv文件名(可以加路径)
     * @param[in]       employeeName        员工名字
     * @return   成功返回 员工结构体信息,失败返回error    错误信息
     */
    func ReadCsvFile(csvName string, employeeName string) ([]WorkInfo, error) {
        if len(csvName) == 0 || len(employeeName) == 0 {
            return nil, errors.New("error: input arguments error")
        }
     
        var WorkInfoSet []WorkInfo
        var AbnormalSet []WorkAbnormalInfo
        var isExistName bool
        var dayCount float64 = 0
        var isNormal string
        var isNormalFlag bool
     
        var index int = 0
        var indexWorkDate int
        var indexStartTime int
        var indexEndTime int
        var indexLaborHour int
        var indexNormalInfo int
        var indexIsNormal int
     
        var i int = 0
     
        f, err := os.Open(csvName)
        if err != nil {
            return nil, err
        }
        defer f.Close()
     
        rd := bufio.NewReader(f)
        for {
            gbk_line, err := rd.ReadString('\n') //以'\n'为结束符读入一行
            if err != nil || io.EOF == err {
                break
            }
            //p("gbk:", gbk_line)
     
            //把每一行gbk格式的字符串 转换为 utf-8格式字符串
            utf8_line, _ := ConvertToString(gbk_line, "gbk", "utf-8")
     
            //对第一行进行处理
            if i == 0 {
                i = 1 //保证 只有第一行被处理
                p("utf8:", utf8_line)
                first_line := strings.Split(utf8_line, ",")
     
                for _, val := range first_line {
     
                    if val == "日期" {
                        indexWorkDate = index
                    }
                    if val == "上班" {
                        indexStartTime = index
                    }
                    if val == "下班" {
                        indexEndTime = index
                    }
                    if val == "工时" {
                        indexLaborHour = index
                    }
                    if val == "是否有考勤异常" {
                        indexIsNormal = index
                    }
                    if val == "工时异常" {
                        indexNormalInfo = index
                    }
     
                    index++
                }
            }
     
            if strings.Contains(utf8_line, employeeName) {
                //把存在员工标记为true
                isExistName = true
     
                split_line := strings.Split(utf8_line, ",")
                person_temp := WorkInfo{split_line[indexWorkDate],
                    split_line[indexStartTime],
                    split_line[indexEndTime],
                    split_line[indexLaborHour],
                }
     
                //考勤表天数加1
                dayCount++
                isNormal = split_line[indexIsNormal]
                //统计打卡异常的信息
                if isNormal == "是" {
                    aInfo := WorkAbnormalInfo{split_line[indexWorkDate], split_line[indexNormalInfo]}
                    AbnormalSet = append(AbnormalSet, aInfo)
     
                    gAbnormalDays++
                    isNormalFlag = true
                }
     
                WorkInfoSet = append(WorkInfoSet, person_temp)
            }
        }
     
        //统计考勤表里所有天数
        gAllDays = dayCount
        //对于不存在指定员工名字 的处理
        if !isExistName {
            p("\nRemind: There is no employee is csv file!\n")
            os.Exit(1)
        }
     
        //显示员工所有考勤信息
        p("\n员工姓名:", employeeName)
        p("\n全部考勤信息:")
        for _, temp := range WorkInfoSet {
            fmt.Printf(
                "日期:%s ,上班:%s,下班:%s,工时:%s\n",
                temp.WorkDate,
                temp.StartTime,
                temp.EndTime,
                temp.LaborHour,
            )
        }
     
        //显示员工打卡异常信息
        if isNormalFlag {
            p("\n异常考勤信息:")
            for _, val := range AbnormalSet {
                fmt.Printf("日期:%s , 异常信息:%s\n", val.WorkDate, val.NormalInfo)
            }
            p("温馨提示:考勤出现异常信息,请及时给助理说明情况~_~\n")
        }
     
        return WorkInfoSet, nil
    }
     
    /**
    * @brief  写入json文件
    * @param[in]       employeeName        员工名字
    * @param[in]       workInfoSet            员工结构体信息
    * @return   成功返回 nil,失败返回error    错误信息
     */
    func WriteJsonFile(employeeName string, workInfoSet []WorkInfo) error {
        if len(employeeName) == 0 || workInfoSet == nil {
            return errors.New("error: input arguments error")
        }
     
        //把输出内容写入name.json文件中
        filename := fmt.Sprintf("%s%s", employeeName, ".json")
        str, _ := json.Marshal(workInfoSet)
        err := WriteFile(string(str), filename)
        if err != nil {
            return err
        }
        return nil
    }
     
    /**
    * @brief  读取json文件
    * @param[in]       employeeName        员工名字
    * @return   成功返回 nil,失败返回error    错误信息
     */
    func ReadJsonFile(employeeName string) error {
        if len(employeeName) == 0 {
            return errors.New("error: input arguments error")
        }
     
        var WorkInfoSet []WorkInfo
        filename := fmt.Sprintf("%s%s", employeeName, ".json")
     
        ReadJsonBuf, err := ReadFile(filename)
        if err != nil {
            p(err.Error())
            return err
        }
        var sumHour float64 = 0.0
        var dayCount float64 = 0
        var weekCounts float64 = 0.0
        var averageWeekHour float64 = 0.0
     
        json.Unmarshal(ReadJsonBuf, &WorkInfoSet)
     
        for _, one_work := range WorkInfoSet {
     
            //去掉打卡异常情况和周六末情况 (如果周六末加班 数据依然计算进入总工时)
            if one_work.StartTime == "" || one_work.EndTime == "" {
                continue
            }
     
            one_day_hour, _ := strconv.ParseFloat(one_work.LaborHour, 64)
            sumHour += one_day_hour
            dayCount++
        }
     
        fmt.Printf("根据json文件计算工时,考勤正常天数:%2.0f, 异常天数:%d\n", dayCount, gAbnormalDays)
        weekCounts = gAllDays / 7
        averageWeekHour = sumHour / weekCounts
        //p("考勤表总天数:", gAllDays, ",共多少周:", week_counts)
     
        fmt.Printf("月总工时:%.4f 每周的平均工时:%.4f\n\n", sumHour, averageWeekHour)
     
        return nil
    }
     
    func main() {
        args := os.Args
     
        input := flag.String("i", "查无此人", "input employee name")
        path := flag.String("p", "./data.csv", "input csv file path")
     
        flag.Parse()
     
        if len(args) == 1 {
            fmt.Println("./main: missing operand")
            fmt.Println("Try `./main -h' or './main --help' for more information.")
            return
        }
     
        var csvName string = *path
        var employeeName string = *input
     
        //读取csv文件并打印指定员工信息
        WorkInfoSet, err := ReadCsvFile(csvName, employeeName)
        if err != nil {
            p(err.Error())
            return
        }
     
        //把指定员工信息写入json文件
        err = WriteJsonFile(employeeName, WorkInfoSet)
        if err != nil {
            p(err.Error())
            return
        }
     
        //读取json文件并计算指定员工总工时和平均工时
        err = ReadJsonFile(employeeName)
        if err != nil {
            p(err.Error())
            return
        }
    }

    README.md

    – USAGE: Analysis csv file command [arguments] …

    – The commands are:
    –    -h , –help    cmd help.

    – The commands are:
    –    -i  input employee name.

    – The commands are:
    –    -p  input csv file path.

    -当文件中不存在指定员工名字时,返回提醒信息

    -参考链接:
    – Golang GBK转UTF-8 参考链接:https://blog.csdn.net/qq_33285730/article/details/73239263
    – golang 文件按行读取:https://studygolang.com/articles/282
    – golang strings包方法:https://studygolang.com/articles/2881

    附:使用Golang导出CSV数据并解决数据乱码问题

    在日常开发中,针对数据导出,我们可以导出Excel格式,但是如果是针对大数据量的导出,直接导出为Excel格式可能需要占用大量内存,且导出速度很慢。这个时候我们就需要导出为CSV格式。

    CSV 格式

    CSV本质上是文本文件,该文件有以下要求:

    • 列之间用逗号分隔,行之间用换行分隔
    • 单元格如果有逗号、引号之类的字符,该单元格需要使用双引号括起来
    • 如果内容包含中文,直接输出可能会乱码

    实现方式

    golang 官方有csv的库,可以很容易的实现csv数据的写入。

    golang实现csv数据写文件

    func main() {
    	f, err := os.Create("data.csv")
    	if err != nil {
    		panic(err)
    	}
    	defer f.Close()
    
    	f.WriteString("\xEF\xBB\xBF") // 写入UTF-8 BOM,避免使用Microsoft Excel打开乱码
    
    	writer := csv.NewWriter(f)
    	writer.Write([]string{"编号", "姓名", "年龄"})
    	writer.Write([]string{"1", "张三", "23"})
    	writer.Write([]string{"2", "李四", "24"})
    	writer.Write([]string{"3", "王五", "25"})
    	writer.Write([]string{"4", "赵六", "26"})
    	writer.Flush() // 此时才会将缓冲区数据写入
    }

    golang实现web导出csv数据

    此处以gin框架为例,如果用的go官方web库,其实差不多是一样的:

    func ExportCsv(c *gin.Context) {
    	bytesBuffer := &bytes.Buffer{}
    	bytesBuffer.WriteString("\xEF\xBB\xBF") // 写入UTF-8 BOM,避免使用Microsoft Excel打开乱码
    
    	writer := csv.NewWriter(bytesBuffer)
    	writer.Write([]string{"编号", "姓名", "年龄"})
    	writer.Write([]string{"1", "张三", "23"})
    	writer.Write([]string{"2", "李四", "24"})
    	writer.Write([]string{"3", "王五", "25"})
    	writer.Write([]string{"4", "赵六", "26"})
    
    	writer.Flush() // 此时才会将缓冲区数据写入
    
    	// 设置下载的文件名
    	c.Writer.Header().Set("Content-Disposition", "attachment;filename=data.csv")
    	// 设置文件类型以及输出数据
    	c.Data(http.StatusOK, "text/csv", bytesBuffer.Bytes())
    	return
    }

    总结

    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。