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

go之构建简单的web服务器1.0,第1张

目录架构雏形

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("Hello World"))
}


mysql.go代码

package storage
import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
	"fmt"
	"errors"
)

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)/shop")
	if err != nil {
		// fmt.Println("数据源的名称不合法!")
		return nil,err
	}
	//Ping检查与数据库的连接是否仍有效,如果需要会创建连接。
	connect_err := db.Ping()
	if connect_err != nil {
		// fmt.Printf("连接失败!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.RowsAffected()
}

//查询方法
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("新增了 %d 行数据",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("更新了 %d 行数据",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("删除了 %d 行数据",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("name=%s ",user_name)))
	//
}

经验总结

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

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

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

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

netstat -antp | grep ::9527 | awk '{print }'|cut -d '/' -f 1 | xargs kill -9

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/995168.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存