【REST2SQL】04 REST2SQL第一版Oracle版实现

【REST2SQL】01RDB关系型数据库REST初设计
【REST2SQL】02 GO连接Oracle数据库
【REST2SQL】03 GO读取JSON文件

REST2SQL的第一个版本,只支持Oracle数据库,以后会逐步加入其它数据看的支持。

项目文件组织如下:
在这里插入图片描述

1 REST2SQL为项目主目录

主控main()函数、请求日志函数、请求响应函数、请求参数返回函数在此目录。

1.1 import引用包

import (
	"encoding/json"
	"fmt"

	"io"
	"log"
	"net/http"
	"rest2sql/config" //配置信息在config.json文件
	do "rest2sql/dothing"
	"strings"
	"time"
)

1.2 请求信息放在Map里

// 请求信息map
var (
	req   map[string]interface{} = make(map[string]interface{}) //请求参数
	count int                    = 0                            //请求计数器
)

1.3 main() 主控函数

// main()
func main() {
	// 打印配置信息
	fmt.Println("config:", config.Conf)

	//响应所有的请求
	http.HandleFunc("/", handler)
	// http.HandleFunc("/REST", restHandler)
	// http.HandleFunc("/SQL", sqlHandler)

	println("Starting Http Server at", config.Conf.HostPort, "\n")

	//启动监听和服务
	//log.Println(http.ListenAndServe(Conf.HostPort, RequestLogger(http.DefaultServeMux)))
	http.ListenAndServe(config.Conf.HostPort, RequestLogger(http.DefaultServeMux))
	//log.Println(err)

	//测试可以用这个
	//curl -X POST -d "{\"gpdm\":600800}" -H "Content-Type:application/json" http://localhost:8080/rest/blma

}

1.4 请求日志函数

// 请求日志
func RequestLogger(targetMux http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		count++
		start := time.Now()
		targetMux.ServeHTTP(w, r)
		log.Printf(
			"(%v)\t%s\t\t%s\t\t%s\t\t%v",
			count,
			r.Method,
			r.RemoteAddr,
			r.RequestURI,
			time.Since(start),
		)
	})
}

1.5 请求响应函数

// handler
func handler(w http.ResponseWriter, r *http.Request) {
	// 1请求主机Host
	req["Host"] = r.Host

	// 2请求路径Path
	req["Path"] = r.URL.Path
	path := strings.Split(r.URL.Path, "/")
	if len(path) < 3 {
		w.Write([]byte("400 Bad Request错误请求。请尝试/rest/xxx or /sql/xxx"))
		return
	}

	//fmt.Println(path)
	// 3 请求类型REST or SQL
	rors := strings.ToUpper(fmt.Sprint(path[1]))
	// 支持的请求类型
	if !(rors == "REST" || rors == "SQL") {
		w.Write([]byte("400 Bad Request错误请求。请尝试/REST/xxx or /SQL/xxx"))
		return
	}
	req["RESTorSQL"] = rors //请求类型SQL or REST

	// 4 资源名 ResName
	req["ResName"] = path[2] //资源名,表名ResName

	// 5请求方法Method
	req["Method"] = r.Method

	// 6请求头Content-Type
	req["Content-Type"] = r.Header.Get("Content-Type")

	// 7请求数据Data
	data, err := io.ReadAll(r.Body)

	if err != nil {
		w.Write([]byte(err.Error()))
		return
	}
	defer r.Body.Close()
	//反序列化
	if len(data) > 0 {
		var idata interface{}
		//fmt.Println("data:", data)
		err = json.Unmarshal(data, &idata)
		if err != nil {
			w.Write([]byte(err.Error()))
			return
		}
		//fmt.Println("idata:", idata)
		req["Data"] = idata
	} else {
		req["Data"] = ""
	}

	// 8 请求参数
	query := r.URL.Query()
	req["Where"] = query.Get("where")
	req["OrderBy"] = query.Get("orderby")

	//返回http请求参数
	resReturn(w, req)

	//根据请求参数执行不同的操作
	do.DoThing(w, req)
}

1.6 请求参数返回函数

// http请求主要参数直接返回
func resReturn(w http.ResponseWriter, req map[string]interface{}) {
	w.Write([]byte("{\"Request\":"))
	str, err := json.MarshalIndent(req, "", "   ")
	if err != nil {
		w.Write([]byte(err.Error()))
	}
	//fmt.Println(str)
	w.Write(str)
	w.Write([]byte(","))
}

2 config配置文件读取子目录

