add: bql exec impl and query api

This commit is contained in:
BaoXuebin 2021-11-21 22:37:13 +08:00
parent 34adc87098
commit 5c05aaa102
13 changed files with 176 additions and 54 deletions

View File

@ -1,6 +1,6 @@
{ {
"title": "我的账本", "title": "我的账本",
"dataPath": "D:\\beancount", "dataPath": "E:\\beancount",
"operatingCurrency": "CNY", "operatingCurrency": "CNY",
"startDate": "1970-01-01", "startDate": "1970-01-01",
"isBak": true "isBak": true

7
go.mod
View File

@ -2,9 +2,10 @@ module github.com/beancount-gs
go 1.17 go 1.17
require github.com/gin-gonic/gin v1.7.4
require ( require (
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.7.4 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.9.0 // indirect github.com/go-playground/validator/v10 v10.9.0 // indirect
@ -15,8 +16,8 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ugorji/go/codec v1.2.6 // indirect github.com/ugorji/go/codec v1.2.6 // indirect
golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8 // indirect golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c // indirect golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect

4
go.sum
View File

@ -62,6 +62,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8 h1:5QRxNnVsaJP6NAse0UdkRgL3zHMvCRRkrDVLNdNpdy4= golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8 h1:5QRxNnVsaJP6NAse0UdkRgL3zHMvCRRkrDVLNdNpdy4=
golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -73,6 +75,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c h1:DHcbWVXeY+0Y8HHKR+rbLwnoh2F4tNCY7rTiHJ30RmA= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c h1:DHcbWVXeY+0Y8HHKR+rbLwnoh2F4tNCY7rTiHJ30RmA=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=

57
script/bql.go Normal file
View File

@ -0,0 +1,57 @@
package script
import (
"fmt"
"os/exec"
"reflect"
"strings"
)
type QueryParams struct {
Year int `bql:"year ="`
Month int `bql:"month ="`
AccountType string `bql:"account ~"`
}
func BQLQuery(ledgerConfig *Config, queryParams QueryParams, queryResult interface{}) error {
bql := "SELECT '\\', id, '\\', date, '\\', payee, '\\', narration, '\\', account, '\\', position, '\\', tags, '\\' WHERE"
queryParamsType := reflect.TypeOf(queryParams)
queryParamsValue := reflect.ValueOf(queryParams)
for i := 0; i < queryParamsValue.NumField(); i++ {
typeField := queryParamsType.Field(i)
valueField := queryParamsValue.Field(i)
switch valueField.Kind() {
case reflect.String:
val := valueField.String()
if val != "" {
bql = fmt.Sprintf("%s %s '%s' AND", bql, typeField.Tag.Get("bql"), val)
}
case reflect.Int:
val := valueField.Int()
if val != 0 {
bql = fmt.Sprintf("%s %s %d AND", bql, typeField.Tag.Get("bql"), val)
}
}
}
bql = strings.TrimRight(bql, " AND")
output, err := queryByBQL(ledgerConfig, bql)
if err != nil {
return err
}
fmt.Println(output)
//panic("Unsupported result type")
return nil
}
func queryByBQL(ledgerConfig *Config, bql string) (string, error) {
beanFilePath := ledgerConfig.DataPath + "/index.bean"
LogInfo(ledgerConfig.Mail, bql)
cmd := exec.Command("bean-query", beanFilePath, bql)
output, err := cmd.Output()
if err != nil {
return "", err
}
return string(output), nil
}

View File

@ -73,10 +73,10 @@ func LoadServerConfig() error {
} }
err = json.Unmarshal(fileContent, &serverConfig) err = json.Unmarshal(fileContent, &serverConfig)
if err != nil { if err != nil {
LogError("Failed unmarshall config file (/config/config.json)") LogSystemError("Failed unmarshall config file (/config/config.json)")
return err return err
} }
LogInfo("Success load config file (/config/config.json)") LogSystemInfo("Success load config file (/config/config.json)")
// load white list // load white list
fileContent, err = ReadFile("./config/white_list.json") fileContent, err = ReadFile("./config/white_list.json")
if err != nil { if err != nil {
@ -84,10 +84,10 @@ func LoadServerConfig() error {
} }
err = json.Unmarshal(fileContent, &whiteList) err = json.Unmarshal(fileContent, &whiteList)
if err != nil { if err != nil {
LogError("Failed unmarshal whitelist file (/config/white_list.json)") LogSystemError("Failed unmarshal whitelist file (/config/white_list.json)")
return err return err
} }
LogInfo("Success load whitelist file (/config/white_list.json)") LogSystemInfo("Success load whitelist file (/config/white_list.json)")
return nil return nil
} }
@ -99,10 +99,10 @@ func LoadLedgerConfigMap() error {
} }
err = json.Unmarshal(fileContent, &ledgerConfigMap) err = json.Unmarshal(fileContent, &ledgerConfigMap)
if err != nil { if err != nil {
LogError("Failed unmarshal config file (" + path + ")") LogSystemError("Failed unmarshal config file (" + path + ")")
return err return err
} }
LogInfo("Success load ledger_config file (" + path + ")") LogSystemInfo("Success load ledger_config file (" + path + ")")
return nil return nil
} }
@ -110,11 +110,11 @@ func WriteLedgerConfigMap(newLedgerConfigMap ConfigMap) error {
path := GetServerLedgerConfigFilePath() path := GetServerLedgerConfigFilePath()
mapBytes, err := json.Marshal(ledgerConfigMap) mapBytes, err := json.Marshal(ledgerConfigMap)
if err != nil { if err != nil {
LogError("Failed marshal ConfigMap") LogSystemError("Failed marshal ConfigMap")
return err return err
} }
err = WriteFile(path, string(mapBytes)) err = WriteFile(path, string(mapBytes))
ledgerConfigMap = newLedgerConfigMap ledgerConfigMap = newLedgerConfigMap
LogInfo("Success write ledger_config file (" + path + ")") LogSystemInfo("Success write ledger_config file (" + path + ")")
return err return err
} }

