Desarrollo de proyectos Golang Devops (1)

1.1 Conceptos básicos del idioma GO

1 Conociendo el idioma Go por primera vez

1.1.1 Construcción del entorno de desarrollo

Documento de referencia: "Construir el entorno de idioma de Windows Go"

1.2.1 Funciones de idioma de Go - Recolección de basura

a. La memoria se recupera automáticamente y ya no es necesario que los desarrolladores administren la memoria

b. Los desarrolladores se enfocan en la implementación comercial, reduciendo la carga mental

c. Solo se necesita nueva para asignar memoria, no es necesario liberar

D. recolección de basura gc

1.2.2 Características del lenguaje Go: simultaneidad natural

a. Soporte de concurrencia a nivel de idioma, muy simple

b. Goroutine, subproceso ligero, es posible crear miles de goroutines

c. Implementación basada en el modelo CSP (Communicating Sequential Process)

package main

import(
	"time"
)

func main() {

	for i := 0; i < 100; i++ {
		go test_goroute(i)
	}

	time.Sleep(time.Second)
}

Lectura adicional: "Problemas de interbloqueo y simultaneidad del canal GO" https://www.cnblogs.com/show58/p/12699083.html

"Implementación del modelo de concurrencia CSP de Go: M, P, G" https://www.cnblogs.com/sunsky303/p/9115530.html

1.2.3 Ir a la función de idioma - canal

a. Pipeline, similar a pipe en unix/linux

b. Comunicación entre múltiples gorutinas a través de canales

C. Admite cualquier tipo

func main() {

    pipe := make(chan int, 3)

    pipe <- 1

    pipe <- 2

}
package main

import "fmt"

func test_pipe() {
	pipe := make(chan int, 3)
	pipe <- 1
	pipe <- 2
	pipe <- 3
	var t1 int
	t1 = <-pipe
	fmt.Println("t1: ", t1)

}

func sum(s []int, c chan int) {
	test_pipe()
	sum := 0
	for _, v := range s {
		sum += v
	}
	fmt.Println("sum:", sum)
	c <- sum // send sum to c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int)
	go sum(s[:len(s)/2], c) // 7+2+8 = 17, -9 + 4+0 = -5
	go sum(s[len(s)/2:], c)
	// x, y := <-c, <-c // receive from c
	x := <-c
	y := <-c
	fmt.Println(x, y, x+y)
}

1.2.4 Características del idioma Go: múltiples valores de retorno

Una función devuelve múltiples valores

func calc(a int, b int) (int, int) {
	sum := a+b
	avg := (a+b)/2
	return sum, avg
}

1.4.1 El concepto de paquete

1. Al igual que Python, coloque el código con la misma función en un directorio, llamado paquete.

2. Los paquetes pueden ser referenciados por otros paquetes

3. El paquete principal se usa para generar archivos ejecutables, cada programa tiene solo un paquete principal

4. El objetivo principal del paquete es mejorar la reutilización del código.

2. Ir a los conceptos básicos del idioma

2 Operadores y tipos de datos básicos

1. Nombre de archivo, palabra clave e identificador

2. Estructura básica del programa Go

3. Constantes y variables

4. Tipos de datos y operadores

5. Tipo de cadena

2.1 Nombre de archivo, palabra clave e identificador

1. Todo el código fuente de go termina con .go

2. El identificador comienza con una letra o guión bajo y distingue entre mayúsculas y minúsculas, por ejemplo:

a. chico

b. Chico

C. a+b

d. 0chico

mi. _chico

F. =_niño

gramo. _

3. _ es un identificador especial usado para ignorar el resultado

4. Palabras clave reservadas

2.1 Nombre de archivo, palabra clave e identificador: palabra clave 1

2.1 Nombre de archivo, palabra clave e identificador: palabra clave 2

◼ var y const: declaración de variables y constantes

var varName tipo o varName : = valor

◼ paquete e importación: paquete e importación

◼ func: se utiliza para definir funciones y métodos

◼ retorno: se usa para regresar de una función

◼ defer someCode: ejecutar antes de que la función salga

◼ go : para paralelismo

◼ select se utiliza para seleccionar diferentes tipos de comunicaciones

◼ interfaz se utiliza para definir la interfaz

◼ struct se usa para definir tipos de datos abstractos

2.1 Nombre de archivo, palabra clave e identificador: palabra clave 3

◼ romper, caso, continuar, para, fallthrough, else, if, switch, goto, control de flujo predeterminado

◼ Resumen del uso de fallthrough [lectura recomendada

https://blog.csdn.net/ajdfhajdkfakr/article/details/79086125]

◼ 1. Después de agregar fallas, ejecutará el caso [inmediatamente después] o la declaración predeterminada directamente, sin importar si la condición se cumple o no.

ejecutará

◼ 2. Después de agregar la declaración fallthrough, la condición de caso [inmediatamente siguiente] no puede definir constantes y variables

◼ 3. Saltar directamente a la siguiente instrucción condicional después de ejecutar la falla, y la instrucción posterior a la instrucción de ejecución condicional no se ejecutará

◼ chan se usa para la comunicación del canal

◼ type se usa para declarar tipos personalizados

◼ map se utiliza para declarar datos de tipo de mapa

◼ el rango se usa para leer datos de corte, mapa y canal

2.2 Estructura básica del programa Go 1

1. Cualquier archivo de código pertenece a un paquete

2. palabra clave de importación, consulte otros paquetes:

2.2 Estructura básica del programa Go 2

3. programa ejecutable golang, paquete principal,

Y hay una y solo una función de entrada principal

4. Llamada de función en el paquete:

a. Funciones en el mismo paquete, llámelas directamente

B. Funciones en diferentes paquetes, a través del nombre del paquete + punto +

nombre de la función a llamar

5. Reglas de control de acceso a paquetes:

a. Mayúsculas significa que esta función/variable es exportable

b. minúsculas significa que esta función/variable es privada,

no accesible desde fuera del paquete

2.4 Constantes 1

1. Las constantes están decoradas con const, lo que significa que siempre son de solo lectura y no se pueden modificar.

2. const solo puede modificar booleanos, números (tipos relacionados con enteros, tipos de punto flotante, complejos) y cadenas.

3. Sintaxis: const identificador [tipo] = valor, donde se puede omitir el tipo.

Ejemplo: const b string = "hola mundo"

const b = “hola mundo”

constante Pi = 3.1414926

constante a = 9/3

constante c = obtener valor ()

package main

import (
	"fmt"
	"time"
)

const (
	Man    = 1
	Female = 2
)

func main() {
	for {
		second := time.Now().Unix()
		if second%Female == 0 {
			fmt.Println("female")
		} else {
			fmt.Println("man")
		}
		time.Sleep(1000 * time.Millisecond)
	}
}

2.4 Constantes 2

4. Una forma más elegante de escribir:

Minimice nuestro código de escritura const (

un = 0

segundo = 1

c = 2

)

5. Escritura más profesional:

constante (

a = iota

si //1

do // 2

)

2.5 Variable 1

1. Sintaxis: tipo de identificador var

2.5 Variable 2

Era (

    a int // El valor predeterminado es 0

    b string //El valor predeterminado es ""

    c bool // por defecto es falso

    re = 8

    e = “hola mundo”

    )

práctica

Escriba un programa para obtener el nombre del sistema operativo actualmente en ejecución y el valor de la variable de entorno PATH, e imprímalo en la terminal

package main

import (
	"fmt"
	"os"
)

func main() {
	var goos string = os.Getenv("GOOS")
	fmt.Printf("The operating system is: %s\n", goos)
	path := os.Getenv("Path")
	fmt.Printf("Path is %s\n", path)
}

2.6 Tipos de valor y tipos de referencia

1. Tipo de valor: las variables almacenan valores directamente y la memoria generalmente se asigna en la pila.

2. Tipo de referencia: La variable almacena una dirección, y esta dirección almacena el valor final. La memoria generalmente se asigna en el montón. Reciclado por GC.

2.6 Tipos de valor y tipos de referencia

1. Tipos de valor: tipos de datos básicos int, float, bool, string y matrices y estructuras.

2. Tipos de referencia: punteros, cortes, mapas, chan, etc. son todos tipos de referencia.

Ejercicio: Escribe un programa que intercambie los valores de dos enteros. Por ejemplo: a=3; b=4; después de intercambiar: a=4;b=3

Código: 2-6-swap.go

package main

import "fmt"

func swap(a *int, b *int) {
	tmp := *a
	*a = *b
	*b = tmp
	return
}

func swap1(a int, b int) (int, int) {
	return b, a
}

func test() {
	var a = 100
	fmt.Println(a)
	//var b int
	for i := 0; i < 100; i++ {
		var b = i * 2
		fmt.Println(b)
	}

	//fmt.Println(c)
	//fmt.Println(b)
}

func test2() {
	var a int8 = 100
	var b int16 = int16(a)

	fmt.Printf("a=%d b=%d\n", a, b)
}

func main() {
	first := 100
	second := 200
	//swap(&first, &second)
	//first, second = swap1(first, second)
	first, second = second, first
	fmt.Println("first=", first)
	fmt.Println("second=", second)

	test()

	test2()
}

Ejercicio: escriba un programa para imprimir variables de tipo de valor y tipo de referencia en el terminal y observe la salida.

Código: 2-6-value_quote.go

package main

import (
	"fmt"
)

func modify(a int) {
	a = 10
	return
}

func modify1(a *int) {
	*a = 10
}

func main() {
	a := 5
	b := make(chan int, 1)
	

	fmt.Println("a=", a)
	fmt.Println("b=", b)

	modify(a)
	fmt.Println("a=", a)
	modify1(&a)
	fmt.Println("a=", a)
}

2.7 Alcance de las Variables

1. Una variable declarada dentro de una función se denomina variable local y su vida útil se limita al interior de la función.

2. Las variables declaradas fuera de la función se denominan variables globales, y el ciclo de vida actúa sobre todo el paquete, si está en mayúsculas,

aplicar a todo el programa.

Indique cuál es la salida del siguiente programa
package main
var a = "G"
func main() {
n() // G
m() // O
n() //O
} 
func n() { 
fmt.Println(a) 
}
func m() {
a = "O"
fmt.Println(a) 
}

2.8 Tipos de datos y operadores 1

1. tipo bool, solo se pueden almacenar verdadero y falso
2. El operador de correlación, ! , && , ||

2.8 Tipos de datos y operadores 2

3. Tipos de números, principalmente int, int8, int16, int32, int64, uint8, uint16, uint32, uint64,

flotador32, flotador64

4. Tipo de conversión, tipo (variable), por ejemplo: var a int=8, var b int32=int32(a)

package main
func main() {
var a int
var b int32
a = 15
b = a + a // compiler error cannot use a + a (value of type int) as int32 value in assignment
b = b + 5 // ok: 5 is a constant
}
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

func main() {

	for i := 0; i < 10; i++ {
		a := rand.Int()
		fmt.Println(a)
	}

	for i := 0; i < 10; i++ {
		a := rand.Intn(100)
		fmt.Println(a)
	}

	for i := 0; i < 10; i++ {
		a := rand.Float32()
		fmt.Println(a)
	}

}

2.8 Tipos de datos y operadores 3

5. Tipo de carácter: var un byte

var un byte = 'c'

6. Tipo de cadena: cadena var str

Ejercicio: ¿Cuál es la salida del siguiente programa?

paquete principal

importar "fmt"

función principal() {

    var n int16 = 34

    varm int32

    metro = norte

    m = int32(n)

    fmt.Printf("El entero de 32 bits es: %d\n", m)

    fmt.Printf("El entero de 16 bits es: %d\n", n)

}

PS D:\Workspace\Go\src\projects\demo> go run main.go
# command-line-arguments
.\main.go:8:6: no se puede usar n (variable de tipo int16) como tipo int32 en la asignación

Ejercicio: use matemáticas/rand para generar 10 números enteros aleatorios, 10 de los cuales son menores que 100
Número entero aleatorio y 10 códigos de punto flotante aleatorios:

2.9 Tipos de datos y operadores 1

1. Operadores lógicos: == , !=,

<

<=

> y >=

2.9 Tipos de datos y operadores 2

2. Operadores matemáticos: +, -,

*

,/etc

package main

import (
	"fmt"
	"strings"
	"unsafe"
	_ "unsafe"
)

func test1() {
	bytes := []byte("I am byte array !")
	str := string(bytes)
	bytes[0] = 'i' //注意这一行,bytes在这里修改了数据,但是str打印出来的依然没变化,
	fmt.Println(str)
}
func test2() {
	bytes := []byte("I am byte array !")
	str := (*string)(unsafe.Pointer(&bytes))
	bytes[0] = 'i'
	fmt.Println(*str)
}
func test3() {
	var data [10]byte
	data[0] = 'T'
	data[1] = 'E'
	var str string = string(data[:])
	fmt.Println(str)
}

func str2bytes(s string) []byte {
	x := (*[2]uintptr)(unsafe.Pointer(&s))
	h := [3]uintptr{x[0], x[1], x[1]}
	return *(*[]byte)(unsafe.Pointer(&h))
}

func bytes2str(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}
func test4() {
	s := strings.Repeat("abc", 3)
	fmt.Println("str2bytes")
	b := str2bytes(s)
	fmt.Println("bytes2str")
	s2 := bytes2str(b)
	fmt.Println(b, s2)
}

func main() {
	test1()
	test2()
	test3()
	test4()
}

2.9 Tipos de datos y operadores 3

Las cadenas se representan de dos formas: 1) comillas dobles 2) `` (comillas invertidas)

package main

import "fmt"

func main() {
	var str = "hello world\n\n"
	var str2 = `hello \n \n \n
	this is a test string
	This is a test string too·`
	fmt.Println("str=", str)
	fmt.Println("str2=", str2)
}
package main

import (
	"fmt"
)

func test_switch1() {
	a := 2
	switch a {
	case 1:
		fmt.Println("a=1")
	case 2:
		fmt.Println("a=2")
	case 3:
		fmt.Println("a=3")
	case 4:
		fmt.Println("a=4")
	default:
		fmt.Println("default")
	}
}

func test_switch2() {
	a := 2
	switch a {
	case 1:
		fmt.Println("a=1")
	case 2:
		fmt.Println("a=2")
		fallthrough
	case 3:
		fmt.Println("a=3")
	case 4:
		fmt.Println("a=4")
	default:
		fmt.Println("default")
	}
}

func main() {
	fmt.Printf("执行test_switch%d\n", 1)
	test_switch1()
	fmt.Printf("执行test_switch%d\n", 2)
	test_switch2()
}

3. Ir a la función

3 control de procesos

para declaración de rango

str := "hola mundo, China"

para i, v := rango str {

fmt.Printf("índice[%d] valor[%c]\n", i, v)

}

Se utiliza para atravesar matrices, cortes, mapas y chan.

package main

import "fmt"

func modify(p *int) {

	fmt.Println(p)
	*p = 1000900
	return
}

func main() {

	var a int = 10
	fmt.Println(&a)

	var p *int
	p = &a

	fmt.Println("the address of p:", &p)
	fmt.Println("the value of p:", p)
	fmt.Println("the value of p point to variable:", *p)

	fmt.Println(*p)
	*p = 100
	fmt.Println(a)

	var b int = 999
	p = &b
	*p = 5

	fmt.Println(a)
	fmt.Println(b)

	modify(&a)
	fmt.Println(a)
}

3 función 1

1. Sintaxis de la declaración: nombre de la función func (lista de parámetros) [(lista de valores devueltos)] {}

función agregar ()

{

}

3 función 2

2. Características de la función Golang:

a. No se admite la sobrecarga, un paquete no puede tener dos funciones con el mismo nombre

b. Las funciones son ciudadanos de primera clase, las funciones también son un tipo y una función se puede asignar a una variable

C. Función anónima

D. Múltiples valores de retorno

3 función 2

2. Características de la función Golang:

package main

import (
	"fmt"
	"reflect"
)

func add(a, b int) int {
	return a + b
}
func main() {
	c := add
	fmt.Println(c)
	sum := c(10, 20)
	fmt.Println(sum)
	sf1 := reflect.ValueOf(c)
	sf2 := reflect.ValueOf(add)
	if sf1 == sf2 {
		fmt.Println("c equal add")
	}

}
package main

import (
	"fmt"
)

type add_func func(int, int) int

func add(a, b int) int {
	return a + b
}
func operator(op add_func, a int, b int) int {
	return op(a, b)
}
func main() {
	c := add
	fmt.Println(c)
	sum := operator(c, 100, 200)
	fmt.Println(sum)
}

3 función 3

3. Método de paso de parámetros de función:

1) Pasar por valor

2) Pasar por referencia

Nota 1: ya sea que se pase por valor o por referencia, lo que se pasa a la función es una copia de la variable, pero el valor

Pasar es una copia del valor. Pasar por referencia es una copia de la dirección, en general, la dirección

Copiar es más eficiente. La copia de valor depende del tamaño del objeto copiado, cuanto más grande sea el objeto, menor será el rendimiento.

3 función 3

3. Método de paso de parámetros de función: 1) Paso de valor

2) Pasar por referencia

Nota 2: el mapa, la división, el canal, el puntero y la interfaz se pasan por referencia de forma predeterminada, ¿cuál debe verificarse?

ir solo transferencia de valor, copia superficial

package main

import "fmt"

func modify(a int) {
	a = 100
}
func main() {
	a := 8
	fmt.Println(a)
	modify(a)
	fmt.Println(a)
}

3 función 4

4. Nombre el nombre del valor de retorno

función suma(a, b int) (c int) {

    c = a + b

    devolver

}

func calc(a, b int) (suma int, promedio int) {

    suma = a + b

    promedio = (a + b) / 2

    devolver

}

3 función 5

5. _Identifier, utilizado para ignorar el valor de retorno:

func calc(a, b int) (suma int, promedio int) {

suma = a + b

promedio = (a +b)/2

devolver

}

función principal() {

suma, _ := calc(100, 200)

}

3 función 6

6. Parámetros variables: func add(arg…int) int {

}

0 o más argumentos

func add(a int, arg…int) int {

}

1 o más parámetros

func add(a int, b int, arg…int) int {

}

2 o más parámetros

Nota: donde arg es un segmento, podemos acceder a todos los parámetros a través de arg[index]

Use len(arg) para juzgar el número de parámetros pasados

package main

import "fmt"

func add(a int, arg ...int) int {
	var sum int = a
	for i := 0; i < len(arg); i++ {
		sum += arg[i]
	}

	return sum
}

func concat(a string, arg ...string) (result string) {

	result = a
	for i := 0; i < len(arg); i++ {
		result += arg[i]
	}

	return
}

func main() {
	sum := add(10, 3, 3, 3, 3)
	fmt.Println(sum)

	res := concat("hello", " ", "world")
	fmt.Println(res)
}

3 función 7

7. Uso de aplazamiento:

1. Cuando la función regrese, ejecute la instrucción defer. Por lo tanto, se puede utilizar para la limpieza de recursos.

2. Múltiples sentencias diferidas se ejecutan en forma de primero en entrar, último en salir

3. Las variables en la declaración de aplazamiento se determinan cuando se declara el aplazamiento.

3 Función 7 diferir propósito

paquete principal

importar "fmt"

función principal() {

    yo := 0

    diferir fmt.Println(i)

    yo ++

    devolver

}

¿Qué es la impresión?

PS D:\Workspace\Go\src\projects\demo> ir a ejecutar main.go
0

paquete principal

importar "fmt"

función principal() {

    para yo := 0; yo < 5; yo++ {

        diferir fmt.Printf("%d", i)

    }

}

PS D:\Workspace\Go\src\projects\demo> ir a ejecutar main.go
43210

3 Función 7 diferir propósito

1 cierra el identificador del archivo

función leer() {

    archivo := abrir(nombre de archivo)

    diferir archivo.Cerrar()

    // operación de archivo

}

3 Función 7 diferir propósito

2. Liberación de recursos de bloqueo

función leer() {

mc.Lock()

aplazar mc.Desbloquear()

// otras operaciones

}

3 Función 7 diferir propósito

3. Liberar la conexión a la base de datos

función leer() {

conexión := openDatabase()

diferir conn.Close()

// otras operaciones

}

4. Haz arreglos y cortes

4 estructuras comunes

1. Funciones y cierres incorporados

2. Matrices y cortes

3. estructura de datos del mapa

4. introducción del paquete

4.1 Funciones integradas

1. cerrar: se utiliza principalmente para cerrar el canal

2. len: se usa para encontrar la longitud, como cadena, matriz, segmento, mapa, canal

3. nuevo: se utiliza para asignar memoria, principalmente para asignar tipos de valores, como int y struct. devuelve un puntero

4. hacer: se usa para asignar memoria, se usa principalmente para asignar tipos de referencia, como chan, map, slice

5. agregar: se usa para agregar elementos a matrices y sectores

6. pánico y recuperación: utilizado para el manejo de errores

7. La diferencia entre nuevo y hecho

package main

import (
	"fmt"
	"strings"
)

// 缩小变量作用域,减少对全局变量的污染。下面的累加如果用全局变量进行实现,全局变量容易被其他人污染。
// 同时,所有我要实现n个累加器,那么每次需要n个全局变量。利用闭包,
// 每个生成的累加器myAdder1, myAdder2 := adder(), adder()有自己独立的sum,sum可以看作为myAdder1.sum与myAdder2.sum。
func Adder() func(int) int {
	var x int
	f := func(d int) int {
		x += d
		return x
	}
	return f
}

func makeSuffix(suffix string) func(string) string {
	f := func(name string) string {

		if strings.HasSuffix(name, suffix) == false {
			return name + suffix
		}
		return name
	}

	return f
}

func main() {

	f := Adder()
	fmt.Println(f(1))
	fmt.Println(f(100))
	// fmt.Println(f(1000))
	/*
		f1 := makeSuffix(".bmp")
		fmt.Println(f1("test"))
		fmt.Println(f1("pic"))

		f2 := makeSuffix(".jpg")
		fmt.Println(f2("test"))
		fmt.Println(f2("pic"))
	*/
}

4.2 Cierres

1. Cierre: una entidad compuesta por una función y su entorno de referencia asociado

paquete principal

importar "fmt"

función principal() {

var f = sumador()

fmt.Imprimir(f(1),” - “)

fmt.Imprimir(f(20),” - “)

fmt.Imprimir(f(300))

}

func Sumador() func(int) int {

var x int

return func(delta int) int {

x += delta

volver x

}

}

4.2 Ejemplo de cierre

paquete principal

importar (

"fmt"

"instrumentos de cuerda"

)

func makeSuffixFunc(cadena de sufijo) func(cadena) cadena {

return func(cadena de nombre) cadena {

if!strings.HasSuffix(nombre, sufijo) {;

devolver nombre + sufijo

}

devolver el nombre

}

}

función principal() {

func1 := hacerSufijoFunc(".bmp")

func2 := hacerSufijoFunc(".jpg")

fmt.Println(func1("prueba"))

fmt.Println(func2("prueba"))

}

4.3 Arreglos y rebanadas

1. Array: Es una secuencia de longitud fija del mismo tipo de datos.

2. Definición de matriz: var a [len]int, por ejemplo: var a[5]int Una vez definida, la longitud no se puede cambiar

3. La longitud es parte del tipo de matriz, por lo que var a[5] int y var a[10] int son tipos diferentes

4. Se puede acceder a la matriz por subíndice, el subíndice comienza desde 0 y el último subíndice del elemento es: len-1

para yo := 0; yo < len(a); yo++ {

}

5. El acceso está fuera de los límites. Si el subíndice está fuera del rango legal de la matriz, activará el acceso fuera de los límites y entrará en pánico.

6. La matriz es un tipo de valor, por lo que cambiar el valor de la copia no cambiará el valor de sí mismo

arr2 := arr1

arr2[2] = 100

package main

import "fmt"

func test1() {
	var a [10]int

	//j := 10
	a[0] = 100
	//a[j] = 200

	fmt.Println(a)

	for i := 0; i < len(a); i++ {
		fmt.Println(a[i])
	}

	for index, val := range a {
		fmt.Printf("a[%d]=%d\n", index, val)
	}
}

func test3(arr *[5]int) {
	(*arr)[0] = 1000
}

func test2() {
	var a [10]int
	b := a

	b[0] = 100
	fmt.Println(a)
}

func main() {

	//test1()
	test2()
	var a [5]int
	test3(&a)
	fmt.Println(a)
}

4.3数组与切片-案例

package main

import (

"fmt"

)

func modify(arr [5]int) {

arr[0] = 100

return

}

func main() {

var a [5]int //数组大小是固定的

modify(a)

for i := 0; i < len(a); i++ {

fmt.Println(a[i])

}

}

package main

import (

"fmt"

)

func modify(arr *[5]int) {

(*arr)[0] = 100

return

}

func main() {

var a [5]int

modify(&a)

for i := 0; i < len(a); i++ {

fmt.Println(a[i])

}

}

4.3 数组与切片-数组

1. 数组初始化 对于数组 []里面肯定要有东西

b. var age1 = [5]int{1,2,3,4,5}

c. var age2 = […]int{1,2,3,4,5,6}

a. var age0 [5]int = [5]int{1,2,3}

d. var str = [5]string{3:”hello world”, 4:”tom”}

2. 多维数组

a. var age [5][3]int

b. var f [2][3]int = [...][3]int{ {1, 2, 3}, {7, 8, 9}}

3. 多维数组遍历

package main

import (

"fmt"

)

func main() {

var f [2][3]int = [...][3]int{ {1, 2, 3}, {7, 8, 9}}

for k1, v1 := range f {

for k2, v2 := range v1 {

fmt.Printf("(%d,%d)=%d ", k1, k2, v2)

}

fmt.Println()

}

}

4.3 数组与切片-切片定义

1. 切片:切片是数组的一个引用,因此切片是引用类型

2. 切片的长度可以改变,因此,切片是一个可变的数组

3. El método de corte transversal es el mismo que el de una matriz, puede usar len() para encontrar la longitud

4. Cap puede encontrar la capacidad máxima de segmento, 0 <= len(segmento) <= (matriz), donde matriz es la matriz a la que hace referencia segmento

5. Definición de segmento: var nombre de variable [] tipo, como var str [] string var arr [] int

4.3 Matrices y sectores: inicialización de sectores

1. Inicialización de segmento: var segmento []int = arr[inicio:fin]

Contiene elementos de principio a fin, pero no al final.

2. Var slice []int = arr[0:end] se puede abreviar como var slice []int=arr[:end]

3. Var slice []int = arr[start:len(arr)] se puede abreviar como var slice[]int = arr[start:]

4. Var slice []int = arr[0, len(arr)] se puede abreviar como var slice[]int = arr[:]

5. Si desea eliminar el último elemento de la rebanada, puede escribir así:

Rebanada = rebanada[:len(rebanada)-1]

package main

import "fmt"

type slice struct {
	ptr *[100]int
	len int
	cap int
}

func make1(s slice, cap int) slice {
	s.ptr = new([100]int)
	s.cap = cap
	s.len = 0
	return s
}

func modify(s slice) {
	s.ptr[1] = 1000
}

func testSlice2() {
	var s1 slice
	s1 = make1(s1, 10)

	s1.ptr[0] = 100
	modify(s1)

	fmt.Println(s1.ptr)
}

func testSlice() {
	var slice []int
	var arr [5]int = [...]int{1, 2, 3, 4, 5}

	slice = arr[:]
	slice = slice[1:]
	slice = slice[:len(slice)-1]
	fmt.Println(slice)
	fmt.Println(len(slice))
	fmt.Println(cap(slice))

	slice = slice[0:1]
	fmt.Println(len(slice))
	fmt.Println(cap(slice))

}

func modify1(a []int) {
	a[1] = 1000
}

func testSlice3() {
	var b []int = []int{1, 2, 3, 4}
	modify1(b)
	fmt.Println(b)
}

func testSlice4() {
	var a = [10]int{1, 2, 3, 4}

	b := a[1:5]
	fmt.Printf("%p\n", b)
	fmt.Printf("%p\n", &a[1])
}

func main() {
	//testSlice()
	//testSlice2()
	//testSlice3()
	testSlice4()
}

4.3 Arrays y Slices - Slices en acción 1

1. Ejercicio: escriba un programa que demuestre los diversos usos de las rebanadas

Código: 4-3-slice1.go

package main

import "fmt"

func testSlice() {
	var a [5]int = [...]int{1, 2, 3, 4, 5}
	s := a[1:]
	fmt.Printf("before len[%d] cap[%d]\n", len(s), cap(s))
	s[1] = 100
	fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
	fmt.Println("before a:", a)

	s = append(s, 10)
	s = append(s, 10)
	fmt.Printf("after len[%d] cap[%d]\n", len(s), cap(s))
	s = append(s, 10)
	s = append(s, 10)
	s = append(s, 10)

	s[1] = 1000
	fmt.Println("after a:", a)
	fmt.Println(s)
	fmt.Printf("s=%p a[1]=%p\n", s, &a[1])
}

func testCopy() {

	var a []int = []int{1, 2, 3, 4, 5, 6}
	b := make([]int, 1)

	copy(b, a)
	fmt.Printf("copy len[%d] cap[%d]\n", len(b), cap(b))
	fmt.Println(b)
}

func testString() {
	s := "hello world"
	s1 := s[0:5]
	s2 := s[6:]

	fmt.Println(s1)
	fmt.Println(s2)

}

func testModifyString() {
	s := "我hello world"
	s1 := []rune(s)

	s1[0] = 200
	s1[1] = 128
	s1[2] = 64
	str := string(s1)
	fmt.Println(str)
}

func main() {
	//testSlice()
	//testCopy()
	//testString()
	testModifyString()
}

4.3 Arrays y Slices - Slices en acción 2

2. El diseño de la memoria del segmento, similar al vector de C++:

2. Practique, escriba un programa para demostrar el diseño de la memoria de los segmentos

4.3 Arrays y Slices - Slices en acción 3

3. Usa make para crear cortes

var rebanada [] tipo = hacer ([] tipo, largo)

rebanada := hacer([]tipo, largo)

rebanada := hacer([]tipo, longitud, límite)

4.3 Arrays y slices - slices en acción 4

4. Use la función incorporada de agregar para manipular sectores

rebanada = agregar (rebanada, 10)

var a = []int{1,2,3}

var b = []int{4,5,6}

a = añadir (a, b…)

5. Para corte transversal de rango

para índice, val := segmento de rango {

}

6. Redimensionar rebanada

var a = []int {1,3,4,5}

b := a[1:2]

b = b[0:3]

7. Copia de rebanada

s1 := []int{1,2,3,4,5}

s2 := hacer([]int, 10)

copiar (s2, s1)

s3 := []int{1,2,3}

s3 = añadir (s3, s2…)

s3 = agregar (s3, 4,5,6)

4-3-slice-make.go

4.3 Arrays y Slices - Slices en acción 5

8. ensartar y cortar

La capa inferior de la cadena es una matriz de bytes, por lo tanto, también

se puede cortar

str := “hola mundo”

s1 := cadena[0:5]

fmt.Println(s1)

s2 := cadena[5:]

fmt.Println(s2)

9. El diseño subyacente de la cadena.

4.3 Arrays y slices - slices en acción 6

10. ¿Cómo cambiar el valor del carácter en una cadena?

La cadena en sí es inmutable, por lo que para cambiar los caracteres de la cadena, debe hacer lo siguiente:

str := “hola mundo”

s := []byte(cadena)

s[0] = 'o'

str = cadena(s)

4.4 Diferencias entre arrays y slices 1

Sus definiciones:

 Array: Tipo [n]T significa un arreglo con n valores de tipo T.

 Sector: El tipo []T representa un sector con elementos de tipo T.

Ejemplo de matriz

var x[3]int = [3]int{1,2,3}

var y[3]int = x

fmt.Println(x,y)

y[0]=999

fmt.Println(x,y)

ejemplo de corte

var x[]int = []int{1,2,3}

var y[]int = x

fmt.Println(x,y)

y[0]=999

fmt.Println(x,y)

4.4 La diferencia entre arrays y slices 2

Sus definiciones:

 Array: Tipo [n]T significa un arreglo con n valores de tipo T.

 Sector: El tipo []T representa un sector con elementos de tipo T.

Las matrices necesitan especificar el número, pero los sectores no. La asignación de arreglos también se puede hacer de la siguiente manera, ignorando el número de elementos, usando

"..."reemplazar

x:= [...]int{1,2,3}
y := x
fmt.Println(x,y)
y[0]=999
fmt.Println(x,y)

4.5 La diferencia entre nuevo y hecho

nuevo

función principal() {

var i *int

i=nuevo(int)

*i=10

fmt.Println(*i)

}

 hacer

func make(t Tipo, tamaño ...IntegerType) Tipo func new(Tipo) *Tipo

make también se usa para la asignación de memoria, pero a diferencia de new, solo se usa para