2.1 包名改为 config

//全局变量包

package config

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"strings"
)

var Conf config //全局变量

func init() {
	Conf = getConfig()
}

// 配置结构体
type config struct {
	DBType     string //数据库类型 :oracle、mysql等
	ConnString string `json:"connString"`
	HostPort   string `json:"hostPort"`
	REST       string `json:"REST"`
	SQL        string `json:"SQL"`
}

// 取配置信息
func getConfig() config {
	bytes, err := ioutil.ReadFile("config.json")
	if err != nil {
		log.Println("读取json文件失败:", err)
		panic(nil)
	}
	conf := &config{}
	err = json.Unmarshal(bytes, conf)
	if err != nil {
		log.Println("json解析失败", err)
		panic(nil)
	}
	//数据库类型为数据库的第一部分
	end := strings.Index(conf.ConnString, ":/")
	if end < 0 {
		log.Println("连接字符串设置有误。")
		panic(nil)
	}
	conf.DBType = conf.ConnString[0:end]
	// fmt.Println(conf)
	// fmt.Println("connString:", conf.ConnString)
	// fmt.Println("hostPort:", conf.HostPort)
	return *conf
}

2.2 doc.go 文件设置

// config project doc.go

/*
config document
*/
package config

3 dboracle子目录,Oracle数据库操作

3.1 包名:dboracle

// gooracle project main.go
package dboracle

import (
	"database/sql/driver"
	"encoding/json"

	"io"
	"log"
	"rest2sql/config"

	go_ora "github.com/sijms/go-ora/v2" // 1 go get github.com/sijms/go-ora/v2
)

// Oracle连接字符串
//var ConnStr string = "oracle://blma:5217@127.0.0.1:1521/CQYH"

var ConnString string = config.Conf.ConnString

/*
func main() {
	var (
		sqls   string //sql语句
		result string //sql执行后返回的结果
	)

	// select查询数据
	sqls = "select sysdate from dual"
	result = selectData(sqls)
	fmt.Println(result)

	// delete 删除数据
	sqls = "delete from atop where p_id = -5"
	result = deleteData(sqls)
	fmt.Println(result)

	// update 更新数据
	sqls = "update atop set f_dm = '005217' where p_id = -5217"
	result = updateData(sqls)
	fmt.Println(result)

	// insert 插入一行数据
	sqls = "insert into atop (p_id) values (FLOOR(DBMS_RANDOM.value(0, 100)))"
	result = insertData(sqls)
	fmt.Println(result)
}
*/

// 连接Oracle数据库
func connDB(connStr string) *go_ora.Connection {
	//创建连接
	DB, err := go_ora.NewConnection(connStr)
	dieOnError("Can't open the driver:", err)
	//打开连接
	err = DB.Open()
	dieOnError("Can't open the connection:", err)
	return DB
}

// delete
func DeleteData(deleteSql string) string {
	result, _ := execSQL(deleteSql)
	rows, err := result.RowsAffected()
	dieOnError("Can't delete", err)
	ret := map[string]int{
		"Delete rowsAffected": int(rows),
	}
	jsonBytes, err := json.MarshalIndent(ret, "", "   ")
	dieOnError("map 转 json失败:", err)
	return string(jsonBytes)
}

// update
func UpdateData(updateSql string) string {
	result, _ := execSQL(updateSql)
	rows, err := result.RowsAffected()
	dieOnError("Can't update", err)
	ret := map[string]int{
		"Update rowsAffected": int(rows),
	}
	jsonBytes, err := json.Marshal(ret)
	dieOnError("map 转 json失败:", err)
	return string(jsonBytes)
}

// insert
func InsertData(insertSql string) string {
	result, _ := execSQL(insertSql)
	rows, err := result.RowsAffected()
	dieOnError("Can't insert", err)
	ret := map[string]int{
		"Insert rowsAffected": int(rows),
	}
	jsonBytes, err := json.MarshalIndent(ret, "", "   ")
	dieOnError("map 转 json失败:", err)
	return string(jsonBytes)
}

// 执行SQL, execute stmt (INSERT, UPDATE, DELETE, DML, PLSQL) and return driver.Result object
func execSQL(sqls string) (result driver.Result, err error) {
	//连接数据库
	DB := connDB(ConnString)
	//延迟关闭连接
	defer DB.Close()

	//准备sql语句
	stmt := go_ora.NewStmt(sqls, DB)
	//延迟关闭SQL
	defer stmt.Close()

	//执行SQL, execute stmt (INSERT, UPDATE, DELETE, DML, PLSQL) and return driver.Result object
	result, err = stmt.Exec(nil)
	dieOnError("Can't execSql() ", err)

	return result, err
}