View File

@ -19,40 +19,40 @@ func FileIfExist(filePath string) bool {
func ReadFile(filePath string) ([]byte, error) { func ReadFile(filePath string) ([]byte, error) {
content, err := ioutil.ReadFile(filePath) content, err := ioutil.ReadFile(filePath)
if nil != err { if nil != err {
LogError("Failed to read file (" + filePath + ")") LogSystemError("Failed to read file (" + filePath + ")")
return content, err return content, err
} }
LogInfo("Success read file (" + filePath + ")") LogSystemInfo("Success read file (" + filePath + ")")
return content, nil return content, nil
} }
func WriteFile(filePath string, content string) error { func WriteFile(filePath string, content string) error {
err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm) err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm)
if err != nil { if err != nil {
LogError("Failed to write file (" + filePath + ")") LogSystemError("Failed to write file (" + filePath + ")")
return err return err
} }
LogInfo("Success write file (" + filePath + ")") LogSystemInfo("Success write file (" + filePath + ")")
return nil return nil
} }
func CreateFile(filePath string) error { func CreateFile(filePath string) error {
f, err := os.Create(filePath) f, err := os.Create(filePath)
if nil != err { if nil != err {
LogError(filePath + " create failed") LogSystemError(filePath + " create failed")
return err return err
} }
defer f.Close() defer f.Close()
LogInfo("Success create file " + filePath) LogSystemInfo("Success create file " + filePath)
return nil return nil
} }
func MkDir(dirPath string) error { func MkDir(dirPath string) error {
err := os.MkdirAll(dirPath, os.ModePerm) err := os.MkdirAll(dirPath, os.ModePerm)
if nil != err { if nil != err {
LogError("Failed mkdir " + dirPath) LogSystemError("Failed mkdir " + dirPath)
return err return err
} }
LogInfo("Success mkdir " + dirPath) LogSystemInfo("Success mkdir " + dirPath)
return nil return nil
} }

View File

@ -5,10 +5,18 @@ import (
"time" "time"
) )
func LogInfo(message string) { func LogInfo(ledgerName string, message string) {
fmt.Printf("[Info] [%s] System: %s\n", time.Now().Format("2006-01-02 15:04:05"), message) fmt.Printf("[Info] [%s] [%s]: %s\n", time.Now().Format("2006-01-02 15:04:05"), ledgerName, message)
} }
func LogError(message string) { func LogSystemInfo(message string) {
fmt.Printf("[Error] [%s] System: %s\n", time.Now().Format("2006-01-02 15:04:05"), message) LogInfo("System", message)
}
func LogError(ledgerName string, message string) {
fmt.Printf("[Error] [%s] [%s]: %s\n", time.Now().Format("2006-01-02 15:04:05"), ledgerName, message)
}
func LogSystemError(message string) {
LogError("System", message)
} }

View File