La creación de memoria de chan, map y slice, y el tipo que devuelve es este

Los tres tipos en sí mismos, en lugar de sus tipos de puntero, porque los tres tipos

Son tipos de referencia, por lo que no es necesario devolver sus punteros.

5. Ir al método de prueba

5 Ir a prueba

Condiciones previas:

1. El nombre del archivo debe terminar con "_test.go"

2. El nombre del método debe comenzar con "Test", y el parámetro formal es (t *testing.T)

5 Ir ejemplo de prueba

Ejemplo: gotest.go

paquete mi prueba

importar (

"errores"

)

función División(a, b float64) (float64, error) {

si segundo == 0 {

devuelve 0, errores.Nuevo("El divisor no puede ser 0")

}

volver a/b, cero

}

gotest_test.go

paquete mi prueba

importar (

"pruebas"

)

func Test_Division_1(t *testing.T) {

si i, e := División(6, 2); yo != 3 || e != nil { //intentar una prueba unitaria en la función

t.Error("La prueba de la función de división falló") // Si no es como se esperaba, informe un error

} demás {

t.Log("Pasó la primera prueba") //Registre alguna información que espera registrar

}

}

func Test_Division_2(t *testing.T) {

si _, e := División(6, 0); e == nil { // intenta una prueba unitaria en la función

t.Error("La división no funcionó como se esperaba") // si no es como se esperaba, hazlo

informar error

} demás {

t.Log("una prueba superada.", e) //Registre alguna información que espera registrar

}

}

5 Ir prueba prueba

1. Ejecute go test en el directorio para probar todos los archivos que terminan en XXX_test.go en el directorio.

2. Probar un solo método

ir prueba -v -run="Test_Division_1" -cuenta 5

3. Ver ayuda ir prueba de ayuda

package mytest

import (
	"testing"
)

func Test_Division_1(t *testing.T) {
	if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
		t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
	} else {
		t.Log("第一个测试通过了") //记录一些你期望记录的信息
	}
}

func Test_Division_2(t *testing.T) {
	if _, e := Division(6, 0); e == nil { //try a unit test on function
		t.Error("Division did not work as expected.") // 如果不是如预期的那么就报错
	} else {
		t.Log("one test passed.", e) //记录一些你期望记录的信息
	}
}

5 Introducción del comando Go test 1

Puede ver las instrucciones de uso de go test a través de go help test:

El formato es como:

ir a prueba [-c] [-i] [indicadores de compilación] [paquetes] [indicadores para prueba binaria]

Interpretación de parámetros:

-c : compila go test en un binario ejecutable, pero no ejecuta las pruebas.

-i: instala los paquetes de los que depende el paquete de prueba, pero no ejecuta las pruebas.

Con respecto a los indicadores de compilación, llame a la ayuda de compilación, estos son los parámetros que deben usarse en el proceso de compilación y ejecución, y generalmente se establecen en vacío

Acerca de los paquetes, llame a los paquetes de ayuda, se trata de la administración de paquetes, generalmente configurados en vacío

Con respecto a las banderas para el binario de prueba, llame a go help testflag, estos son los parámetros que se usan a menudo en el proceso de go test

-test.v: Ya sea para generar todos los casos de prueba de unidad (independientemente del éxito o la falla), el valor predeterminado no se agrega, por lo que solo se generan los casos de prueba de unidad fallidos.

-test.run patrón: solo ejecuta qué casos de prueba de unidad

-test.bench patten: solo ejecute esos casos de prueba de rendimiento

-test.benchmem: si se deben generar condiciones de memoria durante las pruebas de rendimiento

-test.benchtime t: el tiempo de ejecución de la prueba de rendimiento, el valor predeterminado es 1s

-test.cpuprofile cpu.out: si se genera un archivo de análisis de rendimiento de la CPU

-test.memprofile mem.out: si se debe generar el archivo de perfil del perfil de memoria

5 Introducción al comando Go test 2-continuación

-test.blockprofile block.out: si se debe generar el archivo de análisis de rendimiento del bloqueo interno de goroutine

-test.memprofilerate n : al perfilar el rendimiento de la memoria, hay un problema de grabación cuando se asigna. Este parámetro es para configurar el

El intervalo de asignación de memoria del punto, es decir, el tamaño de memoria representado por una muestra en el perfil. El valor predeterminado es 512*1024. si lo configuras

Si es 1, habrá un punto en el perfil cada vez que se asigne un bloque de memoria y habrá muchas muestras del perfil generado. Si lo pones a 0,

Eso no es hacer RBI.

Puede desactivar la recopilación de memoria configurando memprofilerate=1 y GOGC=off, y observe la asignación de cada bloque de memoria.

-test.blockprofilerate n: Básicamente lo mismo que arriba, controla la cantidad de nanosegundos cuando se bloquea la gorutina. El valor predeterminado no establecido es equivalente a -

test.blockprofilerate=1, registra cada nanosegundo

-test.parallel n : el número de CPU paralelas para el programa de prueba de rendimiento, que es igual a GOMAXPROCS de forma predeterminada.

-test.timeout t: si el caso de prueba se ejecuta durante más de t, lanza un pánico

-test.cpu 1,2,4: en qué CPU se ejecuta el programa, representado por el bit del binario 1, que es lo mismo que nginx_worker_cpu_affinity de nginx

-test.short: acorta el tiempo de ejecución de esos casos de prueba de larga duración

1.2 Interfaz de idioma Go y reflexión

Ir interfaz de idioma y reflexión

1. Estructura

2. Interfaz

3. Reflexión

1. Estructura

1.1 Introducción a la estructura

◼ Go realiza OOP (programación orientada a objetos) a través de la estructura y la interfaz

◼ Los miembros de struct (también llamados propiedades o campos) pueden ser de cualquier tipo, como tipo común, tipo compuesto, función, mapa, interfaz,

estructura etc

1.2 Explicación detallada de la definición struct-struct

1.2 Explicación detallada de struct - declaración e inicialización

Declaración e inicialización

era stu1 estudiante

var stu2 *Student= &Student{} //简写student2 := &Student{}

var stu3 *Estudiante = nuevo(Estudiante) //简写stu3 := nuevo(Estudiante)

1.2 Explicación detallada de struct - uso de struct

Para acceder a sus miembros, utilice "."
Struct asigna memoria usando new y devuelve un puntero
struct no tiene constructor, pero podemos definir el "constructor" por nosotros mismos
struct es un tipo definido por nosotros mismos y no se puede convertir con otros tipos

escriba la estructura del estudiante {

    cadena de nombre

    edad int

    cadena de clase

    }

var stu1 Student
stu1.age = 34
stu1.name = "dar"
stu1.Class = "class1"
fmt.Println(stu1.name) //dar
var stu2 *Student = new(Student)
stu2.name = "ki"
stu2.age = 33
fmt.Println(stu2.name, (*stu2).name) //ki
var stu3 *Student = &Student{name: "rose", age: 18, Class: "class3"}
fmt.Println(stu3.name, (*stu3).name) //rose rose
package main

import (
	"fmt"
	"unsafe"
)

type Student struct {
	name  string
	age   int32  // 小写 私密 只能在自己的包里面用
	Class string // 大写 公开 类似C++  public
}

func main() {
	// 1 值形式
	var stu1 Student // 里面的变量全是零 栈上的
	fmt.Println("stu1:", stu1)
	stu1.age = 34
	stu1.name = "dar"
	stu1.Class = "class1"
	fmt.Println(stu1.name) //dar

	// 2 new 函数创建
	var stu2 *Student = new(Student) // new出来的是堆上
	stu2.name = "king"
	stu2.age = 33
	fmt.Println(stu2.name, (*stu2).name) //king

	// &形式创建
	var stu3 *Student = &Student{
		name:  "rose",
		age:   18,
		Class: "class3", // 如果分行的时候每行都要,
	}
	// var stu3 *Student = &Student{name: "rose", age: 18, Class: "class3"}
	fmt.Println(stu3.name, (*stu3).name) //rose  rose
	fmt.Printf("addr: %p, %p, %p\n", &stu1, stu2, stu3)

	// 值 初始化
	var stu4 Student = Student{ // KV 形式初始化值
		name:  "老师",
		age:   18,
		Class: "Go", // 注意这里的逗号不能少
	}
	fmt.Println("stu4:", stu4) // stu4: {柚子老师 18 }

	// 值顺序初始化
	var stu5 Student = Student{ // 顺序形式 形式初始化值
		"1",
		18,
		"音视频", // 注意这里的逗号不能少
	}
	fmt.Println("stu5:", stu5)

	// nil结构体
	var stu6 *Student = nil
	fmt.Println("stu6:", stu6)

	// 结构体大小
	fmt.Println("unsafe.Sizeof(stu5):", unsafe.Sizeof(stu5))
	fmt.Println("unsafe.Sizeof(stu6):", unsafe.Sizeof(stu6))
	// fmt.Println("unsafe.Sizeof(string):", unsafe.Sizeof(string))
	// fmt.Println("unsafe.Sizeof(int):", unsafe.Sizeof(int))
}

1.2 Explicación detallada de struct - constructor personalizado

◼ Personaliza el método constructor a través del patrón de fábrica

func Newstu(nombre1 cadena,edad1 int,clase1 cadena) *Estudiante {

    return &Estudiante{nombre:nombre1,edad:edad1,Clase:clase1}

    }

    función principal() {

    stu1 := Newstu(“dar",34,"matemáticas")

    fmt.Println(stu1.name) // dar

    }

package main

import "fmt"

type Student struct {
	name  string
	age   int
	Class string
}

func Newstu(name1 string, age1 int, class1 string) *Student {
	return &Student{name: name1, age: age1, Class: class1}
}
func main() {
	stu1 := Newstu("dar", 34, "math")
	fmt.Println(stu1.name) // dar
}

1.3 struct tag

◼ tag可以为结构体的成员添加说明或者标签便于使用,这些说明可以通过反射获取到。

◼ 结构体中的成员首字母小写对外不可见,但是我们把成员定义为首字母大写这样与外界进行数据

交互会带来极大的不便,此时tag带来了解决方法

type Student struct {

    Name string "the name of student"

    Age int "the age of student"

    Class string "the class of student"

   }

1.3 struct tag –应用场景json示例

应用场景示例,json序列化操作(序列化和反序列化演示)

type Student struct {
	Name string `json:"name"`
	Age int `json:"age"`
	}
	func main() {
	var stu = Student{Name: "dar", Age: 34}
	data, err := json.Marshal(stu)
	if err != nil {
	fmt.Println("json encode failed err:", err)
	return
	}
	fmt.Println(string(data)) //{"name":"dar","age":34}
	}
package main

import (
	"encoding/json"
	"fmt"
)

// stu:  序列化后:{"name":"dar","age":34}
// 				 {"name1":"dar","age2":34}
type Student struct {
	Name string `json:"name1"`
	Age  int    `json:"age2"`
}

func main() {
	var stu = Student{Name: "dar", Age: 34}
	data, err := json.Marshal(stu) //   {"name1":"dar","age2":34}
	if err != nil {
		fmt.Println("json encode failed err:", err)
		return
	}
	fmt.Println("stu: ", string(data)) //{"name":"dar","age":34}

	var stu2 Student
	err = json.Unmarshal(data, &stu2) // 反序列化 
	fmt.Println("stu2: ", stu2)       // {dar 34}
}

1.4 struct匿名成员(字段、属性)

◼ 结构体中,每个成员不一定都有名称,也允许字段没有名字,即匿名成员。

◼ 匿名成员的一个重要作用,可以用来实现oop中的继承。

◼ 同一种类型匿名成员只允许最多存在一个。

◼ 当匿名成员是结构体时,且两个结构体中都存在相同字段时,优先选择最近的字段。

type Person struct {

    Name string

    Age int

    }

    type Student struct {

    score string

    Age int

    Person // 匿名内嵌结构体

    }

func main() {
	var stu = new(Student)
	stu.Age = 34                         //优先选择Student中的Age
	fmt.Println(stu.Person.Age, stu.Age) // 0,34
}
// 1.4 struct匿名成员(字段、属性)

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}
type Student struct {
	score  string
	Age    int
	Person // 匿名内嵌结构体
}

func main() {
	var stu = new(Student)
	stu.Age = 22                         //优先选择Student中的Age
	fmt.Println(stu.Person.Age, stu.Age) // 0,22

	var stu2 = Student{
		score: "100",
		Age:   20,
		Person: Person{
			Name: "柚子老师",
			Age:  18,
		},
	}
	fmt.Println("stu2: ", stu2)
}

1.5 struct-继承、多继承

◼ 当结构体中的成员也是结构体时,该结构体就继承了这个结构体,继承了其所有的方法与属性,当然有多个

结构体成员也就是多继承。

◼ 访问父结构中属性也使用“.”,但是当子结构体中存在和父结构中的字段相同时候,只能使用:"子结构体.父

结构体.字段"访问父结构体中的属性,如上面示例的stu.Person.Age

◼ Las estructuras de herencia pueden utilizar alias, a los que se accede a través de alias al acceder, como en el siguiente ejemplo man1.job.Salary:

tipo Persona estructura {

    Cadena de nombre

    edad interna

    }

    tipo de estructura del maestro {

    Salario internacional

    cadena de clases

   }

type man struct {
	sex string
	job Teacher //别名,继承Teacher 这个时候就不是匿名了
	Person //继承Person
	}
	func main() {
	var man1 = new(man)
	man1.Age = 34
	man1.Name = "dar"
	man1.job.Salary = 100000
	fmt.Println(man1, man1.job.Salary) //&{ {8500 } {dar 34}} 8500
	}
// 1.5 struct-继承、多继承
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}
type Teacher struct {
	Salary int
	Class  string
}

type Man struct {
	sex    string
	job    Teacher //别名,继承Teacher
	Person         //继承Person
}

func main() {
	var man1 = new(Man)
	man1.Age = 34
	man1.Name = "dar"
	man1.job.Salary = 100000
	fmt.Println("man1:", man1, man1.job.Salary) //&{ {8500 } {dar 34}} 8500

	var man2 = Man{
		sex: "女",
		job: Teacher{
			Salary: 8000,
			Class:  "班班",
		},
		Person: Person{ // 匿名初始化方式
			Name: "老师",
			Age:  18,
		},
	}
	fmt.Println("man2", man2)
}

1.6 Métodos en estructura-estructura

cual es el metodo

◼ Un método Go es una función que actúa sobre el receptor (entendido personalmente como el objeto de la acción), y el receptor es una variable de cierto tipo, por lo que el método es un

tipo de función.

◼ El receptor puede ser de cualquier tipo, no solo la estructura, sino también los tipos básicos en Go (int, string, bool, etc.), o el alias del arreglo

e incluso puede ser un tipo de función. Sin embargo, el receptor no puede ser un tipo de interfaz, porque una interfaz es una definición abstracta y un método es una definición concreta.

lograr.

◼ Un tipo más sus métodos es equivalente a una clase en orientación a objetos. Una distinción importante es esta: en Go, el código de un tipo y el

El código del método no se puede colocar juntos, pueden existir en diferentes archivos fuente, el único requisito es: deben estar en el mismo paquete.

◼ El conjunto de todos los métodos del tipo T (o *T) se denomina conjunto de métodos del tipo T (o *T).

◼ Debido a que los métodos son funciones, tampoco se permite la sobrecarga de métodos, es decir, solo puede haber un método con un nombre determinado para un tipo. Pero si se basa en

Los tipos de receptores están sobrecargados: los métodos con el mismo nombre pueden existir en 2 o más tipos de receptores diferentes, como en el mismo paquete

hacer está permitido

◼ Un tipo con alias no puede tener métodos definidos en su tipo original (ya que el tipo con alias está subyacente al mismo que el tipo original).

formato para definir métodos

func(recv recevier_type(结构体))methodName(parameter_list)(return_value_list){}

1.6 struct-método en estructura-ejemplo

tipo Persona estructura {

    Cadena de nombre

    edad interna

    }

    func (p Person) Getname() string { //p representa la columna real de la estructura en sí, similar a self en python, donde p puede escribirse como self

    fmt.Println(p.Name)

    return p.Name

    }

    func main() {

    var person1 = new(Person)

    person1.Age = 34

    person1.Name = "dar"

    person1.Getname() // dar

    }

结构体的指针方法

package main

import (
	"fmt"
	"math"
)

type Circle struct {
	x      int
	y      int
	Radius int
}

// 面积
func (c Circle) Area() float64 {
	return math.Pi * float64(c.Radius) * float64(c.Radius)
}

// 周长
func (c Circle) Circumference() float64 {
	return 2 * math.Pi * float64(c.Radius)
}

func (c Circle) expand() {
	c.Radius *= 2
}

func (c *Circle) expand2() {
	c.Radius *= 2
}
func main() {
	var c = Circle{Radius: 50}
	fmt.Println(c.Area(), c.Circumference())
	// 指针变量调用方法形式上是一样的
	var pc = &c
	pc.expand2()
	fmt.Println(pc.Area(), pc.Circumference())

}
type Person struct {
Name string
Age int
}
func (p Person) Getname() string { //p代表结构体本身的实列,类似python中的self,这里p可以写为self
fmt.Println(p.Name)
return p.Name
}
func main() {
var person1 = new(Person)
person1.Age = 34
person1.Name = "dar"
person1.Getname() // dar
}

1.6 struct-结构体中的方法-方法和函数的区别

◼ 方法只能被其接受者调用

◼ 接收者是指针时,方法可以改变接受者的值(或状态),函数也能做到

◼ 接受者和方法必须在同一个包内

1.6 struct-结构体中的方法-方法的接受者是值或者指针的区别

◼ 当接受者是一个值的时候,这个值是该类型实例的拷贝

◼ 如果想要方法改变接受者的数据,就在接受者的指针类型上定义该方法。否则,就在普

通的值类型上定义方法。

总结:指针方法和值方法都可以在指针或者非指针上被调用。也就是说,方法接收者是

指针类型时,指针类型的值也是调用这个方法,反之亦然。

1.7 struct-内存分布

go中的结构体内存布局和c结构体布局类似,每个成员的内存分布是连续的

type Student struct {
Name string
Age int64
wight int64
high int64
score int64
}

package main

import (
	"fmt"
	"reflect"
)

// type Student struct {
// 	Name  string // 16
// 	Age   int64  // 8
// 	wight int64  // 8
// 	high  int64  // 8
// 	score int64  // 8
// }
type Student struct {
	Name  string // 16  有两个变量: 指针, length
	Age   int8   //
	wight int64  // 8
	high  int8   // 8
	score int64  // 8
}

func main() {
	var stu1 = new(Student)
	stu1.Name = "只为你"
	fmt.Printf("地址分布:")
	fmt.Printf("%p\n", &stu1.Name)
	fmt.Printf("%p\n", &stu1.Age)
	fmt.Printf("%p\n", &stu1.wight)
	fmt.Printf("%p\n", &stu1.high)
	fmt.Printf("%p\n", &stu1.score)
	typ := reflect.TypeOf(Student{})
	fmt.Printf("Struct is %d bytes long\n", typ.Size())
	// We can run through the fields in the structure in order
	n := typ.NumField()
	for i := 0; i < n; i++ {
		field := typ.Field(i) // 反射出filed
		fmt.Printf("%s at offset %v, size=%d, align=%d\n",
			field.Name, field.Offset, field.Type.Size(),
			field.Type.Align())
	}
}

2. 接口

2.1 interface简介

interface(接口)是golang最重要的特性之一,Interface类型可以定义一组方法,但是这些不需要实

现。并且interface不能包含任何变量。

◼ interface 是方法的集合

◼ interface是一种类型,并且是指针类型

◼ interface的 更重要的作用在于多态实现

◼ interface 不能包含任何变量

2.2 interface定义

type 接口名称 interface {

    method1 (参数列表) 返回值列表

    method2 (参数列表) 返回值列表

    ...

    }

2.3 interface使用

◼ 接口的使用不仅仅针对结构体,自定义类型、变量等等都可以实现接口。

◼ Si una interfaz no tiene métodos, la llamamos interfaz vacía. Dado que una interfaz vacía no tiene métodos, cualquier estructura

La interfaz vacía se implementa implícitamente.

◼ Para implementar una interfaz, se deben implementar todos los métodos de la interfaz.

// 2.3 interface使用

package main

import "fmt"

//定义接口
type Skills interface {
	Running()
	Getname() string
}

type Student struct {
	Name string
	Age  int
}

type Teacher struct {
	Name string
	Age  int
}

// 实现接口
func (p Student) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Student) Running() { // 实现 Running方法
	fmt.Printf("%s running\n", p.Name)
}

func (p Teacher) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Teacher) Running() { // 实现 Running方法
	fmt.Printf("%s running\n", p.Name)
}

func (p Teacher) Running2() { // 实现 Running方法
	fmt.Printf("%s running\n", p.Name)
}

// 想用接口,那就要实现对应接口的所有方法
func main() {
	var skill Skills // 一个接口变量
	var stu1 Student // 结构体变量
	stu1.Name = "dar"
	stu1.Age = 34
	skill = stu1
	skill.Running() //调用接口

	var teacher Teacher = Teacher{"老师", 18}
	skill = teacher
	skill.Running() //调用接口
	teacher.Running()
}
// 空接口
package main

import "fmt"

// Go 语言为了避免用户重复定义很多空接口,它自己内置了一个,这个空接口的名字特别奇怪,叫 interface{}
/*
空接口里面没有方法,所以它也不具有任何能力,其作用相当于 Java 的 Object 类型,可以容纳任意对象,
它是一个万能容器。比如一个字典的 key 是字符串,但是希望 value 可以容纳任意类型的对象,
类似于 Java 语言的 Map 类型,这时候就可以使用空接口类型 interface{}。*/
// 空接口 map 里面用
func main() {
	// map  k-v  一个map里面是有key都是同一类型,value也是同一类型
	// map[string]int   k-string  v-int
	// 连续两个大括号,是不是看起来很别扭
	// 代码中 user 字典变量的类型是 map[string]interface{},
	// 从这个字典中直接读取得到的 value 类型是 interface{},需要通过类型转换才能得到期望的变量。
	var user = map[string]interface{}{
		"age":     30,
		"address": "Beijing",
		"married": true,
	}
	fmt.Println(user)
	// 类型转换语法来了
	var age = user["age"].(int)
	var address = user["address"].(string)
	var married = user["married"].(bool)
	fmt.Println(age, address, married)

	user["price"] = 5.5
	var price = user["price"].(float64) // ?报错?
	fmt.Println("user: ", user, price)

	fmt.Println("user2: ")
	var user2 = map[interface{}]interface{}{
		111:        30,
		"address2": "Beijing",
		1.2:        true,
	}
	fmt.Println("user2: ", user2)
}

2.4 interfaces

◼ La interfaz en el lenguaje go es una forma de polimorfismo. El llamado polimorfismo se refiere a múltiples formas de una cosa.

◼ Se puede llamar a la misma interfaz, implementaciones de diferentes tipos, y todas funcionan de acuerdo con la interfaz unificada

// 2.4 interface多态

package main

import "fmt"

type Skills interface {
	Running()
	Getname() string
}

type Student struct {
	Name string
	Age  int
}

type Teacher struct {
	Name   string
	Salary int
}

func (p Student) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Student) Running() { // 实现 Running方法
	fmt.Printf("%s running", p.Name)
}

func (p Teacher) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Teacher) Running() { // 实现 Running方法
	fmt.Printf("\n%s running", p.Name)
}
func main() {
	var skill Skills
	var stu1 Student
	var t1 Teacher
	t1.Name = "ki"
	stu1.Name = "dar"
	stu1.Age = 22
	skill = stu1
	skill.Running()
	skill = t1
	t1.Running()
}

2.5 anidamiento de interfaz de interfaz

◼ Las interfaces en el idioma go se pueden anidar, lo que se puede entender como herencia, y las subinterfaces tienen todos los métodos de la interfaz principal

◼ Si se utiliza esta subinterfaz, se deben implementar todos los métodos de la interfaz principal y la subinterfaz

type Skills interface {
	Running()
	Getname() string
	}
	type Test interface {
	sleeping()
	Skills //继承Skills
	}
// 2.5 interface接口嵌套

package main

import "fmt"

type Skills interface {
	Running()
	// Running(is int)		// 函数名是唯一的
	Getname() string
}

type Test interface {
	Sleeping()
	Skills //继承Skills
}
type Student struct {
	Name string
	Age  int
}

type Teacher struct {
	skill  Skills // skill也只能当变量去用
	Name   string
	Salary int
}

func (p Student) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Student) Running() { // 实现 Running方法
	fmt.Printf("%s running", p.Name)
}

func (p Teacher) Getname() string { //实现Getname方法
	fmt.Println(p.Name)
	return p.Name
}

func (p Teacher) Running() { // 实现 Running方法
	fmt.Printf("\n%s running", p.Name)
}

func (p Teacher) Sleeping() { // 实现 Running方法
	fmt.Printf("\n%s Sleeping", p.Name)
}
func main() {
	var skill Skills
	var stu1 Student
	var t1 Teacher
	t1.Name = "ki"
	stu1.Name = "dar"
	stu1.Age = 22
	skill = stu1
	skill.Running()
	skill = t1
	t1.Running()

	var test Test
	test = t1
	test.Sleeping()

	// test = stu1
}

2.6 combinación de interfaz

◼ La definición de interfaz también admite herencia de composición

// 2.6 接口的组合继承
package main

import "fmt"

// 可以闻
type Smellable interface {
	smell()
}

// 可以吃
type Eatable interface {
	eat()
}

type Fruitable interface {
	Smellable
	Eatable
}

// 苹果既可能闻又能吃
type Apple struct{}

func (a Apple) smell() {
	fmt.Println("apple can smell")
}

func (a Apple) eat() {
	fmt.Println("apple can eat")
}

// 花只可以闻
type Flower struct{}

func (f Flower) smell() {
	fmt.Println("flower can smell")
}

// func TestType(items ...interface{}) {
// 	for k, v := range items {
// 		switch v.(type) {
// 		case string:
// 			fmt.Printf("type is string, %d[%v]\n", k, v)
// 		case bool:
// 			fmt.Printf("type is bool, %d[%v]\n", k, v)
// 		case int:
// 			fmt.Printf("type is int, %d[%v]\n", k, v)
// 		case float32, float64:
// 			fmt.Printf("type is float, %d[%v]\n", k, v)
// 		case Smellable:
// 			fmt.Printf("type is Smellable, %d[%v]\n", k, v)
// 		case *Smellable:
// 			fmt.Printf("type is *Smellable, %d[%p]\n", k, v)
// 		case Eatable:
// 			fmt.Printf("type is Eatable, %d[%v]\n", k, v)
// 		case *Eatable:
// 			fmt.Printf("type is Eatable, %d[%p]\n", k, v)
// 		case Fruitable:
// 			fmt.Printf("type is Fruitable, %d[%v]\n", k, v)
// 		case *Fruitable:
// 			fmt.Printf("type is Fruitable, %d[%p]\n", k, v)
// 		}
// 	}
// }
func main() {
	var s1 Smellable
	var s2 Eatable
	var apple = Apple{}
	var flower = Flower{}
	s1 = apple
	s1.smell()
	s1 = flower
	s1.smell()
	s2 = apple
	s2.eat()

	fmt.Println("\n组合继承")
	var s3 Fruitable
	s3 = apple
	s3.smell()
	s3.eat()

	// TestType(s1, s2, s3, apple, flower)
}

2.7 conversión de tipo de interfaz

Dado que la interfaz es de tipo general, cuando usamos la interfaz, es posible que no sepamos qué tipo está implementada.

Para los tipos de datos básicos, tenemos los métodos correspondientes para la conversión de tipos. Por supuesto, los tipos de interfaz también tienen conversión de tipos.

var s int

interfaz var x

x = s

y , ok := x.(int) //Convertir la interfaz a int, se puede omitir ok, pero si la conversión falla después de omitir, se informará un error.

La conversión verdadera tiene éxito, la conversión falsa falla y se usa el valor predeterminado

// 2.7 interface类型转换

package main

import "fmt"

func main() {
	var x interface{}

	s := "dar"
	x = s // 为什么能赋值到空接口, 每种类型都已经隐藏实现了空接口
	y, ok := x.(int)
	z, ok1 := x.(string)
	fmt.Println(y, ok)
	fmt.Println(z, ok1)
}

//0 false
//dar true

2.8 evaluación del tipo de interfaz

func TestType(elementos ...interfaz{}) {

para k, v := elementos de rango {

cambiar v.(tipo) {

cadena de casos:

fmt.Printf("el tipo es una cadena, %d[%v]\n", k, v)

caso booleano:

fmt.Printf("el tipo es booleano, %d[%v]\n", k, v)

caso int:

fmt.Printf("el tipo es int, %d[%v]\n", k, v)

caso float32, float64:

fmt.Printf("type is float, %d[%v]\n", k, v)

case Student:

fmt.Printf("type is Student, %d[%v]\n", k, v)

case *Student:

fmt.Printf("type is Student, %d[%p]\n", k, v)

}

}

}

package main

import "fmt"

type Student struct {
	Name string
}

func TestType(items ...interface{}) {
	for k, v := range items {
		switch v.(type) {
		case string:
			fmt.Printf("type is string, %d[%v]\n", k, v)
		case bool:
			fmt.Printf("type is bool, %d[%v]\n", k, v)
		case int:
			fmt.Printf("type is int, %d[%v]\n", k, v)
		case float32, float64:
			fmt.Printf("type is float, %d[%v]\n", k, v)
		case Student:
			fmt.Printf("type is Student, %d[%v]\n", k, v)
		case *Student:
			fmt.Printf("type is Student, %d[%p]\n", k, v)
		}
	}
}

func main() {
	var stu Student
	TestType("dar", 100, stu, 3.3)
}

//type is string, 0[dar]
//type is int, 1[100]
//type is Student, 2[{}]
//type is float, 3[3.3]

2.9 指向指针的接口变量

3 reflect反射是什么,为什么需要反射

反射定义:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修

改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修

改自己的行为。

GO 反射的意义:Go 语言的 ORM 库离不开它,Go 语言的 json 序列化库离不开它, fmt包字

符串格式化离不开它,Go 语言的运行时更是离不开它。

反射的目标:

1. 获取变量的类型信息,例如这个类型的名称、占用字节数、所有的方法列表、所有的内部字段结

构、它的底层存储类型等等。

2. 动态的修改变量内部字段值。比如 json 的反序列化,你有的是对象内部字段的名称和相应的值,

你需要把这些字段的值循环填充到对象相应的字段里

package main

import "fmt"

type Rect struct {
	Width  int
	Height int
}

func main() {
	var a interface{}
	var r = Rect{50, 50}
	a = &r // 指向了结构体指针

	var rx = a.(*Rect) // 转换成指针类型
	r.Width = 100
	r.Height = 100
	fmt.Println("r:", r)
	fmt.Println("rx:", rx)
	fmt.Printf("rx:%p, r:%p\n", rx, &r)
}

3 reflect反射

◼ go语言中的反射通过refect包实现,reflect包实现了运行时反射,允许程序操作任意类型的对象

◼ reflect包中的两个关键数据类Type和Value

func TypeOf(v interface{}) Type // 返回类型 实际是接口

func ValueOf(v interface{}) Value // 返回值 结构体

/*
但是有时你希望在运行时使用变量的在编写程序时还不存在的信息。
比如你正在尝试将文件或网络请求中的数据映射到变量中。或者你想构建一个适用于不同类型的工具。
在这种情况下,你需要使用反射。反射使您能够在运行时检查类型。
它还允许您在运行时检查,修改和创建变量,函数和结构体。
*/
package main

import (
	"fmt"
	"reflect"
	"strings"
)

type Foo struct {
	A int `tag1:"First Tag" tag2:"Second Tag"`
	B string
}

func main() {
	sl := []int{1, 2, 3}
	greeting := "hello"
	greetingPtr := &greeting
	f := Foo{A: 10, B: "Salutations"}
	fp := &f

	slType := reflect.TypeOf(sl)
	gType := reflect.TypeOf(greeting)
	grpType := reflect.TypeOf(greetingPtr)
	fType := reflect.TypeOf(f)
	fpType := reflect.TypeOf(fp)

	examiner(slType, 0)
	examiner(gType, 0)
	examiner(grpType, 0)
	examiner(fType, 0)
	examiner(fpType, 0)
}

func examiner(t reflect.Type, depth int) {
	fmt.Println(strings.Repeat("\t", depth), "Type is", t.Name(), "and kind is", t.Kind())
	switch t.Kind() {
	case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
		fmt.Println(strings.Repeat("\t", depth+1), "Contained type:")
		examiner(t.Elem(), depth+1)
	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			f := t.Field(i)
			fmt.Println(strings.Repeat("\t", depth+1), "Field", i+1, "name is", f.Name, "type is", f.Type.Name(), "and kind is", f.Type.Kind())
			if f.Tag != "" {
				fmt.Println(strings.Repeat("\t", depth+2), "Tag is", f.Tag)
				fmt.Println(strings.Repeat("\t", depth+2), "tag1 is", f.Tag.Get("tag1"), "tag2 is", f.Tag.Get("tag2"))
			}
		}
	}
}