// select查询,结果为json
func SelectData(sqls string) string {
	//连接数据库
	DB := connDB(ConnString)
	//延迟关闭连接
	defer DB.Close()

	//准备sql语句
	stmt := go_ora.NewStmt(sqls, DB)
	//延迟关闭SQL
	defer stmt.Close()

	rows, err := stmt.Query(nil)
	dieOnError("Can't query", err)

	defer rows.Close()

	//fmt.Println(rows)

	columns := rows.Columns()
	//fmt.Println("columns:", columns)
	values := make([]driver.Value, len(columns))
	var dataset []map[string]interface{} //元素为map的切片
	//Header(columns)
	for {
		err = rows.Next(values)
		if err != nil {
			break
		}
		//fmt.Println("values:", values)
		row1 := record(columns, values)
		dataset = append(dataset, row1)

	}
	if err != io.EOF {
		dieOnError("Can't Next", err)
	}

	//切片转json
	jsonBytes, err := json.MarshalIndent(dataset, "", "   ")
	dieOnError("slice 转 json失败:", err)
	//fmt.Println(string(jsonBytes))
	return string(jsonBytes)
}

// 发生错误退出1
func dieOnError(msg string, err error) {
	if err != nil {
		log.Println(msg, err)
		//os.Exit(1)
	}
}

// func Header(columns []string) {

// }

// 一行记录加入 map
func record(columns []string, values []driver.Value) map[string]interface{} {
	mc := make(map[string]interface{}) //一行记录信息放入 map
	for i, c := range values {
		//fmt.Printf("\"%s\":%v,", columns[i], c)
		mc[columns[i]] = c
	}
	//fmt.Println(mc)

	return mc //返回一行记录的信息map
}

/* 查询表的主键方法
select * from user_cons_columns where table_name = 'ATOP'
and constraint_name = (
select constraint_name from user_constraints
where table_name = 'ATOP' and constraint_type = 'P')
order by position
*/

// func returnErr(err error) error {
// 	if err != nil {
// 		return err
// 	}
// 	return nil
// }

// // 7调用存储过程
// func callStoredProcedure() error {
// 	var (
// 		id  int
// 		msg string = strings.Repeat(" ", 2000) //先赋值内容
// 	)
// 	//执行存储过程,
// 	_, err := db.Exec(`BEGIN ora_test2_pro(:1, :2 ); END;`,
// 		id,
// 		sql.Out{Dest: &msg},
// 	)
// 	if err != nil {
// 		return err
// 	}
// 	//输出结果
// 	fmt.Println(msg)
// 	return nil
// }

// // 8.调用函数
// func callFunction() error {
// 	var (
// 		id  int
// 		msg string = strings.Repeat(" ", 2000) //先赋值内容
// 	)
// 	//执行存储过程,
// 	_, err := db.Exec(`BEGIN :1 := ora_test2_func(:2 ); END;`,
// 		sql.Out{Dest: &msg},
// 		id,
// 	)
// 	if err != nil {
// 		return err
// 	}
// 	//输出结果
// 	fmt.Println(msg)
// 	return nil
// }

3.2 doc.go

// gooracle project doc.go

/*
gooracle document
*/
package dboracle

4 dothing主要逻辑处理

4.1 包名dothing

// dothing project dothing.go
package dothing

import (
	"encoding/json"
	"fmt"
	"net/http"
	"rest2sql/config"
	"rest2sql/dboracle"
	"strings"
)

// 当前连接的数据库类型oracle
var (
	DBType string = config.Conf.DBType //数据库类型
	REST   string = config.Conf.REST   //支持的REST:GET,POST,PUT,DELETE
	SQL    string = config.Conf.SQL    //支持的SQL:SELECT,INSERT,UPDATE,DELETE
)

// 根据请求类型参数执行不同的操作 ////////////////////////////////////////////
func DoThing(w http.ResponseWriter, req map[string]interface{}) {
	w.Write([]byte("\n"))
	//请求类型 REST or SQL
	switch req["RESTorSQL"] {
	case "REST":
		//REST请求方法过滤
		sMethod := strings.ToUpper(req["Method"].(string))
		if !strings.Contains(REST, sMethod) {
			w.Write([]byte("!!!不准许的REST请求,检查配置文件config.json的REST项。"))
			return
		}
		//执行REST请求
		doREST(w, req)
	case "SQL":
		//SQL过滤
		resSQL := req["ResName"].(string)
		sqlToUpper := strings.ToUpper(resSQL)
		sql6 := sqlToUpper[:6]
		if !strings.Contains(SQL, sql6) {
			w.Write([]byte("!!!不准许的SQL请求,检查配置文件config.json的SQL项。"))
			return
		}
		//执行SQL
		doSQL(w, req)
	}
}

