目录


练习要求:

data.csv
日期上班下班工时姓名.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