PS D:\Workspace\Go\src\projects\demo> go run main.go
 El tipo es y el tipo es un segmento
         Tipo contenido:
         El tipo es int y el tipo es int
 El tipo es una cadena y el tipo es una cadena
 El tipo es y el tipo es ptr
         El tipo contenido :
         El tipo es cadena y el tipo es cadena
 El tipo es Foo y el tipo es estructura
         El nombre del campo 1 es A el tipo es int y el tipo es int
                 La etiqueta es etiqueta1: "Primera etiqueta" etiqueta2: "Segunda etiqueta"
                 etiqueta1 es la primera etiqueta etiqueta2 es el segundo
         campo de etiqueta 2 el nombre es B el tipo es una cadena y el tipo es una cadena
 El tipo es y el tipo es ptr El
         tipo contenido:
         el tipo es Foo y el tipo es una estructura
                 El nombre del campo 1 es tipo A es int y el tipo es int
                         La etiqueta es etiqueta1:"Primera etiqueta" etiqueta2:"Segunda etiqueta"
                         etiqueta1 es la primera etiqueta etiqueta2 es la segunda etiqueta
                 El nombre del campo 2 es B el tipo es cadena y el tipo es cadena

// 使用反射创建新实例
/*
除了检查变量的类型外,还可以使用反射来读取,设置或创建值。
首先,需要使用refVal := reflect.ValueOf(var)为变量创建一个reflect.Value实例。
如果希望能够使用反射来修改值,则必须使用refPtrVal := reflect.ValueOf(&var);
获得指向变量的指针。如果不这样做,则可以使用反射来读取该值,但不能对其进行修改。

一旦有了reflect.Value实例就可以使用Type()方法获取变量的reflect.Type。

如果要修改值,请记住它必须是一个指针,并且必须首先对其进行解引用。
使用refPtrVal.Elem().Set(newRefVal)来修改值,并且传递给Set()的值也必须是reflect.Value。
*/

package main

import (
	"fmt"
	"reflect"
)

type Foo struct {
	A int 
	B string
}

// 使用反射创建新实例
func main() {
	greeting := "hello"
	f := Foo{A: 10, B: "Salutations"}

	gVal := reflect.ValueOf(greeting)
	// not a pointer so all we can do is read it
	fmt.Println(gVal.Interface()) // hello

	gpVal := reflect.ValueOf(&greeting)
	// it’s a pointer, so we can change it, and it changes the underlying variable
	gpVal.Elem().SetString("goodbye")
	fmt.Println(greeting) // 修改成了goodbye

	fType := reflect.TypeOf(f)
	fVal := reflect.New(fType)
	fVal.Elem().Field(0).SetInt(20)
	fVal.Elem().Field(1).SetString("Greetings")
	f2 := fVal.Elem().Interface().(Foo) // 调用Interface()方法从reflect.Value回到普通变量值
	fmt.Printf("f2: %+v, %d, %s\n", f2, f2.A, f2.B)
	fmt.Println("f2:", f2)
	fmt.Println("f:", f)
}

PS D:\Workspace\Go\src\projects\demo> go run main.go
hola
adiós
f2: {A:20 B:Saludos}, 20, Saludos
f2: {20 Saludos}
f: {10 Saludos}

// 反射创建引用类型的实例
/*
使用反射来生成通常需要make函数的实例。可以使用reflect.MakeSlice,
reflect.MakeMap和reflect.MakeChan函数制作切片,Map或通道。
在所有情况下,都提供一个reflect.Type,然后获取一个reflect.Value,
可以使用反射对其进行操作,或者可以将其分配回一个标准变量。
*/
package main

import (
	"fmt"
	"reflect"
)

func main() {
	// 定义变量
	intSlice := make([]int, 0)
	mapStringInt := make(map[string]int)

	// 获取变量的 reflect.Type
	sliceType := reflect.TypeOf(intSlice)
	mapType := reflect.TypeOf(mapStringInt)

	// 使用反射创建类型的新实例
	intSliceReflect := reflect.MakeSlice(sliceType, 0, 0)
	mapReflect := reflect.MakeMap(mapType)

	// 将创建的新实例分配回一个标准变量
	v := 10
	rv := reflect.ValueOf(v)
	intSliceReflect = reflect.Append(intSliceReflect, rv)
	intSlice2 := intSliceReflect.Interface().([]int)
	fmt.Println("intSlice2: ", intSlice2)
	fmt.Println("intSlice : ", intSlice)

	k := "hello"
	rk := reflect.ValueOf(k)
	mapReflect.SetMapIndex(rk, rv)
	mapStringInt2 := mapReflect.Interface().(map[string]int)
	fmt.Println("mapStringInt2: ", mapStringInt2)
	fmt.Println("mapStringInt : ", mapStringInt)
}

PS D:\Workspace\Go\src\projects\demo> ve a ejecutar main.go
intSlice2: [10]
intSlice: []
mapStringInt2: map[hello:10]
mapStringInt: map[

/*
使用反射创建函数
反射不仅仅可以为存储数据创造新的地方。还可以使用reflect.MakeFunc函数使用reflect来创建新函数。
该函数期望我们要创建的函数的reflect.Type,以及一个闭包,其输入参数为[]reflect.Value类型,
其返回类型也为[] reflect.Value类型。下面是一个简单的示例,它为传递给它的任何函数创建一个定时包装器
*/
package main

import (
	"fmt"
	"reflect"
	"runtime"
	"time"
)

func MakeTimedFunction(f interface{}) interface{} {
	rf := reflect.TypeOf(f)
	fmt.Println("rf: ", rf)
	if rf.Kind() != reflect.Func {
		panic("expects a function")
	}
	vf := reflect.ValueOf(f)
	fmt.Println("vf: ", vf)
	wrapperF := reflect.MakeFunc(rf, func(in []reflect.Value) []reflect.Value {
		start := time.Now()
		out := vf.Call(in)
		end := time.Now()
		fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
		return out
	})
	return wrapperF.Interface()
}

func timeMe() {
	fmt.Println("starting")
	time.Sleep(1 * time.Second)
	fmt.Println("ending")
}

func timeMeToo(a int) int {
	fmt.Println("starting")
	time.Sleep(time.Duration(a) * time.Second)
	result := a * 2
	fmt.Println("ending")
	return result
}

func main() {

	fmt.Println("MakeTimedFunction1: ")
	timed := MakeTimedFunction(timeMe).(func())
	fmt.Println("转成普通函数1: ")
	timed()

	fmt.Println("\n\nMakeTimedFunction2: ")
	timedToo := MakeTimedFunction(timeMeToo).(func(int) int)
	fmt.Println("转成普通函数2: ")
	fmt.Println(timedToo(5))
}

PS D:\Workspace\Go\src\projects\demo> go run main.go
MakeTimedFunction1: 
rf: func()
vf: 0x733a80
Convertir a la función normal 1:
comenzar
a finalizar
llamando a main.timeMe tomó 1.0150935s


MakeTimedFunction2:
rf: func(int) int
vf: 0x733b40
Convertir a la función normal 2:
comenzar
a finalizar
llamando a main.timeMeToo tomó 5.0133999s
10

3 reflejar reflejo - Tipo y valor

El método TypeOf() devuelve la información de tipo de la variable para obtener una variable de tipo reflect.Type,

El método ValueOf() devuelve la información del valor de la variable para obtener una variable de tipo reflect.Value.

package main

import (
	"fmt"
	"reflect"
)

type Skills interface {
	reading()
	running()
}

type Student struct {
	Age  int
	Name string
}

func (self Student) runing() {
	fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
	fmt.Printf("%s is reading\n", self.Name)
}
func main() {
	stu1 := Student{Name: "dar", Age: 34}
	inf := new(Skills)
	stu_type := reflect.TypeOf(stu1)
	inf_type := reflect.TypeOf(inf).Elem() //  获取指针所指的对象类型
	fmt.Println("类型stu_type:", stu_type)
	fmt.Println(stu_type.String())  //main.Student
	fmt.Println(stu_type.Name())    //Student
	fmt.Println(stu_type.PkgPath()) //main
	fmt.Println(stu_type.Kind())    //struct
	fmt.Println(stu_type.Size())    //24

	fmt.Println("\n类型inf_type:", inf_type)
	fmt.Println(inf_type.NumMethod())                        //2
	fmt.Println(inf_type.Method(0), inf_type.Method(0).Name) // {reading main func() <invalid Value> 0} reading
	fmt.Println(inf_type.MethodByName("reading"))            //{reading main func() <invalid Value> 0} true

}

PS D:\Workspace\Go\src\projects\demo> go run main.go
类型stu_type: main.Student
main.Student
Student estructura
principal 24

类型inf_type: main.Skills
2
{lectura función principal() <valor no válido> 0} lectura
{lectura función principal() <valor no válido> 0} verdadero

package main

import (
	"fmt"
	"reflect"
)

type Skills interface {
	reading()
	running()
}

type Student struct {
	Name string "json:name"
	Age  int    "json:age"
}

func (self Student) runing() {
	fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
	fmt.Printf("%s is reading\n", self.Name)
}
func main() {
	stu1 := Student{Name: "dar", Age: 34}
	stu_type := reflect.TypeOf(stu1)
	fmt.Println(stu_type.NumField())          //2
	fmt.Println(stu_type.Field(0))            //{Name  string  0 [0] false}
	fmt.Println(stu_type.FieldByName("Name")) //{
       
       {Age  int  16 [1] false} true
	fmt.Println(stu_type.Field(1))            //{Name  string  0 [0] false}
	fmt.Println(stu_type.FieldByName("Age"))  //{
       
       {Age  int  16 [1] false} true
}
package main

import (
	"fmt"
	"reflect"
)

func main() {
	str := "dar"
	val := reflect.ValueOf(str).Kind()
	fmt.Println(val) //string
}

3 reflejar reflejo - pros y contras

Beneficios de la reflexión

1. Para reducir la tasa de errores causada por más escritura de código, haga una mejor reducción y abstracción

2. Para ser flexible, fácil de usar y conveniente, realice análisis, llamadas y procesamiento dinámicos

3. Para que el código se vea bien, sea fácil de leer y mejore la eficiencia del desarrollo, compense algunas diferencias con lenguajes dinámicos

Desventajas de la reflexión

1. El código relacionado con la reflexión suele ser difícil de leer. En ingeniería de software, la legibilidad del código también es un indicador muy importante.

2. El lenguaje Go es un lenguaje estático.Durante el proceso de codificación, el compilador puede detectar algunos errores de tipo por adelantado, pero no puede hacer nada con respecto al código de reflexión. Por lo tanto, es probable que el código que contiene la reflexión se ejecute durante mucho tiempo antes de cometer un error. En este momento, a menudo entra en pánico directamente, lo que puede causar graves consecuencias.

3. La reflexión tiene un impacto relativamente grande en el rendimiento, ya que se ejecuta uno o dos órdenes de magnitud más lento que el código normal. Por lo tanto, para el código en un proyecto que se encuentra en una posición crítica en la eficiencia operativa, intente evitar el uso de funciones de reflexión.

3.1 Tipo de reflexión

Tipo: El tipo Tipo se utiliza para representar un tipo de ir.

No todos los valores de tipo de todos los tipos de go pueden usar todos los métodos. Consulte la documentación de cada método para conocer las restricciones de uso. En la convocatoria hay puntos

Al calificar métodos de clase, primero debe usar el método Kind para conocer la clasificación del tipo. Llamar a un método no compatible con esta clase da como resultado un tiempo de ejecución

pánico.

El método para obtener el objeto Tipo:

func TypeOf(i interfaz{}) Tipo

str := "dar"
res_type := reflect.TypeOf(str)
fmt.Println(res_type) //string
int1 := 1
res_type2 := reflect.TypeOf(int1)
fmt.Println(res_type2) //int

3.1 Reflect Reflection-Type continuación-reflect.Type método general

func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。
func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。
func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。
func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。
func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。
func (t *rtype) Align() int // 获取 t 类型的值在分配内存时的字节对齐值。
func (t *rtype) FieldAlign() int // 获取 t 类型的值作为结构体字段时的字节对齐值。
func (t *rtype) NumMethod() int // 获取 t 类型的方法数量。
func (t *rtype) Method() reflect.Method // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。
// 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。
// 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。
func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。
func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。
func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。
func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。
func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作
//注意对于:数组、切片、映射、通道、指针、接口
func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型

3.1 reflejar reflexión-Escriba continuación-refleje.Escriba otros métodos

// valor

func (t *rtype) Bits() int // Obtiene el ancho de bit del tipo numérico, t debe ser entero, punto flotante o complejo

// matriz

func (t *rtype) Len() int // Obtiene el número de elementos en la matriz

// 映射

func (t *rtype) Key() reflect.Type // 获取映射的键类型

// 通道

func (t *rtype) ChanDir() reflect.ChanDir // 获取通道的方向

// 结构体

func (t *rtype) NumField() int // 获取字段数量

func (t *rtype) Field(int) reflect.StructField // 根据索引获取字段

func (t *rtype) FieldByName(string) (reflect.StructField, bool) // 根据名称获取字段

func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) // 根据指定的匹配函数 math 获取字段

func (t *rtype) FieldByIndex(index []int) reflect.StructField // 根据索引链获取嵌套字段

// 函数

func (t *rtype) NumIn() int // 获取函数的参数数量

func (t *rtype) In(int) reflect.Type // 根据索引获取函数的参数信息

func (t *rtype) NumOut() int // 获取函数的返回值数量

func (t *rtype) Out(int) reflect.Type // 根据索引获取函数的返回值信息

func (t *rtype) IsVariadic() bool // 判断函数是否具有可变参数

3.1 reflect反射-Type结构

,rtype 实现了 Type 接口的所有方法。剩下的不同的部分信

息各种特殊类型结构体都不一样。可以将 rtype 理解成父类,

特殊类型的结构体是子类,会有一些不一样的字段信息。

// 基础类型 rtype 实现了 Type 接口
type rtype struct {
size uintptr // 占用字节数
ptrdata uintptr
hash uint32 // 类型的hash值
...
kind uint8 // 元类型
...
}
// 切片类型
type sliceType struct {
rtype
elem *rtype // 元素类型
}
// 结构体类型
type structType struct {
rtype
pkgPath name // 所在包名
fields []structField // 字段列表
}
// 获取和设置普通类型的值
package main

import (
	"fmt"
	"reflect"
)

func main() {
	str := "dar"
	age := 11
	fmt.Println(reflect.ValueOf(str).String()) //获取str的值,结果dar
	fmt.Println(reflect.ValueOf(age).Int())    //获取age的值,结果age
	str2 := reflect.ValueOf(&str)              //获取Value类型
	str2.Elem().SetString("ki")              //设置值
	fmt.Println(str2.Elem(), age)              //ki 11

	age2 := reflect.ValueOf(&age) //获取Value类型
	fmt.Println("age2:", age2)

	age2.Elem().SetInt(40) //设置值
	fmt.Println("age:", age)
	fmt.Println("reflect.ValueOf(age):", reflect.ValueOf(age))
}

3.1 reflect反射- reflect.Value方法

reflect.Value.Kind():获取变量类别,返回常量
const (
Invalid Kind = iota //不存在的类型
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr // 指针的整数类型
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)

str := "dar"
val := reflect.ValueOf(str).Kind()
fmt.Println(val) //string

3.2 método de reflexión-reflexión-reflexión.Valor

Obtener método de valor:

func (v Value) Int() int64 // Obtener el valor de tipo int, si el valor de v no es un entero con signo, pánico.

func (v Value) Uint() uint64 // Obtenga el valor del tipo de unidad, si el valor de v no es un número entero sin signo (incluido uintptr), pánico.

func (v Value) Float() float64 // Obtenga el valor de tipo flotante, si el valor de v no es un tipo de coma flotante, pánico.

func (v Value) Complex() complex128 // Obtener el valor del tipo complejo, si el valor de v no es complejo, pánico.

func (v Value) Bool() bool // Obtener el valor de tipo booleano, si el valor de v no es booleano, pánico.

func (v Value) Len() int // Obtener la longitud del valor v, el valor v debe ser cadena, matriz, sector, mapa, canal.

func (v Value) Cap() int // Obtenga la capacidad de v value, v value debe ser un valor, segmento o canal.

func (v Value) Index(i int) reflect.Value // Obtiene el i-ésimo elemento del valor v, el valor v debe ser una cadena, una matriz o un segmento, y no puedo exceder el rango.

func (v Value) Bytes() []byte // Obtenga el valor del tipo de byte, si el valor v no es un segmento de byte, pánico.

func (v Value) Slice(i, j int) reflect.Value // Obtener la porción del valor v, longitud de la porción = j - i, capacidad de la porción = v.Cap() - i.

// v debe ser una cadena, un número, un segmento o direccionable si es una matriz. No puedo estar fuera de rango.

func (v Value) Slice3(i, j, k int) reflect.Value // Obtener el segmento del valor v, longitud del segmento = j - i, capacidad del segmento = k - i.

// i, j, k no pueden exceder la capacidad de v. yo <= j <= k.

// v debe ser una cadena, un número, un segmento o direccionable si es una matriz. No puedo estar fuera de rango.

func (v Value) MapIndex(key Value) reflect.Value // Obtenga el contenido del valor v según la clave, y el valor v debe ser un mapa.

// Si el elemento especificado no existe, o el valor v es un mapa no inicializado, devuelve un valor cero (reflect.ValueOf(nil))

func (v Value) MapKeys() []reflect.Value // Obtiene una lista desordenada de todas las claves para el valor de v, que debe ser un mapa.

// Si el valor v es un mapa no inicializado, devuelve una lista vacía.

func (v Value) OverflowInt(x int64) bool // Determine si x excede el rango de valores de v value, v value debe ser un entero con signo.

func (v Value) OverflowUint(x uint64) bool // Determine si x excede el rango de valores de v value, v value debe ser un entero sin signo.

func (v Value) OverflowFloat(x float64) bool // Determine si x excede el rango de valores de v value, v value debe ser un tipo de coma flotante.

func (v Value) OverflowComplex(x complex128) bool // Determine si x excede el rango de valores de v value, v value debe ser un número complejo.

//简单结构体操作
package main

import (
	"fmt"
	"reflect"
)

type Skills interface {
	reading()
	running()
}

type Student struct {
	Name string
	Age  int
}

func (self Student) runing() {
	fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
	fmt.Printf("%s is reading\n", self.Name)
}
func main() {
	stu1 := Student{Name: "dar", Age: 18}
	stu_val := reflect.ValueOf(stu1)                //获取Value类型
	fmt.Println(stu_val.NumField())                 //2
	fmt.Println(stu_val.Field(0), stu_val.Field(1)) //dar 18
	fmt.Println(stu_val.FieldByName("Age"))         //18

	stu_val2 := reflect.ValueOf(&stu1).Elem() // 要修改传引用或者指针
	stu_val2.FieldByName("Age").SetInt(33)    //设置字段值 ,结果33
	fmt.Println(stu1.Age)

}

3.2 método de reflexión-reflexión-reflexión.Valor

Establecer método de valor:

func (v Value) SetInt(x int64) //Establecer el valor del tipo int

func (v Value) SetUint(x uint64) // establece el valor del entero sin signo

func (v Value) SetFloat(x float64) // Establece el valor del tipo de coma flotante

func (v Value) SetComplex(x complex128) //Establecer valor de tipo complejo

func (v Value) SetBool(x bool) //设置布尔类型的值

func (v Value) SetString(x string) //设置字符串类型的值

func (v Value) SetLen(n int) // 设置切片的长度,n 不能超出范围,不能为负数。

func (v Value) SetCap(n int) //设置切片的容量

func (v Value) SetBytes(x []byte) //设置字节类型的值

func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在

添加

//通过反射调用结构体中的方法,通过reflect.Value.Method(i int).Call()
//或者reflect.Value.MethodByName(name string).Call()实现
package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name string
	Age  int
}

func (this *Student) SetName(name string) {
	this.Name = name
	fmt.Printf("set name %s\n", this.Name)
}

func (this *Student) SetAge(age int) {
	this.Age = age
	fmt.Printf("set age %d\n", age)
}

func (this *Student) String() string {
	fmt.Printf("this is %s\n", this.Name)
	return this.Name
}

func (this *Student) SetAgeAndName(age int, name string) {
	this.Age = age
	fmt.Printf("set age %d, name:%s\n", age, name)
}

func main() {
	stu1 := &Student{Name: "dar", Age: 18}
	val := reflect.ValueOf(stu1)         //获取Value类型,也可以使用reflect.ValueOf(&stu1).Elem()
	val.MethodByName("String").Call(nil) //调用String方法

	params := make([]reflect.Value, 1)
	params[0] = reflect.ValueOf(18)
	val.MethodByName("SetAge").Call(params) //通过名称调用方法

	params[0] = reflect.ValueOf("ki")
	// val.Method(1).Call(params) //通过方法索引调用
	val.Method(2).Call(params) //通过方法索引调用	通过索引的方式拿到函数不安全
	fmt.Println(stu1.Name, stu1.Age)

	params = make([]reflect.Value, 2)
	params[0] = reflect.ValueOf(18)
	params[1] = reflect.ValueOf("老师")
	val.MethodByName("SetAgeAndName").Call(params)
}

//this is dar
//set age 18
//set name ki
//ki 18

3.2 reflect反射- reflect.Value方法

其他方法:

//结构体相关:

func (v Value) NumField() int // 获取结构体字段(成员)数量

func (v Value) Field(i int) reflect.Value //根据索引获取结构体字段

func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段

func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)

func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回

零值(reflect.ValueOf(nil))

//通道相关:

func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。

func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。

func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。

func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。

func (v Value) Close() // 关闭通道

//函数相关

func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。

// 要传入多少参数就在 in 中存入多少元素。

// Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。

func (v Value) CallSlice(in []Value) []Value // 调用变参函数

3.2 reflect反射- reflect.Value 结构体

type Value struct {

    typ *rtype // 变量的类型结构体

    ptr unsafe.Pointer // 数据指针

    flag uintptr // 标志位

    }

3.3 Go 语言官方的反射三大定律1

官方对 Go 语言的反射功能做了一个抽象的描述,总结出了三大定律

1. Reflection goes from interface value to reflection object.

2. Reflection goes from reflection object to interface value.

3. To modify a reflection object, the value must be settable.

La primera ley significa que la reflexión convierte las variables de la interfaz en objetos de reflexión Tipo y valor

func TypeOf(v interfaz{}) Tipo

func ValueOf(v interfaz{}) Valor

La segunda ley significa que la reflexión se puede restaurar a la variable de interfaz original a través del objeto de reflexión Valor, que se refiere a Valor

El método Interface() proporcionado por la estructura.

func (v Valor) Interfaz() interfaz{}

La función de la tercera ley no se entiende bien, significa que desea utilizar la función de reflexión para modificar el valor de una variable, siempre que esta

El valor se puede modificar.

package main

import (
	"fmt"
	"reflect"
)

func test1() {
	var s int = 42
	var v = reflect.ValueOf(s)
	v.SetInt(43)
	fmt.Println(s)
}
func test2() {
	var s int = 42
	// 反射指针类型
	var v = reflect.ValueOf(&s)
	// 要拿出指针指向的元素进行修改
	v.Elem().SetInt(43)
	fmt.Println(s)
}
func main() {
	test1()
}

3.3 Tres leyes oficiales de reflexión en lenguaje Go 2

Las variables de tipo de valor no se pueden modificar por reflexión, porque antes de la reflexión, la variable de valor debe convertirse a

Si se reemplaza con una variable de interfaz, el contenido del valor se copiará superficialmente y la dirección de la memoria de datos a la que apunta el objeto de reflexión Valor no es el contenido de la variable original.

La dirección de almacenamiento es la dirección de memoria copiada. Esto significa que si una variable de tipo de valor se puede modificar por reflexión,

Entonces la operación de modificación no afectará en absoluto el valor de la variable original, por lo que la modificación es en vano. Por lo tanto, el paquete de reflejo está directamente prohibido.

Modifica variables de tipos de valor a través de la reflexión.

package main

import (
	"fmt"
	"reflect"
)

type Rect struct {
	Width  int
	Height int
	Name   string
}

// 通过统一的接口去实现属性设置的
func SetRectAttr(r *Rect, name string, value int) {
	var v = reflect.ValueOf(r)
	var field = v.Elem().FieldByName(name)
	field.SetInt(int64(value))
}

// 结构体也是值类型,也必须通过指针类型来修改。
func main() {
	var r = Rect{50, 100}
	SetRectAttr(&r, "Width", 100) // 修改属性的接口
	SetRectAttr(&r, "Height", 200)
	SetRectAttr(&r, "Name", "正方形")
	fmt.Println(r)
}
package main

import (
	"fmt"
	"reflect"
	"time"
)

const N = 1000000

func test1() {
	var sum = 0
	t0 := time.Now()

	for i := 0; i < (N); i++ {
		var s int = 42
		// 反射指针类型
		var v = reflect.ValueOf(&s)
		// 要拿出指针指向的元素进行修改
		v.Elem().SetInt(43)
		sum += s
	}

	elapsed := time.Since(t0)
	fmt.Println("反射赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
}

func test2() {
	var sum = 0
	t0 := time.Now()

	for i := 0; i < (N); i++ {
		var s int = 42
		s = 43
		sum += s
	}

	elapsed := time.Since(t0)
	fmt.Println("直接赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
}
func main() {
	test1()
	test2()
}
// 3 reflect反射 基础
package main

import (
	"fmt"
)

func main() {
	newValue := make(map[interface{}]interface{}, 0)
	newValue[11] = "dar"
	newValue["age"] = 18
	fmt.Println(newValue)
}

4 Ir mapa de combate

◼ El mapa en go es una referencia a la tabla hash, y el tipo se escribe como: map[key]value, donde la clave y el valor están emparejados respectivamente

Debe ser un tipo de datos, como map[string]string

◼ Se requiere que todas las claves tengan el mismo tipo de datos y todos los valores tengan el mismo tipo de datos (nota: la clave y el valor pueden tener diferentes

Tipo de datos, si quiere ser diferente, use la interfaz como valor)

El tipo de datos de la clave en el mapa.

◼ Cada clave en el mapa es única en el conjunto de claves y debe admitir operaciones == o !=

◼ Tipos comunes de clave: int, runa, cadena, estructura (cada elemento debe admitir la operación == o !=), puntero, según esto

Algunos tipos de tipos personalizados

El tipo float32/64 se puede usar sintácticamente como tipo de clave, pero generalmente no se usa como clave porque su tipo tiene errores.

4.1 Ejemplos de tipos de datos clave de combate reales del mapa Go

package main

import "fmt"

func main() {
	// m0 可以, key类型为string, 支持 == 比较操作
	{
		fmt.Println("---- m0 ----")
		var m0 map[string]string // 定义map类型变量m0,key的类型为string,value的类型string
		fmt.Println(m0)
	}

	// m1 不可以, []byte是slice,不支持 == != 操作,不可以作为map key的数据类型
	{
		// fmt.Println("---- m1 ----");
		//var m1 map[[]byte]string // 报错: invalid map key type []byte
		//fmt.Println(m1)

		// 准确说slice类型只能与nil比较,其他的都不可以,可以通过如下测试:
		// var b1,b2 []byte
		// fmt.Println(b1==b2) // 报错: invalid operation: b1 == b2 (slice can only be compared to nil)
	}

	// m2 可以, interface{}类型可以作为key,但是需要加入的key的类型是可以比较的
	{
		fmt.Println("---- m2 ----")
		var m2 map[interface{}]string
		m2 = make(map[interface{}]string)
		//m2[[]byte("k2")]="v2" // panic: runtime error: hash of unhashable type []uint8
		m2[123] = "123"
		m2[12.3] = "123"
		fmt.Println(m2)

		var str string = m2[12.3]
		fmt.Println(str)
	}

	// m3 可以, 数组支持比较
	{
		fmt.Println("---- m3 ----")
		a3 := [3]int{1, 2, 3}
		var m3 map[[3]int]string
		m3 = make(map[[3]int]string)
		m3[a3] = "m3"
		fmt.Println(m3)
	}

	// m4 可以,book1里面的元素都是支持== !=
	{
		fmt.Println("---- m4 ----")
		type book1 struct {
			name string
		}
		var m4 map[book1]string
		fmt.Println(m4)
	}

	// m5 不可以, text元素类型为[]byte, 不满足key的要求
	{
		fmt.Println("---- m5 ----")
		// type book2 struct {
		// 	name string
		// 	text []byte //没有这个就可以
		// }
		//var m5 map[book2]string //invalid map key type book2
		//fmt.Println(m5)
	}
}

4.2 Funcionamiento básico del mapa

creación de mapas

Hay dos formas de crear: una es a través del valor literal; la otra es a través de la función make

Adición, eliminación, modificación y consulta de mapas

recorrido del mapa

• El orden de recorrido es aleatorio

• Cuando se usa para recorrido de rango, k y v usan el mismo bloque de memoria, que también es propenso a errores

dirección

Añadir, modificar: m["c"] = "11"

Comprobar: v1 := m["x"]

v2, ok2 := m["x"]

Borrar: borrar(m, "x")

para k, v := rango m { fmt.Printf("k:[%v].v:[%v]\n", k, v) //

valor k,v de salida }

5 Ir cadena

Las cadenas generalmente vienen en dos diseños, una es una cadena de "caracteres" y la otra es una cadena de "bytes". Cada una de las cadenas de "caracteres"

Las palabras tienen una longitud fija, mientras que cada palabra de la cadena "byte" tiene una longitud variable. Las cadenas en el lenguaje Go son cadenas de "bytes".

Los caracteres ingleses ocupan 1 byte y los caracteres no ingleses ocupan varios bytes.

Donde el punto de código es el desplazamiento real de cada "palabra". Las cadenas en el lenguaje Go usan la codificación utf8 y los caracteres chinos generalmente requieren
Ocupa 3 bytes, el inglés solo necesita 1 byte. La función len() obtiene el número de bytes y se accede a la cadena a través del subíndice
Llegó en "bytes".
// 4-2 map基本操作
package main

import "fmt"

func create() {
	fmt.Println("map创建方式:")
	// 1 字面值
	{
		m1 := map[string]string{
			"m1": "v1", // 定义时指定的初始key/value, 后面可以继续添加
		}
		_ = m1

	}

	// 2 使用make函数
	{
		m2 := make(map[string]string) // 创建时,里面不含元素,元素都需要后续添加
		m2["m2"] = "v2"               // 添加元素
		_ = m2

	}

	// 定义一个空的map
	{
		m3 := map[string]string{}
		m4 := make(map[string]string)
		_ = m3
		_ = m4
	}
}

func curd() {
	fmt.Println("map增删改查:")
	// 创建
	fmt.Println("建:")
	m := map[string]string{
		"a": "va",
		"b": "vb",
	}
	fmt.Println(len(m)) // len(m) 获得m中key/value对的个数

	// 增加,修改
	fmt.Println("增改:")
	{
		// k不存在为增加,k存在为修改
		m["c"] = ""
		m["c"] = "11"                      // 重复增加(key相同),使用新的值覆盖
		fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"a":"va", "b":"vb", "c":"11"} 3
	}

	// 查
	fmt.Println("查:")
	{
		// v := m[k] // 从m中取键k对应的值给v,如果k在m中不存在,则将value类型的零值赋值给v
		// v, ok := m[k] // 从m中取键k对应的值给v,如果k存在,ok=true,如果k不存在,将value类型的零值赋值给v同时ok=false
		// 查1 - 元素不存在
		v1 := m["x"] //
		v2, ok2 := m["x"]
		fmt.Printf("%#v %#v %#v\n", v1, v2, ok2) // "" "" false

		// 查2 - 元素存在
		v3 := m["a"]
		v4, ok4 := m["a"]
		fmt.Printf("%#v %#v %#v\n", v3, v4, ok4) //"va" "va" true
	}
	fmt.Println("删:")
	// 删, 使用内置函数删除k/v对
	{
		// delete(m, k) 将k以及k对应的v从m中删掉;如果k不在m中,不执行任何操作
		delete(m, "x")                     // 删除不存在的key,原m不影响
		delete(m, "a")                     // 删除存在的key
		fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"b":"vb", "c":"11"} 2
		delete(m, "a")                     // 重复删除不报错,m无影响
		fmt.Printf("%#v %#v\n", m, len(m)) /// map[string]string{"b":"vb", "c":"11"} 2
	}
}

