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

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

相关推荐
仍然.几秒前
MYSQL--- 聚合查询,分组查询和联合查询
数据库
一 乐5 分钟前
校园二手交易|基于springboot + vue校园二手交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
啦啦啦_99997 分钟前
Redis-0-业务逻辑
数据库·redis·缓存
自不量力的A同学39 分钟前
Redisson 4.2.0 发布,官方推荐的 Redis 客户端
数据库·redis·缓存
Exquisite.41 分钟前
Mysql
数据库·mysql
全栈前端老曹1 小时前
【MongoDB】深入研究副本集与高可用性——Replica Set 架构、故障转移、读写分离
前端·javascript·数据库·mongodb·架构·nosql·副本集
R1nG8631 小时前
CANN资源泄漏检测工具源码深度解读 实战设备内存泄漏排查
数据库·算法·cann
阿钱真强道1 小时前
12 JetLinks MQTT直连设备事件上报实战(继电器场景)
linux·服务器·网络·数据库·网络协议
逍遥德2 小时前
Sring事务详解之02.如何使用编程式事务?
java·服务器·数据库·后端·sql·spring
笨蛋不要掉眼泪2 小时前
Redis哨兵机制全解析:原理、配置与实战故障转移演示
java·数据库·redis·缓存·bootstrap