// 根据请求参数执行不同的操作
func doREST(w http.ResponseWriter, req map[string]interface{}) {
	//w.Write([]byte("\ndoREST()"))
	//资源名
	resName := req["ResName"].(string)

	// 检查是否有效资源
	if !isRes(resName) {
		w.Write([]byte("\nerror:无效资源" + resName))
		return
	} else {
		//w.Write([]byte("\nresName:" + resName))
	}

	// 查询条件检查
	var qry map[string]string = make(map[string]string)
	qry["ResName"] = resName
	qry["Where"] = req["Where"].(string)
	qry["OrderBy"] = req["OrderBy"].(string)

	// 有效资源,再看请求方法Get、Post、Put、Delete
	sMethod := strings.ToUpper(req["Method"].(string))
	switch sMethod {
	case "GET":
		getAll(w, qry)

	case "POST":
		var iData interface{}
		iData = req["Data"]
		postAdd(w, resName, iData)

	case "PUT":
		var iData interface{}
		iData = req["Data"]
		putUpdate(w, qry, iData)

	case "DELETE":
		deleteDel(w, qry)
	}
}

// 根据请求参数执行不同的操作 ////////////////////////////////////////////////
func doSQL(w http.ResponseWriter, req map[string]interface{}) {
	//w.Write([]byte("\ndoSQL()\n"))
	w.Write([]byte("\"Response\":"))
	//资源名sql语句
	resSQL := req["ResName"].(string)
	fmt.Println("SQL://", resSQL)
	sqlToUpper := strings.ToUpper(resSQL)
	sql6 := sqlToUpper[:6]
	var result string
	switch sql6 {
	case "SELECT":
		result = dboracle.SelectData(resSQL)
	case "INSERT":
		result = dboracle.InsertData(resSQL)
	case "UPDATE":
		result = dboracle.UpdateData(resSQL)
	case "DELETE":
		result = dboracle.DeleteData(resSQL)
	default:
		// 过滤sql ,只能执行 SELECT INSERT UPDATE DELETE
		result = "\"只能执行 SELECT INSERT UPDATE DELETE\""
	}
	fmt.Println("SQL://", resSQL)
	w.Write([]byte(result))
	w.Write([]byte("}"))
}

// 检查资源是否存在 /////////////////////////
func isRes(resName string) bool {
	resname := strings.ToUpper(resName)
	var selectSQL string
	switch DBType {
	case "oracle":
		{
			//表和视图
			selectSQL = "select object_name from user_objects where object_type in ('TABLE','VIEW') and object_name = '" + resname + "'"
		}
	case "":
		{
		}

	}

	//执行数据库查询
	result := dboracle.SelectData(selectSQL)
	//检查数据库是否有此表
	if strings.Contains(result, resname) {
		return true
	} else {
		return false
	}
}

// GET all //////////////////////////////////////////////////
func getAll(w http.ResponseWriter, qry map[string]string) {
	//w.Write([]byte("\nGET ALL"))
	w.Write([]byte("\"Response\":"))
	selectSQL := "select * from " + qry["ResName"]
	if len(qry["Where"]) > 0 {
		//fmt.Println("where ", qry["Where"])
		selectSQL += " where " + qry["Where"] + " and rownum < 52"
	}
	if len(qry["OrderBy"]) > 0 {
		//fmt.Println("OrderBy ", qry["OrderBy"])
		selectSQL += " order by " + qry["OrderBy"]
	}

	//执行 sql并返回 json 结果
	fmt.Println("REST://", selectSQL)
	result := dboracle.SelectData(selectSQL)
	w.Write([]byte(result))
	w.Write([]byte("}"))
}

// GET 1
func get1(w http.ResponseWriter) {
	w.Write([]byte("\nGET ONE"))
}