func travel() {
	fmt.Println("map遍历:")
	m := map[string]int{
		"a": 1,
		"b": 2,
	}
	for k, v := range m {
		fmt.Printf("k:[%v].v:[%v]\n", k, v) // 输出k,v值
	}

	var fruits = map[string]int{
		"apple":  2,
		"banana": 5,
		"orange": 8,
	}

	for name, score := range fruits {
		fmt.Println(name, score)
	}

	for name := range fruits {
		fmt.Println(name)
	}
}

func main() {
	create()
	curd()
	travel()
}

5.1 Go string string - transversal

paquete principal

importar "fmt"

función principal() {

    var s = "hip hop china"

    para yo := 0; i < len(s); yo++ {

        fmt.Printf("%x ", s[i])

    }

}

e5 98 bb e5 93 88 63 68 69 6e 61 

función principal() {

    var s = "hip hop china"

    para punto de código, runeValue := range s {

        fmt.Printf("[%d]: %x", punto de código, int32(runeValue))

    }

}

PS D:\Workspace\Go\src\projects\demo> ve a ejecutar main.go
[0]: 563b[3]: 54c8[6]: 63[7]: 68[8]: 69[9]: 6e[ 10]: 61 

5-2 Representación de memoria y manipulación de cadenas de bytes Go string

// 按字符 rune 遍历
package main

import "fmt"

func splice() {
	var s1 = "hello" // 静态字面量
	var s2 = ""
	for i := 0; i < 10; i++ {
		s2 += s1 // 动态构造
	}
	fmt.Println(len(s1))
	fmt.Println(len(s2))
}

// 字符串是只读的
func onlyread() {
	var s = "hello"
	s[0] = 'H'
}

// 切割切割
func cut() {
	var s1 = "hello world"
	var s2 = s1[3:8]
	fmt.Println(s2)
}

// 字节切片和字符串的相互转换
func string2bytes() {
	var s1 = "hello world"
	var b = []byte(s1) // 字符串转字节切片
	var s2 = string(b) // 字节切片转字符串
	fmt.Println(b)
	fmt.Println(s2)
}
func main() {
	splice()
	onlyread()
	cut()
	string2bytes()
}

1.3 Programación concurrente del lenguaje Go

1. Gorutina

1 Go corrutina Gorrutina
1.1 Uso de rutinas
1.2 Principio de rutina

1.1 Cómo usar Goroutines

Agregue la palabra clave go delante de una función o llamada de método, y ejecutará un nuevo Goroutine al mismo tiempo

1.2 El impacto de la salida anormal de la subrutina

Al utilizar subrutinas, se debe prestar especial atención a la protección de cada subrutina para garantizar su funcionamiento normal y seguro. Porque la rutina infantil

La salida anormal propagará la excepción a la rutina principal, lo que provocará que la rutina principal se cuelgue y luego todo el programa colapsará.

1.3 Manejo de excepciones Coroutine -recover

Recover es una función integrada del lenguaje Go, que puede recuperar la rutina go en el proceso de tiempo de inactividad.

Válido en la función defer.

Si la goroutine actual entra en pánico, llamar a recovery puede capturar el valor de entrada de panic y restaurar la normalidad

implementar.

// 1.3 协程异常处理-recover

package main

import (
	"fmt"
	"runtime"
)

// 崩溃时需要传递的上下文信息
type panicContext struct {
	function string // 所在函数
}

// 保护方式允许一个函数
func ProtectRun(entry func()) {
	// 延迟处理的函数
	defer func() {
		// 发生宕机时,获取panic传递的上下文并打印
		err := recover()
		switch err.(type) {
		case runtime.Error: // 运行时错误
			fmt.Println("runtime error:", err)
		default: // 非运行时错误
			fmt.Println("error:", err)
		}
	}()
	entry()
}
func main() {
	fmt.Println("运行前")
	// 允许一段手动触发的错误
	ProtectRun(func() {
		fmt.Println("手动宕机前")
		// 使用panic传递上下文
		panic(&panicContext{
			"手动触发panic",
		})
		fmt.Println("手动宕机后")
	})
	// 故意造成空指针访问错误
	ProtectRun(func() {
		fmt.Println("赋值宕机前")
		var a *int
		*a = 1
		fmt.Println("赋值宕机后")
	})
	fmt.Println("运行后")
}
// 1.3 协程异常处理-recover

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("run in main goroutine")
	go func() {
		fmt.Println("run in child goroutine")
		defer func() { // 要在对应的协程里执行
			fmt.Println("执行defer:")
			if err := recover(); err != nil {
				fmt.Println("捕获error:", err)
			}
		}()
		fmt.Println("run in grand grand child goroutine")
		var ptr *int
		*ptr = 0x12345 // 故意制造崩溃  ,该协程运行到这里结束
		go func() {
			fmt.Println("子子run in grand child goroutine")	// 这里也不会运行
			go func() {
				
			}()
		}()
		// time.Sleep(time.Second * 1)
		fmt.Println("离开: run in child goroutine leave") // 这里能否执行到
	}()
	time.Sleep(2 * time.Second)
	fmt.Println("main goroutine will quit")
}

1-4 Iniciar millones de rutinas

Go language puede administrar millones de rutinas al mismo tiempo

// 1-4 启动百万协程

package main

import (
	"fmt"
	"runtime"
	"time"
)

const N = 1000000

func main() {
	fmt.Println("run in main goroutine")
	i := 1
	for {
		go func() {
			for {
				time.Sleep(time.Second)
			}
		}()
		if i%10000 == 0 {
			fmt.Printf("%d goroutine started\n", i)
		}
		i++
		if i == N {
			break
		}
	}
	fmt.Println("NumGoroutine:", runtime.NumGoroutine())
	time.Sleep(time.Second * 15)
}

1-5 bucle infinito

Si hay un ciclo infinito de corrutinas individuales, ¿se privarán de hambre a otras corrutinas y no se ejecutarán?

package main

import (
	"fmt"
	"runtime"
	"syscall"
	"time"
)
// 获取的是线程ID,不是协程ID
func GetCurrentThreadId() int {
	var user32 *syscall.DLL
	var GetCurrentThreadId *syscall.Proc
	var err error

	user32, err = syscall.LoadDLL("Kernel32.dll")	// Windows用的
	if err != nil {
		fmt.Printf("syscall.LoadDLL fail: %v\n", err.Error())
		return 0
	}
	GetCurrentThreadId, err = user32.FindProc("GetCurrentThreadId")
	if err != nil {
		fmt.Printf("user32.FindProc fail: %v\n", err.Error())
		return 0
	}

	var pid uintptr
	pid, _, err = GetCurrentThreadId.Call()

	return int(pid)
}

func main() {
	// runtime.GOMAXPROCS(1)		
	// 读取当前的线程数
	fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // 本身电脑物理核心是4核 支持超线程 8核
	fmt.Println("run in main goroutine")
	n := 5
	for i := 0; i < n; i++ {
		go func() {
			fmt.Println("dead loop goroutine start, threadId:", GetCurrentThreadId())
			for {
			} // 死循环
			fmt.Println("dead loop goroutine stop")
		}()
	}

	go func() {
		var count = 0
		for {
			time.Sleep(time.Second)
			count++
			fmt.Println("for goroutine running:", count, "threadId:", GetCurrentThreadId())
		}
	}()
	fmt.Println("NumGoroutine: ", runtime.NumGoroutine())
	var count = 0
	for {
		time.Sleep(time.Second)
		count++
		fmt.Println("main goroutine running:", count, "threadId:", GetCurrentThreadId())
	}
}

1.6 设置线程数

Go的调度器使用了一个叫做GOMAXPROCS的变量来决定会有多少个操作系统的线程同时执

行Go的代码。其默认的值是运行机器上的CPU的核心数,所以在一个有8个核心的机器上

时,调度器一次会在8个OS线程上去调度GO代码。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println("runtime.NumCPU():", runtime.NumCPU())
	// 读取默认的线程数
	fmt.Println(runtime.GOMAXPROCS(0))
	// 设置线程数为 10
	runtime.GOMAXPROCS(10)
	// 读取当前的线程数
	fmt.Println(runtime.GOMAXPROCS(0))
}

1-7 G-P-M模型-为什么引入协程?

核心原因为goroutine的轻量级,无论是从进程到线程,还是从线程到协程,其核心都是为了使得

我们的调度单元更加轻量级。可以轻易得创建几万几十万的goroutine而不用担心内存耗尽等问题。

1-7 G-P-M模型-系统调用

调用system call陷入内核没有返回之前,为保证调度的并发性,golang 调度器在进入系统调用之前

从线程池拿一个线程或者新建一个线程,当前P交给新的线程M1执行。

G0 返回之后,需要找一个可用的 P 继续运行,
如果没有则将其放在全局队列等待调度。 M0
G0 返回后退出或放回线程池。

1-7 G-P-M模型-工作流窃取

在P队列上的goroutine全部调度完了之后,对应的M首先会尝试从global runqueue中获取

goroutine进行调度。如果golbal runqueue中没有goroutine,当前M会从别的M对应P的local

runqueue中抢一半的goroutine放入自己的P中进行调度。具体要看C代码去了。

2. Channel

2 通道channel

如果说goroutine是Go语音程序的并发体的话,那么channels它们之间的通信机制。

作为协程的输出,通道是一个容器,它可以容纳数据。

作为协程的输入,通道是一个生产者,它可以向协程提供数据。

通道作为容器是有限定大小的,满了就写不进去,空了就读不出来。

通道有它自己的类型,它可以限定进入通道的数据的类型。

2.1 创建通道

Solo hay una sintaxis para crear un canal, use la función make

Hay dos tipos de canales:

"Canal almacenado en búfer" var bufferedChannel = make(chan int(aquí está el tipo, cualquier tipo

fila), 1024)

"Canal sin búfer" var unbufferedChannel = make(chan int)

Se pueden comparar dos canales del mismo tipo utilizando el operador ==. Si dos canales se refieren al mismo objeto, entonces

El resultado de la comparación es cierto. Un canal también se puede comparar con cero.

package main

import "fmt"

func send(ch chan int) {
	i := 0
	for {
		i++
		ch <- i
	}
}

func recv(ch chan int) {
	value := <-ch
	fmt.Println(value)
	value = <-ch
	fmt.Println(value)
	close(ch)
}

// 向一个已经关闭的通道执行写操作会抛出异常,这意味着我们在写通道时一定要确保通道没有被关闭。
func main() {
	var ch = make(chan int, 4)
	go recv(ch)
	send(ch)
}

2.2 Canales de lectura y escritura

Go language ha diseñado un azúcar sintáctico de flecha especial <- para la lectura y escritura de canales, lo que hace que sea muy conveniente para nosotros usar canales. escribe la flecha en

El lado derecho de la variable de canal es el canal de escritura, y escribir la flecha en el lado izquierdo del canal es el canal de lectura. Solo se puede leer y escribir un elemento a la vez

// 2.2 读写通道

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan float32, 4)
	for i := 0; i < cap(ch); i++ {
		ch <- 1.0 // 写通道
	}
	for len(ch) > 0 {
		value := <-ch // 读通道
		fmt.Println(value)
	}

	// ch1 := make(chan int, 1) // 这里是缓存 有一个1元素
	ch1 := make(chan int) // 非缓存的,实际是0个,并不是1个

	go func() {
		time.Sleep(1 * time.Second)
		ch1 <- 1
		ch1 <- 1             // 这里已经阻塞
		fmt.Println("写入ch1") //这里没打印
	}()

	value1 := <-ch1
	value1 = <-ch1
	time.Sleep(5 * time.Second)
	fmt.Println("退出, value1:", value1)
}

Como contenedor, un canal se puede usar como un segmento, utilizando las funciones globales cap() y len() para obtener la capacidad y el contenido actual del canal.

El número de elementos en la sección.

2-3 Bloqueo de lectura y escritura

Cuando el canal está lleno, la operación de escritura se bloqueará y la corrutina se dormirá hasta que otras corrutinas lean el canal para hacer espacio y la corrutina

será despertado. Si las operaciones de escritura de varias corrutinas están bloqueadas, una operación de lectura solo activará una corrutina.

// 2-3 读写阻塞
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func send(ch chan int) {
	for {
		var value = rand.Intn(100)
		ch <- value
		fmt.Printf("send %d\n", value) // 这里没有延时
	}
}

func recv(ch chan int) {
	for {
		value := <-ch
		fmt.Printf("recv %d\n", value)
		time.Sleep(time.Second)
	}
}

func main() {
	var ch = make(chan int, 1)
	// 子协程循环读
	go recv(ch)
	// 主协程循环写
	send(ch)
}

2.4 Cerrar el canal

El canal del lenguaje Go es un poco como un archivo, que no solo admite operaciones de lectura y escritura, sino que también admite el cierre. Leer un canal cerrado inmediatamente

Es decir, se devuelve el "valor cero" del tipo de canal y escribir en un canal cerrado generará una excepción. Si los elementos del canal son enteros,

La operación de lectura no puede determinar si el canal está cerrado devolviendo un valor.

// 2.4 关闭通道
/*
Go 语言的通道有点像文件,不但支持读写操作, 还支持关闭。
读取一个已经关闭的通道会立即返回通道类型的「零值」,而写一个已经关闭的通道会抛异常。
如果通道里的元素是整型的,读操作是不能通过返回值来确定通道是否关闭的。
*/
package main

import "fmt"

func main() {
	var ch = make(chan int, 4)
	ch <- 1
	ch <- 2

	fmt.Println("len(ch):", len(ch), "cap(ch):", cap(ch))
	close(ch)

	value := <-ch
	fmt.Println(value)
	value = <-ch
	fmt.Println(value)
	value = <-ch
	fmt.Println(value)
	value = <-ch
	fmt.Println(value)
	ch <- 3
}

Seguro contra escritura de 2 a 5 canales

Escribir en un canal cerrado generará una excepción, lo que significa que debemos asegurarnos de que el canal no esté cerrado al escribir en el canal.

ha sido cerrado

 ¿Qué pasa si varias personas escriben?

// 2-5 通道写安全
package main

import "fmt"

func send(ch chan int) { // 在写入端关闭, 没有太多的通用性
	ch <- 1
	ch <- 2
	ch <- 3
	ch <- 4
	close(ch)
}

func recv(ch chan int) {
	for v := range ch {
		fmt.Println(v)
	}
	value := <-ch // 判别不了是否已经读取完毕
	fmt.Println("value:", value)
}

// 确保通道写安全的最好方式是由负责写通道的协程自己来关闭通道,读通道的协程不要去关闭通道。
func main() {
	var ch = make(chan int, 1)
	go send(ch)
	recv(ch)
}

2-6 Grupo de espera

Cerrar el canal en el lado de escritura es efectivo para programas de una sola escritura, pero ¿qué pasa con los programas de escritura múltiple?

Use el objeto WaitGroup proporcionado por el paquete de sincronización integrado, que usa un conteo para esperar a que se complete el evento especificado.

// 2-6 WaitGroup 在写端关闭channel对单写的程序有效,但是多写的时候呢?

package main

import (
	"fmt"
	"sync"
	"time"
)

func send(ch chan int, wg *sync.WaitGroup) {
	defer wg.Done() // 计数值减一
	i := 0
	for i < 4 {
		i++
		ch <- i
	}
}

func recv(ch chan int) {
	for v := range ch {
		fmt.Println(v)
	}
}
// 只要一个值能做界定符 比如nil, 比如0xfffe
func main() {
	var ch = make(chan int, 4)
	var wg = new(sync.WaitGroup)
	wg.Add(2)       // 增加计数值
	go send(ch, wg) // 写
	go send(ch, wg) // 写
	go recv(ch)
	// Wait() 阻塞等待所有的写通道协程结束
	// 待计数值变成零,Wait() 才会返回
	wg.Wait()
	// 关闭通道
	close(ch)
	time.Sleep(time.Second)
}

2-7 multicanal

En el mundo real, también existe un escenario de paso de mensajes, es decir, los consumidores tienen múltiples fuentes de consumo, siempre que haya un

La fuente produce los datos y el consumidor puede leer los datos para su consumo. En este momento, el número de múltiples canales de origen puede ser

Los datos se agregan al canal de destino y luego se consumen en el canal de destino

/*
2-7 多路通道
在真实的世界中,还有一种消息传递场景,那就是消费者有多个消费来源,只要有一个来源生产了数据,
消费者就可以读这个数据进行消费。这时候可以将多个来源通道的数据汇聚到目标通道,然后统一在目标通道进行消费。

*/
package main

import (
	"fmt"
	"time"
)

// 每隔一会生产一个数
func send(ch chan int, gap time.Duration) {
	i := 0
	for {
		i++
		ch <- i
		time.Sleep(gap)
	}
}

// 将多个原通道内容拷贝到单一的目标通道
func collect(source chan int, target chan int) {
	for v := range source {
		target <- v // ch3 <- ch2 ; ch3 <- ch1
	}
}

func collect2(ch1 chan int, ch2 chan int, target chan int) {
	for {
		select {
		case v := <-ch1:
			target <- v
		case v := <-ch2:
			target <- v
		default: // 非阻塞
			fmt.Println("collect2")
		}
	}
}

// 从目标通道消费数据
func recv(ch chan int) {
	for v := range ch {
		fmt.Printf("receive %d\n", v)
	}
}

func main() {
	var ch1 = make(chan int)
	var ch2 = make(chan int)
	var ch3 = make(chan int)
	go send(ch1, time.Second)
	go send(ch2, 2*time.Second)
	// go collect(ch1, ch3)
	// go collect(ch2, ch3)
	go collect2(ch1, ch2, ch3)
	recv(ch3)
}

Selección de multiplexación 2-8

// 2-8 多路复用select
package main

import (
	"fmt"
	"time"
)

func send(ch chan int, gap time.Duration) {
	i := 0
	for {
		i++
		ch <- i
		time.Sleep(gap)
	}
}

func recv(ch1 chan int, ch2 chan int) {
	for {
		select {
		case v := <-ch1:
			fmt.Printf("recv %d from ch1\n", v)
		case v := <-ch2:
			fmt.Printf("recv %d from ch2\n", v)
		}
	}
}

func main() {
	var ch1 = make(chan int)
	var ch2 = make(chan int)
	go send(ch1, time.Second)
	go send(ch2, 2*time.Second)
	recv(ch1, ch2)
}

2-9 Lectura y escritura sin bloqueo

Lectura y escritura sin bloqueo de canales. Las operaciones de lectura no se bloquean cuando el canal está vacío y las operaciones de escritura no se bloquean cuando el canal está lleno. No

El bloqueo de lectura y escritura debe basarse en la rama predeterminada de la declaración de selección. Cuando todos los canales de la instrucción select no se pueden leer ni escribir, si

Si se define la rama predeterminada, se ejecutará la lógica de la rama predeterminada, logrando así el efecto de no bloqueo.

// 2-9 非阻塞读写
package main

import (
	"fmt"
	"time"
)

func send(ch1 chan int, ch2 chan int) {
	i := 0
	for {
		i++
		select {
		case ch1 <- i:
			fmt.Printf("send ch1 %d\n", i)
		case ch2 <- i:
			fmt.Printf("send ch2 %d\n", i)
		default:
			fmt.Printf("ch block\n")
			time.Sleep(2 * time.Second) // 这里只是为了演示
		}
	}
}

func recv(ch chan int, gap time.Duration, name string) {
	for v := range ch {
		fmt.Printf("receive %s %d\n", name, v)
		time.Sleep(gap)
	}
}

func main() {
	// 无缓冲通道
	var ch1 = make(chan int)
	var ch2 = make(chan int)
	// 两个消费者的休眠时间不一样,名称不一样
	go recv(ch1, time.Second, "ch1")
	go recv(ch2, 2*time.Second, "ch2")
	send(ch1, ch2)
}

2-10 Modelo de productor y consumidor

Modelo de consumo del productor

// 2-10 生产者、消费者模型
package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"
)

// 生产者
func Producer(factor int, out chan<- int) {
	for i := 0; ; i++ {
		out <- i * factor
		time.Sleep(5 * time.Second)
	}
}

// 消费者
func Consumer(in <-chan int) {
	for v := range in {
		fmt.Println(v)
	}
}

func main() {
	ch := make(chan int, 64)
	go Producer(3, ch) // 生成3的倍数序列
	go Producer(5, ch) // 生成5的倍数序列

	go Consumer(ch)

	//Ctrl +C 退出
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	fmt.Printf("wait Ctrl +C")
	fmt.Printf("quit (%v)\n", <-sig)
}

3. Seguridad del hilo

Seguridad de subprocesos 3-1 - Bloqueo mutex

Las herramientas de verificación de carreras se basan en la inspección del código en tiempo de ejecución en lugar del análisis estático del código. Esto significa que aquellos que no

Si existe un riesgo de seguridad en la lógica del código que tiene la oportunidad de ejecutarse, no se puede verificar.

Necesidad de agregar -ejecución de carrera

package main

import "fmt"
// go多协程 是有竞态,不像以前的ntyco,libco没有竞态的
func write(d map[string]int) {
	d["fruit"] = 2
}

func read(d map[string]int) {
	fmt.Println(d["fruit"])
}

// go run -race 3-1-unsafe.go
func main() {
	d := map[string]int{}
	go read(d)
	write(d)
}

3-2 Evitar la duplicación de bloqueo

sync.Mutex es un objeto de estructura, este objeto debe evitar ser copiado en el proceso de uso - copia superficial. La copia resultará en

Si la cerradura está "partida", no podrá protegerla. Así que trate de usar su tipo de puntero en uso normal. los lectores pueden

Intente reemplazar el tipo anterior con un tipo que no sea de puntero, y luego ejecute la herramienta de verificación de carrera, y verá mensajes de advertencia en todo el

Pantalla. La replicación de bloqueo existe en la asignación de variables de estructura, el paso de parámetros de función y el paso de parámetros de método, todos los cuales necesitan atención.

package main

import (
	"fmt"
	"sync"
)

type SafeDict struct {
	data  map[string]int
	mutex *sync.Mutex
}

func NewSafeDict(data map[string]int) *SafeDict {
	return &SafeDict{
		data:  data,
		mutex: &sync.Mutex{},
	}
}

// defer 语句总是要推迟到函数尾部运行,所以如果函数逻辑运行时间比较长,
// 这会导致锁持有的时间较长,这时使用 defer 语句来释放锁未必是一个好注意。
func (d *SafeDict) Len() int {
	d.mutex.Lock()
	defer d.mutex.Unlock()
	return len(d.data)
}

// func (d *SafeDict) Test() int {
// 	d.mutex.Lock()
// 	length := len(d.data)
// 	d.mutex.Unlock() // 手动解锁 减少粒度	// 这种情况就不要用 defer d.mutex.Unlock()
// 	fmt.Println("length: ", length)
// 	// 这里还有耗时处理 耗时1000ms
// }

func (d *SafeDict) Put(key string, value int) (int, bool) {
	d.mutex.Lock()
	defer d.mutex.Unlock()
	old_value, ok := d.data[key]
	d.data[key] = value
	return old_value, ok
}

func (d *SafeDict) Get(key string) (int, bool) {
	d.mutex.Lock()
	defer d.mutex.Unlock()
	old_value, ok := d.data[key]
	return old_value, ok
}

func (d *SafeDict) Delete(key string) (int, bool) {
	d.mutex.Lock()
	defer d.mutex.Unlock()
	old_value, ok := d.data[key]
	if ok {
		delete(d.data, key)
	}
	return old_value, ok
}

func write(d *SafeDict) {
	d.Put("banana", 5)
}

func read(d *SafeDict) {
	fmt.Println(d.Get("banana"))
}

// go run -race 3-2-lock.go
func main() {
	d := NewSafeDict(map[string]int{
		"apple": 2,
		"pear":  3,
	})
	go read(d)
	write(d)
}

3-3 Uso de un campo de bloqueo anónimo

En el capítulo sobre estructuras, sabemos que la estructura externa puede heredar automáticamente todos los métodos de la estructura interna anónima. si lo anterior

La estructura SafeDict se modifica para hacer que el campo de bloqueo sea anónimo, lo que puede simplificar un poco el código.

package main

import (
	"fmt"
	"sync"
)

type SafeDict struct {
	data map[string]int
	*sync.Mutex
}

func NewSafeDict(data map[string]int) *SafeDict {
	return &SafeDict{
		data,
		&sync.Mutex{}, // 一样是要初始化的
	}
}

func (d *SafeDict) Len() int {
	d.Lock()
	defer d.Unlock()
	return len(d.data)
}

func (d *SafeDict) Put(key string, value int) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	d.data[key] = value
	return old_value, ok
}

func (d *SafeDict) Get(key string) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	return old_value, ok
}

func (d *SafeDict) Delete(key string) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	if ok {
		delete(d.data, key)
	}
	return old_value, ok
}

func write(d *SafeDict) {
	d.Put("banana", 5)
}

func read(d *SafeDict) {
	fmt.Println(d.Get("banana"))
}

func main() {
	d := NewSafeDict(map[string]int{
		"apple": 2,
		"pear":  3,
	})
	go read(d)
	write(d)
}

3-4 Uso de bloqueos de lectura y escritura

En las aplicaciones diarias, la mayoría de las estructuras de datos concurrentes se leen más y se escriben menos. Para ocasiones en las que se lee más y se escribe menos, puede reemplazar el bloqueo mutex con

Los bloqueos de lectura y escritura pueden mejorar efectivamente el rendimiento. El paquete de sincronización también proporciona un objeto de bloqueo de lectura y escritura RWMutex, que es diferente de un mutex con solo dos

Hay dos métodos de uso común Lock() y Unlock() El bloqueo de lectura y escritura proporciona cuatro métodos de uso común, que son el bloqueo de escritura Lock() y el bloqueo de liberación de escritura.

Desbloquear(), leer bloquear RLock() y leer liberar bloquear RUnlock(). El bloqueo de escritura es un bloqueo exclusivo, y cuando se agrega el bloqueo de escritura, se bloquearán otras rutinas.

Agregue bloqueo de lectura y bloqueo de escritura. El bloqueo de lectura es un bloqueo compartido. Agregar bloqueo de lectura también puede permitir que otras rutinas agreguen bloqueo de lectura, pero bloqueará la adición de bloqueo de escritura.

// 3-4 使用读写锁
package main

import (
	"fmt"
	"sync"
)

type SafeDict struct {
	data map[string]int
	*sync.RWMutex	//  sync.Mutex API也有点不一样
}

func NewSafeDict(data map[string]int) *SafeDict {
	return &SafeDict{data, &sync.RWMutex{}}
}

func (d *SafeDict) Len() int {
	d.RLock()
	defer d.RUnlock()
	return len(d.data)
}

func (d *SafeDict) Put(key string, value int) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	d.data[key] = value
	return old_value, ok
}

func (d *SafeDict) Get(key string) (int, bool) {
	d.RLock()
	defer d.RUnlock()
	old_value, ok := d.data[key]
	return old_value, ok
}

func (d *SafeDict) Delete(key string) (int, bool) {
	d.Lock()
	defer d.Unlock()
	old_value, ok := d.data[key]
	if ok {
		delete(d.data, key)
	}
	return old_value, ok
}

func write(d *SafeDict) {
	d.Put("banana", 5)
}

func read(d *SafeDict) {
	fmt.Println(d.Get("banana"))
}

func main() {
	d := NewSafeDict(map[string]int{
		"apple": 2,
		"pear":  3,
	})
	go read(d)
	write(d)
}

3.5 Modelo de publicación y suscripción

Integral previamente aprendido

Tema de configuración de filtro de soporte

// 3.5 发布订阅模型
package main

import (
	"fmt"
	"strings"
	"sync"
	"time"
)

type (
	subscriber chan interface{}         // 订阅者为一个通道
	topicFunc  func(v interface{}) bool // 主题为一个过滤器
)

// 发布者对象
type Publisher struct {
	m           sync.RWMutex             //读写锁
	buffer      int                      // 订阅队列的缓存大小
	timeout     time.Duration            // 发布超时时间
	subscribers map[subscriber]topicFunc // 订阅者信息
}

// 构建一个发布者对象,可以设置发布超时时间和缓存队列的长度
func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher {
	return &Publisher{
		buffer:      buffer,
		timeout:     publishTimeout,
		subscribers: make(map[subscriber]topicFunc),
	}

}

// 关闭发布者对象,同时关闭所有的订阅通道
func (p *Publisher) Close() {
	p.m.Lock()
	defer p.m.Unlock()
	for sub := range p.subscribers {
		delete(p.subscribers, sub)
		close(sub)
	}
}

// 添加一个新的订阅者,订阅过滤器筛选后的主题
func (p *Publisher) SubscribeTopic(topic topicFunc) chan interface{} {
	ch := make(chan interface{}, p.buffer)
	p.m.Lock()
	p.subscribers[ch] = topic
	p.m.Unlock()
	return ch
}

// 添加一个新的订阅者,订阅全部主题
func (p *Publisher) Subscribe() chan interface{} {
	return p.SubscribeTopic(nil)
}

// 退出订阅
func (p *Publisher) Evict(sub chan interface{}) {
	p.m.Lock()
	defer p.m.Unlock()
	delete(p.subscribers, sub)
	close(sub)
}

// 发送主题,可以容忍一定的超时
func (p *Publisher) sendTopic(
	sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup,
) {
	defer wg.Done()
	if topic != nil && !topic(v) { // 过滤信息
		return
	}
	select {
	case sub <- v:
	case <-time.After(p.timeout): // 超时
	}
}

// 发布一个主题
func (p *Publisher) Publish(v interface{}) {
	p.m.Lock()
	defer p.m.Unlock()
	var wg sync.WaitGroup
	for sub, topic := range p.subscribers {
		wg.Add(1)
		go p.sendTopic(sub, topic, v, &wg)
	}
	wg.Wait()
}

func main() {
	p := NewPublisher(100*time.Millisecond, 10)
	defer p.Close()

	all := p.Subscribe()

	golang := p.SubscribeTopic(func(v interface{}) bool {
		if s, ok := v.(string); ok {
			return strings.Contains(s, "golang")
		}
		return false
	})

	p.Publish("hello world")
	p.Publish("hello, golang")
	go func() {
		for msg := range all {
			fmt.Println("all:", msg)
		}
	}()
	go func() {
		for msg := range golang {
			fmt.Println("golang:", msg)
		}
	}()

	// 运行一段时间后退出
	time.Sleep(3 * time.Second)
}

3.6 sincronización. Una vez inicialización

sync.Once.Do(f func()) es algo muy interesante, puede garantizar que una vez se ejecute solo una vez, sin importar si reemplaza once.Do(xx)

En este método, este bloque sync.Once solo se ejecutará una vez.

package main

import (
	"fmt"
	"sync"
	"time"
)

var once sync.Once

func main() {
	for i, v := range make([]string, 10) {
		once.Do(onces)
		fmt.Println("count:", v, "---", i)
	}
	for i := 0; i < 5; i++ {

		go func() {
			once.Do(onced)
			fmt.Println("213")
		}()
	}
	time.Sleep(4000)
}
func onces() {
	fmt.Println("执行onces")
}
func onced() {
	fmt.Println("执行onced")
}

4. contexto

4 Ir Contexto de idioma

¿Por qué necesitas Contexto?

• Cada transacción debe tener un límite de tiempo de espera

• Necesidad de pasar este tiempo de espera en la llamada

• Por ejemplo, cuando comenzamos a procesar la solicitud, decimos que es un tiempo de espera de 3 segundos

• Entonces, en medio de una llamada de función, ¿cuánto tiempo queda en este tiempo de espera?

• ¿Dónde se debe almacenar esta información para que la solicitud se procese en el medio?

puede parar

El contexto es coroutine seguro. Se puede pasar un solo Contexto a cualquier número de rutinas en el código, y el

Cuando se cancela el contexto, la señal se puede pasar a todas las rutinas.

4.1 Interfaz de contexto

escriba la interfaz de contexto {

Deadline() (tiempo límite.Tiempo, ok bool)

Listo() <-estructura chan{}

Err () error

Valor(interfaz clave{}) interfaz{}

}

◼ El método Fecha límite significa obtener la fecha límite establecida. El primer tipo de devolución es la fecha límite. Cuando se alcanza este punto de tiempo, el Contexto

Se iniciará automáticamente una solicitud de cancelación; cuando el segundo valor de retorno es ok==false, significa que no se ha establecido una fecha límite. Si se requiere la cancelación, se debe llamar a la función de cancelación para cancelar

◼ El método Done devuelve un chan de solo lectura, el tipo es struct{}, estamos en la gorutina, si el chan devuelto por el método puede

Leer significa que el contexto principal ha iniciado una solicitud de cancelación.Después de recibir esta señal a través del método Listo, debemos

Realice la limpieza, luego salga de la rutina y libere los recursos

◼ El método Err devuelve el motivo del error de la cancelación, porque se canceló el Contexto.

