Informática de servicios (4) -desarrollo de paquetes, lea el archivo de configuración simple v1

1. Información general

El archivo de configuración (Archivo de configuración, CF) es un archivo de texto que configura los parámetros y las configuraciones iniciales para un sistema o programa informático. El archivo de configuración tradicional es una línea de texto, en todas partes en los sistemas Unix utilice normalmente .conf, .config, .cfgcomo un sufijo, y poco a poco formaron una key = valuehábitos de configuración. sectionSoporte agregado para sistemas Windows , generalmente usado .inicomo sufijo. Con el auge de los lenguajes orientados a objetos, los programadores necesitan deserializar directamente el texto en objetos de memoria como configuración y proponer gradualmente algunos nuevos formatos de archivo de configuración, incluidos JSON, YAML, TOML, etc.

2. Tareas del curso

objetivo de la misión

  1. Familiarizado con hábitos de programación (modismos) y estilos (convenciones)
  2. Familiarizado con el funcionamiento de la biblioteca io
  3. Utilice un enfoque basado en pruebas
  4. Uso del programa Simple Go
  5. Notificación de eventos

Contenido de la tarea

Publique un paquete de lectura de archivos de configuración en Gitee o GitHub. La primera versión solo necesita leer la configuración ini. Ejemplos de formato de archivo de configuración:

# possible values : production, development
app_mode = development

