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初设计
需要运行程序的可以在评论区留言。