◼ El método Value obtiene el valor vinculado al contexto, que es un par clave-valor, por lo que el valor correspondiente solo se puede obtener a través de una clave, que generalmente es segura para subprocesos.

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	ctx, cancel1 := context.WithCancel(context.Background())
	go func(ctx context.Context) {
		for {
			select {
			case v := <-ctx.Done():
				fmt.Println("监控退出,停止了..., v: ", v, ", err:", ctx.Err())
				return
			default:
				time.Sleep(2 * time.Second)
				fmt.Println("goroutine监控中...")
				// time.Sleep(2 * time.Second)
			}
		}
	}(ctx)

	time.Sleep(5 * time.Second)
	fmt.Println("可以了,通知监控停止")
	cancel1()

	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

4.1 Fondo() y TODO()

◼ Hay dos funciones integradas en el lenguaje Go: Background() y TODO(). Estas dos funciones devuelven un fondo y una tarea pendiente que implementan la interfaz Context respectivamente.

◼ Background() se usa principalmente en la función principal, la inicialización y el código de prueba, como la estructura de árbol superior de Context

Contexto, que es el Contexto raíz.

◼ TODO(), aún no conoce el escenario de uso específico, puede usar esto cuando no sabe qué Contexto usar.

◼ background y todo son esencialmente del tipo de estructura emptyCtx, que es un contexto que no se puede cancelar, no se establece una fecha límite y no se lleva ningún valor.

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second))
	// var wg sync.WaitGroup
	go func(ctx context.Context) {
		// wg.Add(1)
		// defer wg.Done()
		for {
			select {
			case <-ctx.Done():
				fmt.Println("监控退出,停止了..., err:", ctx.Err())
				return
			default:
				time.Sleep(2 * time.Second)
				fmt.Println("goroutine监控中...")
				// time.Sleep(2 * time.Second)
			}
		}
	}(ctx)
	// cancel()
	time.Sleep(5 * time.Second)
	fmt.Println("可以了,通知监控停止")

	cancel()
	// wg.Wait() // 等待协程退出
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

4.2 Herencia y derivación del Contexto

func WithCancel(contexto principal) (contexto ctx, cancel CancelFunc)

func WithDeadline(Contexto principal, fecha límite.Tiempo) (Contexto, CancelFunc)

func WithTimeout(Contexto principal, tiempo de espera. Duración) (Contexto, CancelFunc)

func WithValue(Contexto padre, interfaz clave{}, interfaz val{}) Contexto

Las cuatro funciones With reciben un parámetro de partent, que es el Contexto principal, y necesitamos crear un elemento secundario basado en este Contexto principal.

Contexto significa

◼ Con la función Cancelar, pase un Contexto principal como parámetro, devuelva el Contexto secundario y una función de cancelación para cancelar el Contexto

◼ La función WithDeadline, similar a WithCancel, pasará un parámetro de fecha límite adicional, lo que significa que cuando se alcance este punto de tiempo, se cancelará automáticamente

Contexto, por supuesto, podemos cancelarlo por adelantado a través de la función cancelar sin esperar hasta este momento

◼ WithTimeout es básicamente lo mismo que WithDeadline. Esto significa que el tiempo de espera se cancela automáticamente. Significa que el Contexto se cancela automáticamente después de cierto tiempo, pero los parámetros son diferentes.

◼ La función WithValue no tiene nada que ver con cancelar el Contexto. Es para generar un Contexto vinculado a un par de datos clave-valor. Se puede acceder a los datos vinculados a través del método Context.Value

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func work(ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		select {
		case <-ctx.Done():
			fmt.Println("监控退出,停止了...")
			return
		default:
			fmt.Println("hello")
			time.Sleep(time.Second)
		}
	}
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel() // 在建立之后,立即 defer cancel() 是一个好习惯。
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go work(ctx, &wg)
	}
	time.Sleep(time.Second)

	wg.Wait()
}

4.3 Principio de uso del contexto

◼ No pongas el Contexto en la estructura, sino pásalo como parámetro

◼ Los métodos de función que toman Contexto como parámetro deben tomar Contexto como primer parámetro

◼ Al pasar Contexto a un método de función, no pase nil, si no sabe qué pasar, use

context.TODO

◼ Los métodos de contexto relacionados con el valor deben pasar los datos necesarios del campo de solicitud y no deben usarse para pasar parámetros opcionales;

◼ El contexto es seguro para subprocesos y se puede pasar de forma segura entre múltiples Goroutines.

package main

import (
	"context"
	"fmt"
	"time"
)

var key string = "name"
var key2 string = "name1"

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	//附加值
	valueCtx := context.WithValue(ctx, key, "key【监控1】") // 是否可以有多个key
	valueCtx2 := context.WithValue(valueCtx, key2, "key【监控2】")

	go watch(valueCtx2)
	time.Sleep(5 * time.Second)
	fmt.Println("可以了,通知监控停止")
	cancel()
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

func watch(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			//取出值
			fmt.Println(ctx.Value(key), "监控退出,停止了...")
			fmt.Println(ctx.Value(key2), "监控退出,停止了...")
			return
		default:
			//取出值
			fmt.Println(ctx.Value(key), "goroutine监控中...")
			time.Sleep(2 * time.Second)
		}
	}
}

4.4 Contextos derivados contexto derivado

El paquete de contexto proporciona funciones para derivar nuevos valores de contexto a partir de valores de contexto existentes. Estos valores forman un árbol: cuando un

Cuando se cancela un contexto, también se cancelan todos los contextos derivados de él.

package main

import (
	"context"
	"fmt"
	"time"
)

func work(ctx context.Context, str string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("退出 ", str)
			return
		}
	}
}

func main() {
	ctx1 := context.Background()
	ctx2, cancel2 := context.WithCancel(ctx1)
	ctx3, cancel3 := context.WithTimeout(ctx2, time.Second*5)
	ctx4, cancel4 := context.WithTimeout(ctx3, time.Second*3)
	ctx5, cancel5 := context.WithTimeout(ctx4, time.Second*6)
	ctx6 := context.WithValue(ctx5, "userID", 12)
	go work(ctx1, "ctx1")
	go work(ctx2, "ctx2")
	go work(ctx3, "ctx3")
	go work(ctx4, "ctx4")
	go work(ctx5, "ctx5")
	go work(ctx6, "ctx6")

	time.Sleep(1 * time.Second)
	cancel5()
	time.Sleep(5 * time.Second)
	cancel3()
	cancel4()
	cancel5()
	cancel2()
}

Tutorial recomendado

https://geektutu.com/post/geecache-day1.html

Cómo usar Context correctamente en Golang

https://studygolang.com/articles/23247?fr=sidebar 

cgo ir y c mezclar

#include <stdio.h>  
#include <string.h>
char *fun(char *p1, char *p2)
{
    int i = 0;
    i = strcmp(p1, p2);
    if (0 == i)
    {
        return (p1);
    }
    else
    {
        return (p2);
    }
}
int main()
{
    char *(*pf)(char *p1, char *p2);
    pf = &fun;
    (*pf)("aa", "bb");
    return (0);
}

2.1 Programación en red de lenguaje Go y Redis combat

Ir a la programación de la red de idiomas y el uso común de la biblioteca

1. Programación de redes

En la actualidad, los servidores principales generalmente usan "multiplexación de E/S + sin bloque" (algunos también combinan multilínea).

proceso, multiproceso). Sin embargo, la multiplexación de E/S también trae mucha complejidad a los usuarios, por lo que posteriores

Hay muchos marcos de multiplexación de E/S de alto rendimiento, como libevent, libev, libuv, etc., para ayudar a los desarrolladores.

Simplifique la complejidad del desarrollo y reduzca la carga mental. Sin embargo, los diseñadores de Go parecen pensar que la generalidad de la multiplexación de E/S

El método de dividir el flujo de control a través del mecanismo de devolución de llamada sigue siendo complicado y contrario al diseño de "lógica general". Por esta razón, el lenguaje Go

La "complejidad" está oculta en el tiempo de ejecución: los desarrolladores de Go no necesitan prestar atención a si el socket no está bloqueado,

Tampoco es necesario registrar personalmente la devolución de llamada del descriptor de archivo, solo use "bloquear

I/O" forma de tratar el procesamiento de sockets

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

func main() {
	//设置连接模式 , ip和端口号
	conn, err := net.Dial("tcp", "127.0.0.1:8888")
	if err != nil {
		fmt.Println("client dial err=", err)
		return
	}
	defer conn.Close()
	// 在命令行输入单行数据
	reader := bufio.NewReader(os.Stdin)
	for {
		//从终端读取一行用户的输入,并发给服务器
		line, err := reader.ReadString('\n')
		if err != nil {
			fmt.Println("readString err=", err)
		}
		//去掉输入后的换行符
		line = strings.Trim(line, "\r\n")
		//如果是exit,则退出客户端
		if line == "exit" {
			fmt.Println("客户端退出了")
			break
		}
		//将line发送给服务器
		n, e := conn.Write([]byte(line))
		if e != nil {
			fmt.Println("conn.write err=", e)
		}
		fmt.Printf("客户端发送了%d字节的数据\n", n)
	}
}
package main

import (
	"fmt"
	"net"
	_ "time"
)

func process(conn net.Conn) {
	//这里接受客户端的数据
	defer conn.Close()
	for {
		//创建一个新的切片
		buf := make([]byte, 1024)
		//等待客户端发送信息,如果客户端没发送,协程就阻塞在这
		// fmt.Printf("服务器在等待客户端%v的输入\n", conn.RemoteAddr().String())
		// conn.SetReadDeadline(time.Now().Add(time.Duration(1) * time.Second))
		n, err := conn.Read(buf) // 默认是阻塞的
		if err != nil {
			fmt.Println("服务器read err=", err)
			fmt.Println("客户端退出了")
			return
		}
		//显示客户端发送内容到服务器的终端
		fmt.Print(string(buf[:n]) + "\n")

	}
}

func main() {
	fmt.Println("服务器开始监听...")
	//协议、端口
	listen, err := net.Listen("tcp", "0.0.0.0:8888")
	if err != nil {
		fmt.Println("监听失败,err=", err)
		return
	}
	//延时关闭
	defer listen.Close() // 函数退出的时候调用
	for {
		//循环等待客户端连接
		fmt.Println("等待客户端连接...")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Accept() err=", err)
		} else {
			fmt.Printf("Accept() suc con=%v,客户端Ip=%v\n", conn, conn.RemoteAddr().String())
		}
		//这里准备起个协程为客户端服务
		go process(conn)
	}
	//fmt.Printf("监听成功,suv=%v\n", listen)
}

API de socket 1.0 TCP

•Read(): Leer datos de la conexión.

•Escribir(): Escribir datos en la conexión.

•Close(): cierra la conexión.

•LocalAddr(): Devuelve la dirección de la red local.

•RemoteAddr(): Devuelve la dirección de red remota.

•SetDeadline(): establezca los plazos de lectura y escritura relacionados con la conexión. equivalente a al mismo tiempo

Llame a SetReadDeadline() y SetWriteDeadline().

•SetReadDeadline(): Establecer llamadas de lectura futuras y llamadas de lectura actualmente bloqueadas

La fecha límite de tiempo de espera para .

•SetWriteDeadline(): establece llamadas de escritura futuras y llamadas de escritura actualmente bloqueadas

La fecha límite de tiempo de espera para .

1.1 Establecimiento de conexión TCP

El servidor es una estructura estándar Escuchar + Aceptar (consulte el código anterior), y el lenguaje Go en el lado del cliente usa net.Dial() o net.DialTimeout() para establecer una conexión.

Servidor

Consulte la página anterior

cliente

Dial de bloqueo: Dial con mecanismo de tiempo de espera:

1.2 Análisis de conexión de cliente anómala

1. No se puede acceder a la red o el servicio de la otra parte no se inicia

2. La acumulación de escuchas del servicio de la otra parte está llena

3. El retraso de la red es grande, la marcación está bloqueada y se agotó el tiempo de espera

1.2.1 La conexión del cliente es anormal: no se puede acceder a la red o el servicio de la otra parte no se inicia

Si el Addr pasado a Dial puede determinar inmediatamente que la red es inalcanzable, o el servicio correspondiente al puerto en Addr no se inicia,

Si el puerto no está escuchando, Dial devolverá un error casi de inmediato, como:

package main

import (
	"log"
	"net"
)

func main() {
	log.Println("begin dial...")
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		log.Println("dial error:", err)
		return
	}
	defer conn.Close()
	log.Println("dial ok")
}

1.2.2 Conexión de cliente anómala: la acumulación de escucha del servicio de la otra parte está llena

El servidor de la otra parte está muy ocupado y hay una gran cantidad de conexiones del lado del cliente que intentan establecerse con el servidor en un instante, y la cola de espera de escucha en el lado del servidor está llena.

La aceptación del servidor no es oportuna ((incluso si no acepta, la conexión será exitosa dentro del alcance de la cantidad de trabajo pendiente, porque

Debido a que la nueva conexión se ha agregado a la cola de escucha del lado del servidor, acepte simplemente saca una conexión de la cola y

Ya), lo que provocará el bloqueo de marcación en el lado del cliente.

package main

import (
	"log"
	"net"
	"time"
)

func establishConn(i int) net.Conn {
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		log.Printf("%d: dial error: %s", i, err)
		return nil
	}
	log.Println(i, ":connect to server ok")
	return conn
}

func main() {
	var sl []net.Conn
	for i := 1; i < 1000; i++ {
		conn := establishConn(i)
		if conn != nil {
			sl = append(sl, conn)
		}
	}

	time.Sleep(time.Second * 10000)
}

1.2.3 Conexión de cliente anómala: gran retraso en la red, marcación bloqueada y tiempo de espera agotado

Si el retraso de la red es grande, el proceso de protocolo de enlace TCP será más difícil y accidentado (pérdida de varios paquetes), y el consumo de tiempo será, naturalmente, más largo. marca esto

Si no se puede establecer la conexión durante mucho tiempo, Dial también devolverá el error "getsockopt: tiempo de espera de la operación agotado".

En la fase de establecimiento de la conexión, en la mayoría de los casos, Dial puede atender la demanda, incluso si está bloqueada por un corto tiempo. Pero para algunos programas,

Se requiere un límite de tiempo de conexión estricto. Si la conexión no se puede establecer con éxito dentro de un cierto período de tiempo, es posible que el programa deba ejecutar un período de procesamiento "anormal".

Lógica razonable, para esto necesitamos DialTimeout.

Los resultados de la ejecución son los siguientes, es necesario simular un entorno con un gran retraso en la red

package main

import (
	"log"
	"net"
	"time"
)

func main() {
	log.Println("begin dial...")
	conn, err := net.DialTimeout("tcp", "192.168.204.130:8888", 2*time.Second)
	if err != nil {
		log.Println("dial error:", err)
		return
	}
	defer conn.Close()
	log.Println("dial ok")
}

 1.3 Lectura y escritura de sockets

Después de que Dial tenga éxito, el método devuelve un valor de variable de tipo de interfaz net.Conn, y el tipo dinámico de esta variable de interfaz es un

*Conexión TCP:

1.3.1 Características de comportamiento de conn.Read

1 No hay datos en el zócalo

Una vez establecida la conexión, si la otra parte no envía datos al zócalo, el receptor (servidor) se bloqueará en la operación de lectura, lo que es coherente con el principio de "modelo" mencionado anteriormente.

También se suspenderá la rutina que ejecuta la operación de lectura. El tiempo de ejecución monitoreará el socket hasta que tenga datos antes de reiniciar

Programe la Goroutine correspondiente al socket para completar la lectura.

2 Hay algunos datos en el Socket

Si hay algunos datos en el socket y la longitud es menor que la longitud de los datos que se espera leer mediante una operación de lectura, la lectura leerá correctamente esta parte de los datos y regresará en lugar de esperar.

Regrese después de que se hayan leído todos los datos esperados.

3 Hay suficientes datos en el Socket

Si hay datos en el socket, y la longitud es mayor o igual que la longitud de los datos que se espera leer mediante una operación de lectura, Read leerá correctamente esta parte de los datos y regresará. este

情景是最符合我们对Read的期待的了:Read将用Socket中的数据将我们传入的slice填满后返回:n = 10, err = nil

4 Socket关闭

有数据关闭是指在client关闭时,socket中还有server端未读取的数据。当client端close socket退出后,server依旧没有开始Read,

10s后第一次Read成功读出了所有的数据,当第二次Read时,由于client端 socket关闭,Read返回EOF error

无数据关闭情形下的结果,那就是Read直接返回EOF error

5 读取操作超时

有些场合对Read的阻塞时间有严格限制,在这种情况下,Read的行为到底是什么样的呢?在返回超时错误时,是否也同时Read了

一部分数据了呢?

不会出现“读出部分数据且返回超时错误”的情况

1.3.2 conn.Write的行为特点

1 成功写

前面例子着重于Read,client端在Write时并未判断Write的返回值。所谓“成功写”指的就是Write调用返回的n

与预期要写入的数据长度相等,且error = nil。这是我们在调用Write时遇到的最常见的情形,这里不再举例了

2 写阻塞

TCP连接通信两端的OS都会为该连接保留数据缓冲,一端调用Write后,实际上数据是写入到OS的协议栈的

数据缓冲的。TCP是全双工通信,因此每个方向都有独立的数据缓冲。当发送方将对方的接收缓冲区以及自

身的发送缓冲区写满后,Write就会阻塞

3 写入部分数据

Write操作存在写入部分数据的情况。没有按照预期的写入所有数据。这时候循环写入便是

综上例子,虽然Go给我们提供了阻塞I/O的便利,但在调用Read和Write时依旧要综合需要方法返回的n和err

的结果,以做出正确处理。net.conn实现了io.Reader和io.Writer接口,因此可以试用一些wrapper包进行

socket读写,比如bufio包下面的Writer和Reader、io/ioutil下的函数等

1.4 Goroutine safe

基于goroutine的网络架构模型,存在在不同goroutine间共享conn的情况,那么conn的读写是

否是goroutine safe的呢。

Write

Read 内部是goroutine安全的,内部都有Lock保护

1.5 Socket属性

SetKeepAlive

SetKeepAlivePeriod

SetLinger

SetNoDelay (默认no delay)

SetWriteBuffer

SetReadBuffer

要使用上面的Method的,需要type assertion

tcpConn, ok := conn.(*TCPConn)

if !ok { //error handle }

tcpConn.SetNoDelay(true)

1.6 关闭连接

socket是全双工的,client和server端在己方已关闭的socket和对方关闭的socket上操作的

结果有不同。

package main

import (
	"log"
	"net"
	"time"
)

func main() {
	log.Println("begin dial...")
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		log.Println("dial error:", err)
		return
	}
	conn.Close()
	log.Println("close ok")

	var buf = make([]byte, 32)
	n, err := conn.Read(buf)
	if err != nil {
		log.Println("read error:", err)
	} else {
		log.Printf("read % bytes, content is %s\n", n, string(buf[:n]))
	}

	n, err = conn.Write(buf)
	if err != nil {
		log.Println("write error:", err)
	} else {
		log.Printf("write % bytes, content is %s\n", n, string(buf[:n]))
	}

	time.Sleep(time.Second * 1000)
}
package main

import (
	"fmt"
	"log"
	"net"
)

func handleConn(c net.Conn) {
	defer c.Close()

	// read from the connection
	var buf = make([]byte, 10)
	log.Println("start to read from conn")
	n, err := c.Read(buf)
	if err != nil {
		log.Println("conn read error:", err)
	} else {
		log.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))
	}

	n, err = c.Write(buf)
	if err != nil {
		log.Println("conn write error:", err)
	} else {
		log.Printf("write %d bytes, content is %s\n", n, string(buf[:n]))
	}
}

func main() {
	listen, err := net.Listen("tcp", ":8888")
	if err != nil {
		fmt.Println("listen error: ", err)
		return
	}

	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept error: ", err)
			break
		}

		// start a new goroutine to handle the new connection
		go handleConn(conn)
	}
}

1-7 读写超时

SetDeadline(t time.Time) error 设置读写超时

SetReadDeadline(t time.Time) error 设置读超时

SetWriteDeadline(t time.Time) error 设置写超时

package main

import (
	"log"
	"net"
	"os"
	"time"
)

func main() {
	connTimeout := 3 * time.Second
	conn, err := net.DialTimeout("tcp", "127.0.0.1:8080", connTimeout) // 3s timeout
	if err != nil {
		log.Println("dial failed:", err)
		os.Exit(1)
	}
	defer conn.Close()

	readTimeout := 2 * time.Second

	buffer := make([]byte, 512)

	for {
		err = conn.SetReadDeadline(time.Now().Add(readTimeout)) // timeout
		if err != nil {
			log.Println("setReadDeadline failed:", err)
		}

		n, err := conn.Read(buffer)
		if err != nil {
			log.Println("Read failed:", err)
			//break
		}

		log.Println("count:", n, "msg:", string(buffer))
	}

}
package main

import (
	"log"
	"net"
	"time"
)

func main() {
	addr := "0.0.0.0:8080"

	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)

	if err != nil {
		log.Fatalf("net.ResovleTCPAddr fail:%s", addr)
	}

	listener, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		log.Fatalf("listen %s fail: %s", addr, err)
	} else {
		log.Println("listening", addr)
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println("listener.Accept error:", err)
			continue
		}

		go handleConnection(conn)
	}

}

func handleConnection(conn net.Conn) {
	defer conn.Close()

	var buffer []byte = []byte("You are welcome. I'm server.")

	for {
		time.Sleep(3 * time.Second) // sleep 3s
		n, err := conn.Write(buffer)
		if err != nil {
			log.Println("Write error:", err)
			break
		}
		log.Println("send:", n)
	}

	log.Println("connetion end")

}

2. Redis库redigo

2 redis

参考文档: github.com/garyburd/redigo/redis

https://pkg.go.dev/github.com/garyburd/redigo/redis#pkg-index

使用第三方开源的redis库: github.com/gomodule/redigo/redis

import(

“go get github.com/gomodule/redigo/redis”

)

go get github.com/gomodule/redigo/redis

2.1 连接redis

package main

import (
	"fmt"
	// "time"

	"github.com/gomodule/redigo/redis"
)

func main() {
	c, err := redis.Dial("tcp", "192.168.204.132:6379")
	c, err := redis.Dial("tcp", "192.168.204.132:6379", 
		redis.DialConnectTimeout(time.Duration(1) * time.Second),
		redis.DialPassword("111"),
		redis.DialDatabase(1))
	if err != nil {
		fmt.Println("conn redis failed,", err)
		return
	}

	defer c.Close()
}

2.2 redis set操作

package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	c, err := redis.Dial("tcp", "192.168.204.132:6379")
	if err != nil {
		fmt.Println("conn redis failed,", err)
		return
	}

	defer c.Close()
	_, err = c.Do("Set", "count", 100)
	if err != nil {
		fmt.Println(err)
		return
	}

	r, err := redis.Int(c.Do("Get", "count"))
	if err != nil {
		fmt.Println("get count failed,", err)
		return
	}

	fmt.Println(r)
}

2.3 redis Hash操作

package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	c, err := redis.Dial("tcp", "192.168.204.132:6379")
	if err != nil {
		fmt.Println("conn redis failed,", err)
		return
	}

	defer c.Close()
	_, err = c.Do("HSet", "books", "count", 100)
	if err != nil {
		fmt.Println(err)
		return
	}

	r, err := redis.Int(c.Do("HGet", "books", "count"))
	if err != nil {
		fmt.Println("get count failed,", err)
		return
	}

	fmt.Println(r)
}

2.4 redis mset操作

package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	c, err := redis.Dial("tcp", "192.168.204.132:6379")
	if err != nil {
		fmt.Println("conn redis failed,", err)
		return
	}

	defer c.Close()
	_, err = c.Do("MSet", "count", 100, "efg", 300)
	if err != nil {
		fmt.Println(err)
		return
	}

	r, err := redis.Ints(c.Do("MGet", "count", "efg"))
	if err != nil {
		fmt.Println("get count failed,", err)
		return
	}

	for _, v := range r {
		fmt.Println(v)
	}
}

2.5 operación de caducidad redis

2.6 operación de lista redis

2-7 operación de subpub 

package main

import (
	"fmt"
	"time"

	red "github.com/gomodule/redigo/redis"
)

type Redis struct {
	pool *red.Pool
}

var redis *Redis

func initRedis() {
	redis = new(Redis)
	redis.pool = &red.Pool{
		MaxIdle:     256,
		MaxActive:   0,
		IdleTimeout: time.Duration(120),
		Dial: func() (red.Conn, error) {
			return red.Dial(
				"tcp",
				"127.0.0.1:6379",
				red.DialReadTimeout(time.Duration(1000)*time.Millisecond),
				red.DialWriteTimeout(time.Duration(1000)*time.Millisecond),
				red.DialConnectTimeout(time.Duration(1000)*time.Millisecond),
				red.DialDatabase(0),
				//red.DialPassword(""),
			)
		},
	}
}

func Exec(cmd string, key interface{}, args ...interface{}) (interface{}, error) {
	con := redis.pool.Get()
	if err := con.Err(); err != nil {
		return nil, err
	}
	defer con.Close()
	parmas := make([]interface{}, 0)
	parmas = append(parmas, key)

	if len(args) > 0 {
		for _, v := range args {
			parmas = append(parmas, v)
		}
	}
	return con.Do(cmd, parmas...)
}

func main() {
	initRedis()

	Exec("set", "hello", "world")
	fmt.Print(2)
	result, err := Exec("get", "hello")
	if err != nil {
		fmt.Print(err.Error())
	}
	str, _ := red.String(result, err)
	fmt.Print(str)
}
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/gomodule/redigo/redis"
)

// listenPubSubChannels listens for messages on Redis pubsub channels. The
// onStart function is called after the channels are subscribed. The onMessage
// function is called for each message.
func listenPubSubChannels(ctx context.Context, redisServerAddr string,
	onStart func() error,
	onMessage func(channel string, data []byte) error,
	channels ...string) error {
	// A ping is set to the server with this period to test for the health of
	// the connection and server.
	const healthCheckPeriod = time.Minute

	c, err := redis.Dial("tcp", redisServerAddr,
		// Read timeout on server should be greater than ping period.
		redis.DialReadTimeout(healthCheckPeriod+10*time.Second),
		redis.DialWriteTimeout(10*time.Second))
	if err != nil {
		return err
	}
	defer c.Close()

	psc := redis.PubSubConn{Conn: c}

	if err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil {
		return err
	}

	done := make(chan error, 1)

	// Start a goroutine to receive notifications from the server.
	go func() {
		for {
			switch n := psc.Receive().(type) {
			case error:
				done <- n
				return
			case redis.Message:
				if err := onMessage(n.Channel, n.Data); err != nil {
					done <- err
					return
				}
			case redis.Subscription:
				switch n.Count {
				case len(channels):
					// Notify application when all channels are subscribed.
					if err := onStart(); err != nil {
						done <- err
						return
					}
				case 0:
					// Return from the goroutine when all channels are unsubscribed.
					done <- nil
					return
				}
			}
		}
	}()

	ticker := time.NewTicker(healthCheckPeriod)
	defer ticker.Stop()
loop:
	for err == nil {
		select {
		case <-ticker.C:
			// Send ping to test health of connection and server. If
			// corresponding pong is not received, then receive on the
			// connection will timeout and the receive goroutine will exit.
			if err = psc.Ping(""); err != nil {
				break loop
			}
		case <-ctx.Done():
			break loop
		case err := <-done:
			// Return error from the receive goroutine.
			return err
		}
	}

	// Signal the receiving goroutine to exit by unsubscribing from all channels.
	psc.Unsubscribe()

	// Wait for goroutine to complete.
	return <-done
}

func publish() {
	c, err := dial()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()

	c.Do("PUBLISH", "c1", "hello")
	c.Do("PUBLISH", "c2", "world")
	c.Do("PUBLISH", "c1", "goodbye")
}

// This example shows how receive pubsub notifications with cancelation and
// health checks.
func main() {
	redisServerAddr := "192.168.204.132:6379"

	ctx, cancel := context.WithCancel(context.Background())

	err := listenPubSubChannels(ctx,
		redisServerAddr,
		func() error {
			// The start callback is a good place to backfill missed
			// notifications. For the purpose of this example, a goroutine is
			// started to send notifications.
			go publish()
			return nil
		},
		func(channel string, message []byte) error {
			fmt.Printf("channel: %s, message: %s\n", channel, message)

			// For the purpose of this example, cancel the listener's context
			// after receiving last message sent by publish().
			if string(message) == "goodbye" {
				cancel()
			}
			return nil
		},
		"c1", "c2")

	if err != nil {
		fmt.Println(err)
		return
	}

}

2-8 Grupo de conexiones

MaxIdle: el número máximo de conexiones inactivas, lo que significa que se pueden mantener N conexiones inactivas incluso si no hay una conexión redis, en lugar de

Despejado y en espera en todo momento.

MaxActive: el número máximo de conexiones, lo que indica que hay como máximo N conexiones al mismo tiempo. 0 significa ilimitado.

IdleTimeout: El tiempo máximo de espera de conexión inactiva, después de este tiempo, la conexión inactiva se cerrará. Si se establece en 0,

Las conexiones inactivas no se cerrarán. Debe establecerse en un tiempo más corto que el tiempo de espera del servidor redis.

DialConnectTimeout: tiempo de espera de conexión de Redis.

DialReadTimeout: tiempo de espera para leer datos de Redis.

DialWriteTimeout: tiempo de espera para escribir datos en Redis.

El proceso de conexión es probablemente así

1. Intente obtener una conexión disponible de la lista libre MaxIdle, si tiene éxito, regresa directamente, si falla, intente con el paso 2

2. Si MaxIdle actual < número de conexiones < MaxActive, intente crear una nueva conexión, si falla, intente el paso 3

3. Si el número de conexiones > MaxActive, espere hasta que se cumpla la condición del paso 2 y repita el paso 2

2-8 El pozo de la piscina de conexión redis

package main

import (
	"fmt"
	"time"

	red "github.com/gomodule/redigo/redis"
)

type Redis struct {
	pool *red.Pool
}

var redis *Redis

func initRedis() {
	redis = new(Redis)
	redis.pool = &red.Pool{
		MaxIdle:     256,
		MaxActive:   0,
		IdleTimeout: time.Duration(120),
		Dial: func() (red.Conn, error) {
			return red.Dial(
				"tcp",
				"127.0.0.1:6379",
				red.DialReadTimeout(time.Duration(1000)*time.Millisecond),
				red.DialWriteTimeout(time.Duration(1000)*time.Millisecond),
				red.DialConnectTimeout(time.Duration(1000)*time.Millisecond),
				red.DialDatabase(0),
				//red.DialPassword(""),
			)
		},
	}
}

func Exec(cmd string, key interface{}, args ...interface{}) (interface{}, error) {
	con := redis.pool.Get()
	if err := con.Err(); err != nil {
		return nil, err
	}
	defer con.Close()
	parmas := make([]interface{}, 0)
	parmas = append(parmas, key)

	if len(args) > 0 {
		for _, v := range args {
			parmas = append(parmas, v)
		}
	}
	return con.Do(cmd, parmas...)
}

func main() {
	initRedis()

	Exec("set", "hello", "world")
	fmt.Print(2)
	result, err := Exec("get", "hello")
	if err != nil {
		fmt.Print(err.Error())
	}
	str, _ := red.String(result, err)
	fmt.Print(str)
}

problemas encontrados

Hasta ahora, el problema del grupo de conexiones solo se ha encontrado una vez y fue en un entorno de prueba. La configuración en ese momento era

DialConnectTimeout:tiempo.Duración(200)*tiempo.Milisegundo

DialReadTimeout:tiempo.Duración(200)*tiempo.Milisegundo

DialWriteTimeout:tiempo.Duración(200)*tiempo.Milisegundo

La configuración es de 200 milisegundos. Cuando usé hgetall una vez, seguí informando un error, probablemente similar al siguiente mensaje

leer tcp 127.0.0.1:6379: tiempo de espera de E/S

El significado literal es que leer tcp se agota, y también puede informar cuando se escriben datos grandes, escribir tcp

se acabó el tiempo.

Posteriormente, los tiempos de espera de lectura y escritura se cambiaron a 1000 milisegundos y nunca más se produjeron errores similares.

Para saber más sobre el uso de Redis, puedes leer la documentación oficial:

github.com/gomodule/redigo/redis

3. Sincronización de grupo de objetos temporales.Pool

3 grupo de objetos temporales

El valor de tipo sync.Pool se utiliza como contenedor para almacenar valores temporales. Dichos contenedores son autoescalables, eficientes y concurrentes.

seguro.

El tipo sync.Pool tiene solo dos métodos:

◼ Put, utilizado para almacenar objetos temporales en el grupo actual, acepta un valor de un tipo de interfaz vacío