// POST /////////////////////////////////////////////////////////////////////////
func postAdd(w http.ResponseWriter, resName string, iData interface{}) {
	//curl "http://localhost:5217/rest/atop" --data "{\"p_id\":190,\"s_mc\":\"龙\"}" -X POST
	//w.Write([]byte("\nPOST ADD"))
	w.Write([]byte("\"Response\":{\"Data\":"))
	//fmt.Println("iData:", iData)
	str, err := json.MarshalIndent(iData, "", "   ")
	if err != nil {
		w.Write([]byte(err.Error()))
	}
	//fmt.Println("str", str)
	w.Write(str)
	w.Write([]byte(",\"Row\":"))

	var mapData map[string]interface{}
	err = json.Unmarshal(str, &mapData)
	if err != nil {
		w.Write([]byte(err.Error()))
	}
	//fmt.Println(mapData)
	var keys, values string
	for k, v := range mapData {
		//fmt.Printf("%s %v %T\n", k, v, v)
		keys += k + ","
		if typeofVar(v) == "string" {
			values += "'" + v.(string) + "',"
		}

		if typeofVar(v) == "float64" || typeofVar(v) == "int" {
			values += fmt.Sprintf("%f", v) + ","
		}

	}

	keys = strings.Trim(keys, ",")
	values = strings.Trim(values, ",")
	//fmt.Println(keys, values)
	insertSQL := "insert into " + resName + "(" + keys + ")" + " values( " + values + " )"
	//执行 insertSQL 并返回 json 结果
	fmt.Println("REST://:", insertSQL)
	result := dboracle.InsertData(insertSQL)
	w.Write([]byte(result))
	w.Write([]byte("}}"))

}

// 数据类型断言 ////////////////////////////////////////
func typeofVar(variable interface{}) string {
	switch variable.(type) {
	case string:
		return "string"
	case int:
		return "int"
	case float32:
		return "float32"
	case float64:
		return "float64"
	case bool:
		return "boolean"
	case []string:
		return "[]string"
	default:
		return "unknown"
	}
}

// PUT
func putUpdate(w http.ResponseWriter, qry map[string]string, iData interface{}) {
	//w.Write([]byte("\nPUT UPDATE"))
	w.Write([]byte("\"Response\":{\"Data\":"))

	str, err := json.MarshalIndent(iData, "", "   ")
	if err != nil {
		w.Write([]byte(err.Error()))
	}
	//fmt.Println("str", str)
	w.Write(str)
	w.Write([]byte(",\"Row\":"))

	var mapData map[string]interface{}
	err = json.Unmarshal(str, &mapData)
	if err != nil {
		w.Write([]byte(err.Error()))
	}
	//fmt.Println(mapData)
	var sets string
	for k, v := range mapData {
		//fmt.Printf("%s %v %T\n", k, v, v)
		sets += k + "="
		if typeofVar(v) == "string" {
			sets += "'" + v.(string) + "',"
		}

		if typeofVar(v) == "float64" || typeofVar(v) == "int" {
			sets += fmt.Sprintf("%f", v) + ","
		}

	}

	sets = strings.Trim(sets, ",")

	updateSQL := "update " + qry["ResName"] + "  set " + sets + " where " + qry["Where"]
	//执行 insertSQL 并返回 json 结果
	fmt.Println("REST://", updateSQL)
	result := dboracle.UpdateData(updateSQL)

	w.Write([]byte(result))
	w.Write([]byte("}}"))
}

// DELETE
func deleteDel(w http.ResponseWriter, qry map[string]string) {
	// 查询条件在URL/?后面
	//w.Write([]byte("\nDELETE DEL"))
	w.Write([]byte("\"Response\":"))
	deleteSQL := "delete from " + qry["ResName"]
	if len(qry["Where"]) > 0 {
		//fmt.Println("where ", qry["Where"])
		deleteSQL += " where " + qry["Where"] + " and rownum < 52"
	}

	//执行 sql并返回 json 结果
	fmt.Println("REST://", deleteSQL)
	result := dboracle.DeleteData(deleteSQL)
	w.Write([]byte(result))
	w.Write([]byte("}"))
}

4.2 doc.go

// Dothing project doc.go

/*
dothing document
*/
package dothing

5 部分运行效果图

5.1 启动REST2SQL.exe服务

编译后的rest2sql.exe为14M,启动后的窗口为:
在这里插入图片描述

5.2 浏览器操作演示效果

5.2.1 浏览器REST之GET请求

在这里插入图片描述

5.2.2 浏览器SQL之Select

在这里插入图片描述

5.2.3 执行RESR或SQL请求后,服务窗口返回操作日志

在这里插入图片描述

5.3 详细的操作说明参阅

【REST2SQL】01RDB关系型数据库REST初设计

需要运行程序的可以在评论区留言。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值