go之构建简单的web服务器1.0

目录架构雏形

在这里插入图片描述

main.go:程序入口,路由管理。
storage目录:存放MySQL等数据库操作。
user:用户模块。
后期可以继续新增一些模块:比如common,存放公共方法的包。

main.go代码

package main
import (
	"net/http"
	//内部包 
	// mysql "etcd_admin/storage"
	user_edit "etcd_admin/user"
)

func main() {
  // 当访问 http://127.0.0.1:9527/hello 时,进入这个方法
  http.HandleFunc("/hello", HelloHandler)

  //设置管理员(方法提取到etcd_admin/user目录下的文件)
  http.HandleFunc("/addAdmin", user_edit.AddAdmin)
  http.HandleFunc("/updateAdmin", user_edit.UpdateAdmin)
  http.HandleFunc("/delAdmin", user_edit.DelAdmin)
  http.HandleFunc("/getAdminList", user_edit.GetAdminList)

  // 启动一个Web服务,并监听9527端口
  http.ListenAndServe(":9527",nil)
}

//单纯作为测试演示方法
func HelloHandler(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("<h1>Hello World</h1>"))
}


mysql.go代码

package storage

import (
	"database/sql"
	"errors"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

type Storage struct {
	db *sql.DB // 创建连接对象
}

func NewMySqlInit() (*Storage, error) {
	//Open函数可能只是验证其参数,而不创建与数据库的连接。如果要检查数据源的名称是否合法,应调用返回值的Ping方法。
	//返回的DB可以安全的被多个go程同时使用,并会维护自身的闲置连接池。这样一来,Open函数只需调用一次。很少需要关闭DB。
	db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/oem")

	if err != nil {
		// fmt.Println("数据源的名称不合法!")
		log.Fatalln("db connect err:", err)
		return nil, err
	}
	//Ping检查与数据库的连接是否仍有效,如果需要会创建连接。
	connect_err := db.Ping()
	if connect_err != nil {
		log.Fatalf("连接失败!error:%v", connect_err)
		return nil, err
	}
	// fmt.Println("连接数据库成功")
	//结构体实例化
	myStorage := &Storage{db: db}
	return myStorage, nil
}

// 使用Exec()完成对INSERT、UPDATE、DELETE操作
// 调用这个方法的方式就是storage.insert()
func (s *Storage) Exec(sql string, args ...interface{}) (int64, error) {
	//预编译语句(PreparedStatement)提供了诸多好处。PreparedStatement可以实现自定义参数的查询,通常会比手动拼接字符SQL串高效;
	//还可以防止SQL注入攻击。因此一般情况下用PreparedStatement和Exec()完成对INSERT、UPDATE、DELETE操作。
	stm, pre_err := s.db.Prepare(sql)
	if pre_err != nil {
		return 0, errors.New("prepare err: " + pre_err.Error())
	}
	defer stm.Close()
	res, exec_err := stm.Exec(args...)
	if exec_err != nil {
		return 0, errors.New("exec err:  " + exec_err.Error())
	}
	return res.LastInsertId()
}

//查询方法
func (s *Storage) Query(sql string, args ...interface{}) (*sql.Rows, error) {
	//预编译语句(PreparedStatement)提供了诸多好处。PreparedStatement可以实现自定义参数的查询,通常会比手动拼接字符SQL串高效;
	//还可以防止SQL注入攻击。因此一般情况下用PreparedStatement和Exec()完成对INSERT、UPDATE、DELETE操作。
	stm, pre_err := s.db.Prepare(sql)
	if pre_err != nil {
		return nil, errors.New("Query prepare err: " + pre_err.Error())
	}
	defer stm.Close()
	rows, exec_err := stm.Query(args...)

	if exec_err != nil {
		return nil, errors.New("Query exec err:  " + exec_err.Error())
	}
	return rows, exec_err
}

//*Row类型返回值的Scan方法会返回ErrNoRows;否则,Scan方法会扫描结果第一行并丢弃其余行。
func (s *Storage) QueryRow(sql string, args ...interface{}) (*sql.Row, error) {
	//预编译语句(PreparedStatement)提供了诸多好处。PreparedStatement可以实现自定义参数的查询,通常会比手动拼接字符SQL串高效;
	//还可以防止SQL注入攻击。因此一般情况下用PreparedStatement和Exec()完成对INSERT、UPDATE、DELETE操作。
	stm, pre_err := s.db.Prepare(sql)
	if pre_err != nil {
		return nil, errors.New("Query prepare err: " + pre_err.Error())
	}
	defer stm.Close()
	row := stm.QueryRow(args...)
	return row, nil
}

user_edit.go代码

package user

import (
	"net/http"
	"fmt"
	"log"
	sql1 "database/sql"
	_ "github.com/go-sql-driver/mysql"
	//内部包
	mysql "etcd_admin/storage"
)

type Admin struct {
	Id int
	User_name string
	Prefix_key string
	Is_write int
}

//新增管理员
func AddAdmin(w http.ResponseWriter, r *http.Request) {
	var sql string
	//获取参数
	user_name := r.FormValue("user_name")
	password := r.FormValue("password")
	prefix_key := r.FormValue("prefix_key")
	is_write := r.FormValue("is_write")

	//参数判断
	if user_name == "" {
		w.Write([]byte(fmt.Sprintf("用户名不能为空")))
		return
	}
	if password == "" {
		w.Write([]byte(fmt.Sprintf("密码不能为空")))
		return
	}
	//默认是1可读可写,2代表只读
	if is_write == "" {
		is_write = "1";
	}
	//获取数据库连接
	mysqlConnect,err := mysql.NewMySqlInit();
	if err != nil {
		w.Write([]byte(fmt.Sprintf("连接出错了:%v",err)))
		return
	}
	//判断用户名是否已经存在
	sql = "select id from etcd_admin where user_name=? limit 1";
	row,err := mysqlConnect.QueryRow(sql,user_name)
	var id int
	//如果id为默认值0,则代表这个数据不存在,大于0,则代表已存在
	err = row.Scan(&id)
	if err != sql1.ErrNoRows && id >0 {
		w.Write([]byte(fmt.Sprintf("数据已存在了:%v",err)))
		return
	}

	//新增数据
	sql = "insert into etcd_admin(user_name,password,prefix_key,is_write) values(?,?,?,?)"
	//调用结构体的方法
	rows,err := mysqlConnect.Exec(sql,user_name,password,prefix_key,is_write);
	if err != nil {
		w.Write([]byte(fmt.Sprintf("新增出错了:%v",err)))
		return
	}
	
	w.Write([]byte(fmt.Sprintf("<h1>新增了 %d 行数据</h1>",rows)))
}


//更新管理员
func UpdateAdmin(w http.ResponseWriter, r *http.Request) {
	var sql string
	//获取参数
	id := r.FormValue("id")
	user_name := r.FormValue("user_name")
	password := r.FormValue("password")
	prefix_key := r.FormValue("prefix_key")
	is_write := r.FormValue("is_write")

	//参数判断
	if id == "" {
		w.Write([]byte(fmt.Sprintf("用户id不能为空")))
		return
	}

	if user_name == "" {
		w.Write([]byte(fmt.Sprintf("用户名不能为空")))
		return
	}
	if password == "" {
		w.Write([]byte(fmt.Sprintf("密码不能为空")))
		return
	}
	//默认是1可读可写,2代表只读
	if is_write == "" {
		is_write = "1";
	}
	//获取数据库连接
	mysqlConnect,err := mysql.NewMySqlInit();
	if err != nil {
		w.Write([]byte(fmt.Sprintf("连接出错了:%v",err)))
		return
	}
	//新增数据
	sql = "update etcd_admin set user_name=?,password=?,prefix_key=?,is_write=? where id=?"
	//调用结构体的方法
	rows,err := mysqlConnect.Exec(sql,user_name,password,prefix_key,is_write,id);
	if err != nil {
		w.Write([]byte(fmt.Sprintf("更新出错了:%v",err)))
		return
	}
	
	w.Write([]byte(fmt.Sprintf("<h1>更新了 %d 行数据</h1>",rows)))

}

//删除管理员
func DelAdmin(w http.ResponseWriter, r *http.Request) {
	var sql string
	//获取参数
	id := r.FormValue("id")
	//参数判断
	if id == "" {
		w.Write([]byte(fmt.Sprintf("用户id不能为空")))
		return
	}
	//获取数据库连接
	mysqlConnect,err := mysql.NewMySqlInit();
	if err != nil {
		w.Write([]byte(fmt.Sprintf("连接出错了:%v",err)))
		return
	}

	//新增数据 
	sql = "delete from etcd_admin where id=?"
	//调用结构体的方法
	rows,err := mysqlConnect.Exec(sql,id);
	if err != nil {
		w.Write([]byte(fmt.Sprintf("删除出错了:%v",err)))
		return
	}
	
	w.Write([]byte(fmt.Sprintf("<h1>删除了 %d 行数据</h1>",rows)))
}

//获取管理员列表
func GetAdminList(w http.ResponseWriter, r *http.Request) {
	var sql string
	//获取参数
	// user_id := r.FormValue("user_id")
	//获取数据库连接
	mysqlConnect,err := mysql.NewMySqlInit();
	if err != nil {
		w.Write([]byte(fmt.Sprintf("连接出错了:%v",err)))
		return
	}

	//参数判断
	// if user_id == "" {
	// 	fmt.Println("all")
	// 	sql = "select * from etcd_admin"
	// 	rows,err := mysqlConnect.Query(sql);
	// } else {
	// 	fmt.Println("one")
	// 	sql = "select * from etcd_admin where id=?"
	// 	//调用结构体的方法
	// 	rows,err := mysqlConnect.Query(sql,user_id);
	// }
	sql = "select id,user_name,prefix_key,is_write from etcd_admin"
	rows,err := mysqlConnect.Query(sql);

	defer rows.Close()
	if err != nil {
		w.Write([]byte(fmt.Sprintf("查询出错了:%v",err)))
		return
	}
	
	var id,is_write int
	var user_name,prefix_key string
	var adminList []Admin
	for rows.Next() {
		//Scan的字段要与select筛选的字段数量和顺序一致
        err := rows.Scan(&id,&user_name,&prefix_key,&is_write)
        if err != nil {
            log.Fatal(err)
        }
		u := Admin{
			Id:       id,
			User_name: user_name,
			Prefix_key: prefix_key,
			Is_write:  is_write,
		}
		adminList = append(adminList,u)

    }
    fmt.Println(adminList)
    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
	// fmt.Println(rows)
	// fmt.Printf("类型是%T",rows)
	// rows := 6777;
	w.Write([]byte(fmt.Sprintf("<h1>name=%s </h1>",user_name)))
	//
}

经验总结

  1. 不要把所有方法(接口)都写在main.go里面,将方法提取到其他模块下(比如user模块下),然后在main.go通过import引入外部包。
  2. 为什么go的SQL查询那么繁琐呢???查询数据需要next(),scan()去处理数据。无语……
  3. 修改代码后,需要杀掉端口占用的进程,再重新执行 go run main.go才能生效。我索性写个shell脚本,每次修改代码后,手动执行这个脚本,就会帮我找到端口占用的进程id并杀死它。然后我就能愉快的执行 go run main.go。(听起来我的脚本好像可有可无?如果是linux环境确实一行命令行就能完成了,可是我在window环境调试的,所以暂时用脚本代替)
    不过,我更想知道,怎么才能不用每次杀死进程id以及重新执行main.go。
  4. mysql.go在storage包下,所以要写成package storage,不要写成package mysql。(一个目录下的同级文件归属一个包)

附录“举足轻重”的shell脚本。注:window环境下。

#!bin/bash
#获取占用9527端口的进程id。等号中间不能有空格!!!!
PID=`netstat -aon|findstr 9527 |findstr LISTENING | awk '{print $5}' | head -n1`
echo "pid is $PID"
#杀死这个进程id
echo `taskkill -pid $PID -f`

linux环境下杀死927端口占用的进程的命令:

netstat -antp | grep ::9527 | awk '{print $7}'|cut -d '/' -f 1 | xargs kill -9
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值