◼ Get, se usa para obtener un objeto temporal del pool actual, devuelve un valor de un tipo de interfaz vacío

nuevo campo

El campo Nuevo del tipo sync.Pool es una función para crear un objeto temporal. Su tipo no tiene parámetros pero devuelve una interfaz vacía

tipo de función. A saber: interfaz func(){}.

Esta función es el último medio del método Get para obtener el objeto temporal. El resultado de la función no se almacenará en el grupo de objetos temporales actual,

En su lugar, regresa directamente a la persona que llama del método Get.

El valor real del campo Nuevo aquí debe proporcionarse al inicializar el conjunto de objetos temporales. De lo contrario, cuando el método Get lo llame

obtendrá cero.

package main

import (
	"bytes"
	"fmt"
	"io"
	"sync"
)

// 存放数据块缓冲区的临时对象
var bufPool sync.Pool

// 预定义定界符
const delimiter = '\n'

// 一个简易的数据库缓冲区的接口
type Buffer interface {
	Delimiter() byte                    // 获取数据块之间的定界符
	Write(contents string) (err error)  // 写入一个数据块
	Read() (contents string, err error) // 读取一个数据块
	Free()                              // 释放当前的缓冲区
}

// 实现一个上面定义的接口
type myBuffer struct {
	buf       bytes.Buffer
	delimiter byte
}

func (b *myBuffer) Delimiter() byte {
	return b.delimiter
}

func (b *myBuffer) Write(contents string) (err error) {
	if _, err = b.buf.WriteString(contents); err != nil {
		return
	}
	return b.buf.WriteByte(b.delimiter)
}

func (b *myBuffer) Read() (contents string, err error) {
	return b.buf.ReadString(b.delimiter)
}

func (b *myBuffer) Free() {
	bufPool.Put(b)
}

func init() {
	bufPool = sync.Pool{
		New: func() interface{} {
			return &myBuffer{delimiter: delimiter}
		},
	}
}

// 获取一个数据库缓冲区
func GetBuffer() Buffer {
	return bufPool.Get().(Buffer) // 做类型转换
}

func main() {
	buf := GetBuffer()
	defer buf.Free()
	buf.Write("写入第一行,")	// 写入数据
	buf.Write("接着写第二行。")	// 写入数据
	fmt.Println("数据已经写入,准备把数据读出")
	for {
		block, err := buf.Read()
		if err != nil {
			if err == io.EOF {
				break
			}
			panic(fmt.Errorf("读取缓冲区时ERROR: %s", err))
		}
		fmt.Print(block)
	}
}
package main

import (
	"fmt"
	_ "runtime"
	"runtime/debug"
	"sync"
	"sync/atomic"
)

func main() {
	// 禁用GC,并保证在main函数执行结束前恢复GC
	defer debug.SetGCPercent(debug.SetGCPercent(-1))
	var count int32

	// 实现一个函数 ,生成新对象
	newFunc := func() interface{} {
		fmt.Println("newFunc:", count)
		return atomic.AddInt32(&count, 1)
	}
	pool := sync.Pool{New: newFunc}	// 传入生成对象的函数....

	// New 字段值的作用
	v1 := pool.Get()	// 调用GET接口去取
	fmt.Printf("v1: %v\n", v1)
	pool.Put(v1)		// 放回去
	// 临时对象池的存取
	pool.Put(newFunc())
	// pool.Put(newFunc())
	// pool.Put(newFunc())
	v2 := pool.Get()
	// pool.Put(v2)
	fmt.Printf("v2: %v\n", v2)		// 这个时候v1和v2应该是一样

	// 垃圾回收对临时对象池的影响
	// debug.SetGCPercent(100)
	// runtime.GC()
	v3 := pool.Get()
	fmt.Printf("v3: %v\n", v3)
	pool.New = nil
	v4 := pool.Get()
	fmt.Printf("v4: %v\n", v4)
}

3.1 Obtener

Pool mantendrá un pool local para cada P, y el pool local de P se divide en pool privado privado y pool compartido compartido. Piscina privada

Los elementos en el grupo solo pueden ser utilizados por el P local, y los elementos en el grupo compartido pueden ser robados por otros P, así que use el grupo privado privado

No es necesario bloquear cuando se usa el grupo compartido, pero se requiere bloqueo cuando se usa el grupo compartido.

Get buscará primero el local privado, luego el local compartido y finalmente el compartido de otros Ps. Si todo lo anterior

No hay ningún elemento disponible y, finalmente, se llamará a la función New para obtener un nuevo elemento.

3.2 Poner

Put coloca primero el elemento en el grupo privado; si privado no está vacío, se coloca en el grupo compartido. interesante

Además, hay una probabilidad de 1/4 de que el elemento se descarte antes de ingresar al grupo.

 

4. El archivo de configuración lee goconfig

4 Uso del analizador de archivos de configuración goconfig

archivo de configuración ini leer y escribir conf.ini

;redis cache
USER_LIST = USER:LIST
MAX_COUNT = 50
MAX_PRICE = 123456
IS_SHOW = true

[test]
dbdns = root:@tcp(127.0.0.1:3306)

[prod]
dbdns = root:@tcp(172.168.1.1:3306)

4-1 en configs.go

package main

import (
	"fmt"
	"log"

	"github.com/Unknwon/goconfig"
)

func main() {
	cfg, err := goconfig.LoadConfigFile("./conf.ini")	// 读取后文件关闭了
	if err != nil {
		log.Fatalf("无法加载配置文件:%s", err)
	}
	userListKey, err := cfg.GetValue("", "USER_LIST")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(userListKey)
	userListKey2, _ := cfg.GetValue(goconfig.DEFAULT_SECTION, "USER_LIST")
	fmt.Println(userListKey2)
	maxCount := cfg.MustInt("", "MAX_COUNT")
	fmt.Println(maxCount)
	maxPrice := cfg.MustFloat64("", "MAX_PRICE")
	fmt.Println(maxPrice)
	isShow := cfg.MustBool("", "IS_SHOW")
	fmt.Println(isShow)

	db := cfg.MustValue("test", "dbdns")
	fmt.Println(db)

	dbProd := cfg.MustValue("prod", "dbdns")
	fmt.Println("dbProd: ",dbProd)

	//set 值
	cfg.SetValue("", "MAX_NEW", "100")
	maxNew := cfg.MustInt("", "MAX_NEW")
	fmt.Println(maxNew)

	maxNew1, err := cfg.Int("", "MAX_NEW")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(maxNew1)
	cfg.AppendFiles("conf1.ini")
	// cfg.DeleteKey("", "MAX_NEW")
}

5. Analizar el indicador de línea de comando

5 análisis de línea de comando Go flag

cmd -flag // solo admite tipo bool

cmd -bandera=xxx

cmd -flag xxx // solo admite tipos no bool

1. Defina el parámetro de la bandera

Hay tres parámetros: el primero es el nombre del parámetro, el segundo es el valor predeterminado y el tercero es la instrucción

(1) A través de los métodos flag.Xxx() como flag.String(), Bool(), Int(), etc., este método devuelve un puntero correspondiente

var ip = flag.Int("nombre de la bandera", 1234, "mensaje de ayuda para el nombre de la bandera")

(2) Vincular la bandera a una variable a través del método flag.XxxVar(), que devuelve el tipo de valor

donde banderas int

flag.IntVar(&flagvar, "flagname", 1234, "mensaje de ayuda para flagname")

(3) Vincule el tipo personalizado a través de flag.Var(), y el tipo personalizado debe implementar la interfaz de valor (el receptor debe ser un puntero)

fmt.Println("flagvar tiene valor", flagvar)

5-1-cli-bandera.go

package main

import (
	"flag"
	"fmt"
	//    "os"
)

// go run 4-1-cli-flag.go -ok -id 11111 -port 8899 -name TestUser very goo
func main() {
	//    fmt.Println(os.Args)
	ok := flag.Bool("ok", false, "is ok")		// 不设置ok 则为false
	id := flag.Int("id", 0, "id")
	port := flag.String("port", ":8080", "http listen port")
	var name string
	flag.StringVar(&name, "name", "Jack", "name")

	flag.Parse()
	//    flag.Usage()
	others := flag.Args()

	fmt.Println("ok:", *ok)
	fmt.Println("id:", *id)
	fmt.Println("port:", *port)
	fmt.Println("name:", name)
	fmt.Println("other:", others)
}

5-2-auto-bandera.go

package main

import (
	"flag"
	"fmt"
)

type FlagSet struct {
	Usage func()
}

var myFlagSet = flag.NewFlagSet("myflagset", flag.ExitOnError)
var stringFlag = myFlagSet.String("abc", "default value", "help mesage")

func main() {
	myFlagSet.Parse([]string{"-abc", "def", "ghi", "123"})
	args := myFlagSet.Args()
	for i := range args {
		fmt.Println(i, myFlagSet.Arg(i))
	}
}

6. esquema de generación de uuid

6 fluidos

Algoritmo de copo de nieve Copo de nieve y copo de Sony https://www.cnblogs.com/li-peng/p/12124249.html

Prueba de rendimiento de uuid de uso común

Los datos enteros de 64 bits utilizados por el algoritmo de copo de nieve se dividen en cuatro partes.

1. No incluye el primer bit al principio, porque es un bit de signo;

2,41 bits para indicar la marca de tiempo cuando se recibe la solicitud, con una precisión de 1 milisegundo;

3,5 bits indica la identificación del centro de datos, 5 bits indica la identificación de la instancia de la máquina

4. Un total de 10 bits de bits de máquina, por lo que se puede implementar en 1024 nodos de máquina

en identificación;

Número de serie de autoincremento cíclico de 5,12 bits, vuelve a 0 después de aumentar al máximo, generación máxima en 1 milisegundo

El número de ID únicos es 4096.

copo de nieve

En sonyflake, la marca de tiempo aquí usa 39 bits para tener una precisión de 10 ms, por lo que puede alcanzar los 174 años, en comparación con

mucho tiempo de copo de nieve.

Se utiliza 8 bits como número de serie, se puede generar un máximo de 256 cada 10 milisegundos y se puede generar un máximo de 1 segundo

25600, mucho menos que el Snowflake original, si sientes que no es suficiente,

La solución actual es ejecutar varias instancias para generar el ID de la misma empresa para compensarlo.

Se utiliza 16 bits como número de máquina y el valor predeterminado son los dos últimos dígitos de la IP privada de la máquina actual.

La mejora de Sony flake sobre snowflake es intercambiar espacio por tiempo y reducir la cantidad de marcas de tiempo para aumentar de 69 años a 174 años.

Sin embargo, la cantidad máxima de identificaciones generadas en un segundo se redujo de 409,6w a 2,56w.

  

6-1-1-uuid.ir

package main

import (
	"fmt"
	"log"
	"math/rand"
	"reflect"
	"time"

	"gitee.com/GuaikOrg/go-snowflake/snowflake"
	"github.com/chilts/sid"
	"github.com/kjk/betterguid"
	"github.com/oklog/ulid"
	"github.com/rs/xid"
	uuid "github.com/satori/go.uuid"
	"github.com/segmentio/ksuid"
	"github.com/sony/sonyflake"
)

const FOR_LOOP = 100000

func genXid() {
	id := xid.New()
	fmt.Printf("github.com/rs/xid:           %s, len:%d\n", id.String(), len(id.String()))
}

func genKsuid() {
	id := ksuid.New()
	fmt.Printf("github.com/segmentio/ksuid:  %s, len:%d\n", id.String(), len(id.String()))
}

func genBetterGUID() {
	id := betterguid.New()
	fmt.Printf("github.com/kjk/betterguid:   %s, len:%d\n", id, len(id))
}

func genUlid() {
	t := time.Now().UTC()
	entropy := rand.New(rand.NewSource(t.UnixNano()))
	id := ulid.MustNew(ulid.Timestamp(t), entropy)
	fmt.Printf("github.com/oklog/ulid:       %s, len:%d\n", id.String(), len(id.String()))
}

// https://gitee.com/GuaikOrg/go-snowflake
func genSnowflake() {
	flake, err := snowflake.NewSnowflake(int64(0), int64(0))
	if err != nil {
		log.Fatalf("snowflake.NewSnowflake failed with %s\n", err)
	}
	id := flake.NextVal()
	fmt.Printf("gitee.com/GuaikOrg/go-snowflake:%x, type:%s\n", id, reflect.TypeOf(id))
}

func genSonyflake() {
	flake := sonyflake.NewSonyflake(sonyflake.Settings{})
	id, err := flake.NextID()
	if err != nil {
		log.Fatalf("flake.NextID() failed with %s\n", err)
	}
	fmt.Printf("github.com/sony/sonyflake:   %x, type:%s\n", id, reflect.TypeOf(id))
}

func genSid() {
	id := sid.Id()
	fmt.Printf("github.com/chilts/sid:       %s, len:%d\n", id, len(id))
}

func genUUIDv4() {
	id, err := uuid.NewV4()
	if err != nil {
		fmt.Printf("get uuid error [%s]", err)
	}
	fmt.Printf("github.com/satori/go.uuid:   %s, len:%d\n", id, len(id))
}

func testGenXid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_ = xid.New()
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/rs/xid          n:", n, "time:", elapsed)
}

func testGenKsuid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_ = ksuid.New()
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/segmentio/ksuid n:", n, "time:", elapsed)
}

func testGenBetterguid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_ = betterguid.New()
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/kjk/betterguid  n:", n, "time:", elapsed)
}

func testGenUlid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		t := time.Now().UTC()
		entropy := rand.New(rand.NewSource(t.UnixNano()))
		_ = ulid.MustNew(ulid.Timestamp(t), entropy)
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/oklog/ulid      n:", n, "time:", elapsed)
}

func testGenSnowflake(n int) {
	t0 := time.Now()
	flake, err := snowflake.NewSnowflake(int64(0), int64(0))
	if err != nil {
		log.Fatalf("snowflake.NewSnowflake failed with %s\n", err)
	}
	for i := 0; i < n; i++ {
		_ = flake.NextVal()
	}
	elapsed := time.Since(t0)
	fmt.Println("gitee.com/GuaikOrg/go-snowflake n:", n, "time:", elapsed)
}
func testGenSonyflake(n int) {
	t0 := time.Now()
	flake := sonyflake.NewSonyflake(sonyflake.Settings{}) // 注意这一行的位置
	for i := 0; i < n; i++ {
		_, err := flake.NextID()
		if err != nil {
			log.Fatalf("flake.NextID() failed with %s\n", err)
		}
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/sony/sonyflake  n:", n, "time:", elapsed)
}

func testGenSid(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_ = sid.Id()
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/chilts/sid      n:", n, "time:", elapsed)
}

func testGenUUIDv4(n int) {
	t0 := time.Now()
	for i := 0; i < n; i++ {
		_, err := uuid.NewV4()
		if err != nil {
			fmt.Printf("get uuid error [%s]", err)
		}
	}
	elapsed := time.Since(t0)
	fmt.Println("github.com/satori/go.uuid  n:", n, "time:", elapsed)
}

func main() {
	fmt.Printf("效果展示...\n")
	genXid()
	genXid()
	genXid()
	genKsuid()
	genBetterGUID()
	genUlid()
	genSnowflake()
	genSonyflake()
	genSid()
	genUUIDv4()
	fmt.Printf("性能测试...\n")
	testGenXid(FOR_LOOP)
	testGenKsuid(FOR_LOOP)
	testGenBetterguid(FOR_LOOP)
	testGenUlid(FOR_LOOP)
	testGenSnowflake(FOR_LOOP)
	testGenSonyflake(FOR_LOOP)
	testGenSid(FOR_LOOP)
	testGenUUIDv4(FOR_LOOP)
}

// github.com/rs/xid          n: 1000000 time: 29.2665ms
// github.com/segmentio/ksuid n: 1000000 time: 311.4816ms
// github.com/kjk/betterguid  n: 1000000 time: 89.2803ms
// github.com/oklog/ulid   n: 1000000 time: 11.746259s
// github.com/sony/sonyflake   n: 1000000 time: 39.0713342s
// thub.com/chilts/sid        n: 1000000 time: 254.9442ms
// github.com/satori/go.uuid     n: 1000000 time: 270.3201ms

2.2 Desarrollo web de lenguaje Go y combate de base de datos

1 programación HTTP

a. Go admite de forma nativa http, import("net/http")

b. El rendimiento del servicio http de Go es relativamente similar al de nginx

c. Unas pocas líneas de código pueden implementar un servicio web

1.1 Métodos comunes de solicitud HTTP

5. Métodos de solicitud comunes HTTP

1) Obtener solicitud

2) Publicar solicitud

3) Poner solicitud

4) Eliminar solicitud

5) Solicitud de cabeza

1.2 Códigos de estado comunes de HTTP

Códigos de estado comunes de HTTP

http.EstadoContinuar = 100

http.EstadoOK = 200

http.Estado encontrado = 302

http.StatusBadRequest = 400

http.Estado no autorizado = 401

http.Estado Prohibido = 403

http.Estado no encontrado = 404

http.StatusInternalServerError = 500

2 ClienteCliente

El paquete http proporciona muchas funciones para acceder al servidor web, como http.Get(), http.Post(), http.Head(), etc.

Los datos del mensaje de respuesta de lectura se almacenan en la estructura de respuesta.

Definición de estructura de respuesta

estructura de respuesta de tipo {

Cadena de estado // por ejemplo, "200 OK"

StatusCode int // por ejemplo, 200

Protocadena // por ejemplo, "HTTP/1.0"

ProtoMayor int // por ejemplo 1

protomenor int // por ejemplo

Encabezado Encabezado

Cuerpo io.ReadCloser

//...

}

El paquete de respuesta enviado por el servidor se guarda en Body. Puede usar el método de lectura que proporciona para obtener el contenido de los datos. guardar para cortar

En el búfer, empalmado en una cadena completa para ver.

Al final, debe llamar al método Close() en el cuerpo para cerrar el io.

2.1 Solicitudes HTTP/HTTPS básicas

Las funciones Get, Head, Post y PostForm realizan solicitudes HTTP/HTTPS

resp, err := http.Get("http://ejemplo.com/")

...

resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)

...

resp, err := http.PostForm("http://example.com/form",url.Values{"key":

{"Valor"}, "id": {"123"}})

El cuerpo de la respuesta debe cerrarse después de usar la respuesta.

resp, err := http.Get("http://ejemplo.com/")

si yerra != nil {

// manejar el error

}

diferir resp.Body.Close()

cuerpo, err := ioutil.ReadAll(resp.Body)

// ...

2.2.1 Ejemplo de método Get sin parámetros

2-2-1-http-get-client.go

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {
	// resp, err := http.Get("http://127.0.0.1:9000")
	resp, err := http.Get("https://www.baidu.com/")
	if err != nil {
		fmt.Println("get err:", err)
		return
	}
	defer resp.Body.Close() // 做关闭
	// data byte
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("get data err:", err)
		return
	}

	fmt.Println("body:", string(data))
	fmt.Println("resp:", resp)
}

2.2.2 Ejemplo de método Get con parámetros

Los parámetros de la solicitud GET deben procesarse utilizando la biblioteca estándar net/url integrada en el lenguaje Go

2-2-2-http-get-client.go

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
)

func main() {

	//1.处理请求参数
	params := url.Values{}
	params.Set("name", "dar")
	params.Set("hobby", "足球")

	//2.设置请求URL
	rawUrl := "http://127.0.0.1:9000"
	reqURL, err := url.ParseRequestURI(rawUrl)
	if err != nil {
		fmt.Printf("url.ParseRequestURI()函数执行错误,错误为:%v\n", err)
		return
	}

	//3.整合请求URL和参数
	//Encode方法将请求参数编码为url编码格式("bar=baz&foo=quux"),编码时会以键进行排序。
	reqURL.RawQuery = params.Encode()

	//4.发送HTTP请求
	//说明: reqURL.String() String将URL重构为一个合法URL字符串。
	fmt.Println("Get url:", reqURL.String())
	resp, err := http.Get(reqURL.String())
	if err != nil {
		fmt.Printf("http.Get()函数执行错误,错误为:%v\n", err)
		return
	}
	defer resp.Body.Close()

	//5.一次性读取响应的所有内容
	body, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		fmt.Printf("ioutil.ReadAll()函数执行出错,错误为:%v\n", err)
		return
	}

	fmt.Println("Response: ", string(body))
}

2-2-2-http-get-server.go

package main

import (
	"fmt"
	"net/http"
)
// 响应: http.ResponseWriter
// 请求:http.Request 
func myHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	params := r.URL.Query()
	fmt.Println("r.URL: ", r.URL)
	fmt.Fprintln(w, "name:", params.Get("name"), "hobby:", params.Get("hobby")) // 回写数据
}
func main() {

	http.HandleFunc("/", myHandler)
	err := http.ListenAndServe("127.0.0.1:9000", nil)
	if err != nil {
		fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
		return
	}
}

2.3.1 método de publicación

发送POST请求的示例代码

2-3-1-http-post-client.go

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

// net/http post demo

func main() {
	url := "http://127.0.0.1:9000/post"
	contentType := "application/json"
	data := `{"name":"darren","age":18}`
	resp, err := http.Post(url, contentType, strings.NewReader(data))
	if err != nil {
		fmt.Println("post failed, err:%v\n", err)
		return
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("get resp failed,err:%v\n", err)
		return
	}
	fmt.Println("StatusCode:", resp.StatusCode)
	fmt.Println(string(b))
}

2-3-1-http-post-server.go

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func postHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	fmt.Println("Method ", r.Method)
	if r.Method == "POST" {
		// 1. 请求类型是application/json时从r.Body读取数据
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			fmt.Println("read request.Body failed, err:%v\n", err)
			return
		}
		fmt.Println(string(b))
		answer := `{"status": "ok"}`
		w.Write([]byte(answer))
	} else {
		fmt.Println("can't handle ", r.Method)
		w.WriteHeader(http.StatusBadRequest)
	}

}
func main() {

	http.HandleFunc("/post", postHandler)
	err := http.ListenAndServe("0.0.0.0:9000", nil)
	if err != nil {
		fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
		return
	}
}

2.4 head方法-client

HEAD请求常常被忽略,但是能提供很多有

用的信息,特别是在有限的速度和带宽下。

主要有以下特点:

1、只请求资源的首部;

2、检查超链接的有效性;

3、检查网页是否被修改;

4、多用于自动搜索机器人获取网页的标志

信息,获取rss种子信息,或者传递安全认证

信息等

package main

import (
	"fmt"
	"net"
	"net/http"
	"time"
)

func main() {

	url := "http://www.baidu1.com"
	c := http.Client{
		Transport: &http.Transport{
			Dial: func(network, addr string) (net.Conn, error) {
				timeout := time.Second * 2
				return net.DialTimeout(network, addr, timeout)
			},
		},
	}
	resp, err := c.Head(url)
	if err != nil {
		fmt.Printf("head %s failed, err:%v\n", url, err)
	} else {
		fmt.Printf("%s head succ, status:%v\n", url, resp.Status)
	}

}

2.5 表单处理

package main

import (
	"fmt"
	"io"
	"net/http"
)

const form = `<html><body><form action="#" method="post" name="bar">
                    <input type="text" name="in"/>
                    <input type="text" name="in"/>
                     <input type="submit" value="Submit"/>
             </form></html></body>`

func HomeServer(w http.ResponseWriter, request *http.Request) {
	io.WriteString(w, "/test1 或者/test2")
	// io.WriteString(w, "<h1>/test1 或者/test2</h1>")
}

func SimpleServer(w http.ResponseWriter, request *http.Request) {
	io.WriteString(w, "<h1>hello, world</h1>")
}

func FormServer(w http.ResponseWriter, request *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	switch request.Method {
	case "GET":
		io.WriteString(w, form)
	case "POST":
		request.ParseForm()
		fmt.Println("request.Form[in]:", request.Form["in"])
		io.WriteString(w, request.Form["in"][0])
		io.WriteString(w, "\n")
		io.WriteString(w, request.Form["in"][1])	// go web开发
		// var ptr *int
		// *ptr = 0x123445 // 模拟异常
	}
}
func main() {
	http.HandleFunc("/", HomeServer)
	http.HandleFunc("/test1", SimpleServer)
	http.HandleFunc("/test2", FormServer)
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
		return
	}
}

2.6 panic处理

2-6-panic-server.go

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

const form = `<html><body><form action="#" method="post" name="bar">
                    <input type="text" name="in"/>
                    <input type="text" name="in"/>
                     <input type="submit" value="Submit"/>
             </form></html></body>`

func HomeServer(w http.ResponseWriter, request *http.Request) {
	io.WriteString(w, "<h1>/test1 或者/test2</h1>")
}

func SimpleServer(w http.ResponseWriter, request *http.Request) {
	io.WriteString(w, "<h1>hello, world</h1>")
}

func FormServer(w http.ResponseWriter, request *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	switch request.Method {
	case "GET":
		io.WriteString(w, form)
	case "POST":
		request.ParseForm()
		fmt.Println("request.Form[in]:", request.Form["in"])
		io.WriteString(w, request.Form["in"][0])
		io.WriteString(w, "\n")
		io.WriteString(w, request.Form["in"][1])
		// var ptr *int
		// *ptr = 0x123445 // 模拟异常  注意协程的异常处理
		var ptr *int
		var a int
		ptr = &a
		*ptr = 0x123445 // 也是可以取地址写入的
	}
}
func main() {
	http.HandleFunc("/", HomeServer)
	http.HandleFunc("/test1", logPanics(SimpleServer))
	http.HandleFunc("/test2", logPanics(FormServer))
	err := http.ListenAndServe(":9000", nil)
	if err != nil {
		fmt.Printf("http.ListenAndServe()函数执行错误,错误为:%v\n", err)
		return
	}
}

func logPanics(handle http.HandlerFunc) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		defer func() {
			if x := recover(); x != nil {
				log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
			}
		}()
		handle(writer, request)
	}
}

3 模板

3 模板

1)替换 { {.字段名}}

3-1-template.go

package main

import (
	"fmt"
	"html/template"
	"io"
	"net/http"
)

var myTemplate *template.Template

type Result struct {
	output string
}

func (p *Result) Write(b []byte) (n int, err error) {
	fmt.Println("called by template")
	p.output += string(b)
	return len(b), nil
}

type Person struct {
	Name  string
	Title string
	Age   int
}

func userInfo(w http.ResponseWriter, r *http.Request) {
	fmt.Println("handle hello")
	//fmt.Fprintf(w, "hello ")
	var arr []Person
	p := Person{Name: "Dar", Age: 18, Title: "个人网站"}
	p1 := Person{Name: "Ki", Age: 19, Title: "个人网站"}
	p2 := Person{Name: "子", Age: 20, Title: "个人网站"}
	arr = append(arr, p)
	arr = append(arr, p1)
	arr = append(arr, p2)

	fmt.Println("arr:", arr)

	resultWriter := &Result{}
	io.WriteString(resultWriter, "hello 模板")
	err := myTemplate.Execute(w, arr) // 模板替换, 执行完后, html模板和参数arr就写入 w http.ResponseWriter
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("template render data:", resultWriter.output)
	//myTemplate.Execute(w, p)
	//myTemplate.Execute(os.Stdout, p)
	//file, err := os.OpenFile("C:/test.log", os.O_CREATE|os.O_WRONLY, 0755)
	//if err != nil {
	//	fmt.Println("open failed err:", err)
	//	return
	//}

}

func initTemplate(filename string) (err error) {
	myTemplate, err = template.ParseFiles(filename)
	if err != nil {
		fmt.Println("parse file err:", err)
		return
	}
	return
}

func main() {
	initTemplate("./index.html")
	http.HandleFunc("/user/info", userInfo)
	err := http.ListenAndServe("0.0.0.0:9000", nil)
	if err != nil {
		fmt.Println("http listen failed")
	}
}

3.1 模板-替换 { {.字段名}}

4 Mysql

建库建表

在MySQL中创建一个名为go_test的数据库

CREATE DATABASE go_test;

进入该数据库:

use go_test;

创建一张用于测试的数据表:

CREATE TABLE `user` (

`id` BIGINT(20) NOT NULL AUTO_INCREMENT,

`name` VARCHAR(20) DEFAULT '',

`age` INT(11) DEFAULT '0',

PRIMARY KEY(`id`)

)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT

CHARSET=utf8mb4;

4.0 连接mysql

Open函数:

db, err := sql.Open("mysql", "用户名:密码@tcp(IP:端口)/数据库?charset=utf8")

例如:db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/test?charset=utf8")

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql" // 注释掉后异常 _ 调用初始化函数
)

// https://github.com/go-sql-driver/mysql#usage
func main() {
	db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	fmt.Println("err:", err) // err: <nil>
	if db == nil {
		fmt.Println("db open failed:", err)
	}

	err = db.Ping() //Ping verifies a connection to the database is still alive, establishing a connection if necessary
	if err != nil {
		fmt.Println("数据库链接失败", err)
	}
	defer db.Close()
}

4-1 insertar datos mysql

4-1-mysql.go

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

// 插入数据
func insertRowDemo(db *sql.DB) {
	sqlStr := "insert into user(name, age) values (?,?)"
	ret, err := db.Exec(sqlStr, "darren", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	theID, err := ret.LastInsertId() // 新插入数据的id
	if err != nil {
		fmt.Printf("get lastinsert ID failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", theID)
}
func main() {
	db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	insertRowDemo(db)
	defer db.Close()
}

4-2 consulta mysql - consulta de una sola línea

consulta de una sola fila

Consulta de una sola fila db.QueryRow() ejecuta una consulta y espera devolver como máximo una fila de resultados (es decir, Fila). QueryRow siempre devuelve no nulo

valor, el error retrasado no se devuelve hasta que se llama al método Scan que devuelve el valor. (por ejemplo: no se encontraron resultados)

func (db *DB) QueryRow(cadena de consulta, argumentos...interfaz{}) *Fila

4-2-mysql-query copy.go

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

type user struct {
	id   int
	name string
	age  int
}

// 查询单条数据示例
func queryRowDemo(db *sql.DB) {
	sqlStr := "select id, name, age from user where id=?"
	var u user
	// 非常重要:确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放
	err := db.QueryRow(sqlStr, 3).Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("scan failed, err:%v\n", err)
		return
	}
	fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}
func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	queryRowDemo(db)
	defer db.Close()
}

4-2 consulta mysql - consulta de varias líneas

Consulta de varias líneas db.Query() ejecuta una consulta y devuelve varias filas de resultados (es decir, filas), que generalmente se usa para ejecutar el comando de selección. tabla de argumentos de parámetros

Indica los parámetros de marcador de posición en la consulta.

func (db *DB) Consulta(cadena de consulta, argumentos...interfaz{}) (*Filas, error)

4-2-mysql-multi-consulta.go

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

type user struct {
	id   int
	name string
	age  int
}

// 查询多条数据示例
func queryMultiRowDemo(db *sql.DB) {
	sqlStr := "select id, name, age from user where id > ?"
	rows, err := db.Query(sqlStr, 0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	// 非常重要:关闭rows释放持有的数据库链接
	defer rows.Close()

	// 循环读取结果集中的数据
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age) // 通过SCAN读取出来
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	queryMultiRowDemo(db)
	defer db.Close()
}

4-3 actualización de mysql

4-3-mysql-update.go

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

type user struct {
	id   int
	name string
	age  int
}

