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

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初设计

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

相关推荐
云和恩墨2 小时前
云计算、AI与国产化浪潮下DBA职业之路风云变幻,如何谋破局启新途?
数据库·人工智能·云计算·dba
明月看潮生2 小时前
青少年编程与数学 02-007 PostgreSQL数据库应用 11课题、视图的操作
数据库·青少年编程·postgresql·编程与数学
阿猿收手吧!2 小时前
【Redis】Redis入门以及什么是分布式系统{Redis引入+分布式系统介绍}
数据库·redis·缓存
奈葵2 小时前
Spring Boot/MVC
java·数据库·spring boot
leegong231112 小时前
Oracle、PostgreSQL该学哪一个?
数据库·postgresql·oracle
中东大鹅2 小时前
MongoDB基本操作
数据库·分布式·mongodb·hbase
夜光小兔纸3 小时前
Oracle 普通用户连接hang住处理方法
运维·数据库·oracle
兩尛4 小时前
订单状态定时处理、来单提醒和客户催单(day10)
java·前端·数据库
web2u4 小时前
MySQL 中如何进行 SQL 调优?
java·数据库·后端·sql·mysql·缓存
Elastic 中国社区官方博客5 小时前
使用 Elasticsearch 导航检索增强生成图表
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索