Magic Studio

专心做有逼格的APP!

2018年10月2日 #

Golang处理数据库的nil数据

在用golang获取数据库的数据的时候,难免会遇到可控field。这个时候拿到的数据如果直接用string, time.Time这样的类型来解析的话会遇到panic。

那么如何处理这个问题呢,第一个出现在眼前的办法就是用database/sql。这个包里包含了很多的可以处理可控字段的类型,比如:sql.NullString, sql.NullBool等。所以,model可以用这些类型来定义,如:

package main
import (
    "database/sql"
    "fmt"
    "github.com/go-sql-driver/mysql"
)
type Article struct {
    Id      int            `json:"id"`
    Title   string         `json:"title"`
    PubDate mysql.NullTime `json:"pub_date"`
    Body    sql.NullString `json:"body"`
    User    sql.NullInt64  `json:"user"`
}

这样的定义是可以work的,但是会有一点奇怪:没谁会用数据库的类型来代替平时的类型。所以,我们可以改一种思路。在解析数据库的时候会有专用的数据库类型字段来接收,但是在返回json的时候有专门的model来使用,这个model用的是普通的类型。

所以,写起来是这样的:

var person Person

var personID int64
var password sql.NullString
var lastLogin mysql.NullTime
var isSuperuser sql.NullBool
var userName sql.NullString
var firstName sql.NullString
var lastName sql.NullString
var email sql.NullString
var isStaff sql.NullBool
var isActive sql.NullBool
var dateJoined mysql.NullTime

err = ret.Scan(
	&personID,
    &password,
    &lastLogin,
	&isSuperuser,
	&userName,
	&firstName,
	&lastName,
	&email,
	&isStaff,
	&isActive,
	&dateJoined,
)

上面定义了解析,下面定义model:

type Person struct {
	ID          int64      `json:"id"`
	Password    string     `json:"password"`
	LastLogin   *time.Time `json:"last_login"`
	IsSuperuser bool       `json:"is_superuser"`
	Username    string     `json:"username"`
	FirstName   string     `json:"first_name"`
	LastName    string     `json:"last_name"`
	Email       string     `json:"email"`
	IsStaff     bool       `json:"is_staff"`
	IsActive    bool       `json:"is_active"`
	DateJoined  *time.Time `json:"date_joined"`
}

有了前两部之后,现在可以装配这些数据了:

person.ID = personID
person.Password = If(password.Valid, password.String, "").(string)
if tempTime, ok := If(lastLogin.Valid, lastLogin.Time, nil).(*time.Time); ok {
	person.LastLogin = tempTime
} else {
	person.LastLogin = nil	
}

person.IsSuperuser = If(isSuperuser.Valid, isSuperuser.Bool, false).(bool)
person.Username = If(userName.Valid, userName.String, "").(string)
person.FirstName = If(firstName.Valid, firstName.String, "").(string)
person.LastName = If(lastName.Valid, lastName.String, "").(string)
person.Email = If(email.Valid, email.String, "").(string)
person.IsStaff = If(isStaff.Valid, isStaff.Bool, false).(bool)
person.IsActive = If(isActive.Valid, isActive.Bool, false).(bool)
if tempTime, ok := If(dateJoined.Valid, dateJoined.Time, nil).(*time.Time); ok {
	person.DateJoined = tempTime
} else {
	person.DateJoined = nil
}

有一点需要注意的。在golang里类型转换之前需要先做一个type assertion才行,否则报错。而且nil是不能做类型转换的,比如:

if tempTime, ok := If(dateJoined.Valid, dateJoined.Time, nil).(*time.Time); ok {
	person.DateJoined = tempTime
} else {
	person.DateJoined = nil
}

以上是同sqlite做为数据库是遇到的问题。还有一点,sqlite没有处理时间为空的类型,所以上面使用的是mysql的driver里的NullTime,奇怪的是用自定义的NullTime不行。懒得深究了,哪位知道的话希望留言。

下面是全部的app代码:


package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"database/sql"
	"log"

	"github.com/go-sql-driver/mysql"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
	_ "github.com/mattn/go-sqlite3"
)

func main() {
	// Echo instance
	e := echo.New()

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// Routes
	e.GET("/", hello)

	// Start server
	e.Logger.Fatal(e.Start(":1323"))
}

func hello(c echo.Context) error {
	ret, err := executeSQL("select id,password,last_login,is_superuser,username,first_name,last_name,email,is_staff,is_active,date_joined from person where id = ?")
	if err != nil {
		return c.JSON(http.StatusOK, map[string]string{"error": "Something went wrong when getting data from db"})
	}
	return c.JSON(http.StatusOK, ret)
}

// Execute sql statement from parameter, which looks like this:
// select a, b, c from some_tabble where id = ?
// Return a map
func executeSQL(sqlStmt string) ([]Person, error) {
	db, err := sql.Open("sqlite3", "./db.sqlite3")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	stmt, err := db.Prepare(sqlStmt)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}
	defer stmt.Close()

	ret, err := stmt.Query(3)
	if err != nil {
		log.Fatal(err)
		return nil, err
	}

	personList := make([]Person, 0)
	for ret.Next() {
		var person Person

		var personID int64
		var password sql.NullString
		var lastLogin mysql.NullTime
		var isSuperuser sql.NullBool
		var userName sql.NullString
		var firstName sql.NullString
		var lastName sql.NullString
		var email sql.NullString
		var isStaff sql.NullBool
		var isActive sql.NullBool
		var dateJoined mysql.NullTime

		err = ret.Scan(
			&personID,
			&password,
			&lastLogin,
			&isSuperuser,
			&userName,
			&firstName,
			&lastName,
			&email,
			&isStaff,
			&isActive,
			&dateJoined,
		)

		if err != nil {
			log.Fatal(err)
		}

		person.ID = personID
		person.Password = If(password.Valid, password.String, "").(string)
		if tempTime, ok := If(lastLogin.Valid, lastLogin.Time, nil).(*time.Time); ok {
			person.LastLogin = tempTime
		} else {
			person.LastLogin = nil
		}

		person.IsSuperuser = If(isSuperuser.Valid, isSuperuser.Bool, false).(bool)
		person.Username = If(userName.Valid, userName.String, "").(string)
		person.FirstName = If(firstName.Valid, firstName.String, "").(string)
		person.LastName = If(lastName.Valid, lastName.String, "").(string)
		person.Email = If(email.Valid, email.String, "").(string)
		person.IsStaff = If(isStaff.Valid, isStaff.Bool, false).(bool)
		person.IsActive = If(isActive.Valid, isActive.Bool, false).(bool)
		if tempTime, ok := If(dateJoined.Valid, dateJoined.Time, nil).(*time.Time); ok {
			person.DateJoined = tempTime
		} else {
			person.DateJoined = nil
		}

		personList = append(personList, person)
	}

	j, err := json.Marshal(personList)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(j)

	return personList, nil
}

posted @ 2018-10-02 23:02 Mr 布鲁斯 阅读(1435) 评论(0) 推荐(1) 编辑

2018年6月23日 #

[译]迁移到新的 React Context Api

摘要: 随着 React 16.3.0 的发布,context api 也有了很大的更新。我已经从旧版的 api 更新到了新版。这里就分享一下我(作者)的心得体会。 回顾 下面是一个展示如何使用旧版 api 的例子: 上面的代码会返回一个复合组件 。这个组件可以让子组件共享隐式的状态。在某些简单的情况下可以 阅读全文

posted @ 2018-06-23 20:18 Mr 布鲁斯 阅读(489) 评论(0) 推荐(1) 编辑

2018年5月28日 #

自定义react-navigation的TabBar

摘要: 在某些情况下,默认的react navigation的tab bar无法满足开发者的要求。这个时候就需要自定义一个tab bar了。本文就基于react navigtion v2来演示如何实现一个自定义tab bar。 这里主要处理的是再android里,当界面中有输入框,唤起软键盘的时候位于底部的 阅读全文