// 更新数据
func updateRowDemo(db *sql.DB) {
	sqlStr := "update user set age=? where id = ?"
	ret, err := db.Exec(sqlStr, 20, 2)
	if err != nil {
		fmt.Printf("update failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("update success, affected rows:%d\n", n)
}
func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	// fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	updateRowDemo(db)
	defer db.Close()
}

4-4 mysql eliminar

4-4-mysql-delete.go

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

type user struct {
	id   int
	name string
	age  int
}

// 删除数据
func deleteRowDemo(db *sql.DB) {
	sqlStr := "delete from user where id = ?"
	ret, err := db.Exec(sqlStr, 1)
	if err != nil {
		fmt.Printf("delete failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("delete success, affected rows:%d\n", n)
}

func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	// fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	deleteRowDemo(db)
	defer db.Close()
}

5 Preprocesamiento de MySQL

¿Qué es el preprocesamiento?

Proceso común de ejecución de sentencias SQL:

1. El cliente reemplaza los marcadores de posición de la instrucción SQL para obtener una instrucción SQL completa.

2. El cliente envía una declaración SQL completa al servidor MySQL

3. El servidor MySQL ejecuta la instrucción SQL completa y devuelve el resultado al cliente.

Proceso de ejecución de preprocesamiento:

1. Divida la instrucción SQL en dos partes, la parte de comando y la parte de datos.

2. Primero envíe la parte del comando al servidor MySQL, y el servidor MySQL realiza el preprocesamiento de SQL.

3. Luego, envíe la parte de datos al servidor MySQL, y el servidor MySQL reemplaza los marcadores de posición para la instrucción SQL.

4. El servidor MySQL ejecuta la instrucción SQL completa y devuelve el resultado al cliente.

¿Por qué preprocesamiento?

1. Optimice el método de ejecución repetida de SQL en el servidor MySQL, lo que puede mejorar el rendimiento del servidor, permitir que el servidor compile por adelantado y compilar varias veces a la vez

Ejecución, ahorrando el coste de compilación posterior.

2. Evite problemas de inyección SQL.

5.1 Go implementa el preprocesamiento de MySQL

func (db *DB) Prepare (cadena de consulta) (*Stmt, error)

El método Prepare primero enviará la declaración sql al servidor MySQL y devolverá un estado listo para consultas y comandos posteriores.

El valor devuelto puede ejecutar múltiples consultas y comandos al mismo tiempo. 4-5-mysql-prepare.go

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

type user struct {
	id   int
	name string
	age  int
}

// 预处理查询示例
func prepareQueryDemo(db *sql.DB) {
	sqlStr := "select id, name, age from user where id > ?"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	rows, err := stmt.Query(0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	defer rows.Close()
	// 循环读取结果集中的数据
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

// 预处理插入示例
// 插入、更新和删除操作的预处理十分类似
func prepareInsertDemo(db *sql.DB) {
	sqlStr := "insert into user(name, age) values (?,?)"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec("darren", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	_, err = stmt.Exec("柚子老师", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	fmt.Println("insert success.")
}

func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	// fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	prepareInsertDemo(db)
	prepareQueryDemo(db)
	defer db.Close()
}

6 Go implementa transacciones MySQL

Métodos relacionados con transacciones Los siguientes tres métodos se utilizan en el lenguaje Go para implementar operaciones de transacción en MySQL.

Iniciar transacción: func (db *DB) Begin() (*Tx, error)

Confirmar transacción: func (tx *Tx) Commit() error

Transacción de reversión: func (tx * Tx) Error de reversión ()

6-mysql-transaction.go

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
)

type user struct {
	id   int
	name string
	age  int
}

// 预处理查询示例
func prepareQueryDemo(db *sql.DB) {
	sqlStr := "select id, name, age from user where id > ?"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	rows, err := stmt.Query(0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	defer rows.Close()
	// 循环读取结果集中的数据
	for rows.Next() {
		var u user
		err := rows.Scan(&u.id, &u.name, &u.age)
		if err != nil {
			fmt.Printf("scan failed, err:%v\n", err)
			return
		}
		fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
	}
}

// 预处理插入示例
// 插入、更新和删除操作的预处理十分类似
func prepareInsertDemo(db *sql.DB) {
	sqlStr := "insert into user(name, age) values (?,?)"
	stmt, err := db.Prepare(sqlStr)
	if err != nil {
		fmt.Printf("prepare failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	_, err = stmt.Exec("darren", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	_, err = stmt.Exec("柚子老师", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	fmt.Println("insert success.")
}

// 事务操作示例
func transactionDemo(db *sql.DB) {
	tx, err := db.Begin() // 开启事务
	if err != nil {
		if tx != nil {
			tx.Rollback() // 回滚
		}
		fmt.Printf("begin trans failed, err:%v\n", err)
		return
	}
	sqlStr1 := "Update user set age=30 where id=?"
	_, err = tx.Exec(sqlStr1, 2)
	if err != nil {
		tx.Rollback() // 回滚
		fmt.Printf("exec sql1 failed, err:%v\n", err)
		return
	}
	sqlStr2 := "Update user set age=40 where id=?"
	_, err = tx.Exec(sqlStr2, 4)
	if err != nil {
		tx.Rollback() // 回滚
		fmt.Printf("exec sql2 failed, err:%v\n", err)
		return
	}
	err = tx.Commit() // 提交事务
	if err != nil {
		tx.Rollback() // 回滚
		fmt.Printf("commit failed, err:%v\n", err)
		return
	}
	fmt.Println("exec trans success!")
}
func main() {
	db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	if db != nil {
		defer db.Close() // 健壮的写法
	}
	// fmt.Println("err:", err)
	err = db.Ping()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(5)
	stats := db.Stats()
	fmt.Println("stats1:", stats)
	prepareInsertDemo(db)
	prepareQueryDemo(db)
	stats = db.Stats()
	fmt.Println("stats2:", stats)
}

uso de 7 sqlx

La biblioteca de terceros sqlx puede simplificar las operaciones y mejorar la eficiencia del desarrollo.

Instalar

ve a github.com/jmoiron/sqlx

7-mysql-sqlx.go

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

type user struct {
	ID   int    `json:"id" db:"id"`
	Name string `json:"name" db:"name"`
	Age  int    `json:"age" db:"age"`
}

var db *sqlx.DB

// 连接数据库
func initDB() (err error) {
	dsn := "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4"
	// 也可以使用MustConnect连接不成功就panic
	db, err = sqlx.Connect("mysql", dsn)
	if err != nil {
		fmt.Printf("connect DB failed, err:%v\n", err)
		return
	}
	db.SetMaxOpenConns(20)
	db.SetMaxIdleConns(10)
	return
}

// 查询单条数据
func queryRowDemo() {
	sqlStr := "select id, name, age from user where id=?"
	var u user
	err := db.Get(&u, sqlStr, 2) // 单条查询
	if err != nil {
		fmt.Printf("get failed, err:%v\n", err)
		return
	}
	fmt.Printf("id:%d name:%s age:%d\n", u.ID, u.Name, u.Age)
}

// 查询多行数据
func queryMultiRowDemo() {
	sqlStr := "select id, name, age from user where id > ?"
	var users []user
	err := db.Select(&users, sqlStr, 0) // 主要是查询
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	fmt.Printf("users:%#v\n", users)
}

// 插入数据
func insertRowDemo() {
	sqlStr := "insert into user(name, age) values (?,?)"
	ret, err := db.Exec(sqlStr, "隔壁老王", 18)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	theID, err := ret.LastInsertId() // 新插入数据的id
	if err != nil {
		fmt.Printf("get lastinsert ID failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", theID)
}

// 更新数据
func updateRowDemo() {
	sqlStr := "update user set age=? where id = ?"
	ret, err := db.Exec(sqlStr, 39, 6)
	if err != nil {
		fmt.Printf("update failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("update success, affected rows:%d\n", n)
}

// 删除数据
func deleteRowDemo() {
	sqlStr := "delete from user where id = ?"
	ret, err := db.Exec(sqlStr, 6)
	if err != nil {
		fmt.Printf("delete failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("delete success, affected rows:%d\n", n)
}

// 事务操作
func transactionDemo() {
	tx, err := db.Beginx() // 开启事务
	if err != nil {
		if tx != nil {
			tx.Rollback()
		}
		fmt.Printf("begin trans failed, err:%v\n", err)
		return
	}
	sqlStr1 := "Update user set age=40 where id=?"
	tx.MustExec(sqlStr1, 2)
	sqlStr2 := "Update user set age=50 where id=?"
	tx.MustExec(sqlStr2, 4)
	err = tx.Commit() // 提交事务
	if err != nil {
		tx.Rollback() // 回滚
		fmt.Printf("commit failed, err:%v\n", err)
		return
	}
	fmt.Println("exec trans success!")
}

// Get、QueryRowx: 查询一条数据
// QueryRowx可以指定到不同的数据类型中
func getNum() {
	var num int
	_ = db.Get(&num, "select count(*) from user")
	fmt.Printf("数据库一共有:%d 个用户\n", num)
	var u user
	_ = db.Get(&u, "select name, id, age from user where id = ?", 1)
	fmt.Printf("查找用户id==1的用户:%v \n", u)
}
func main() {
	err := initDB()
	if err != nil {
		fmt.Println("数据库链接失败", err)
		return
	}
	insertRowDemo()
	queryRowDemo()
	getNum()
	queryMultiRowDemo()
	// defer db.Close()
}

7-mysql-sqlx-2.go

package main

// 数据库连接初始化
import (
	"fmt"

	_ "github.com/go-sql-driver/mysql" // mysql
	"github.com/jmoiron/sqlx"
)

// DB 数据库模型
var DB *sqlx.DB

const dsn = "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4"

type user struct {
	ID   int    `json:"id" db:"id"`
	Name string `json:"name" db:"name"`
	Age  int    `json:"age" db:"age"`
}

// connect 1.连接数据库
func connect() (db *sqlx.DB, err error) {
	db, err = sqlx.Connect("mysql", dsn)
	db.SetMaxOpenConns(100) // 设置连接池最大连接数
	db.SetMaxIdleConns(20)  // 设置连接池最大空闲连接数
	DB = db
	if err != nil {
		fmt.Println("数据库连接失败==>", err)
	}
	fmt.Println("数据库已连接!")
	return
}

// 添加数据 Exec、MustExec
// MustExec遇到错误的时候直接抛出一个panic错误,程序就退出了;
// Exec是将错误和执行结果一起返回,由我们自己处理错误。 推荐使用!
func createUser() {
	// 创建表
	sql := `
        CREATE TABLE user  (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            name varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '',
            age int(11) NULL DEFAULT 0,
            PRIMARY KEY (id) USING BTREE
        ) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact
    `
	_, err := DB.Exec(sql)
	fmt.Println(err)
}

// 添加数据
func insertUser() {
	sql := `insert into user (name, age) values ("lgx",18)`
	res := DB.MustExec(sql)
	fmt.Println(res.LastInsertId)
	fmt.Println(res.RowsAffected)
}

// 更新数据
func updateUser() {
	sql := `update user set name = ?, age = ? where id = ?`
	res, err := DB.Exec(sql, "LGX", 28, 20)
	fmt.Println(err, res)
}

// Get、QueryRowx: 查询一条数据
// QueryRowx可以指定到不同的数据类型中
func getNum() {
	var num int
	_ = DB.Get(&num, "select count(*) from user")
	fmt.Printf("数据库一共有:%d 个用户\n", num)
	var u user
	_ = DB.Get(&u, "select name, id, age from user where id = ?", 2)
	fmt.Printf("查找用户id==1的用户:%v \n", u)
}

// Select、Queryx:查询多条数据
// Queryx可以指定到不同的数据类型中
func getAll() {
	sql := `select id, name ,age from user where id > 1`
	var us []user
	err := DB.Select(&us, sql)
	fmt.Println(err, us)
}

// 删除
func deleteUser() {
	sql := `delete from user where id = 20`
	_, _ = DB.Exec(sql)
}

// 事务处理
func events() {
	tx, _ := DB.Beginx()
	_, err1 := tx.Exec("update user set age = 10 where id = 20")
	_, err2 := tx.Exec("update user set age = 10 where id = 21")
	fmt.Println(err1, err2)
	if err1 != nil || err2 != nil {
		tx.Rollback()
	}
	tx.Commit()
}

func main() {
	db, _ := connect()
	defer db.Close()
	// 建表
	// createUser()
	// 添加数据
	insertUser()
	// 修改数据
	updateUser()
	// 查数据-Get
	getNum()
	// 查数据-Select
	getAll()
	// 事务
	// events()
}

8 gin + mysql restfull api

repositorio de código

github.com/yunixiangfeng/devops/tree/main/gin_restful

 api\users.go

package api

import (
	"fmt"
	. "gin_restful/models"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
)

//index
func IndexUsers(c *gin.Context) {
	c.String(http.StatusOK, "It works")
}

//增加一条记录
func AddUsers(c *gin.Context) {
	name := c.Request.FormValue("name")
	telephone := c.Request.FormValue("telephone")
	fmt.Println("name:", name)
	fmt.Println("telephone:", telephone)
	if name == "" {
		msg := fmt.Sprintf("name字段错误")
		c.JSON(http.StatusBadRequest, gin.H{
			"msg": msg,
		})
		return
	}
	person := Person{
		Name:      name,
		Telephone: telephone,
	}
	id := person.Create()
	msg := fmt.Sprintf("insert 成功 %d", id)
	c.JSON(http.StatusOK, gin.H{
		"msg": msg,
	})
}

//获得一条记录
func GetOne(c *gin.Context) {
	ids := c.Param("id")
	id, _ := strconv.Atoi(ids)
	p := Person{
		Id: id,
	}
	rs, _ := p.GetRow()
	c.JSON(http.StatusOK, gin.H{
		"result": rs,
	})
}

//获得所有记录
func GetAll(c *gin.Context) {
	p := Person{}
	rs, _ := p.GetRows()
	c.JSON(http.StatusOK, gin.H{
		"list": rs,
	})
}

func UpdateUser(c *gin.Context) {
	ids := c.Request.FormValue("id")
	id, _ := strconv.Atoi(ids)
	telephone := c.Request.FormValue("telephone")
	person := Person{
		Id:        id,
		Telephone: telephone,
	}
	row := person.Update()
	msg := fmt.Sprintf("updated successful %d", row)
	c.JSON(http.StatusOK, gin.H{
		"msg": msg,
	})
}

//删除一条记录
func DelUser(c *gin.Context) {
	ids := c.Request.FormValue("id")
	id, _ := strconv.Atoi(ids)
	row := Delete(id)
	msg := fmt.Sprintf("delete successful %d", row)
	c.JSON(http.StatusOK, gin.H{
		"msg": msg,
	})
}

db\mysql.go

package db

import (
	"database/sql"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

var SqlDB *sql.DB

func init() {
	var err error
	SqlDB, err = sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/go_test?charset=utf8mb4")
	if err != nil {
		log.Fatal(err.Error())
	}
	err = SqlDB.Ping()
	if err != nil {
		log.Fatal(err.Error())
	}
	SqlDB.SetMaxIdleConns(20)
	SqlDB.SetMaxOpenConns(20)
}

modelos\usuarios.ir

package models

import (
	"gin_restful/db"
	"log"
)

type Person struct {
    Id int `json:"id" form:"id"`
    Name string `json:"name" form:"name"`
    Telephone string `json:"telephone" form:"telephone"`
}

//插入
func (person *Person) Create() int64 {
    rs, err := db.SqlDB.Exec("INSERT into users (name, telephone) value (?,?)", person.Name, person.Telephone)
    if err != nil{
        log.Fatal(err)
    }
    id, err := rs.LastInsertId()
    if err != nil{
        log.Fatal(err)
    }
    return id
}

//查询一条记录
func (p *Person) GetRow() (person Person, err error)  {
    person = Person{}
    err = db.SqlDB.QueryRow("select id,name,telephone from users where id = ?", p.Id).Scan(&person.Id, &person.Name, &person.Telephone)
    return
}

//查询所有记录
func (person *Person) GetRows() (persons []Person, err error) {
    rows, err := db.SqlDB.Query("select id,name,telephone from users")
    for rows.Next(){
        person := Person{}
        err := rows.Scan(&person.Id, &person.Name, &person.Telephone)
        if err != nil {
            log.Fatal(err)
        }
        persons = append(persons, person)
    }
    rows.Close()
    return
}

//修改
func (person *Person) Update() int64{
    rs, err := db.SqlDB.Exec("update users set telephone = ? where id = ?", person.Telephone, person.Id)
    if err != nil {
        log.Fatal(err)
    }
    rows, err := rs.RowsAffected()
    if err != nil {
        log.Fatal(err)
    }
    return  rows
}

//删除一条记录
func Delete(id int) int64  {
    rs, err := db.SqlDB.Exec("delete from users where id = ?", id)
    if err != nil {
        log.Fatal()
    }
    rows, err := rs.RowsAffected()
    if err != nil {
        log.Fatal()
    }
    return rows
}

principal.ir

package main

import "gin_restful/db"

// go mod init  xx_project
// go build
// ./xx_project
func main() {
	defer db.SqlDB.Close()
	router := initRouter()
	router.Run(":8806") // 启动服务了
}

enrutador.ir

package main

import (
	. "gin_restful/api"

	"github.com/gin-gonic/gin"
)

func initRouter() *gin.Engine {
	router := gin.Default()
	router.GET("/", IndexUsers) //http://192.168.204.132:8806

	//路由群组
	users := router.Group("api/v1/users")
	{
		users.GET("", GetAll)             //http://192.168.204.132:8806/api/v1/users
		users.POST("/add", AddUsers)      //http://192.168.204.132:8806/api/v1/users/add
		users.GET("/get/:id", GetOne)     //http://192.168.204.132:8806/api/v1/users/get/5
		users.POST("/update", UpdateUser) //http://192.168.204.132:8806/api/v1/users/update
		users.POST("/del", DelUser)       //http://192.168.204.132:8806/api/v1/users/del
	}

	departments := router.Group("api/v1/department")
	{
		departments.GET("", GetAll)             //http://192.168.204.132:8806/api/v1/users
		departments.POST("/add", AddUsers)      //http://192.168.204.132:8806/api/v1/users/add
		departments.GET("/get/:id", GetOne)     //http://192.168.204.132:8806/api/v1/users/get/5
		departments.POST("/update", UpdateUser) //http://192.168.204.132:8806/api/v1/users/update
		departments.POST("/del", DelUser)       //http://192.168.204.132:8806/api/v1/users/del
	}

	return router
}

8.1 gin + mysql rest full api –增

8.2 gin + mysql resto completo api –改

http://192.168.204.132:8806/api/v1/users/update

8.3 gin + mysql rest full api –查

http://192.168.204.132:8806/api/v1/users/get/5

8.4 gin + mysql rest full api – obtén todo

http://192.168.204.132:8806/api/v1/usuarios

8.5 gin + mysql rest full api – eliminado 

repositorio de código

github.com/yunixiangfeng/gin_restful

2.3 Práctica de desarrollo de fondo GO WeChat

Desarrollo de fondo de la cuenta pública de WeChat

repositorio de código

github.com/yunixiangfeng/devops/tree/main/wechat

1 Lógica de desarrollo de la cuenta oficial de WeChat

1.1 Registrar cuenta oficial

Dirección de registro: https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN&token=

1.2 Permisos de desarrollador

Ingrese a la página oficial de administración de cuentas, despliegue el lado izquierdo

1.3 Permisos de la interfaz de fondo de la cuenta oficial de WeChat

Los usuarios normales solo necesitan tener permiso para recibir mensajes y mensajes de respuesta automática

1.4 Respuesta de mensaje de cuenta oficial

1.5 Configuración del servidor

2 servicio HTTP

Primero usamos la interfaz http nativa para procesar y luego usamos gin para procesar

Nos ocupamos principalmente de los métodos Get y Post aquí, vea el código

Obtener: Manejar verificación de token Manejar verificación de token

Post: procesamiento de respuesta de mensaje mensaje de procesamiento

Proyecto: wechat

principal.ir

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
	"wechat/wx"
)

const (
	logLevel = "dev"
	port     = 80
	token    = "NmHrEBBrbIX24JFw" // 生成地址:https://suijimimashengcheng.51240.com/
)

// 处理token的认证
func get(w http.ResponseWriter, r *http.Request) {

	client, err := wx.NewClient(r, w, token)

	if err != nil {
		log.Println(err)
		w.WriteHeader(403) // 校验失败
		return
	}

	if len(client.Query.Echostr) > 0 {
		w.Write([]byte(client.Query.Echostr)) // 校验成功返回的是Echostr
		return
	}

	w.WriteHeader(403)
	return
}

// 微信平台过来消息, 处理 ,然后返回微信平台
func post(w http.ResponseWriter, r *http.Request) {

	client, err := wx.NewClient(r, w, token)

	if err != nil {
		log.Println(err)
		w.WriteHeader(403)
		return
	}
	// 到这一步签名已经验证通过了
	client.Run()
	return
}

// 编译方法
// go mod init wechat
// go build
// ./wechat
// 需要自己修改token,以适应自己公众号的token
func main() {
	server := http.Server{
		Addr:           fmt.Sprintf(":%d", port), // 设置监听地址, ip:port
		Handler:        &httpHandler{},           // 用什么handler来处理
		ReadTimeout:    5 * time.Second,          // 读写超时 微信给出来5
		WriteTimeout:   5 * time.Second,
		MaxHeaderBytes: 0,
	}

	log.Println(fmt.Sprintf("Listen: %d", port))
	log.Fatal(server.ListenAndServe())
	defer CloseLog()
}

ruta.ir

package main

import (
	"io"
	"net/http"
	"regexp"
	"time"
)

type WebController struct {
	Function func(http.ResponseWriter, *http.Request)
	Method   string
	Pattern  string
}

var mux []WebController // 自己定义的路由
// ^ 匹配输入字符串的开始位置
func init() {
	mux = append(mux, WebController{post, "POST", "^/"})
	mux = append(mux, WebController{get, "GET", "^/"})
}

type httpHandler struct{} // 实际是实现了Handler interface
// type Handler interface {
// 	ServeHTTP(ResponseWriter, *Request)
// }

func (*httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	t := time.Now()

	for _, webController := range mux { // 遍历路由
		// 匹配请求的   r.URL.Path  -> webController.Pattern
		if m, _ := regexp.MatchString(webController.Pattern, r.URL.Path); m { // 匹配URL

			if r.Method == webController.Method { // 匹配方法

				webController.Function(w, r) // 调用对应的处理函数

				go writeLog(r, t, "match", webController.Pattern)

				return
			}
		}
	}

	go writeLog(r, t, "unmatch", "")

	io.WriteString(w, "")
	return
}

log.ir

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"
)

var LogFile *os.File

func init() {
	// fmt.Println("log init")
	// LogFile, err := os.OpenFile("wechat.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) //打开日志文件,不存在则创建
	// if err != nil {
	// 	fmt.Println(err)
	// }
	// log.SetOutput(LogFile)
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func CloseLog() {
	if LogFile != nil {
		LogFile.Close()
	}

}

func writeLog(r *http.Request, t time.Time, match string, pattern string) {

	if logLevel != "prod" {

		d := time.Now().Sub(t)

		l := fmt.Sprintf("[ACCESS] | % -10s | % -40s | % -16s | % -10s | % -40s |", r.Method, r.URL.Path, d.String(), match, pattern)

		log.Println(l)
	}
}

func func_log2fileAndStdout() {
	//创建日志文件
	f, err := os.OpenFile("test.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatal(err)
	}
	//完成后,延迟关闭
	defer f.Close()
	// 设置日志输出到文件
	// 定义多个写入器
	writers := []io.Writer{
		f,
		os.Stdout}
	fileAndStdoutWriter := io.MultiWriter(writers...)
	// 创建新的log对象
	logger := log.New(fileAndStdoutWriter, "", log.Ldate|log.Ltime|log.Lshortfile)
	// 使用新的log对象,写入日志内容
	logger.Println("--> logger :  check to make sure it works")
}

 LICENCIA

                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

wx\structs.go

package wx

import (
	"encoding/xml"
	"strconv"
	"time"
)

type Base struct {
	FromUserName CDATAText
	ToUserName   CDATAText
	MsgType      CDATAText
	CreateTime   CDATAText
}

func (b *Base) InitBaseData(w *WeixinClient, msgtype string) {

	b.FromUserName = value2CDATA(w.Message["ToUserName"].(string))
	b.ToUserName = value2CDATA(w.Message["FromUserName"].(string))
	b.CreateTime = value2CDATA(strconv.FormatInt(time.Now().Unix(), 10))
	b.MsgType = value2CDATA(msgtype)
}

type CDATAText struct {
	Text string `xml:",innerxml"`
}

type TextMessage struct {
	XMLName xml.Name `xml:"xml"`
	Base
	Content CDATAText
}

wx\utils.go

package wx

func value2CDATA(v string) CDATAText {
	return CDATAText{"<![CDATA[" + v + "]]>"}
}

wx\wx.ir

package wx

import (
	"crypto/sha1"
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sort"

	"github.com/clbanning/mxj"
)

type weixinQuery struct {
	Signature    string `json:"signature"`
	Timestamp    string `json:"timestamp"`
	Nonce        string `json:"nonce"`
	EncryptType  string `json:"encrypt_type"`
	MsgSignature string `json:"msg_signature"`
	Echostr      string `json:"echostr"`
}

type WeixinClient struct {
	Token          string
	Query          weixinQuery // 请求的一些参数
	Message        map[string]interface{}
	Request        *http.Request
	ResponseWriter http.ResponseWriter
	Methods        map[string]func() bool
}

/// 请求数据Request, 返回数据ResponseWriter, token是自己的
func NewClient(r *http.Request, w http.ResponseWriter, token string) (*WeixinClient, error) {

	weixinClient := new(WeixinClient)

	weixinClient.Token = token // 获取本地的token
	weixinClient.Request = r
	weixinClient.ResponseWriter = w

	weixinClient.initWeixinQuery()
	log.Println("Signature:", weixinClient.Query.Signature)
	if weixinClient.Query.Signature != weixinClient.hashcode() { // 签名认证
		return nil, errors.New("Invalid Signature.")
	}

	return weixinClient, nil
}

func (this *WeixinClient) initWeixinQuery() {

	var q weixinQuery
	log.Println("URL:", this.Request.URL.Path, ", RawQuery:", this.Request.URL.RawPath)
	q.Nonce = this.Request.URL.Query().Get("nonce")
	q.Echostr = this.Request.URL.Query().Get("echostr")
	q.Signature = this.Request.URL.Query().Get("signature")
	q.Timestamp = this.Request.URL.Query().Get("timestamp")
	q.EncryptType = this.Request.URL.Query().Get("encrypt_type")
	q.MsgSignature = this.Request.URL.Query().Get("msg_signature")

	this.Query = q
}

// 根据 Token Timestamp Nonce 生成对应的校验码, Token是不能明文传输的
func (this *WeixinClient) hashcode() string {

	strs := sort.StringSlice{this.Token, this.Query.Timestamp, this.Query.Nonce} // 使用本地的token生成校验
	sort.Strings(strs)
	str := ""
	for _, s := range strs {
		str += s
	}
	h := sha1.New()
	h.Write([]byte(str))
	return fmt.Sprintf("%x", h.Sum(nil))
}

// 读取消息,解析XML
func (this *WeixinClient) initMessage() error {

	body, err := ioutil.ReadAll(this.Request.Body)

	if err != nil {
		return err
	}

	m, err := mxj.NewMapXml(body)

	if err != nil {
		return err
	}

	if _, ok := m["xml"]; !ok {
		return errors.New("Invalid Message.")
	}

	message, ok := m["xml"].(map[string]interface{})

	if !ok {
		return errors.New("Invalid Field `xml` Type.")
	}

	this.Message = message // 保存消息

	log.Println(this.Message)

	return nil
}

func (this *WeixinClient) text() {

	inMsg, ok := this.Message["Content"].(string) // 读取内容

	if !ok {
		return
	}

	var reply TextMessage

	reply.InitBaseData(this, "text")
	reply.Content = value2CDATA(fmt.Sprintf("我收到的是:%s", inMsg)) // 把消息再次封装

	replyXml, err := xml.Marshal(reply) // 序列化

	if err != nil {
		log.Println(err)
		this.ResponseWriter.WriteHeader(403)
		return
	}

	this.ResponseWriter.Header().Set("Content-Type", "text/xml") // 数据类型text/xml
	this.ResponseWriter.Write(replyXml)                          // 回复微信平台
}

func (this *WeixinClient) Run() {

	err := this.initMessage()

	if err != nil {

		log.Println(err)
		this.ResponseWriter.WriteHeader(403)
		return
	}

	MsgType, ok := this.Message["MsgType"].(string)

	if !ok {
		this.ResponseWriter.WriteHeader(403)
		return
	}

	switch MsgType {
	case "text":
		this.text() // 处理文本消息
		break
	default:
		break
	}

	return
}

.github\FONDOS.yml

# These are supported funding model platforms
# leeeboo
github: [wtlyy]

mecanismo de 3 fichas

Analizar los parámetros GET en la solicitud

Método de verificación de firma de la cuenta oficial de WeChat

referencia:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

Fuente: 3-1-token.go

package main

import (
	"bytes"
	"crypto/rand"
	"crypto/sha1"
	"fmt"
	"math/big"
	"sort"
	"strconv"
	"time"
)

func CreateRandomString(len int) string {
	var container string
	var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
	b := bytes.NewBufferString(str)
	length := b.Len()
	bigInt := big.NewInt(int64(length))
	for i := 0; i < len; i++ {
		randomInt, _ := rand.Int(rand.Reader, bigInt)
		container += string(str[randomInt.Int64()])
	}
	return container
}

// 根据 Token Timestamp Nonce 生成对应的校验码, Token是不能明文传输的
func GenerateSignature(token string) (timestamp string, nonce string, signature string) {

	nonce = CreateRandomString(10)
	timestamp = strconv.FormatInt(time.Now().Unix(), 10) //int64转字符串
	// 排序 微信约定好的
	strs := sort.StringSlice{token, timestamp, nonce} // 使用本地的token生成校验
	sort.Strings(strs)                                // strs: [1607173019 qing qvCyrKEuoS]
	fmt.Println("strs:", strs)                        // 排序
	str := ""
	for _, s := range strs {
		str += s // 拼接字符串
	}
	fmt.Println("str:", str) //str: 1607173019qingqvCyrKEuoS
	h := sha1.New()		// 完全都是自己的服务的时候 你这里你用md5
	h.Write([]byte(str))                      // 转成byte
	signature = fmt.Sprintf("%x", h.Sum(nil)) // h.Sum(nil) 做hash  79efadd80a344c0b73b3bd2c403184f7425a5a67
	return
}

func VerifySignature(token string, timestamp string, nonce string, signature string) bool {
	// str = token + timestamp + nonce
	strs := sort.StringSlice{token, timestamp, nonce} // 使用本地的token生成校验
	sort.Strings(strs)
	str := ""
	for _, s := range strs {
		str += s
	}
	h := sha1.New()	// 完全都是自己的服务的时候 你这里你用md5
	h.Write([]byte(str))
	return fmt.Sprintf("%x", h.Sum(nil)) == signature
}
func main() {
	token := "qing"
	// 产生签名
	timestamp, nonce, signature := GenerateSignature(token) // 发送服务器的时候是发送  timestamp, nonce, signature
	fmt.Printf("1. token %s -> 产生签名:%s, timestamp:%s, nonce:%s\n", token, signature, timestamp, nonce)
	// 验证签名
	ok := VerifySignature(token, timestamp, nonce, signature) // 服务进行校验
	if ok {
		fmt.Println("2. 验证签名正常")
	} else {
		fmt.Println("2. 验证签名失败")
	}
}

Algoritmo de fichas 3.1

referencia:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

orden alfabetico

Descripción de parámetros

firma

Firma cifrada de WeChat, firma combinada con abierta

El parámetro token rellenado por el remitente y el

parámetro de marca de tiempo, parámetro nonce.

marca de tiempo

número aleatorio nonce

ecostr

cadena aleatoria

Si la verificación del servidor es exitosa, se devuelve echostr

Si la validación falla, devuelve la cadena ""

Método de autentificación
1. Obtenga los grupos token , nonce y timestamp en el lado del servidor
en una lista
2. Clasificación de listas
3. Resume los elementos ordenados
4. Firma de comparación abstracta
5. Responder a echostr

3.2 Algoritmo de fichas - Diagrama de flujo

Método de autentificación

1. Obtenga los grupos de token, nonce y timestamp en el lado del servidor

en una lista

2. Clasificación de listas

3. Resume los elementos ordenados

4. Firma de comparación abstracta

5. Responder a echostr

referencia:

https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html

4 análisis XML

Los mensajes de WeChat están encapsulados en XML , por lo que primero debemos aprender a analizar el contenido XML

4.1 Análisis de XML - Análisis de XML

En el código, primero cree una estructura de estructura correspondiente para el formato xml

4-1-xml.ir

package main

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
)

// 如果struct中有一个叫做XMLName,且类型为xml.Name字段,
// 那么在解析的时候就会保存这个element的名字到该字段, 比如这里的config
type SConfig struct {
	XMLName      xml.Name   `xml:"config"`     // 指定最外层的标签为config
	SmtpServer   string     `xml:"smtpServer"` // 读取smtpServer配置项,并将结果保存到SmtpServer变量中
	SmtpPort     int        `xml:"smtpPort"`
	Sender       string     `xml:"sender"`
	SenderPasswd string     `xml:"senderPasswd"`
	Receivers    SReceivers `xml:"receivers"` // 读取receivers标签下的内容,以结构方式获取
}

type SReceivers struct {
	Age    int      `xml:"age"`
	Flag   string   `xml:"flag,attr"` // 读取flag属性
	User   []string `xml:"user"`      // 读取user数组
	Script string   `xml:"script"`    // 读取 <![CDATA[ xxx ]]> 数据
}

func main() {
	file, err := os.Open("4-1-xml.xml") // For read access.
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}
	defer file.Close()
	data, err := ioutil.ReadAll(file)
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}
	v := SConfig{}
	err = xml.Unmarshal(data, &v) // 反序列化
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}

	fmt.Println("文本:", v)
	fmt.Println("解析结果:")
	fmt.Println("XMLName : ", v.XMLName)
	fmt.Println("SmtpServer : ", v.SmtpServer)
	fmt.Println("SmtpPort : ", v.SmtpPort)
	fmt.Println("Sender : ", v.Sender)
	fmt.Println("SenderPasswd : ", v.SenderPasswd)
	fmt.Println("Receivers.Flag : ", v.Receivers.Flag)
	for i, element := range v.Receivers.User {
		fmt.Println(i, element)
	}
}

4-1-xml.xml 

<config>
   <smtpServer>smtp.qq.com</smtpServer>
   <smtpPort>25</smtpPort>
   <sender>[email protected]</sender>
  <senderPasswd>123456</senderPasswd>
   <receivers flag="true">
     <user>[email protected]</user>
     <user>[email protected]</user>
     <script>
     <![CDATA[
        function &%< matchwo(a,b) {
            if (a < b && a < 0) then {
                return 1;
            } else {
                return 0;
            }
        }
        ]]>
     </script>
  </receivers>
 </config>

4.2 Análisis de XML - Análisis de CDATA

Un analizador analiza todo el texto de un documento XML.

El analizador solo ignora el texto dentro de las secciones CDATA.

El término CDATA son datos de texto que no deben ser analizados por un analizador XML.

Caracteres como "<" y "&" son ilegales en elementos XML.

"<" produce un error porque el analizador interpreta este carácter como el comienzo de un nuevo elemento.

"&" produce un error porque el analizador interpreta este carácter como el comienzo de una entidad de carácter.

Algunos textos, como el código JavaScript, contienen una gran cantidad de caracteres "<" o "&". Para evitar errores, el código de secuencia de comandos se puede definir como CDATA.

El analizador ignora todo lo que se encuentra dentro de una sección CDATA.

Las secciones de CDATA comienzan con " <![CDATA[ " y terminan con "]]>":

4-2-CDATA.go

package main

import (
	"encoding/xml"
	"fmt"
	"strconv"
	"time"

	"github.com/clbanning/mxj"
)

// tag中含有"-"的字段不会输出
// tag中含有"name,attr",会以name作为属性名,字段值作为值输出为这个XML元素的属性,如上version字段所描述
// tag中含有",attr",会以这个struct的字段名作为属性名输出为XML元素的属性,类似上一条,只是这个name默认是字段名了。
// tag中含有",chardata",输出为xml的 character data而非element。
// tag中含有",innerxml",将会被原样输出,而不会进行常规的编码过程
// tag中含有",comment",将被当作xml注释来输出,而不会进行常规的编码过程,字段值中不能含有"--"字符串
// tag中含有"omitempty",如果该字段的值为空值那么该字段就不会被输出到XML,空值包括:false、0、nil指针或nil接口,任何长度为0的array, slice, map或者string

type CDATAText struct {
	Text string `xml:",innerxml"`
}

type Base struct {
	FromUserName CDATAText
	ToUserName   CDATAText
	MsgType      CDATAText
	CreateTime   CDATAText
}

// 文本消息的封装
type TextMessage struct {
	XMLName xml.Name `xml:"xml"`
	Base
	Content CDATAText
}

// 图片消息的封装
type PictureMessage struct {
	XMLName xml.Name `xml:"xml"`
	Base
	PicUrl  CDATAText
	MediaId CDATAText
}

func value2CDATA(v string) CDATAText {
	return CDATAText{"<![CDATA[" + v + "]]>"}
}

func main() {
	// 1. 解析 XML
	xmlStr := `<xml> 
		<ToUserName><![CDATA[toUser]]></ToUserName> 
		<FromUserName><![CDATA[fromUser]]></FromUserName> 
		<CreateTime>1348831860</CreateTime> 
		<MsgType><![CDATA[text]]></MsgType> 
		<Content><![CDATA[this is a test]]></Content> 
		<MsgId>1234567890123456</MsgId> 
		</xml>`

	var Message map[string]interface{}
	m, err := mxj.NewMapXml([]byte(xmlStr)) //使用了第三方的库

	if err != nil {
		return
	}

	if _, ok := m["xml"]; !ok {
		fmt.Println("Invalid Message.")
		return
	}
	fmt.Println("-->m:", m)
	message, ok := m["xml"].(map[string]interface{}) // 把xml对应的值读取出来

	if !ok {
		fmt.Println("Invalid Field `xml` Type.")
		return
	}

	Message = message

	fmt.Println("1. 解析出来:", Message) // xml对应的字段还是在map

	// 2. 封装XML
	var reply TextMessage
	inMsg, ok := Message["Content"].(string) // 读取内容 .(string)转成什么类型的数据

	if !ok {
		return
	}
	fmt.Println("Message[ToUserName].(string):", Message["ToUserName"].(string)) // 如果服务器要处理

	// 封装回复消息,需要添加 CDATA
	reply.Base.FromUserName = value2CDATA(Message["ToUserName"].(string))
	reply.Base.ToUserName = value2CDATA(Message["FromUserName"].(string))
	reply.Base.CreateTime = value2CDATA(strconv.FormatInt(time.Now().Unix(), 10))
	reply.Base.MsgType = value2CDATA("text")
	reply.Content = value2CDATA(fmt.Sprintf("我收到的是:%s", inMsg))

	replyXml, err := xml.Marshal(reply)                // 得到的是byte
	fmt.Println("2. 生成XML:", string(replyXml))         // []byte -> string
	fmt.Println("2. 生成XML:", []byte(string(replyXml))) // string -> []byte
}

<xml> <ParaNombreDeUsuario><![CDATA[paraUsuario]]></ParaNombreDeUsuario>

<NombreDeUsuario><![CDATA[deUsuario]]></NombreDeUsuario>

<CrearTiempo>1348831860</CrearTiempo>

<Tipo de mensaje><![CDATA[texto]]></Tipo de mensaje>

<Contenido><![CDATA[esto es una prueba]]></Contenido>

<MsgId>1234567890123456</MsgId> </xml>

5 Me pides que responda

1) Comprender el significado de los mensajes pasivos 2) Comprender las funciones pre-implementadas del mecanismo de recepción/envío de mensajes: los fanáticos envían un mensaje de texto a la cuenta oficial,

La cuenta oficial responde inmediatamente un mensaje de texto a los fanáticos y no es necesario operar a través de la página web de la plataforma oficial.

5.1 Me pides una respuesta - Protocolo de recepción de mensajes

referencia:

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standar

d_mensajes.html

Descripción de parámetros

ID de WeChat del desarrollador ToUserName

Número de cuenta del remitente FromUserName (un OpenID)

Hora de creación del mensaje CreateTime (entero)

Tipo de mensaje MsgType, el texto es texto

Contenido del mensaje de texto contenido

MsgId ID de mensaje, entero de 64 bits

<xml> <ParaNombreDeUsuario><![CDATA[paraUsuario]]></ParaNombreDeUsuario>

<NombreDeUsuario><![CDATA[deUsuario]]></NombreDeUsuario>

<CrearTiempo>1348831860</CrearTiempo>

<Tipo de mensaje><![CDATA[texto]]></Tipo de mensaje>

<Contenido><![CDATA[esto es una prueba]]></Contenido>

<MsgId>1234567890123456</MsgId> </xml>

5.2 Me pides que responda - protocolo de mensaje de respuesta pasiva

referencia:

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply

_mensaje.html#0

Si el parámetro debe describir

ToUserName es el número de cuenta del receptor (OpenID recibido)

FromUserName es la cuenta WeChat del desarrollador

CreateTime es el tiempo de creación del mensaje (entero)

MsgType es el tipo de mensaje, el texto es texto

El contenido es

Contenido del mensaje de respuesta (salto de línea: en

Se pueden agregar líneas nuevas en el contenido, cliente de WeChat

soporte de visualización de nueva línea)

6 expresiones regulares avanzadas del lenguaje go

Consulte el sitio web oficial: https://studygolang.com/pkgdoc

Ejemplo: https://www.cnblogs.com/nulige/p/10260149.html

3.1 Conferencia intensiva sobre el conocimiento de los medios de transmisión y el diseño de la arquitectura

1.1 Escenarios de aplicaciones en vivo

1.2 Los elementos de función de transmisión en vivo de uso común se usan comúnmente

1.3 Ejemplo de cuadro en vivo 1

1.4 Ejemplo 2 de marco de transmisión en vivo: marco de cierta universidad de transmisión en vivo 

2 Arquitectura de transmisión en vivo: lógica básica 

2.0 Proceso de transmisión en vivo del protocolo común de transmisión de medios

Protocolo de transporte en tiempo real RTP (Protocolo de transporte en tiempo real o RTP para abreviar)

Protocolo de control RTCP RTP

RTSP (Protocolo de transmisión en tiempo real), RFC2326, protocolo de transmisión en tiempo real

RTMP RTMP es Real Time Messaging Protocol (Protocolo de mensajería en tiempo real)

FLV-HTTP

HTTP-MP4

HLS

WebRTC

2.1 Arquitectura de transmisión en vivo: codificación de software de proceso básico: mejora la compatibilidad de la máquina

2.2 Herramientas comunes para transmisión en vivo

◼ Herramientas de transmisión:

• ffmpeg: https://www.ffmpeg.org/download.html

• Estudio OBS: https://obsproject.com/download

◼ Herramientas de transmisión

• ffplay(): https://www.ffmpeg.org/download.html

• cutv www.cutv.com/demo/live_test.swf reproductor flash

• vlc

• ijkplayer (basado en ffplay): un reproductor de video Android/iOS de código abierto basado en FFmpeg

(fuente abierta)

API es fácil de integrar;

La configuración de compilación se puede recortar, lo cual es conveniente para controlar el tamaño del paquete de instalación;

Admite decodificación acelerada por hardware, más ahorro de energía

Simple y fácil de usar, especifique la URL de transmisión y decodifique y reproduzca automáticamente.

◼ Herramientas de prueba de presión

• carga st

2.3 Servidor de transmisión

SRS: un excelente sistema de servidor de transmisión de medios de código abierto desarrollado por chinos

BMS: También es un sistema de servidor de medios de transmisión, pero no de código abierto, es la versión comercial de SRS,

Más funciones que SRS

nginx: servidor web gratuito de código abierto, también comúnmente utilizado para configurar servidores de transmisión de medios.

Simplemente integre Rtmp_module.

Red5: es un servidor rtmp estable de código abierto escrito en java.

3 CDN del marco de transmisión en vivo

marco de 4 tiros 

1. Secuencia de inicialización del módulo

2. Control de cola de datos de audio y video (packetqueue)

3. Decodificación de audio y video

4. Remuestreo de audio

5. Conversión de tamaño de video

6. Cola de cuadros de audio y video

7. Sincronización de audio y video

8. Verifique en puntos clave de tiempo

9. Otro

4.1 模块初始化顺序

推流模块(网络连接耗时) > 音视频编码模块 >音视频采集模块()

音视频输出模块 >音视频解码模块 > 拉流模块

本质上来讲,就是在数据到来之前准备好一切工作

4.2 音视频数据队列

音视频队列涉及到

1. Audio PacketQueue 还没有解码的

2. Video PacketQueue

两者独立

队列设计要点:

1. 可控制队列大小

1. 根据packet数进行控制

2. 根据总的duration进行控制 音频48khz, 21.3毫秒, 21.3*20 = 426ms

3. 支持packet的size进行入队列累加,出队列则减, 300,200,400, 字节累加

2. 支持packet数量统计

3. 支持packet的duration进行入队列累加,出队列则减

4. 支持阻塞和唤醒

目的:

1. 统计延迟(缓存时间长度)

4.3 音视频数据队列

音视频队列涉及到

1. Audio PacketQueue

2. Video PacketQueue

两者独立

队列设计要点:

1. 可控制队列大小

1. 根据packet数进行控制

2. 根据总的duration进行控制

2. 支持packet数量统计

3. 支持packet的size进行入队列累加,出队列则减

4. 支持packet的duration进行入队列累加,出队列则

5. 支持阻塞和唤醒

4.4 音视频解码

关键点:

1. 编码前: dts

2. 编码后: pts

3. packet释放

4. frame释放

5. 返回值处理

4.5 音频重采样

音频重采样模块AudioResampler:

注意重采样后不能直接使用linesize进行大小判断,需要使用

int size = av_get_bytes_per_sample((AVSampleFormat)(dstframe->format))

* dstframe->channels * dstframe->nb_samples ;

 4.6 视频尺寸变换

图像尺寸变换模块ImageScaler:变换尺寸大小

性能的提升可以考虑 libyuv

4.7 音视频解码后帧队列

FrameQueue

解码后的数据量比较大,需要要控制解码后帧队列的大小

Consulte el tamaño fijo de ffplay

#define VIDEO_PICTURE_QUEUE_SIZE 3 // Tamaño del búfer del cuadro de imagen

#define SUBPICTURE_QUEUE_SIZE 16 // Tamaño del búfer del cuadro de subtítulos

#define SAMPLE_QUEUE_SIZE 9 // Número de búferes de fotogramas de muestra

#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE,

FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))

4.8 Sincronización de audio y video

módulo Avsync

Actualmente solo se admite el método maestro de audio.

4.9 Monitoreo de puntos clave de tiempo de cada módulo

4.10 Otros

1. El primer cuadro del cliente se abre cada segundo, lo cual es esencialmente para mostrar el primer cuadro sin sincronización.

2. Cuando no haya ningún problema con la transmisión push, si la transmisión pull no se puede reproducir normalmente:

1. Sin sonido: ¿Se pueden reproducir normalmente los datos transmitidos por dump rtmp?

2. Sonido anormal: si hay un informe de error de decodificación, si los datos pcm antes del remuestreo son normales

3. Sin imagen: si los datos transmitidos por dump rtmp se pueden reproducir normalmente

4. La pantalla es anormal: si hay un informe de error de decodificación, si los datos antes de la escala son normales

El primer marco del servidor se abre en segundos: esta función no puede reducir la demora

5 Marco de transmisión en vivo: secuencia de inicialización del módulo

Módulo de transmisión (conexión de red que requiere mucho tiempo) > módulo de codificación de audio y video > módulo de captura de audio y video ()

 

5.1 Marca de tiempo de adquisición - modo de intervalo de cuadro

5.2 Marca de tiempo de adquisición: modo de tiempo del sistema directo

5.3 Marca de tiempo de adquisición - intervalo de cuadro + modo de tiempo de sistema directo

5.4 Módulo de códec de audio y video

5.5 Control de colas de audio y video

5.6 Puntos temporales clave

5.7 Otros

6 WebRTC

El servidor de señalización se implementa mediante el lenguaje go.

Arma tu propia web de llamadas de audio y video

Introducción a WebRTC

Modelo de llamada WebRTC

Modelo de llamadas WebRTC Modelo de red de llamadas uno a uno en malla

Modelo de llamada WebRTC Modelo de red de llamada multipartita en malla

Ventajas y desventajas de la topología de red WebRTC Mesh

Modelo de llamada WebRTC Modelo de red de llamada SFU

Modelo de llamada WebRTC Modelo de red de llamada MCU

Selección del modelo de red de llamadas WebRTC

WebRTC crea un sistema de conferencias para varias personas

Campo de aplicación WebRTC

Solución de código abierto basada en webrtc

Empresa de solución de llamadas de audio y video nacionales

Desarrollo avanzado de WebRTC - Cascada SFU

Recursos de aprendizaje

¿Cuál es el número máximo de usuarios en una videollamada WebRTC? https://www.jianshu.com/p/9ef708f93499

Desarrollo multimedia https://www.jianshu.com/c/e5b30935c054

Sitio web chino de WebRTC https://webrtc.org.cn

Sitio web oficial de WebRTC https://webrtc.org/

Ejemplo de WebRTC https://webrtc.github.io/samples/

Fundamentos de AppRTC

Precauciones para construir la demostración de AppRTC

Diseño básico de señalización de llamadas WebRTC: negociación de medios + candidato de información de red + personal de sala

administrar

Análisis de señalización de llamadas 1 a 1

Revisión de práctica de llamadas uno a uno

llamada multipartita

análisis lógico

3.2 Definición de la estructura del código de ingeniería-apidefs

repositorio de código

github.com/yunixiangfeng/devops/tree/main/video_server

2 Análisis de arquitectura y diseño de API

1. Análisis técnico

Puntos técnicos del sitio web de transmisión de medios
◼Diseño de arquitectura del sistema con separación de front-end y back-end
Diseño e implementación de API de estilo RESTful
Go implementa servicios web
◼Servicio de desacoplamiento del sistema
◼ Práctica del canal de go y modelo de concurrencia
◼Utilice la plantilla nativa para completar la implementación de la interfaz de usuario web
Arquitectura general

¿Qué es el desacoplamiento de front-end y back-end?

◼ El desacoplamiento de front-end y back-end es una arquitectura de sitio web popular hoy en día

◼ Las páginas de inicio y los servicios son proporcionados por motores web comunes

◼ Los datos de back-end se presentan a través de la llamada de posprocesamiento del script renderizado

Ventajas del desacoplamiento front-end y back-end

◼ Libere la productividad

◼ La arquitectura débilmente acoplada es más flexible, más cómoda de implementar y más acorde con las características de diseño de los microservicios

◼ Rendimiento mejorado y confiabilidad mejorada

Desventajas del desacoplamiento de front-end y back-end

◼ Gran carga de trabajo

◼ La separación de front-end y back-end genera costos de equipo y costos de aprendizaje

◼ Mayor complejidad del sistema

2. Diseño de la API REST

API

◼ REST es un acrónimo de Representational State Transfer

◼ Comportamientos comunes (ver, crear, editar y eliminar)

Ambos se pueden asignar directamente a los métodos GET, POST, PUT y DELETE implementados en HTTP.

◼ Por lo general, use Json como formato de encapsulación de datos

◼ Interfaz unificada

◼ apátrida

◼ almacenable en caché

Principios de diseño de API

◼ Diseño de API en estilo URL (Uniform Resource Locator)

◼ Distingue CURD para recursos a través de diferentes métodos (GET/POST/PUT/DELETE)

◼ El código de retorno (código de estado) cumple con las normas de descripción de recursos HTTP

3. Práctica de diseño de API

Diseño de API

Diseño API: Usuario

Diseño de API: Vídeo 

Diseño de API: Reseñas 

Diseño de base de datos - Usuario 

CREAR TABLA `video_server`.`users` (
`id` int clave principal sin firmar auto_incremento,
`login_name` varchar(64) clave única,
texto `pwd`
);
Diseño de base de datos - Vídeo
CREAR TABLA `video_server`.`video_info` (
`id` varchar(64) NO NULO,
`autor_id` int(10) NULL,
`nombre` texto NULL,
`display_ctime` texto NULO,
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP,
CLAVE PRINCIPAL (`id`)
);
Diseño de base de datos - Revisión
CREAR TABLA `video_server`.`comentarios` (
`id` varchar(64) NO NULO,
`video_id` varchar(64) NULO,
`autor_id` int(10) NULL,
`contenido` texto NULL,
`hora` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP,
CLAVE PRINCIPAL (`id`)
);

Diseño de base de datos - Sesión

CREAR TABLA `video_server`.`sessions` (

`session_id` varchar(244) NO NULO,

`TTL` texto minúsculo NULO,

`login_name` texto NULL,

CLAVE PRINCIPAL (`session_id`)

);

Diseño de base de datos: tabla de video para eliminar

CREAR TABLA `video_server`.`video_del_rec` (

`video_id` varchar(64) NO NULO,

CLAVE PRINCIPAL (`video_id`)

);

4. Puerto abierto

puerto abierto

◼ API 10000

◼ programador 10001

◼ servidor de flujo 1002

◼ web 10003

repositorio de código

github.com/yunixiangfeng/video_server 

3.3 diseño detallado de stream-scheduler-web

1. diseño del servidor de transmisión

0 arquitectura general

1 servidor de transmisión

◼ vídeo fijo,

◼ Servicio independiente, se puede implementar de forma independiente

◼ Formato API unificado

1.1 Interfaz externa de Stream Server

◼ /videos/:vid-id -> reproducción de archivos streamHandler

◼ /upload/:vid-id -> uploadHandler carga del archivo

1.2 Diseño general del código

◼ Mecanismo de control de flujo

◼ El papel del middleware

1.3 Mecanismo de control de flujo - balde de fichas
◼Por qué es necesario el control de flujo
◼Obtenga el token para continuar con el procesamiento
◼ ¿ Por qué no usar una matriz?
ir a la rutina es concurrente, si usa variables, necesita bloquear
Go maneja la concurrencia con canales
limitador.ir

1.4 Agregar control de flujo al middleware http

escriba middleWareHandler struct {

r *httprouter.Router

l *ConnLimiter

}

func NewMiddleWareHandler(r *httprouter.Router,

cc int) http.Manejador {

m := manejadorintermedio{}

señor = r

ml = NewConnLimiter(cc) // cantidad límite

volver m

}

1.5 Implementación del controlador de flujo

◼ streamHandler leer reproducción de archivos

◼ uploadHandler cargar archivos

2. diseño del programador

2 programador programador

◼ ¿Qué es un programador?

◼ ¿Por qué necesitas un programador?

◼ ¿Qué suele hacer el programador?

Tareas asincrónicas, tareas retrasadas, tareas cronometradas

2.1 ¿Qué contiene el Programador?

◼ Servidor REST ful 的HTTP

◼ Temporizador

◼ Task runner bajo el modelo productor-consumidor

2.2 Arquitectura del programador

2.3 Arquitectura del código

◼ consulta y eliminación de la base de datos dbops

◼ taskrunner ejecuta tareas

◼ proceso de tarea de procesamiento de runner.go (modelo de consumo de producción)

◼ tareas.ir ejecutar tareas (producción específica, consumo)

◼ trmain.go implementa tareas de temporización, como ejecutar cada 3 segundos

◼ handlers.go API de procesamiento

◼ entrada del programa main.go

◼ paquete de respuesta http response.go

2.4 implementación de tareas

escriba la estructura del corredor {

Controlador controlChan

Control de erroresChan

Fecha fechaChan

tamaño de datos int

bool longevo

Despachador fn

Ejecutor fn

}

◼ Canal de control de procesos del controlador

◼ Canal de control de error de error

◼ Canal de datos de tareas reales de datos

corredor.ir
tareas.ir

2.5 implementación del temporizador

trmain.ir

tipo de estructura de trabajo {

ticker *tiempo.Ticker

corredor * corredor

}

Cronometraje de tareas a través de temporizadores

3. diseño web

3 servicio front-end web

◼ Ir al motor de plantillas

◼ Procesamiento de API

◼ Transmisión transparente API

◼ agente delegado

Arquitectura de código 3.0

◼ plantillas plantilla html

◼ client.go maneja la transmisión transparente api

◼ definición de estructura defs.go

◼ función de procesamiento de entrada de handlers.go api

◼ main.go entrada principal

3.1 Ir al motor de plantillas

◼ El motor de plantillas es una herramienta para generar la página final analizando HTML y reemplazando elementos preestablecidos

◼ Hay dos tipos de plantillas en Go: texto/plantilla y html/plantilla

◼ La plantilla de Go adopta un modo generado dinámicamente

3.2 Motor de plantilla Go - proceso de renderizado

3.3 Representación de página

◼ Representación de la página de inicio: homeHandler

◼ Representación de la página de usuario: userHomeHandler

3.4 Implementación del módulo de transmisión transparente api

◼ apiHandler maneja el análisis lógico

3.5 proxy转发的实现

◼ proxyHandler处理逻辑非分析

代码仓库

github.com/yunixiangyu/devops

4.1 Gin和jwt验证实战

代码仓库

github.com/yunixiangyu/devops/tree/main/gin_practice

gin实战

N ⼊⻔

O RESTful API

结构体

基本的REST ful范例

路由参数

:路由

*路由

P URL查询参数

Gin获取查询参数

原理解析

Q 接收数组和 Map

QueryArray

QueryMap

QueryMap 的原理

T 表单参数

Form 表单

Gin 接收表单数据

PostFormArray()⽅法获取表单参数

Gin PostForm系列⽅法

实现原理

⼩结

T 上传⽂件

上传单个⽂件FormFile

上传多个⽂件MultipartForm

V 分组路由

分组路由

路由中间件

分组路由嵌套

原理解析

GIn中间件

Gin默认中间件

中间件实现HTTP Basic Authorization

针对特定URL的Basic Authorization

⾃定义中间件

V 再谈中间件

定义中间件

⼊⻔案例

注册中间件

为全局路由注册

为某个路由单独注册

为路由组注册中间件

跨中间件存取值

中间件注意事项

gin中间件中使⽤goroutine

gin框架中间件c.Next()理解

W json、struct、xml、yaml、protobuf渲染

各种数据格式的响应

范例

X HTML模板渲染

最简单的例⼦

复杂点的例⼦

静态⽂件⽬录

重定向

NL 异步协程

NN Gin源码简要分析

概述

从DEMO开始

ENGINE

ROUTERGROUP & METHODTREE

.路由注册

路由分组

.中间件挂载

.路由匹配

HANDLERFUNC

CONTEXT

.调⽤链流转和控制

.参数解析

.响应处理

总结

参考⽂献

官⽅⽹站

https://gin-gonic.com/

⼯程代码

https://github.com/gin-gonic/gin.git

测试范例

https://github.com/gin-gonic/examples.git

中间件

https://github.com/gin-gonic/contrib.git

gin framework - Práctica de verificación JWT

La diferencia entre token N, cookie y sesión

Galleta

Sesión

Simbólico

O Json-Web-Token (JWT) Introducción

Componentes del token JWT

propósito de la firma

Cuándo usar JWT

¿Cómo funciona JWT (Json Web Tokens)?

P Autenticación basada en token y autenticación basada en servidor

N. Autenticación basada en servidor

Similitudes y diferencias entre O.Session y JWT Token

P. Cómo funciona la autenticación basada en tokens

P. Beneficios de usar Token

La diferencia entre S.JWT y OAuth

Q Ir ejemplo

Recursos S JWT

T usa el framework Gin para integrar JWT

software intermedio personalizado

Definir lógica de codificación y decodificación jwt

Definir la lógica de validación de inicio de sesión

Definir interfaces comunes para ser verificadas

Verifique la interfaz después de usar JWT

V usa go para verificación JWT

Escenarios para usar JWT

Estructura de JWT

Resumir

4.2 Go ORM en la práctica

 Código de almacén github.com/yunixiangfeng/devops/tree/main/jwt-gorm

Práctica GORM

¿Qué es ORM? ¿Por qué usar ORM?

Guía de inicio de NGORM

introducción gorm

Instalar

conectarse a mysql

Ejemplo básico de GORM

GORM opera MySQL

Definición del modelo GORM

gorm.modelo

Ejemplo de definición de modelo

Etiquetas de estructura (tags)

Etiquetas de estructura admitidas

Asociar etiquetas relacionadas (tags)

ejemplo

P Clave primaria, nombre de tabla, convención de nombre de columna

Clave primaria

Nombre de la tabla

Nombre de columna

seguimiento de marca de tiempo

Creado en

Actualizado en

Eliminado en

Q CRUD

crear

crear registro

valores predeterminados

Use el método del puntero para realizar el almacenamiento de valor cero en la base de datos

Use la interfaz Scanner/Valor para almacenar valores cero en la base de datos

Opciones de creación de extensiones

Preguntar

consulta general

donde condición

Consulta SQL común

Estructura y MapQuery

no condición

o condición

condición en línea

Opciones de consulta adicionales

primero o inicial

atributos

Asignar

primero o crear

atributos

Asignar

consulta avanzada

subconsulta

seleccionar campo

para ordenar

cantidad

compensar

total

Group & Having

连接

Pluck

扫描

链式操作相关

链式操作

⽴即执⾏⽅法

范围

多个⽴即执⾏⽅法

2

更新

更新所有字段

更新修改字段

更新选定字段

⽆Hooks更新

批量更新

使⽤SQL表达式更新

修改Hooks中的值

其它更新选项

删除

删除记录

批量删除

软删除

物理删除

S gorm-错误处理、事务、SQL构建、通⽤数据库接⼝、连接池、复合主键、⽇志

S.N. 错误处理

S.O. 事务

S.O.N. ⼀个具体的例⼦

S.P. SQL构建

S.P.N. 执⾏原⽣SQL

S.P.O. sql.Row & sql.Rows

S.P.P. 迭代中使⽤sql.Rows的Scan

S.Q. 通⽤数据库接⼝sql.DB

S.Q.N. 连接池

S.S. 复合主键

S.T. ⽇志

S.T.N. ⾃定义⽇志

4.3 go-admin架构分析和环境配置

GitHub - go-admin-team/go-admin: 基于Gin + Vue + Element UI & Arco Design & Ant Design 的前后端分离权限管理系统脚手架(包含了:多租户的支持,基础用户管理功能,jwt鉴权,代码生成器,RBAC资源控制,表单构建,定时任务等)3分钟构建自己的中后台项目;项目文档》:https://www.go-admin.pro V2 Demo: https://vue2.go-admin.dev V3 Demo: https://vue3.go-admin.dev Antd 订阅版:https://antd.go-admin.pro

go-admin架构分析和环境配置

N 简介

N.N 在线体验

N.O 特性

N.P 内置

O 安装

O.N 开发⽬录创建

O.O 获取代码

O.P 编译后端项⽬和修改配置⽂件

O.Q 初始化数据库,以及后端服务启动

O.S 前端UI交互端启动说明

O.T 发布⽹⻚

P 架构分析

P.N 接⼝

P.O ⽂件⽬录

Q 问题总结

nodejs let notifier = require('update-notifier')({pkg}) 报错

安装NodeJS和NPM

安装命令

更新npm的包镜像源,⽅便快速下载

安装n管理器(⽤于管理nodejs版本)

npm ERR! cb()never called!的错误

Husky requires Git >=O.NP.L. Got vO.V.Q.

Error NPTT: Incorrect string value: '\xEV\xWW\xBN\xET\xWB\xXP...' for column 'dept_name' at row

mysql数据库表结构导出

重点

搭建go-admin项⽬

整体框架分析

各个⽬录和源码的作⽤

jwt鉴权设计

cobra cmd机制 (k8s) 命令⾏功能⾮常强⼤。

使⽤ go cobra创建命令⾏项⽬

代码仓库 github.com/yunixiangfeng/devops/tree/main/cobra

Cobra介绍

实现没有⼦命令的CLIs程序

实现有⼦命令的CLIs程序

附加命令

4.4 go-admin API和数据库设计分析

go-admin后台设计之casbin权限管理

N 概要

O PERM 模型

O casbin 权限库

casbin的主要特性

casbin不做的事情

核⼼概念

model file

model file 定义语法

policy file

RBAC 示例

定义 model file

定义 policy file

测试代码

多租户示例

定义 model file

定义 policy file

测试代码

Has_Role

例⼦:RBAC

Has Tenant Role

gin+gorm+casbin示例

P 总结

Q 参考⽂档

go-admin后台设计之授权机制

N登录过程分析

O ⽤户权限验证

权限⽣成

权限校验

P ⻆⾊权限验证

⻆⾊规则⽣成

接⼝规则

菜单规则

⻆⾊校验

Q 数据库设计

sys_casbin_rule 权限规则

sys_config 配置信息

sys_dept部⻔信息

sys_menu菜单

sys_post nombre de la publicación

categoría de rol sys_role

sección azul sys_role_dept

sys_role_menu menú de roles

usuario sys_user

sys_category

sys_columns

sys_content

datos del diccionario sys_dict_data

tipo de diccionario sys_dict_type

directorio de archivos sys_file_dir

información del archivo sys_file_info

sys_job

registro de inicio de sesión sys_login_log

1

migración_sistema

registro de operaciones sys_opera_log

configuración del sistema sys_setting

sys_tables

4.5 go-admin agregar práctica de aplicación

repositorio de código

github.com/yunixiangfeng/devops/tree/main/go-admin

diseño de fondo go-admin - agregar práctica de aplicación

L contenido principal

N nuevo módulo

O escriba la aplicación go-admin, paso N escriba el código manualmente

iniciar proyecto

servidor para el desarrollo

Crear función de artículo

Escribe tu primera interfaz

camino

P escribe la aplicación go-admin, el paso O genera automáticamente el código

configuración de la base de datos

codigo de GENERACION

importación de estructura de tabla

Editar campos de plantilla

código de vista previa

generar codigo

Menú Configurar sistema

Configurar permisos de rol

Gestión de contenido operativo

Resumen de los recursos lingüísticos de Go

apertura

Qué aprender en el idioma Go

sitio web

proyecto de código abierto

Ginebra

truco

fósforo

cobra

folco

nsq

códigos

ahondar

micro/micro

Supongo que te gusta

Origin blog.csdn.net/niwoxiangyu/article/details/131968294
Recomendado
Clasificación