31
en/01.1.md
31
en/01.1.md
@@ -15,8 +15,9 @@ There are many ways to configure the Go development environment on your computer
|
||||
In case you want to install more than one version of Go on a computer, you should take a look at a tool called [GVM](https://github.com/moovweb/gvm). It is the best tool I've seen so far for accomplishing this task, otherwise you'd have to deal with it yourself.
|
||||
|
||||
## Install from source code
|
||||
Go 1.5 completely remove the C code,Runtime、Compiler、Linker powered by Go,Achieve bootstrapping,You only need the previous version to compile go.
|
||||
|
||||
Because some parts of Go are written in Plan 9 C and AT&T assembler, you have to install a C compiler before taking the next step.
|
||||
But before Go 1.5 some parts of Go are written in Plan 9 C and AT&T assembler, you have to install a C compiler before taking the next step.
|
||||
|
||||
On a Mac, if you have installed Xcode, you already have the compiler.
|
||||
|
||||
@@ -75,15 +76,15 @@ A 64-bit operating system will show the following:
|
||||
|
||||
### Mac
|
||||
|
||||
Go to the [download page](https://golang.org/dl/), choose `go1.4.2.darwin-386.pkg` for 32-bit systems and `go1.4.2.darwin-amd64.pkg` for 64-bit systems. Going all the way to the end by clicking "next", `~/go/bin` will be added to your system's $PATH after you finish the installation. Now open the terminal and type `go`. You should see the same output shown in figure 1.1.
|
||||
Go to the [download page](https://golang.org/dl/), choose `go1.4.2.darwin-386.pkg` for 32-bit systems and `go1.7.4.darwin-amd64.pkg` for 64-bit systems. Going all the way to the end by clicking "next", `~/go/bin` will be added to your system's $PATH after you finish the installation. Now open the terminal and type `go`. You should see the same output shown in figure 1.1.
|
||||
|
||||
### Linux
|
||||
|
||||
Go to the [download page](https://golang.org/dl/), choose `go1.4.2.linux-386.tar.gz` for 32-bit systems and `go1.4.2.linux-amd64.tar.gz` for 64-bit systems. Suppose you want to install Go in the `$GO_INSTALL_DIR` path. Uncompress the `tar.gz` to your chosen path using the command `tar zxvf go1.4.2.linux-amd64.tar.gz -C $GO_INSTALL_DIR`. Then set your $PATH with the following: `export PATH=$PATH:$GO_INSTALL_DIR/go/bin`. Now just open the terminal and type `go`. You should now see the same output displayed in figure 1.1.
|
||||
Go to the [download page](https://golang.org/dl/), choose `go1.7.4.linux-386.tar.gz` for 32-bit systems and `go1.7.4.linux-amd64.tar.gz` for 64-bit systems. Suppose you want to install Go in the `$GO_INSTALL_DIR` path. Uncompress the `tar.gz` to your chosen path using the command `tar zxvf go1.7.4.linux-amd64.tar.gz -C $GO_INSTALL_DIR`. Then set your $PATH with the following: `export PATH=$PATH:$GO_INSTALL_DIR/go/bin`. Now just open the terminal and type `go`. You should now see the same output displayed in figure 1.1.
|
||||
|
||||
### Windows
|
||||
|
||||
Go to the [download page](https://golang.org/dl/), choose `go1.4.2.windows-386.msi` for 32-bit systems and `go1.4.2.windows-amd64.msi` for 64-bit systems. Going all the way to the end by clicking "next", `c:/go/bin` will be added to `path`. Now just open a command line window and type `go`. You should now see the same output displayed in figure 1.1.
|
||||
Go to the [download page](https://golang.org/dl/), choose `go1.7.4.windows-386.msi` for 32-bit systems and `go1.7.4.windows-amd64.msi` for 64-bit systems. Going all the way to the end by clicking "next", `c:/go/bin` will be added to `path`. Now just open a command line window and type `go`. You should now see the same output displayed in figure 1.1.
|
||||
|
||||
## Use third-party tools
|
||||
|
||||
@@ -95,8 +96,8 @@ GVM is a Go multi-version control tool developed by a third-party, like rvm for
|
||||
|
||||
Then we install Go using the following commands:
|
||||
|
||||
gvm install go1.4.2
|
||||
gvm use go1.4.2
|
||||
gvm install go1.7.4
|
||||
gvm use go1.7.4
|
||||
|
||||
After the process has finished, you're all set.
|
||||
|
||||
@@ -108,10 +109,28 @@ Ubuntu is the most popular desktop release version of Linux. It uses `apt-get` t
|
||||
sudo apt-get update
|
||||
sudo apt-get install golang-stable
|
||||
|
||||
###wget
|
||||
```sh
|
||||
|
||||
wget https://storage.googleapis.com/golang/go1.7.4.linux-amd64.tar.gz
|
||||
sudo tar -xzf go1.7.4.linux-amd64.tar.gz -C /usr/local
|
||||
export PATH=PATH:/usr/local/go/binexportGOROOT=HOME/go
|
||||
export PATH=PATH:GOROOT/bin
|
||||
export GOPATH=HOME/gowork
|
||||
```
|
||||
|
||||
### Homebrew
|
||||
|
||||
Homebrew is a software management tool commonly used in Mac to manage packages. Just type the following commands to install Go.
|
||||
|
||||
1.Install Homebrew
|
||||
|
||||
```sh
|
||||
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
||||
```
|
||||
|
||||
2.Install Go
|
||||
brew update && brew upgrade
|
||||
brew install go
|
||||
|
||||
## Links
|
||||
|
||||
@@ -47,7 +47,9 @@ We see that it's very easy to use concurrency in Go by using the keyword `go`. I
|
||||
|
||||
runtime.Gosched() means let the CPU execute other goroutines, and come back at some point.
|
||||
|
||||
The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing. This function may be removed in the future, see more details about parallel processing and concurrency in this [article](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide).
|
||||
In Go 1.5,the runtime now sets the default number of threads to run simultaneously, defined by GOMAXPROCS, to the number of cores available on the CPU.
|
||||
|
||||
Before Go 1.5,The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing.
|
||||
|
||||
## channels
|
||||
|
||||
|
||||
20
es/03.4.md
20
es/03.4.md
@@ -176,21 +176,21 @@ En escenarios mas complejos también podemos utilizar este método, la ventaja e
|
||||
|
||||
Vamos a echar un vistazo a la lista de flujo de ejecución en conjunto.
|
||||
|
||||
- Se llama http.HandleFunc
|
||||
- Se llama `http.HandleFunc`
|
||||
1. Se Llama HandleFunc de DefaultServeMux
|
||||
2. Se Llama Handle de DefaultServeMux
|
||||
3. Se agregan las reglas del enrutamiento a map[string]muxEntry de DefaultServeMux
|
||||
- Se llama http.ListenAndServe (":9090" , nil )
|
||||
- Se llama `http.ListenAndServe(":9090" , nil)`
|
||||
1. Se instancia el servidor
|
||||
2. Llama ListenAndServe del Servidor
|
||||
3. Llama net.Listen ( " tcp" , addr ) para escuchar en el puerto .
|
||||
2. Se Llama ListenAndServe del Servidor
|
||||
3. Se Llama net.Listen ( "tcp" , addr ) para escuchar en el puerto.
|
||||
4. Iniciar un bucle, y aceptar las solicitudes en el cuerpo del bucle.
|
||||
5. Instanciada una Conn se empieza una goroutine para cada solicitud : ir c.serve ().
|
||||
6. Lee petición de datos : w , err : = c.readRequest ().
|
||||
7. Comprueba si el controlador está vacío, si está vacíoutiliza DefaultServeMux .
|
||||
8. Llama al controlador de ServeHTTP
|
||||
9. Ejecutar código en DefaultServeMux en este caso.
|
||||
10. Elije el controlador URL y ejecutar código del controlador en esta seccion: mux.handler.ServeHTTP ( w , r)
|
||||
5. Instanciar una Conn y se empieza una goroutine para cada solicitud : `go c.serve ()`
|
||||
6. Se Lee petición de datos : `w , err : = c.readRequest ()`
|
||||
7. Se Comprueba si el controlador está vacío, si está vacíoutiliza DefaultServeMux .
|
||||
8. Se Llama al controlador de ServeHTTP
|
||||
9. Se Ejecuta el código en DefaultServeMux en este caso.
|
||||
10. Elije el controlador URL y ejecutar código del controlador en esta seccion: `mux.handler.ServeHTTP (w , r)`
|
||||
11. Cómo elegir handler:
|
||||
A. Normas de router de verificación para esta URL.
|
||||
B. Llamar ServeHTTP en ese controlador, si es que existe.
|
||||
|
||||
141
es/04.2.md
Normal file
141
es/04.2.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# 4.2 Verificando las entradas
|
||||
|
||||
Uno de los principios mas importantes en el desarrollo web es que no puedes confiar en nada de lo que viene en los formularios de usuario. Necesitas validar todos los datos de entrada antes de usarlos. Muchos sitios web son afectados por este problema, lo que es simplemente crucial.
|
||||
|
||||
Existen dos maneras de verificar datos de formularios, que son usadas comunmente. La primera es la validación del lado del cliente en. el front end, y la segunda es la validación del lado del servidor, en el back end. En esta sección vamos a hablar sobre la validación del lado del servidor.
|
||||
|
||||
## Campos requeridos
|
||||
|
||||
Algunas veces requerimos que los usuarios ingresen algunos campos, pero ellos fallan completándolos. Por ejemplo, en la sección anterior, nosotros requerimos un nombre de usuario. Puedes usar la función `len` para obtener el tamaño de un campo para asegurarte que se ha ingresado algo.
|
||||
```
|
||||
if len(r.Form["username"][0])==0{
|
||||
// code for empty field
|
||||
}
|
||||
```
|
||||
`r.Form` trata diferente a las entradas que son de distinto tipo cuando están vacíos. Para cuadros de textos, áreas de texto y campos de archivos, retorna una cadena vacía, para botones circulares y cajas de chequeo, a veces ni siquiera se crean los tipos. En cambio, vas a tener problemas accediendo a los elementos correspondientes. Por esto, es mas seguro usar `r.Form.Get()` para obtener los valores de los campos de una manera que siempre van a retornar vacío si el valor no existe. Por otra parte, `r.Form.Get()` solo puede obtener un valor al tiempo, así que necesitas usar `r.Form` para obtener un mapa de los valores.
|
||||
|
||||
## Números
|
||||
|
||||
Algunas veces necesitamos números mas allá de texto en un campo. Por ejemplo, digamos que necesitas la edad de un usuario como un entero solamente, es decir: 50 o 10, en vez de "lo suficientemente viejo" u "hombre jóven". Si requerimos un número positivo, podemos convertir el valor al tipo `int` y luego procesarlo:
|
||||
```
|
||||
getint,err:=strconv.Atoi(r.Form.Get("age"))
|
||||
if err!=nil{
|
||||
// error occurs when convert to number, it may not a number
|
||||
}
|
||||
|
||||
// check range of number
|
||||
if getint >100 {
|
||||
// too big
|
||||
}
|
||||
```
|
||||
Otra manera de realizar esto es usando expresiones regulares:
|
||||
```
|
||||
if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
|
||||
return false
|
||||
}
|
||||
```
|
||||
Para propósitos de eficiencia, las expresiones regulares no son eficientes, sin embarglo las expresiones regulares simples son lo suficientemente rápidas. Si estas familiarizado con las expresiones regulares, es una manera muy conveniente de verificar datos. Nota que Go usa la sintaxis [RE2](http://code.google.com/p/re2/wiki/Syntax), entonces todos los caracteres de UTF-8 están soportados.
|
||||
|
||||
## Chino
|
||||
|
||||
Algunas veces, necesitamos que los usuarios ingresen su nombre en chino, y necesitamos verificar que todos estén en chino, en vez de caracteres al azar. Para la verificación del chino, las expresiones regulares son la única manera de conseguirlo:
|
||||
```
|
||||
if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", r.Form.Get("realname")); !m {
|
||||
return false
|
||||
}
|
||||
```
|
||||
## Letras del Inglés
|
||||
|
||||
Algunas veces necesitamos que los usuarios solo ingresen letras del Inglés, por ejemplo algún nombre en inglés como astaxie en vez de asta谢. Podemos verificar esto usando la siguiente expresión regular.
|
||||
```
|
||||
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
|
||||
return false
|
||||
}
|
||||
```
|
||||
## Correo electrónico
|
||||
|
||||
Si queremos si el usuario ha ingresado una dirección de correo electrónico válida, puedes usar la siguiente expresión regular:
|
||||
```
|
||||
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
|
||||
fmt.Println("no")
|
||||
}else{
|
||||
fmt.Println("yes")
|
||||
}
|
||||
```
|
||||
## Lista desplegable
|
||||
|
||||
Digamos que vamos a requerir un elemento de una lista desplegable, pero en vez de esto, tenemos un valor fabricado por hackers. ¿Cómo evitamos que esto pase?
|
||||
|
||||
Suponga que tenemos el siguiente `<select>`
|
||||
```
|
||||
<select name="fruit">
|
||||
<option value="apple">apple</option>
|
||||
<option value="pear">pear</option>
|
||||
<option value="banana">banana</option>
|
||||
</select>
|
||||
```
|
||||
Podemos usar la siguiente estrategia para limpiar la entrada:
|
||||
```
|
||||
slice:=[]string{"apple","pear","banana"}
|
||||
|
||||
for _, v := range slice {
|
||||
if v == r.Form.Get("fruit") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
```
|
||||
Todas las funciones que he mostrado arriba están en mi proyecto de código abierto para operar con segmentos y mapas: [https://github.com/astaxie/beeku](https://github.com/astaxie/beeku)
|
||||
|
||||
## Botones circulares
|
||||
|
||||
Si queremos saber cuando un usuario es femenino o masculino, podríamos usar un botón circular, retornando 1 para femenino y 2 para masculino. Sin embargo, algún niño que acaba de leer su primer libro sobre HTTP decide enviar un 3. ¿Nuestro programa levantará una excepción? Como puedes ver, necesitamos usar el mismo método que usamos para la lista desplegable para asegurarnos sobre los valores que son ingresados a nuestro botón circular.
|
||||
```
|
||||
<input type="radio" name="gender" value="1">Femenino
|
||||
<input type="radio" name="gender" value="2">Masculino
|
||||
```
|
||||
Y usualmente utilizamos el siguiente código para validar las entradas:
|
||||
```
|
||||
slice:=[]int{1,2}
|
||||
|
||||
for _, v := range slice {
|
||||
if v == r.Form.Get("gender") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
```
|
||||
## Cajas de chequeo
|
||||
|
||||
Supon que existen algunas cajas de chequeo para los intereses de los usuarios, y tu no quieres valores extraños aquí tampoco, puedes validarlos de la siguiente manera:
|
||||
```
|
||||
<input type="checkbox" name="interest" value="football">Football
|
||||
<input type="checkbox" name="interest" value="basketball">Basketball
|
||||
<input type="checkbox" name="interest" value="tennis">Tennis
|
||||
```
|
||||
En este caso la estrategia de limpieza es un poco diferente a la validación de botones de chequeo y listas desplegables:
|
||||
```
|
||||
slice:=[]string{"football","basketball","tennis"}
|
||||
a:=Slice_diff(r.Form["interest"],slice)
|
||||
if a == nil{
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
```
|
||||
## Fecha y Hora
|
||||
|
||||
Supón que quieres que los usuarios ingresen fechas y horas. Go tiene un paquete `time` para convertir año, mes y día a la hora correspondiente. Después de eso, es fácil verificarlo.
|
||||
```
|
||||
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
fmt.Printf("Go launched at %s\n", t.Local())
|
||||
```
|
||||
Después de eso, también puedes utilizar el paquete `time` para mas operaciones, dependiende de tus necesidades.
|
||||
|
||||
En esta sección hemos discutido algunos métodos comunes para validar los datos del lado del servidor. Espero que ahora entiendas un poco mas sobre la validación de datos en Go, especialmente como usar las evntajas de las expresiones regulares.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Procesando la entrada de los formularios](04.1.md)
|
||||
- Siguiente sección: [Cross site scripting](04.3.md)
|
||||
69
es/04.3.md
Normal file
69
es/04.3.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 4.3 Cross site scripting
|
||||
|
||||
Hoy los sitios web tienen mucho contenido dinámico en orden de mejorar la experiencia de usuario, lo que significa que debemos proveer información dinámica en función de cada comportamiento individual, desafortunadamente, los sitios web dinámicos son suceptibles a ataques maliciosos conocidos como "Cross site scripting" (abreviado "XSS"). Los sitios web estáticos no son vulnerables a este tipo de ataque.
|
||||
|
||||
Los atacantes usualmente inyectan código malicioso como Javascript, VBScript, ActiveX o Flash en sitios que tienen vulnerabilidades. Una vez que han conseguido enyectar sus scripts, la información del usuario puede ser robada y tu sitio web puede ser inundado de Spam. Los atacantes también pueden cambiar las configuraciones de los usuarios como sea que ellos quieran.
|
||||
|
||||
Si deseas prevenir este tipo de ataques, deberías combinar las siguientes dos aproximaciones
|
||||
|
||||
- Validar toda la información de los usuarios, que discutimos en la sección pasada.
|
||||
- Manejar cuidadosamente la información enviada por los clientes en orden de prevenir cualquier inyección de scripts hecha desde los navegadores.
|
||||
|
||||
Entonces ¿Cómo podemos haces estas dos cosas en Go? Afortunadamente, el paquete `http/template` tiene algunas funciones que nos permiten escapar los datos como sigue:
|
||||
|
||||
- `func HTMLEscape(w io.Writer, b []byte)` escapa b to w.
|
||||
- `func HTMLEscapeString(s string) string` retorna una cadena después de escaparla de s
|
||||
- `func HTMLEscaper(args ...interface{}) string` retorna una cadena después de escaparla de algunos argumentos.
|
||||
|
||||
Vamos a cambiar el ejemplo de la sección 4.1:
|
||||
```
|
||||
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // Imprime en el lado del servidor
|
||||
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
|
||||
template.HTMLEscape(w, []byte(r.Form.Get("username"))) // Respuesta a los clientes
|
||||
```
|
||||
Si alguien trata de ingresar el nombre de usuario como `<script>alert()</script>` podremos ver el siguiente contenido en el navegador.
|
||||
|
||||

|
||||
|
||||
Figure 4.3 JavaScript después del escape
|
||||
|
||||
Las funciones en el paquete `html/template` nos ayudan a escapar todas las etiquetas HTML. Y ¿qué pasa si queremos imprimir `<script>alert()</script>` en los navegarores? Deberías usar el paquete `text/template`.
|
||||
```
|
||||
import "text/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
|
||||
```
|
||||
Salida:
|
||||
```
|
||||
Hello, <script>alert('you have been pwned')</script>!
|
||||
```
|
||||
O puedes usar el tipo `template.HTML`:
|
||||
El contenido variable no va a ser escapado si es del tipo `template.HTML`
|
||||
Variable content will not be escaped if its type is `template.HTML`.
|
||||
```
|
||||
import "html/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", template.HTML("<script>alert('you have been pwned')</script>"))
|
||||
```
|
||||
Salida:
|
||||
```
|
||||
Hello, <script>alert('you have been pwned')</script>!
|
||||
```
|
||||
Un ejemplo mas de escape:
|
||||
```
|
||||
import "html/template"
|
||||
...
|
||||
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
|
||||
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
|
||||
```
|
||||
Salida:
|
||||
```
|
||||
Hello, <script>alert('you have been pwned')</script>!
|
||||
```
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Verificando las entradas](04.2.md)
|
||||
- Siguiente sección: [Envíos duplicados](04.4.md)
|
||||
57
es/04.4.md
Normal file
57
es/04.4.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 4.4 Envíos duplicados
|
||||
|
||||
No se si alguna vez has visto blogs que tienen uno o mas de un post que son exactamente iguales, pero puedo decirte que es porque un usuario envió dos veces el mismo formulario. Existen muchas cosas que pueden cambiar envíos duplicados; algunas veces los usuarios hacen doble click en el botón de enviar, o quieren modificar el contenido después de postear y usan el botón de atrás. En algunos casos es por una acción intencioanl de usuarios malicioso. Es facil ver como los envíos duplicados pueden generar muchos problemas. Afortunadamente tenemos herramientas para prevenirlos.
|
||||
|
||||
La solución es añadir un campo oculto con un único token al formulario, y siempre verificar si el token ha sido procesado con anterioridad. También si quieres usar ajax para enviar un formulario, puedes usar javascript para deshabilitar el botón una vez se ha presionado.
|
||||
|
||||
Vamos a mejorar el ejemplo de la sección 4.2:
|
||||
```
|
||||
<input type="checkbox" name="interest" value="football">Futbol
|
||||
<input type="checkbox" name="interest" value="basketball">Basquetbol
|
||||
<input type="checkbox" name="interest" value="tennis">Tenis
|
||||
Nombre de usuario:<input type="text" name="username">
|
||||
Contraseña:<input type="password" name="password">
|
||||
<input type="hidden" name="token" value="{{.}}">
|
||||
<input type="submit" value="Ingresar">
|
||||
```
|
||||
Nosotros usamos un hash MD5 con la hora actual para generar el token, y agregamos esto a un campo oculto del lado del cliente y a una cookie en el lado del servidor (Capítulo 6). Nosotros usamos este token para verificar si el formulario ha sido enviado.
|
||||
```
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method) // get request method
|
||||
if r.Method == "GET" {
|
||||
crutime := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
t, _ := template.ParseFiles("login.gtpl")
|
||||
t.Execute(w, token)
|
||||
} else {
|
||||
// log in request
|
||||
r.ParseForm()
|
||||
token := r.Form.Get("token")
|
||||
if token != "" {
|
||||
// check token validity
|
||||
} else {
|
||||
// give error if no token
|
||||
}
|
||||
fmt.Println("username length:", len(r.Form["username"][0]))
|
||||
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // print in server side
|
||||
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
|
||||
template.HTMLEscape(w, []byte(r.Form.Get("username"))) // respond to client
|
||||
}
|
||||
}
|
||||
```
|
||||

|
||||
|
||||
Figure 4.4 El contenido del navegador después de agregar el Token
|
||||
|
||||
Puedes refrescar la página y verás un token diferente cada vez. Esto asegura que cada formulario es único.
|
||||
|
||||
Por ahora, puedes prevenir ataques de envíos duplicados añadiendo tokens a tus formularios, pero no puedes prevenir todos los ataques de este tipo, aún hay mucho trabajo por hacer.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Cross site scripting](04.3.md)
|
||||
- Siguiente sección: [Subida de archivos](04.5.md)
|
||||
168
es/04.5.md
Normal file
168
es/04.5.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 4.5 Subida de archivos
|
||||
|
||||
Supón que tienes un sitio web como Instagram y quieres que tus usuarios suban sus fotos. ¿Cómo implementarías esa funcionalidad?
|
||||
|
||||
Tienes que agregar la propuedad `enctype` al formulario que vas a usar para subir fotos. Aquí están los tres posibles valores para esta propiedad
|
||||
You have to add property `enctype` to the form that you want to use for uploading photos. There are three possible values for this property:
|
||||
|
||||
```
|
||||
application/x-www-form-urlencoded. Transforma todos los caracteres antes de subirlos (Por defecto).
|
||||
multipart/form-data Sin transformación. Debes usar esta valor cuando vas a subir fotos.
|
||||
text/plain Convierte los espacios en "+" sin transformar los caracteres.
|
||||
```
|
||||
|
||||
Entonces el contenido HTML para subir un archivo debería verse como esto:
|
||||
|
||||
```
|
||||
<html>
|
||||
<head>
|
||||
<title>Upload file</title>
|
||||
</head>
|
||||
<body>
|
||||
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
|
||||
<input type="file" name="uploadfile" />
|
||||
<input type="hidden" name="token" value="{{.}}"/>
|
||||
<input type="submit" value="Subit" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
Necesitamos añadir la funcionalidad al servidor para manejar este formulario.
|
||||
|
||||
```
|
||||
http.HandleFunc("/upload", upload)
|
||||
|
||||
// lógica de subida
|
||||
func upload(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("method:", r.Method)
|
||||
if r.Method == "GET" {
|
||||
crutime := time.Now().Unix()
|
||||
h := md5.New()
|
||||
io.WriteString(h, strconv.FormatInt(crutime, 10))
|
||||
token := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
t, _ := template.ParseFiles("upload.gtpl")
|
||||
t.Execute(w, token)
|
||||
} else {
|
||||
r.ParseMultipartForm(32 << 20)
|
||||
file, handler, err := r.FormFile("uploadfile")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
fmt.Fprintf(w, "%v", handler.Header)
|
||||
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, file)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Como puedes ver, llamamo a `r.ParseMultipartForm` para subir archivos. La función ParseMultipartForm toma el argumento `maxMemory`. Después de llamar `ParseMultipartForm`, el archivo puede ser guardado en el servidor con tamaño `maxMemory`. Si el tamaño del archivo es mayor que `maxMemory`, el resto del archivo será guardado en un archivo temporal del sistema. Puedes usar `r.FormFile` para obtener el archivo y usar `io.Copy` para guardarlo en tu sistema.
|
||||
|
||||
Puede que no necesites llamar a `r.ParseForm` cuando acceses a otros campos, porque Go llamará a este método si es necesario. También llamar a `ParseMultipartForm` una vez es suficiente, múltiples llamadas no hacen diferencia.
|
||||
|
||||
Usamos tres pasos para subir archivos:
|
||||
|
||||
1. Agregar `enctype="multipart/form-data"` a tu formulario
|
||||
2. Llamar `r.ParseMultipartForm` en el lado del servidor para guardar el archivo en memoria o en un archivo temporal.
|
||||
3. Llamar `r.FormFile` para obtener el archivo y guardarlo en el sistema de ficheros.
|
||||
|
||||
El manejador del archivo es `multipart.FileHeader`. Este usa la siguiente estructura:
|
||||
|
||||
```
|
||||
type FileHeader struct {
|
||||
Filename string
|
||||
Header textproto.MIMEHeader
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
Figure 4.5 Imprimir la información del servidor después de recibir el archivo.
|
||||
|
||||
## Cliente para subir archivos
|
||||
|
||||
Les mostré en el ejemplo anterior como usar un formulario para subir un archivo. Nosotros podemos usar un cliente en Go para emular la subida de archivos.
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func postFile(filename string, targetUrl string) error {
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||
|
||||
// este paso es muy importante
|
||||
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
|
||||
if err != nil {
|
||||
fmt.Println("error writing to buffer")
|
||||
return err
|
||||
}
|
||||
|
||||
// Abrir el archivo para manejarlo
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("error opening file")
|
||||
return err
|
||||
}
|
||||
|
||||
//iocopy
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
bodyWriter.Close()
|
||||
|
||||
resp, err := http.Post(targetUrl, contentType, bodyBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
resp_body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(resp.Status)
|
||||
fmt.Println(string(resp_body))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ejemplo de uso
|
||||
func main() {
|
||||
target_url := "http://localhost:9090/upload"
|
||||
filename := "./astaxie.pdf"
|
||||
postFile(filename, target_url)
|
||||
}
|
||||
```
|
||||
|
||||
El código de arriba muestra como usar un cliente para subir archivos. El usa `multipart.Write` para escribir archivos en un cache y enviarlos al servidor a través de un método POST.
|
||||
|
||||
Si tienes otros campos que necesitas escribir en el cuerpo del mensaje, como un nombre d eusuario, llama a `multipart.WriteField` cada que lo necesites.
|
||||
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Envíos duplicados](04.4.md)
|
||||
- Siguiente sección: [Resumen](04.6.md)
|
||||
11
es/04.6.md
Normal file
11
es/04.6.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 4.6 Resumen
|
||||
|
||||
En este capítulo mayormente hemos aprendido como procesar datos de formularios en Go a través de varios ejemplos, como autenticar un usuario y subir archivos. También hemos enfatizado que validar la información es extremadamente importante para la seguridad del sitio web, y también hemos usado una sección para filtrar datos a través de expresiones regulares.
|
||||
|
||||
Espero que ahora sepas mas sobre el proceso de comunicación entre cliente y servidor.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Subida de archivos](04.5.md)
|
||||
- Siguiente sección: [Base de datos](05.0.md)
|
||||
13
es/05.0.md
Normal file
13
es/05.0.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# 5 Bases de Datos
|
||||
|
||||
Para los desarrolladores web, la base de datos está en el núclo del desarrollo web. Puedes guardar casi todo en una base de datos y consultar o actualizar dentro de ella, como información, productos noticias o artículos.
|
||||
|
||||
Go no provee ningún manejador para base de datos, pero si provee una interfaz definida en el paquete `database/sql`. Las personas pueden desarrollar manejadores de bases de datos basado en esa interfaz. En la sección 5.1, vamos a hablar sobre el diseño de un manejador de base de datos en Go. En las secciones 5.2 a 5.4 voy a introducir algunos manejadores de bases de datos. En la sección 5.5 voy a presentar un ORM que he diseñado que está basado en el estandar `database/sql`. Es compatible con la mayoría de manejadores que implementan la interfaz `database/sql` y hace fácil acceder a las bases de datos ideomáticamente en Go.
|
||||
|
||||
NoSQL ha sido un tópico importante en los últimos años. Muchos sitios web están decidiendo usar bases de datos NoSQL como su base de datos principal, o solo con el propósito de caché. Introduciré dos bases de datos: MongoDB y Redis, en la sección 5.6.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Capítulo anterior: [Capítulo 4 Resumen](04.6.md)
|
||||
- Siguiente sección: [Interfaz database/sql](05.1.md)
|
||||
203
es/05.1.md
Normal file
203
es/05.1.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# 5.1 Interfaz database/sql
|
||||
|
||||
Go no provee ningún manejador de base de datos oficial, a diferencia de otros lenguajes como PHP que si lo hacen. Sin embargo, si provee una estándar de interfaz para que los desarrolladores desarrollen manejadores basados en ella. La ventaja es que si tu código es desarrollado de acuerdo a esa interfaz estándar, no vas a tener que cambiar ningún código si tu base de datos cambia. Veamos cuales son los estándares de esta interface.
|
||||
|
||||
## sql.Register
|
||||
|
||||
Esta función está en el paquete `database/sql` para registrar una base de datos cuando usas un manejador de base de datos de terceros. Todos deberían llamar a la función `Register(name String, driver driver.Driver)` en la función `init()` en orden de registrarse a si mismas.
|
||||
|
||||
Vamos a mirar los corespondientes llamados en el código de mymysql y sqlite3:
|
||||
```
|
||||
//manejador https://github.com/mattn/go-sqlite3
|
||||
func init() {
|
||||
sql.Register("sqlite3", &SQLiteDriver{})
|
||||
}
|
||||
|
||||
//manejador https://github.com/mikespook/mymysql
|
||||
// Driver automatically registered in database/sql
|
||||
var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}
|
||||
func init() {
|
||||
Register("SET NAMES utf8")
|
||||
sql.Register("mymysql", &d)
|
||||
}
|
||||
```
|
||||
Vemos que los manejadores de terceros implementan esta función para registrarse a si mismos y Go utiliza un mapa para guardar los manejadores dentro de `database/sql`.
|
||||
```
|
||||
var drivers = make(map[string]driver.Driver)
|
||||
|
||||
drivers[name] = driver
|
||||
```
|
||||
Así, la función de registro puede registrar tantos manejadores como requieras, cada uno con un nombre diferente.
|
||||
|
||||
Siempre veremos el siguiente código cuando usemos manejadores de terceros:
|
||||
```
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
```
|
||||
Aquí, el guión bajo, (también llamado 'blank') `_` puede ser un poco confuso para muchos principiantes, pero es una gran característica de Go. Sabemos que el identificador de guión bajo es usado para no guardar valores que una función retorna, y también que debes usar todos los paquetes que importes en tu código Go. Entonces, cuando el guión bajo es usado con un import, significa que necesitas ejecutar la función init() de ese paquete sin usarlo directamente. lo cual encaja perfectamente en el caso de registrar el manejador de base de datos.
|
||||
|
||||
## driver.Driver
|
||||
|
||||
`Driver` es una interface que contiene un método `Open(name string)` que retorna una interfaz `Conn`.
|
||||
```
|
||||
type Driver interface {
|
||||
Open(name string) (Conn, error)
|
||||
}
|
||||
```
|
||||
Esta es una conexión de una vez, que significa que solamente puede ser usada una vez por rutina. El siguiente código causará errores:
|
||||
```
|
||||
...
|
||||
go goroutineA (Conn) // query
|
||||
go goroutineB (Conn) // insert
|
||||
...
|
||||
```
|
||||
Porque no no tiene idea de que rutina va a realizar cada operación, la operación de consulta podría obtener el resultado de la operación de incersión y viceversa.
|
||||
|
||||
Todos los manejadores de terceros debería tener esta función para analizar el nombre de una Conn y retornar el resultado correcto.
|
||||
|
||||
## driver.Conn
|
||||
|
||||
Esta es la interfaz de conexión con algunos métodos, como dije aarriba, la misma conexión solo puede usarse una vez por rutina.
|
||||
```
|
||||
type Conn interface {
|
||||
Prepare(query string) (Stmt, error)
|
||||
Close() error
|
||||
Begin() (Tx, error)
|
||||
}
|
||||
```
|
||||
- `Prepare` retorna el estado del comando SQL ejecutado para consultas, eliminaciones, etc.
|
||||
- `Close` cierra la conexión actual y limpia los recursos. La mayoría de paquetes de terceros implementan algún tipo de piscina de conexiones, así que no necesitas almacenar las conexiones que puedan causar errores.
|
||||
- `Begin` retorna un Tx que representa una transacción para manejar. Puedes usarlo para consultar, almacenar, o volver hacia atrás algunas transacciones.
|
||||
|
||||
## driver.Stmt
|
||||
|
||||
Este es un estado de listo que corresponde con una Conn, y solamente puede ser usada una vez por rutina (como en el caso de Conn).
|
||||
```
|
||||
type Stmt interface {
|
||||
Close() error
|
||||
NumInput() int
|
||||
Exec(args []Value) (Result, error)
|
||||
Query(args []Value) (Rows, error)
|
||||
}
|
||||
```
|
||||
- `Close` cierra la conexión actual, pero sigue retornando la información de la ejecución de una operación de consulta.
|
||||
- `NumInput` retorna el número de argumentos obligados. Los manejadores de bases de datos debería revisar la cantidad de argumentos cuando los resultados son mayor que 0, y retorna -1 cuando el manejador de la base de datos no conoce ningún argumento obligado.
|
||||
- `Exec` ejecuta un comando `update/insert` preparados en `Prepare`, retorna un `Result`.
|
||||
- `Query` ejecuta un comando `select` preparado en `Prepare`, retorna información.
|
||||
|
||||
## driver.Tx
|
||||
|
||||
Generalmente, las transacciones únicamente tienen métodos de envío y de vuelta atrás, y el manejador de las bases de datos solo necesita implementar estos dos métodos.
|
||||
```
|
||||
type Tx interface {
|
||||
Commit() error
|
||||
Rollback() error
|
||||
}
|
||||
```
|
||||
## driver.Execer
|
||||
Esta es una interfaz opcional.
|
||||
```
|
||||
type Execer interface {
|
||||
Exec(query string, args []Value) (Result, error)
|
||||
}
|
||||
```
|
||||
El el manejador no implementa esta interfaz, cuando llame `DB.Exec`, automáticamente llamará `Prepare`, retornará un `Stmt`. Después ejecutará el método `Exec` de `Smtt`, y cerrará el `Smtm`.
|
||||
|
||||
## driver.Result
|
||||
|
||||
Esta es la interface para los operaciones de `update/insert`.
|
||||
```
|
||||
type Result interface {
|
||||
LastInsertId() (int64, error)
|
||||
RowsAffected() (int64, error)
|
||||
}
|
||||
```
|
||||
- `LastInsertId` retorna el identificador de autoincremento después de una operación de inserción.
|
||||
- `RowsAffected` retorna la cantidad de filas afectadas por la operación.
|
||||
|
||||
## driver.Rows
|
||||
|
||||
Esta interfaz es el resultado de una operación de consulta.
|
||||
```
|
||||
type Rows interface {
|
||||
Columns() []string
|
||||
Close() error
|
||||
Next(dest []Value) error
|
||||
}
|
||||
```
|
||||
- `Columns` retorna la información de los campos de la base de datos. El segmento tiene correspondencia con los campos SQL únicamente. y no retorna todos los campos de la tabla de la base de datos.
|
||||
- `Close` cierra el iterador sobre las columnas.
|
||||
- `Next` retorna la siguiente información y la asigna a dest, convirtiendo todas las cadenas en arreglos de bytes, y retorna un error io.EOF si no existe mas data disponible.
|
||||
|
||||
## driver.RowsAffected
|
||||
|
||||
Este es un alias para int64, pero implementado por la interfaz Result.
|
||||
```
|
||||
type RowsAffected int64
|
||||
|
||||
func (RowsAffected) LastInsertId() (int64, error)
|
||||
|
||||
func (v RowsAffected) RowsAffected() (int64, error)
|
||||
```
|
||||
## driver.Value
|
||||
|
||||
Esta es una interfaz vacía que contiene cualquier tipo de información.
|
||||
```
|
||||
type Value interface{}
|
||||
```
|
||||
El `Value` debe ser algo con lo que los manejadores puedan operar, o nil, así que debería ser alguno de los siguientes tipos:
|
||||
```
|
||||
int64
|
||||
float64
|
||||
bool
|
||||
[]byte
|
||||
string [*] Exceptuando los Rows.Next, que no puede retornar una cadena.
|
||||
time.Time
|
||||
```
|
||||
## driver.ValueConverter
|
||||
|
||||
Define una interfaz para convertir valores nativos en valores de driver.Value.
|
||||
```
|
||||
type ValueConverter interface {
|
||||
ConvertValue(v interface{}) (Value, error)
|
||||
}
|
||||
```
|
||||
Esta interfaz es comunmente usada en manejadores de bases de datos y tiene muchas características útiles:
|
||||
|
||||
- Convierte un driver.Value al tipo de campo correspondiente, por ejemplo convierte un int64 a un uint16.
|
||||
- Conviernte una consulta de base de datos a un driver.Value.
|
||||
- Conviernte un driver.Value a un usuario definido en la función `scan`.
|
||||
|
||||
## driver.Valuer
|
||||
|
||||
Define una interfaz para devolver un driver.Value.
|
||||
```
|
||||
type Valuer interface {
|
||||
Value() (Value, error)
|
||||
}
|
||||
```
|
||||
Muchos tipos implementan esta interface para la conversión entre driver.Value y ellos mismos.
|
||||
|
||||
En este punto, deberías saber un poco mas sobre el desarrollo de manejadores de bases de datos en Go. una vez que hayas implementado interfaces para operaciones como agregar, eliminar, actualizar, etc, existirán algunos problemas relacionados con comunicarse específicamente con algún tipo de bases de datos.
|
||||
|
||||
## database/sql
|
||||
|
||||
`database/sql` define en un alto nivel métodos existentes en `database/sql/driver` para tener las operaciones en un nivel mas conveniente, y sugiera que implementes una piscina de conexiones.
|
||||
```
|
||||
type DB struct {
|
||||
driver driver.Driver
|
||||
dsn string
|
||||
mu sync.Mutex // protects freeConn and closed
|
||||
freeConn []driver.Conn
|
||||
closed bool
|
||||
}
|
||||
```
|
||||
Como puedes ver, la función `Open` retorna una base de datos que tiene una `freeConn`, y esta es una piscina de conexiones simple. Su implementación es simple y fea. Usa `defer db.putConn(ci, err)` en la función `Db.Prepare` para colocar una conexión en la piscina de conexiones. Siempre que se vaya a llamar la función `Conn`, verifica la longitud de `freeConn`. Si es mayor a 0, significa que hay una conexión reusable y la retorna directamente a ti. De otra manera, crea una nueva conexión y la retorna.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Bases de Datos](05.0.md)
|
||||
- Sección siguiente: [MySQL](05.2.md)
|
||||
125
es/05.2.md
Normal file
125
es/05.2.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# 5.2 MySQL
|
||||
|
||||
La pila LAMP ha sido muy popular en internet los últimos años. La M en LAMP significa MySQL. MySQL es famoso porque es de código abierto y fácil de usar. También porque viene como base de datos por defecto en el backend de muchos sitios web.
|
||||
|
||||
## Manejadores MySQL
|
||||
|
||||
Existen varios manejadores que soportan MySQL en Go. Algunos de ellos implementan la interfaz `database/sql`, y otros usan sus propias interfaces estándares.
|
||||
|
||||
- [https://github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) soporta `database/sql`, está escrita en Go puro.
|
||||
- [https://github.com/ziutek/mymysql](https://github.com/ziutek/mymysql) soporta `database/sql` e interfaces definidas por el usuario, está escrita en Go puro.
|
||||
|
||||
Usaré el primer manejador en los siguientes ejemplos (uso este en mis proyectos personales también) y también lo recomiendo por las siguientes razones:
|
||||
|
||||
- Es un manejador de bases de datos nuevo y suporta muchas características
|
||||
- Soporta completamente la interfaz `database/sql`
|
||||
- Soporta el keep-alive, conexiones largas con seguridad entre hilos
|
||||
|
||||
## Ejemplos
|
||||
|
||||
En las siguientes secciones usaré las misma estructura de tablas para diferentes bases de datos, luego crear la base de datos de la siguiente manera:
|
||||
```
|
||||
CREATE TABLE `userinfo` (
|
||||
`uid` INT(10) NOT NULL AUTO_INCREMENT,
|
||||
`username` VARCHAR(64) NULL DEFAULT NULL,
|
||||
`departname` VARCHAR(64) NULL DEFAULT NULL,
|
||||
`created` DATE NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`uid`)
|
||||
);
|
||||
```
|
||||
El siguiente ejemplo muestra como operar en bases de datos con los estándares de `database/sql`.
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8")
|
||||
checkErr(err)
|
||||
|
||||
// insertar
|
||||
stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
|
||||
checkErr(err)
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(id)
|
||||
// actualizar
|
||||
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
// consultar
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created string
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println(uid)
|
||||
fmt.Println(username)
|
||||
fmt.Println(department)
|
||||
fmt.Println(created)
|
||||
}
|
||||
|
||||
// eliminar
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
db.Close()
|
||||
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
Permítanme explicar algunas de las funciones importantes aquí:
|
||||
|
||||
- `sql.Open()` abre un manejador de bases de datos registrado. Go-MySQL-Driver registra el manejador de MySQL aquí. El segundo argumento es el DSN (Data Source Name) que define la información para realizar la conexión con la base de datos. Soporta los siguientes formatos:
|
||||
```
|
||||
user@unix(/path/to/socket)/dbname?charset=utf8
|
||||
user:password@tcp(localhost:5555)/dbname?charset=utf8
|
||||
user:password@/dbname
|
||||
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname
|
||||
```
|
||||
- `db.Prepare()` retorna la operación que va a ser ejecutada. También reetorna el estado de ejecución después de ejecutar el SQL.
|
||||
- `db.Query()` ejecuta el SQL y retorna el resultado en Rows
|
||||
- `stmt.Exec()` ejecuta el SQL que ha sido preparado y almacenado en un Stmt
|
||||
|
||||
Note que usamos el formato `=?` para pasar argumentos. Esto es necesario para prevenir ataques de inyección SQL.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [Interfaz database/sql](05.1.md)
|
||||
- Siguiente sección: [SQLite](05.3.md)
|
||||
149
es/05.3.md
Normal file
149
es/05.3.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# 5.3 SQLite
|
||||
|
||||
SQLite es una base de datos embebida de código abierto. Es una base de datos contenida en si misma, tiene 0 configuracieon y soporta transacciones. Es altamente portable, fácil de usar, compacta, eficiente y confiable. En la mayoría de los casos solamente se necesita crear una archivo de base de datos para crear conectarse y operar en una base de datos. Si estás buscando una solución embebida, SQLite es una opción para considerar. Se puede decir que SQLite es una versión de código abierto de Access.
|
||||
|
||||
## Manejadores SQLite
|
||||
|
||||
Existen muchos manejadores de bases de datos para SQLite en Go, pero muchos de ellos no soportan los estándares de la interfaz `database/sql`.
|
||||
|
||||
- [https://github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) soporta `database/sql`, basado en cgo
|
||||
- [https://github.com/feyeleanor/gosqlite3](https://github.com/feyeleanor/gosqlite3) no soporta `database/sql`, basado en cgo.
|
||||
- [https://github.com/phf/go-sqlite3](https://github.com/phf/go-sqlite3) no soporta `database/sql`, basado en cgo.
|
||||
|
||||
El primer manejador es el único que soporta los estándares de la interfaz `database/sql`, por esto lo uso en mis proyectos, ya que será mas fácil migrar el código en el futuro si lo necesito.
|
||||
|
||||
## Ejemplos
|
||||
|
||||
Crearemos el siguiente SQL:
|
||||
```
|
||||
CREATE TABLE `userinfo` (
|
||||
`uid` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
`username` VARCHAR(64) NULL,
|
||||
`departname` VARCHAR(64) NULL,
|
||||
`created` DATE NULL
|
||||
);
|
||||
```
|
||||
Un ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db, err := sql.Open("sqlite3", "./foo.db")
|
||||
checkErr(err)
|
||||
|
||||
// insertar
|
||||
stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
|
||||
checkErr(err)
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(id)
|
||||
// actualizar
|
||||
stmt, err = db.Prepare("update userinfo set username=? where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec("astaxieupdate", id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
// consultar
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created time.Time
|
||||
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println(uid)
|
||||
fmt.Println(username)
|
||||
fmt.Println(department)
|
||||
fmt.Println(created)
|
||||
}
|
||||
|
||||
rows.Close() // Buen hábito cerrar
|
||||
|
||||
// eliminar
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=?")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(id)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect)
|
||||
|
||||
db.Close()
|
||||
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
Puedes notar que el código es casi el mismo que en la sección pasada, y es porque solamente cambiamos el nombre del manejador de bases de datos registrado llamando a `sql.Open` para conectarnos a SQLite de una manera diferente.
|
||||
|
||||
Nota que algunas veces no puedes usar la sentencia `for` porque no tienes mas de una fila, entonces puedes usar la sentencia `if`:
|
||||
```
|
||||
if rows.Next() {
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println(uid)
|
||||
fmt.Println(username)
|
||||
fmt.Println(department)
|
||||
fmt.Println(created)
|
||||
}
|
||||
```
|
||||
También tienes que ahcer un `rows.Next()` sin usar eso que no puedes obtener en la función `Scan`.
|
||||
|
||||
Transacciones
|
||||
=============
|
||||
|
||||
El ejemplo de arriba muestra como puedes obtener datos de una base de datos, pero cuando quieres escribir aplicaciones web, no solo vas a consultar información de la base de datos sino que también quieres escribir en ella. Para este propósito tu puedes usar transacciones, porque puedes tener múltiples rutinas que accesan a la base de datos, y la base de datos se podría bloquear. Esto no es deseable en una aplicación web y el uso de transacciones es efectivo asegurando que las operaciones sean exitosas en total o fallen completamente, dependiendo de las circunstancias. Es claro que el uso de transacciones puede prevenir un montón de problemas que pueden ocurrir en una aplicación web.
|
||||
```
|
||||
trashSQL, err := database.Prepare("update task set is_deleted='Y',last_modified_at=datetime() where id=?")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
tx, err := database.Begin()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
_, err = tx.Stmt(trashSQL).Exec(id)
|
||||
if err != nil {
|
||||
fmt.Println("doing rollback")
|
||||
tx.Rollback()
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
```
|
||||
Como es claro en el código de arribam primero debes prepara un Statement, después debes ejecutarlo, dependiendo de la salida de esa ejecución, puedes volver atrás o mantenerlo.
|
||||
|
||||
Como última nota de esta sección, aquí está una herramienta de mantenimiento para SQLite: [http://sqlitebrowser.org](http://sqlitebrowser.org)
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [MySQL](05.2.md)
|
||||
- Siguiente sección: [PostgreSQL](05.4.md)
|
||||
123
es/05.4.md
Normal file
123
es/05.4.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# 5.4 PostgreSQL
|
||||
|
||||
PostgreSQL es una base de datos relacional y de objetos disponible en muchas plataformas incluyendo Linux, FreeBSD, Solaris, Microsoft Windows y Mac OS X. Fue liberada bajo la licencia MIT y por eso es gratuita y de código abierto. Es más grande que MySQL porque está diseñada para uso empresarial y es una alternativa a Oracle. PostgreSQL es una buena elección para proyectos empresariales.
|
||||
|
||||
## Manejadores PostgreSQL
|
||||
|
||||
Existen muchos manejadores de bases de datos para PostgreSQL. Aquí hay algunos ejemplos de ellos:
|
||||
|
||||
- [https://github.com/bmizerany/pq](https://github.com/lib/pq) soporta `database/sql`, escrita en Go puro.
|
||||
- [https://github.com/jbarham/gopgsqldriver](https://github.com/jbarham/gopgsqldriver) soporta `database/sql`, escrita en Go puro
|
||||
- [https://github.com/lxn/go-pgsql](https://github.com/lxn/go-pgsql) soporta `database/sql`, escrita en Go puro
|
||||
|
||||
Usaré el primer ejemplo para explicar lo que sigue.
|
||||
|
||||
## Ejemplos
|
||||
|
||||
Crearemos el siguiente SQL:
|
||||
```
|
||||
CREATE TABLE userinfo
|
||||
(
|
||||
uid serial NOT NULL,
|
||||
username character varying(100) NOT NULL,
|
||||
departname character varying(500) NOT NULL,
|
||||
Created date,
|
||||
CONSTRAINT userinfo_pkey PRIMARY KEY (uid)
|
||||
)
|
||||
WITH (OIDS=FALSE);
|
||||
```
|
||||
Un ejemplo:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/lib/pq"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DB_USER = "postgres"
|
||||
DB_PASSWORD = "postgres"
|
||||
DB_NAME = "test"
|
||||
)
|
||||
|
||||
func main() {
|
||||
dbinfo := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable",
|
||||
DB_USER, DB_PASSWORD, DB_NAME)
|
||||
db, err := sql.Open("postgres", dbinfo)
|
||||
checkErr(err)
|
||||
defer db.Close()
|
||||
|
||||
fmt.Println("# Inserting values")
|
||||
|
||||
var lastInsertId int
|
||||
err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId)
|
||||
checkErr(err)
|
||||
fmt.Println("last inserted id =", lastInsertId)
|
||||
|
||||
fmt.Println("# Updating")
|
||||
stmt, err := db.Prepare("update userinfo set username=$1 where uid=$2")
|
||||
checkErr(err)
|
||||
|
||||
res, err := stmt.Exec("astaxieupdate", lastInsertId)
|
||||
checkErr(err)
|
||||
|
||||
affect, err := res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect, "rows changed")
|
||||
|
||||
fmt.Println("# Querying")
|
||||
rows, err := db.Query("SELECT * FROM userinfo")
|
||||
checkErr(err)
|
||||
|
||||
for rows.Next() {
|
||||
var uid int
|
||||
var username string
|
||||
var department string
|
||||
var created time.Time
|
||||
err = rows.Scan(&uid, &username, &department, &created)
|
||||
checkErr(err)
|
||||
fmt.Println("uid | username | department | created ")
|
||||
fmt.Printf("%3v | %8v | %6v | %6v\n", uid, username, department, created)
|
||||
}
|
||||
|
||||
fmt.Println("# Deleting")
|
||||
stmt, err = db.Prepare("delete from userinfo where uid=$1")
|
||||
checkErr(err)
|
||||
|
||||
res, err = stmt.Exec(lastInsertId)
|
||||
checkErr(err)
|
||||
|
||||
affect, err = res.RowsAffected()
|
||||
checkErr(err)
|
||||
|
||||
fmt.Println(affect, "rows changed")
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
Nota que PostgreSQL usa el formato `$1, $2` en vez del `?` de MySQL, y tiene un formato distinto de DSN en el `sql.Open`
|
||||
Otra cosa es que el manejador de PostgreSQL no soporta el `sql.Result.LastInsertId()`.
|
||||
Entonces en lugar de
|
||||
```
|
||||
stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3);")
|
||||
res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
|
||||
fmt.Println(res.LastInsertId())
|
||||
```
|
||||
usa `db.QueryRow()` y `.Scan()` para obtener el último valor insertado.
|
||||
```
|
||||
err = db.QueryRow("INSERT INTO TABLE_NAME values($1) returning uid;", VALUE1").Scan(&lastInsertId)
|
||||
fmt.Println(lastInsertId)
|
||||
```
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior : [SQLite](05.3.md)
|
||||
- Siguiente sección: [Desarrollo de un ORM basado en beedb](05.5.md)
|
||||
256
es/05.5.md
Normal file
256
es/05.5.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# 5.5 Desarrollo de un ORM basado en beedb
|
||||
( ***El proyecto beedb no está mantenido actualmente, pero el código sigue ahí*** )
|
||||
( ***Project beedb is no longer maintained, but the code s still there*** )
|
||||
|
||||
beedb es un ORM ( object relational mapper ) desarrollado en Go, por mi.
|
||||
Usa el lenguaje ideomático de Go para operar con las bases de datos, implementando un mapeo de estructura a base de datos que actua como un Framework ORM liviano de Go. El propósito de desarrollar este ORM no es solamente ayudar a la gente a aprender como implementar un ORM, sino también encontrar un buen balance entre funcionalidad y desempeño en lo que respecta a persistencia de datos.
|
||||
|
||||
beedb es un proyecto de código abierto que soporta la funcionalidad básica de un ORM, pero no soporta las consultas asociativas.
|
||||
|
||||
Como beedb soporta los estándares de la interfaz `database/sql` cualquier manejador que implemente esta interfaz podrá ser usado con beedb. Yo lo he probado con los siguientes manejadores:
|
||||
|
||||
Mysql: [github.com/ziutek/mymysql/godrv](github.com/ziutek/mymysql/godrv)
|
||||
|
||||
Mysql: [code.google.com/p/go-mysql-driver](code.google.com/p/go-mysql-driver)
|
||||
|
||||
PostgreSQL: [github.com/bmizerany/pq](github.com/bmizerany/pq)
|
||||
|
||||
SQLite: [github.com/mattn/go-sqlite3](github.com/mattn/go-sqlite3)
|
||||
|
||||
MS ADODB: [github.com/mattn/go-adodb](github.com/mattn/go-adodb)
|
||||
|
||||
ODBC: [bitbucket.org/miquella/mgodbc](bitbucket.org/miquella/mgodbc)
|
||||
|
||||
## Instalación
|
||||
|
||||
Puedes usar el comando `go get` para instalar beedb localmente.
|
||||
```
|
||||
go get github.com/astaxie/beedb
|
||||
```
|
||||
## Inicialización
|
||||
|
||||
Primero debes importar los paquetes necesarios:
|
||||
```
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/astaxie/beedb"
|
||||
_ "github.com/ziutek/mymysql/godrv"
|
||||
)
|
||||
```
|
||||
Luego necesitas abrir la conexión a la base de datos y crear un objeto beedb (MySQL en este ejemplo):
|
||||
```
|
||||
db, err := sql.Open("mymysql", "test/xiemengjun/123456")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
orm := beedb.New(db)
|
||||
```
|
||||
`beedb.New()` actualmente toma dos argumentos. El primero es el objeto de la base de datos y el segundo es para indicar que motor de base de datos estás usando. Si estás usando MySQL o SQLite puedes omitir el segundo argumento.
|
||||
|
||||
De otra manera, este argumento debe ser incluido, por ejemplo en el caso de SQLServer:
|
||||
```
|
||||
orm = beedb.New(db, "mssql")
|
||||
```
|
||||
PostgreSQL:
|
||||
```
|
||||
orm = beedb.New(db, "pg")
|
||||
```
|
||||
beedb soporta depuración. Usa el siguiente código para habilitarla:
|
||||
```
|
||||
beedb.OnDebug=true
|
||||
```
|
||||
Después debemos tener una estructura para la tabla `Userinfo` que hemos usado en las secciones anteriores.
|
||||
```
|
||||
type Userinfo struct {
|
||||
Uid int `PK` // Si la llave primaria no es id, necesitas añadir la etiqueta `PK` para una llave primaria personalizada.
|
||||
Username string
|
||||
Departname string
|
||||
Created time.Time
|
||||
}
|
||||
```
|
||||
Se conciente que beedb autoconvierte la notación de camello a notación de guiones bajos. Por ejemplo, si tenemos `UserInfo` como el nombre de la estructura, beedb lo convertirá a `user_info` en la base de datos. La misma regla se aplica para los nombres de campos de la estructura.
|
||||
|
||||
## Insertar datos
|
||||
|
||||
El siguiente ejemplo muestra como usar beedb a guardar una estructura en vez de usar comandos planos de SQL. Usamos el método `Save` de beedb para aplicar el cambio.
|
||||
```
|
||||
var saveone Userinfo
|
||||
saveone.Username = "Test Add User"
|
||||
saveone.Departname = "Test Add Departname"
|
||||
saveone.Created = time.Now()
|
||||
orm.Save(&saveone)
|
||||
```
|
||||
Puedes verificar el campo `saveone.Uid` después de que este registro es insertado; su valor es un identificador autoincrementado por el que el método `Save` se ocupa por ti.
|
||||
|
||||
beedb provee otra manera de insertar datos, usando el estilo de mapas de Go.
|
||||
```
|
||||
add := make(map[string]interface{})
|
||||
add["username"] = "astaxie"
|
||||
add["departname"] = "cloud develop"
|
||||
add["created"] = "2012-12-02"
|
||||
orm.SetTable("userinfo").Insert(add)
|
||||
```
|
||||
Insertando varios datos:
|
||||
```
|
||||
addslice := make([]map[string]interface{}, 10)
|
||||
add:=make(map[string]interface{})
|
||||
add2:=make(map[string]interface{})
|
||||
add["username"] = "astaxie"
|
||||
add["departname"] = "cloud develop"
|
||||
add["created"] = "2012-12-02"
|
||||
add2["username"] = "astaxie2"
|
||||
add2["departname"] = "cloud develop2"
|
||||
add2["created"] = "2012-12-02"
|
||||
addslice = append(addslice, add, add2)
|
||||
orm.SetTable("userinfo").InsertBatch(addslice)
|
||||
```
|
||||
El método mostrado arriba es similar a una consulta encadenada, la cual te podría ser familiar si has usado jQuery. Retorna el objeto del ORM después de la llamada, entonces continua haciendo otros trabajos.
|
||||
|
||||
El método `SetTable` le dice al ORM que queremos insertar nuestra data en la tabla `userinfo`.
|
||||
|
||||
## Actualizar datos
|
||||
|
||||
Continuemos trabajando con el ejemplo de arriba, para ver como actualizar los datos. Ahora que tenemos la llave primeria de `saveone(Uid)`, beedb ejecuta una operación de actualizacieon en lugar de insertar un nuevo registro.
|
||||
```
|
||||
saveone.Username = "Update Username"
|
||||
saveone.Departname = "Update Departname"
|
||||
saveone.Created = time.Now()
|
||||
orm.Save(&saveone) // update
|
||||
```
|
||||
Como antes, también puedes usar un mapa para actualizar los datos:
|
||||
```
|
||||
t := make(map[string]interface{})
|
||||
t["username"] = "astaxie"
|
||||
orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t)
|
||||
```
|
||||
Permíteme explicar algunos de los métodos usados arriba:
|
||||
|
||||
- `.SetPK()` le dice al ORM que `uid` es la llave primaria del registro en la tabla `userinfo`
|
||||
- `.Where()` define condiciones y suporta múltiples argumentos. Si el primer arguento es un entero, es una manera de acortar `Where("<llave primaria>=?", <valor>)`.
|
||||
- `.Update()` acepta un mapa y actualiza la base de datos.
|
||||
|
||||
## Consultado datos
|
||||
|
||||
Las consultas con la interface beedb son muy fleibles. Veamos algunos ejemplos:
|
||||
|
||||
Ejemplo 1: Consultando por llaves primarias:
|
||||
```
|
||||
var user Userinfo
|
||||
// Where acepta dos argumentos, el segundo entero
|
||||
orm.Where("uid=?", 27).Find(&user)
|
||||
```
|
||||
Ejemplo 2:
|
||||
```
|
||||
var user2 Userinfo
|
||||
orm.Where(3).Find(&user2) // Forma corta que omite el nombre de la llave primaria
|
||||
```
|
||||
Ejemplo 3, otras condiciones de consulta
|
||||
```
|
||||
var user3 Userinfo
|
||||
// Where acepta dos argumentos, el segundo cadena
|
||||
orm.Where("name = ?", "john").Find(&user3)
|
||||
```
|
||||
Ejemplo 4, condiciones mas complicadas:
|
||||
```
|
||||
var user4 Userinfo
|
||||
// Where con tres argumentos aceptados
|
||||
orm.Where("name = ? and age < ?", "john", 88).Find(&user4)
|
||||
```
|
||||
Ejemplos para obtener múltiples registros
|
||||
|
||||
Ejemplo 1, Obtiene 10 registros donde `id>3` Que comienzan en la posición 20
|
||||
```
|
||||
var allusers []Userinfo
|
||||
err := orm.Where("id > ?", "3").Limit(10,20).FindAll(&allusers)
|
||||
```
|
||||
Ejemplo 2, omite el segundo argumento de límite, entonces comienza en 0 y obtiene 10 registros:
|
||||
```
|
||||
var tenusers []Userinfo
|
||||
err := orm.Where("id > ?", "3").Limit(10).FindAll(&tenusers)
|
||||
```
|
||||
Ejemplo 3, obtener todos los recursos:
|
||||
```
|
||||
var everyone []Userinfo
|
||||
err := orm.OrderBy("uid desc,username asc").FindAll(&everyone)
|
||||
```
|
||||
Como puedes ver, el método límite es para limitar el número de resultados.
|
||||
|
||||
- `.Limit()` suporta dos argumentos: el número de resultados comenzando en la posición. 0 es el valor por defecto de posición inicial.
|
||||
- `.OrderBy()` es para ordenar resultados. El argumento es la condición de orden.
|
||||
|
||||
Todos los ejemplos aquí simplemente mapean registros a estructuras. También puedes colocar data en un mapa como sigue:
|
||||
```
|
||||
a, _ := orm.SetTable("userinfo").SetPK("uid").Where(2).Select("uid,username").FindMap()
|
||||
```
|
||||
- `.Select()` le dice a beedb cuantos campos quieres obtener desde la tabla de la base de datos. Si no es especifica, todos los registros son retornados por defecto.
|
||||
- `.FindMap()` retorna el tipo `[]map[String][]byte`, entonces puedes convertir a otros tipos tu mismo.
|
||||
|
||||
## Eliminar datos
|
||||
|
||||
beedb provee métodos amplios para eliminar datos.
|
||||
|
||||
Ejemplo 1, eliminar un único registro
|
||||
```
|
||||
// saveone es el mismo del ejemplo anterior.
|
||||
orm.Delete(&saveone)
|
||||
```
|
||||
Ejemplo 2, elimintar todos los usuarios
|
||||
```
|
||||
// alluser es un segmento que tiene múltiples usuarios
|
||||
orm.DeleteAll(&alluser)
|
||||
```
|
||||
Ejemplo 3, Eliminar registros con SQL
|
||||
```
|
||||
orm.SetTable("userinfo").Where("uid>?", 3).DeleteRow()
|
||||
```
|
||||
## Consultas de asociación
|
||||
|
||||
beedb no soportar uniones entre asociaciones.
|
||||
Sin embargo, desde que algunas aplicaciones necesitan esta caractarísticas, aquí está una implementación:
|
||||
```
|
||||
a, _ := orm.SetTable("userinfo").Join("LEFT", "userdetail", "userinfo.uid=userdetail.uid")
|
||||
.Where("userinfo.uid=?", 1).Select("userinfo.uid,userinfo.username,userdetail.profile").FindMap()
|
||||
```
|
||||
Nosotros vemos un método llamado `.Join()` que toma tres argumentos
|
||||
- El primer argumento: Tipo del Join; INNER, LEFT, OUTER, CROSS, etc.
|
||||
- El segundo argumento: la tabla con la que quieres hacer la unión
|
||||
- El tercer argumento: la condición de Join
|
||||
|
||||
## Group By y Having
|
||||
|
||||
beedb también tiene una implementación de `group by` y `having`.
|
||||
```
|
||||
a, _ := orm.SetTable("userinfo").GroupBy("username").Having("username='astaxie'").FindMap()
|
||||
```
|
||||
- `.GroupBy()` indica el campo con el que se va a agrupar.
|
||||
- `.Having()` indica la condición del `having`.
|
||||
|
||||
## Futuro
|
||||
|
||||
He recibido un montón de retroalimentación de beedb de muchas personas al rededor del mundo y estoy pensando en reconfigurar los siguientes aspectos:
|
||||
|
||||
- Implementar una interfaz similar a `database/sql/driver` en orden de facilitar las operaciones CRUD
|
||||
- Implementar asociaciones de bases de datos relacionales como uno a uno, muchos a uno y muchos a muchos.
|
||||
|
||||
Aquí hay un ejemplo:
|
||||
```
|
||||
type Profile struct {
|
||||
Nickname string
|
||||
Mobile string
|
||||
}
|
||||
type Userinfo struct {
|
||||
Uid int
|
||||
PK_Username string
|
||||
Departname string
|
||||
Created time.Time
|
||||
Profile HasOne
|
||||
}
|
||||
```
|
||||
- Auto crear tablas e indices.
|
||||
- Implementar una piscina de conexiones usando rutinas.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [PostgreSQL](05.4.md)
|
||||
- Siguiente sección section: [Bases de Datos NoSQL](05.6.md)
|
||||
113
es/05.6.md
Normal file
113
es/05.6.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# 5.6 Bases de Datos NoSQL
|
||||
|
||||
Una base de datos NoSQL provee un mecanismo para el almacenamiento y el retorno de información que usa modelos de consistencia menores que bases de datos relacionales en orden de alcanzar escalabilidad horizontal y mayor disponibilidad. Alguns autores se refieren a ellas como "No solamente SQL" para enfatizar que algunos sistemas NoSQL permiten consultas similares a SQL.
|
||||
|
||||
Al ser el lenguaje C del siglo XXI, Go tiene soporte para bases de datos NoSQL, incluyendo populares como redis, mongoDB, Cassandra y Membase.
|
||||
|
||||
## redis
|
||||
|
||||
redis es un sistema de almacenamiento llave valor como Memcached, que soporta los tipos de cadenas, listas conjuntos y conjuntos ordenados.
|
||||
|
||||
Aquí están algunos de los manejadores de bases de datos para redis:
|
||||
|
||||
- [https://github.com/alphazero/Go-Redis](https://github.com/alphazero/Go-Redis)
|
||||
- [http://code.google.com/p/tideland-rdc/](http://code.google.com/p/tideland-rdc/)
|
||||
- [https://github.com/simonz05/godis](https://github.com/simonz05/godis)
|
||||
- [https://github.com/hoisie/redis.go](https://github.com/hoisie/redis.go)
|
||||
|
||||
Realicé una bifurcación de el último de estos paquetes, arreglé algunos servicios y lo usé en mi sistema de acortado de urls (2 millones de peticiones por día).
|
||||
|
||||
- [https://github.com/astaxie/goredis](https://github.com/astaxie/goredis)
|
||||
|
||||
Veamos como usar el manejador que bifurqué para operar con la base de datos:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/goredis"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var client goredis.Client
|
||||
|
||||
// Definir el puerto por defecto de redis
|
||||
client.Addr = "127.0.0.1:6379"
|
||||
|
||||
// Manipulación de cadenas
|
||||
client.Set("a", []byte("hello"))
|
||||
val, _ := client.Get("a")
|
||||
fmt.Println(string(val))
|
||||
client.Del("a")
|
||||
|
||||
// Operaciones con listas
|
||||
vals := []string{"a", "b", "c", "d", "e"}
|
||||
for _, v := range vals {
|
||||
client.Rpush("l", []byte(v))
|
||||
}
|
||||
dbvals,_ := client.Lrange("l", 0, 4)
|
||||
for i, v := range dbvals {
|
||||
println(i,":",string(v))
|
||||
}
|
||||
client.Del("l")
|
||||
}
|
||||
```
|
||||
Como podemos ver es muy sencillo operar redis en Go, y como tiene un alto rendimiento. Los comandos de este cliente son casi lo mismo que los comandos que vienen con redis.
|
||||
|
||||
## mongoDB
|
||||
|
||||
mongoDB (de "humongous" (enorme)) es una bases de datos orientada a documentos de código abierto desarrollado y mantenida por 10gen. Hace parte de la familia de bases de datos NoSQL. En lugar de almacenar la información en tablas como es hecho en bases de datos relacionales 'clasicas', MongoDB guarda la información en estructurada en documentos al estilo JSON con esquemas dinámicos (MongoDB llama el formato BSON) haciendo que la integración de los datos en cierto tipo de aplicaciones sea mas fácil y rápido.
|
||||
|
||||

|
||||
|
||||
Figura 5.1 MongoDB comparada con Mysql
|
||||
|
||||
El mejor manejador para mongoDB es llamado `mgo`, y es posible que se incluya en la librería estándar en el futuro.
|
||||
|
||||
Aquí está un ejemplo
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"labix.org/v2/mgo"
|
||||
"labix.org/v2/mgo/bson"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Phone string
|
||||
}
|
||||
|
||||
func main() {
|
||||
session, err := mgo.Dial("server1.example.com,server2.example.com")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
session.SetMode(mgo.Monotonic, true)
|
||||
|
||||
c := session.DB("test").C("people")
|
||||
err = c.Insert(&Person{"Ale", "+55 53 8116 9639"},
|
||||
&Person{"Cla", "+55 53 8402 8510"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
result := Person{}
|
||||
err = c.Find(bson.M{"name": "Ale"}).One(&result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("Phone:", result.Phone)
|
||||
}
|
||||
```
|
||||
Como podemos ver no hay muchas diferencias en lo que respecta a operar con mgo o bases de datos beedb; ambas son basadas en estructuras. Esta es la manera en que Go hace las cosas.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección previa: [Desarrollo de un ORM basado en beedb](05.5.md)
|
||||
- Siguiente sección: [Resumen](05.7.md)
|
||||
11
es/05.7.md
Normal file
11
es/05.7.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 5.7 Resumen
|
||||
|
||||
En este capítulo, primero aprendiste sobre el diseño de la interfaz `database/sql` y varios manejadores de bases de datos para varios tipos de bases de datos. Luego introducimos beedb, un ORM para bases de datos relacionales. También mostramos algunos ejemplos de operaciones con bases de datos. Al final, hablamos sobre algunas bases de datos NoSQL. Vimos como Go provee soporte para estas bases de datos NoSQL.
|
||||
|
||||
Después de leer este capítulo, espero que hayas entendido un poco mas como funcionan las bases de datos en Go. Esta es la parte mas importante del desarrollo web, por eso es importante que entiendas bien los conceptos de la interfaz `database/sql`.
|
||||
|
||||
## Enlaces
|
||||
|
||||
- [Índice](preface.md)
|
||||
- Sección anterior: [Bases de Datos NoSQL ](05.6.md)
|
||||
- Siguiente capítulo: [Almacenamiento de datos y sesiones](06.0.md)
|
||||
Reference in New Issue
Block a user