[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
data = /home/git/grafana

[server]
# Protocol (http or https)
protocol = http

# The http port  to use
http_port = 9999

# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
enforce_domain = true

Requisitos de la tarea

Inserte la descripción de la imagen aquí

paquete de lectura y escritura ini , paquete de solución de integración de configuración de lectura Viper , paquete de notificación del sistema de archivos fsnotify

misión cumplida

Cree un nuevo paquete readini, que implemente funciones Watch(filename,listener) (configuration, error).

En primer lugar, debemos diseñar la estructura de datos correspondiente, de acuerdo con los requisitos de la tarea, podemos diseñar de esta manera.

// Section下面的键值对
type Element map[string]string

// ini文件结构(对象)
// Object为各个Section所对应的所有键值对
type Configuration map[string][]Element

// Listener接口
type Listener interface {
    
    
	Listen(inifile string)
}

Al mismo tiempo, los sistemas Unix lo utilizan #como línea de comentarios de forma predeterminada, y los sistemas Windows lo utilizan ;como línea de comentarios de forma predeterminada . Por lo tanto, se necesita una función de inicio para inicializar el símbolo de comentario del sistema actual.

// 当前系统下的注释符
var CommentSymbol byte

// 通过确定当前操作系统,从而确定相应的注释符
func Init() {
    
    
	sysType := runtime.GOOS
	if sysType == "linux" {
    
    
		CommentSymbol = '#'
	} 
 	if sysType == "windows" {
    
    
		CommentSymbol = ';'
	}
}

Entonces puedes diseñar la función Watch.

// 监听自函数运行以来发生的一次配置文件变化并返回最新的配置文件解析内容。
func Watch(filename string, listener Listener) (Configuration, error) {
    
    
	listener.Listen(filename)
	i := make(Configuration)
	var e error = nil
	f, err := os.Open(filename)
	if err != nil {
    
    
		e = errors.New("Open file faild.")
		return i, e
	}
	defer f.Close()
	// 将ini文件转换成一个bufio
	r := bufio.NewReader(f)
	// 当前所解析到的section
	section := ""
	for {
    
    
		// 以'\n'作为结束符读入一行
		line, err := r.ReadString('\n')
		if err == io.EOF {
    
    
			break
		}
		if err != nil {
    
    
			e = errors.New("Read file faild.")
			break
		}
		// 删除行两端的空白字符
		line = strings.TrimSpace(line)
		// 解析一行中的内容
		
		// 空行则跳过
		if line == "" {
    
    
			continue
		}
		// 以符号CommentSymbol作为注释
		if line[0] == CommentSymbol {
    
    
			continue
		}
		length := len(line)
		// 匹配字符串
		if line[0] == '[' && line[length-1] == ']' {
    
     // section
			section = line[1 : length-1]
			// 如果map中没有这个section,添加进来
			if _, ok := i[section]; !ok {
    
    
				i[section] = []Element{
    
    }
			}
		}else {
    
     // 键值对数据
			// 分割字符串
			s := strings.Split(line, "=")
			if len(s) < 2 {
    
    
				e = errors.New("Incorrect key-value pair format.")
				break
			}
			key := strings.TrimSpace(s[0])
			value := strings.TrimSpace(s[1])
			element := make(Element)
			element[key] = value
			// 把键值对添加进section里面
			if section == "" {
    
    
				i[section] = []Element{
    
    }
			}
			if _, ok := i[section]; ok {
    
    
				i[section] = append(i[section], element)
			}
		}
	}
	return i, e
}

Primero, escuche los cambios en el archivo de configuración a través del oyente (mi prueba de seguimiento usa el método de bloqueo de giro). Una vez que se detecta el cambio, salta inmediatamente del bloqueo de giro, analiza el par clave-valor de la sección y finalmente devuelve el contenido analizado Configuración y error de error. Al mismo tiempo, el error de error personalizado utiliza la función de errorsla biblioteca New(string)para determinar el mensaje de error correspondiente.

Desde entonces, se ha completado un paquete readini simple, que proporciona una función Watch(filename,listener) (configuration, error).

Finalmente ejecute la instrucción go build github.com/github-user/test/readini, y otros programas pueden import "github.com/github-user/test/readini"importar este paquete de alguna manera.

prueba

readini bag, eran solo dos funciones, Initcon Watch, respectivamente, para escribir una función de prueba para dos funciones.

func ExampleInit() {
    
    
	Init()
	fmt.Printf("%c\n",CommentSymbol)
	//Output:#
}
type ListenFunc func(string)

func (l ListenFunc) Listen(inifile string) {
    
    
	l(string(inifile))
}

func ExampleWatch() {
    
    
	var lis ListenFunc = func(inifile string) {
    
    
		before_info, err := os.Lstat(inifile)
		if err != nil {
    
    
			panic(err)
		}
		for {
    
    
			after_info, err := os.Lstat(inifile)
			if err != nil {
    
    
				panic(err)
			}
			if !before_info.ModTime().Equal(after_info.ModTime()) {
    
    
				break
			}
			time.Sleep(time.Duration(1)*time.Second)
		}
	}
	go func(){
    
    
		conf, err := Watch("example.ini", lis)
		if err != nil {
    
    
			fmt.Println(err)
		}
		for s, _ := range conf {
    
    
			fmt.Println("Section: ", s)
			for _, value := range conf[s] {
    
    
				for k, v := range value {
    
    
					fmt.Println("Key:", k, "\tValue:", v)
				}
			}
			fmt.Println()
		}
	}()
	file,openErr:=os.Create("example.ini")
	defer os.Remove("example.ini")
	defer file.Close()
	if openErr!=nil {
    
    
		panic(openErr)
	}
	time.Sleep(time.Duration(1)*time.Second)
	writer:=bufio.NewWriter(file)
	_,errWrite := writer.Write([]byte("[test]\n"))
	_,errWrite = writer.Write([]byte("value1 = 123\n"))
	_,errWrite = writer.Write([]byte("value2 = 222\n"))
	if errWrite!=nil {
    
    
		os.Exit(0)
	}
	writer.Flush()
	time.Sleep(time.Duration(2)*time.Second)
/*Output: 
Section:  test
Key: value1 	Value: 123
Key: value2 	Value: 222
*/
}

En ExampleWatch, la idea principal es ejecutar la función Watch a través de un programa go e imprimir la Configuración devuelta. La razón para usar el programa go es porque listen es una implementación de un bloqueo de giro. Si necesita actualizar el archivo afuera para activarlo, no puede bloquear el hilo principal.

Lo más importante a lo que hay que prestar atención aquí es el uso de la función de retardo para garantizar el orden de ejecución de las declaraciones y evitar que las operaciones de escritura de archivos posteriores se ejecuten demasiado rápido, haciendo que la actualización sea indetectable en el programa go. Yo también sufrí un poco aquí

Inserte la descripción de la imagen aquí

Después de las pruebas unitarias, debería ser el turno de las pruebas de integración. Pero debido a que solo tengo dos funciones aquí, y la función Init es un poco más simple que la función Watch. Incluso la prueba posterior a la integración no es muy diferente de la prueba de la función Watch separada, por lo que no estoy escribiendo aquí la integración posterior. En la prueba funcional de seguimiento, puede ver el efecto después de la integración.

Porque lo que queremos diseñar aquí es un paquete readini, ya que es un paquete, debería poder ser importado en otros paquetes mediante importación.

Por lo tanto, main.gose crea uno adicional para probar la llamada a esta función de paquete.

package main

// 主函数
import (
	"fmt"
	"os"
	"time"
	"bufio"
	"github.com/github-user/test/readini"
)

type ListenFunc func(string)

func (l ListenFunc) Listen(inifile string) {
    
    
	l(inifile)
}

func main() {
    
    
	var lis ListenFunc = func(inifile string) {
    
    
		before_info, err := os.Lstat(inifile)
		if err != nil {
    
    
			panic(err)
		}
		for {
    
    
			after_info, err := os.Lstat(inifile)
			if err != nil {
    
    
				panic(err)
			}
			if !before_info.ModTime().Equal(after_info.ModTime()) {
    
    
				fmt.Println("There are something changed in file ", inifile)
				break
			}
			fmt.Println("Listening changes in file ", inifile)
			time.Sleep(time.Duration(1)*time.Second)
		}
	}
	go func(){
    
    
		readini.Init()
		for {
    
    
			conf, err := readini.Watch("../111.ini", lis)
			if err != nil {
    
    
				fmt.Println(err)
			}
			for s, _ := range conf {
    
    
				fmt.Println("Section: ", s)
				for _, value := range conf[s] {
    
    
					for k, v := range value {
    
    
						fmt.Println("Key:", k, "\tValue:", v)
					}
				}
				fmt.Println()
			}
		}
	}()
	time.Sleep(time.Duration(3)*time.Second)
	i := 0
	file,openErr := os.OpenFile("../111.ini", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if openErr != nil {
    
    
		panic(openErr)
	}
	writer := bufio.NewWriter(file)
	_, errWrite := writer.Write([]byte("[test]\n"))
	if errWrite != nil {
    
    
		panic(errWrite)
	}
	writer.Flush()
	for {
    
    
		_, errWrite = writer.Write([]byte(fmt.Sprintf("Test_key%d = Test_value%d\n", i, i)))
		i += 1
		if errWrite != nil {
    
    
			panic(errWrite)
		}
		writer.Flush()
		time.Sleep(time.Duration(3)*time.Second)
	}
}

Después de ejecutar las go build github.com/github-user/test/readiniinstrucciones antes, el programa aún se ejecuta sin problemas.

De manera similar a la prueba de la función Watch, aquí creamos un programa go, después de la inicialización de Init, continuamos ejecutando la función Watch y generamos el contenido de análisis más reciente una vez que se monitorean los cambios del archivo de configuración.

Al mismo tiempo, en el hilo principal, se escribe un nuevo par clave-valor cada 3 segundos. Por lo tanto, después de ejecutar la instrucción go run main.go, lo que podemos ver es que se detecta un cambio en el archivo de configuración aproximadamente cada 3 segundos y se emite la última salida. Analiza el contenido.

Inserte la descripción de la imagen aquí

Desde entonces, la función está casi cumplida.

Generar documentación de API

Primero go get golang.org/x/tools/cmd/godocinstale godoc siguiendo las instrucciones . Debido a que este comando se descarga del sitio web oficial, es muy probable que se agote el tiempo de acceso. En este momento, primero puede conectarse al agente del módulo Go en China , es decir, ejecutar el comando export GO111MODULE=ony, a su vez export GOPROXY=https://goproxy.cn, y luego ejecutar el go get (mejor no Cuando se escribe en el ~/.profilearchivo, el módulo Go que no continúa puede tener de repente una serie de errores indescriptibles, he pisado el foso ).

Luego ejecute el comando go build golang.org/x/tools/cmd/godoc, y luego ejecútelo godoc, puede acceder a él a través de http: // localhost: 6060 / en el navegador , y puede encontrar la ruta correspondiente. (Mis palabras son http: // localhost: 6060 / pkg / github.com / github-user / test / readini /)

Inserte la descripción de la imagen aquí

Finalmente, si guarda el documento, es una instrucción godoc -url "http://localhost:6060/pkg/github.com/github-user/test/readini/" > api.html.

3. Envío de tareas

Mi código completo

Supongo que te gusta

Origin blog.csdn.net/qq_43278234/article/details/109143078
Recomendado
Clasificación