@ -46,6 +46,10 @@ func RegisterRouter(router *gin.Engine) {
{ {
// need authorized // need authorized
authorized.GET("/stats/months", service.MonthsList) authorized.GET("/stats/months", service.MonthsList)
authorized.GET("/transactions", service.QueryTransactions)
// 兼容旧版本
authorized.GET("/entry", service.QueryTransactions)
} }
} }
@ -53,19 +57,19 @@ func main() {
// 读取配置文件 // 读取配置文件
err := script.LoadServerConfig() err := script.LoadServerConfig()
if err != nil { if err != nil {
script.LogError("Failed to load server config, " + err.Error()) script.LogSystemError("Failed to load server config, " + err.Error())
return return
} }
// 初始化账本文件结构 // 初始化账本文件结构
err = InitServerFiles() err = InitServerFiles()
if err != nil { if err != nil {
script.LogError("Failed to init server files, " + err.Error()) script.LogSystemError("Failed to init server files, " + err.Error())
return return
} }
// 加载缓存 // 加载缓存
err = LoadServerCache() err = LoadServerCache()
if err != nil { if err != nil {
script.LogError("Failed to load server cache, " + err.Error()) script.LogSystemError("Failed to load server cache, " + err.Error())
return return
} }
router := gin.Default() router := gin.Default()
@ -75,6 +79,6 @@ func main() {
var port = ":3001" var port = ":3001"
err = router.Run(port) err = router.Run(port)
if err != nil { if err != nil {
script.LogError("Failed to start server, " + err.Error()) script.LogSystemError("Failed to start server, " + err.Error())
} }
} }

View File

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
) )
func OK(c *gin.Context, data string) { func OK(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, gin.H{"code": 200, "message": "ok", "data": data}) c.JSON(http.StatusOK, gin.H{"code": 200, "message": "ok", "data": data})
} }

View File

@ -4,20 +4,28 @@ import (
"github.com/beancount-gs/script" "github.com/beancount-gs/script"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"os/exec" "os/exec"
"strings"
) )
func MonthsList(c *gin.Context) { func MonthsList(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c) months := make([]string, 0)
ledgerConfig := script.GetLedgerConfigFromContext(c)
beanFilePath := ledgerConfig.DataPath + "/index.bean" beanFilePath := ledgerConfig.DataPath + "/index.bean"
bql := "SELECT distinct year(date), month(date)" bql := "SELECT distinct year(date), month(date)"
cmd := exec.Command("bean-query " + beanFilePath + " \"" + bql + "\"") cmd := exec.Command("bean-query", beanFilePath, bql)
output, err := cmd.CombinedOutput() output, err := cmd.Output()
if err != nil { if err != nil {
InternalError(c, "Failed to exec bql") InternalError(c, "Failed to exec bql")
return return
} }
execResult := string(output)
script.LogInfo(string(output)) months = make([]string, 0)
OK(c, "") for _, line := range strings.Split(execResult, "\n")[2:] {
if line != "" {
yearMonth := strings.Fields(line)
months = append(months, yearMonth[0]+"-"+yearMonth[1])
}
}
OK(c, months)
} }

View File

@ -1 +0,0 @@
package service

49
service/transactions.go Normal file
View File

@ -0,0 +1,49 @@
package service
import (
"github.com/beancount-gs/script"
"github.com/gin-gonic/gin"
"strconv"
)
type Transactions struct {
Id string `bql:"id"`
Date string `bql:"date"`
payee string
narration string
account string
position string
tags string
}
func getQueryModel(c *gin.Context) script.QueryParams {
var queryParams script.QueryParams
if c.Query("year") != "" {
val, err := strconv.Atoi(c.Query("year"))
if err == nil {
queryParams.Year = val
}
}
if c.Query("month") != "" {
val, err := strconv.Atoi(c.Query("month"))
if err == nil {
queryParams.Month = val
}
}
if c.Query("type") != "" {
queryParams.AccountType = c.Query("type")
}
return queryParams
}
func QueryTransactions(c *gin.Context) {
ledgerConfig := script.GetLedgerConfigFromContext(c)
queryParams := getQueryModel(c)
transactions := make([]Transactions, 0)
err := script.BQLQuery(ledgerConfig, queryParams, transactions)
if err != nil {
InternalError(c, err.Error())
return
}
OK(c, transactions)
}

View File

@ -1,25 +1,17 @@
package tests package tests
import "fmt" import (
"fmt"
"reflect"
)
type Student struct { type Student struct {
name string Name string
} }
func say(student Student) *Student { func Test(i interface{}) {
return &student t := reflect.TypeOf(i)
} k := t.Kind()
v := reflect.ValueOf(i)
func say2(student Student) Student { fmt.Printf("type: %s, kind: %s, value: %s", t, k, v)
return student
}
func Test() {
str := "Hello~"
fmt.Println(str)
fmt.Println(&str)
student := Student{name: "Bao"}
fmt.Println(student)
fmt.Println(&student)
} }