posted @ 2018-05-28 16:31 Mr 布鲁斯 阅读(5090) 评论(0) 推荐(1) 编辑

2018年5月22日 #

React-router v4教程

摘要: 在这个教程里,我们会从一个例子React应用开始学习react router dom。其中你会学习如何使用 、`NavLink Switch exact`实现排他路由和浏览器路径历史。 也许学习react router最好的办法就是用react router dom v4来写一个多页的react应用 阅读全文

posted @ 2018-05-22 13:46 Mr 布鲁斯 阅读(820) 评论(0) 推荐(0) 编辑

2018年3月4日 #

ANTD mobile源码分析 -- popover

摘要: 最近的开发中要用到很多的各式各样的组件。但是发现ant design mobile(后面简称ANTDM)里很多的资源。于是就分析一下,学习学习。 ANTDM直接使用了typescript,没有用ES2015,不过这不会是障碍,反而是学习typescript的一个好机会。基本上可以学的开源项目里比 阅读全文

posted @ 2018-03-04 21:42 Mr 布鲁斯 阅读(1822) 评论(0) 推荐(0) 编辑

2018年3月3日 #

编写React组件的最佳实践

摘要: 此文翻译自 "这里" 。 当我刚开始写React的时候,我看过很多写组件的方法。一百篇教程就有一百种写法。虽然React本身已经成熟了,但是如何使用它似乎还没有一个“正确”的方法。所以我(作者)把我们团队这些年来总结的使用React的经验总结在这里。希望这篇文字对你有用,不管你是初学者还是老手。 开 阅读全文

posted @ 2018-03-03 21:58 Mr 布鲁斯 阅读(918) 评论(1) 推荐(2) 编辑

2018年1月20日 #

JavaScript的this和作用域

摘要: 本文主要讨论一下JS的作用域和 关键字。作用域,就是你的方法或者变量可访问的区域,是他们执行的上下文。如果你见过这样的代码: 你就会很好奇为什么要用 赋值给一个变量 呢?你看完这篇文章就会清楚这个问题的答案了。 第一种作用域叫做全局作用域( Global Scope )这很容易定义,如果一个方法、变 阅读全文

posted @ 2018-01-20 23:20 Mr 布鲁斯 阅读(380) 评论(0) 推荐(0) 编辑

2017年11月25日 #

一步一步带你实现virtual dom(一)

摘要: "一步一步带你实现virtual dom(一)" "一步一步带你实现virtual dom(二) Props和事件" 要写你自己的虚拟DOM,有两件事你必须知道。你甚至都不用翻看React的源代码,或者其他的基于虚拟DOM的代码。他们代码量都太大,太复杂。然而要实现一个虚拟DOM的主要部分只需要大约 阅读全文

posted @ 2017-11-25 23:47 Mr 布鲁斯 阅读(491) 评论(0) 推荐(0) 编辑

一步一步带你实现virtual dom(二) -- Props和事件

摘要: "一步一步带你实现virtual dom(一)" "一步一步带你实现virtual dom(二) Props和事件" 很高兴我们可以继续分享编写虚拟DOM的知识。这次我们要讲解的是产品级的内容,其中包括:设置和DOM一致性、以及事件的处理。 使用Babel 在继续之前,我们需要弥补前一篇文章中没有详 阅读全文

posted @ 2017-11-25 21:40 Mr 布鲁斯 阅读(1604) 评论(0) 推荐(0) 编辑

2017年11月20日 #

Redux:从action到saga

摘要: 前端应用消失的部分 一个现代的、使用了 redux 的前端应用架构可以这样描述: 1. 一个存储了应用不可变状态(state)的store 2. 状态(state)可以被绘制在组件里(html或者其他的东西)。这个绘制方法通常是简单而且可测试的(并不总是如此)纯方法。 3. 组件可以给store分发 阅读全文

posted @ 2017-11-20 11:29 Mr 布鲁斯 阅读(1334) 评论(0) 推荐(0) 编辑

导航

统计信息

点击右上角即可分享
微信分享提示