diff --git a/app/app.json b/app/app.json index 49b4a6b8..8006cebc 100644 --- a/app/app.json +++ b/app/app.json @@ -7,9 +7,9 @@ "oss": { "comment": "https://www.alibabacloud.com/help/zh/doc-detail/100624.htm", "endpoint": "oss-cn-hangzhou.aliyuncs.com", - "id": "LTAI5tLC5cnKqF2eB4L7De3S", - "secret": "OkD17XjvjNQoiLgRyyjIsTnviULr0w", - "roleArn": "RamOssTest", + "id": "LTAI5tHXfWAMpcsobfqfeXsm", + "secret": "4NeGNw97nFrfmgBWZTlFE0XtYQdMQO", + "roleArn": "acs:ram::31524094:role/ramosstest", "sessionName": "SessionTest" }, "cos": {}, diff --git a/cmd/start.go b/cmd/start.go index e89c2b6e..14be087f 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -10,6 +10,7 @@ import ( "github.com/yaoapp/gou" "github.com/yaoapp/xiang/config" "github.com/yaoapp/xiang/global" + "github.com/yaoapp/xiang/share" ) var startCmd = &cobra.Command{ @@ -34,10 +35,10 @@ var startCmd = &cobra.Command{ // 打印应用目录信息 fmt.Printf(color.WhiteString("\n---------------------------------")) - fmt.Printf(color.GreenString("\n应用名称: %s v%s", global.App.Name, global.App.Version)) + fmt.Printf(color.GreenString("\n应用名称: %s v%s", share.App.Name, share.App.Version)) fmt.Printf(color.GreenString("\n应用根目录: %s", config.Conf.Root)) fmt.Printf(color.GreenString("\n数据存储目录: %s", config.Conf.RootData)) - fmt.Printf(color.GreenString("\n数据存储引擎: %s", global.App.Storage.Default)) + fmt.Printf(color.GreenString("\n数据存储引擎: %s", share.App.Storage.Default)) fmt.Printf(color.WhiteString("\n---------------------------------\n\n")) fmt.Printf(color.GreenString("\n已注册API")) diff --git a/global/app.go b/global/app.go deleted file mode 100644 index dbface95..00000000 --- a/global/app.go +++ /dev/null @@ -1,81 +0,0 @@ -package global - -import ( - jsoniter "github.com/json-iterator/go" - "github.com/yaoapp/kun/exception" - "github.com/yaoapp/xiang/data" - "github.com/yaoapp/xiang/xfs" -) - -// App 应用信息 -var App AppInfo - -// AppInfo 应用信息 -type AppInfo struct { - Name string `json:"name,omitempty"` - Short string `json:"short,omitempty"` - Version string `json:"version,omitempty"` - Description string `json:"description,omitempty"` - Icons map[string]string `json:"icons,omitempty"` - Storage AppStorage `json:"storage,omitempty"` - Option map[string]interface{} `json:"option,omitempty"` -} - -// AppStorage 应用存储 -type AppStorage struct { - Default string `json:"default"` - Buckets map[string]string `json:"buckets,omitempty"` - S3 map[string]interface{} `json:"s3,omitempty"` - OSS map[string]interface{} `json:"oss,omitempty"` - COS map[string]interface{} `json:"cos,omitempty"` -} - -// LoadAppInfo 读取应用信息 -func LoadAppInfo(root string) { - info := defaultAppInfo() - fs := xfs.New(root) - if fs.MustExists("/app.json") { - err := jsoniter.Unmarshal(fs.MustReadFile("/app.json"), &info) - if err != nil { - exception.New("解析应用失败 %s", 500, err).Throw() - } - } - - if fs.MustExists("/xiang/icons/icon.icns") { - info.Icons["icns"] = xfs.Encode(fs.MustReadFile("/xiang/icons/icon.icns")) - } - - if fs.MustExists("/xiang/icons/icon.ico") { - info.Icons["ico"] = xfs.Encode(fs.MustReadFile("/xiang/icons/icon.ico")) - } - - if fs.MustExists("/xiang/icons/icon.png") { - info.Icons["png"] = xfs.Encode(fs.MustReadFile("/xiang/icons/icon.png")) - } - - App = info -} - -// defaultAppInfo 读取默认应用信息 -func defaultAppInfo() AppInfo { - info := AppInfo{} - err := jsoniter.Unmarshal(data.MustAsset("xiang/data/app.json"), &info) - if err != nil { - exception.New("解析默认应用失败 %s", 500, err).Throw() - } - - info.Version = VERSION - info.Icons["icns"] = xfs.Encode(data.MustAsset("xiang/data/icons/icon.icns")) - info.Icons["ico"] = xfs.Encode(data.MustAsset("xiang/data/icons/icon.ico")) - info.Icons["png"] = xfs.Encode(data.MustAsset("xiang/data/icons/icon.png")) - - return info -} - -// Public 输出公共信息 -func (app AppInfo) Public() AppInfo { - app.Storage.COS = nil - app.Storage.OSS = nil - app.Storage.S3 = nil - return app -} diff --git a/global/load.go b/global/load.go index ed5b57d8..572ba33c 100644 --- a/global/load.go +++ b/global/load.go @@ -9,11 +9,14 @@ import ( "path/filepath" "strings" + jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou" "github.com/yaoapp/kun/exception" "github.com/yaoapp/xiang/config" "github.com/yaoapp/xiang/data" + "github.com/yaoapp/xiang/share" "github.com/yaoapp/xiang/table" + "github.com/yaoapp/xiang/xfs" "github.com/yaoapp/xun/capsule" ) @@ -63,6 +66,47 @@ func Load(cfg config.Config) { Conf = cfg } +// LoadAppInfo 读取应用信息 +func LoadAppInfo(root string) { + info := defaultAppInfo() + fs := xfs.New(root) + if fs.MustExists("/app.json") { + err := jsoniter.Unmarshal(fs.MustReadFile("/app.json"), &info) + if err != nil { + exception.New("解析应用失败 %s", 500, err).Throw() + } + } + + if fs.MustExists("/xiang/icons/icon.icns") { + info.Icons["icns"] = xfs.Encode(fs.MustReadFile("/xiang/icons/icon.icns")) + } + + if fs.MustExists("/xiang/icons/icon.ico") { + info.Icons["ico"] = xfs.Encode(fs.MustReadFile("/xiang/icons/icon.ico")) + } + + if fs.MustExists("/xiang/icons/icon.png") { + info.Icons["png"] = xfs.Encode(fs.MustReadFile("/xiang/icons/icon.png")) + } + + share.App = info +} + +// defaultAppInfo 读取默认应用信息 +func defaultAppInfo() share.AppInfo { + info := share.AppInfo{} + err := jsoniter.Unmarshal(data.MustAsset("xiang/data/app.json"), &info) + if err != nil { + exception.New("解析默认应用失败 %s", 500, err).Throw() + } + + info.Icons["icns"] = xfs.Encode(data.MustAsset("xiang/data/icons/icon.icns")) + info.Icons["ico"] = xfs.Encode(data.MustAsset("xiang/data/icons/icon.ico")) + info.Icons["png"] = xfs.Encode(data.MustAsset("xiang/data/icons/icon.png")) + + return info +} + // Reload 根据配置重新加载 API, FLow, Model, Plugin func Reload(cfg config.Config) { gou.APIs = map[string]*gou.API{} diff --git a/global/process.go b/global/process.go index 1059e413..e95dc3e7 100644 --- a/global/process.go +++ b/global/process.go @@ -2,6 +2,7 @@ package global import ( "github.com/yaoapp/gou" + "github.com/yaoapp/xiang/share" "github.com/yaoapp/xiang/xfs" ) @@ -28,13 +29,13 @@ func processPing(process *gou.Process) interface{} { // processInspect 返回系统信息 func processInspect(process *gou.Process) interface{} { - App.Icons["favicon"] = "/api/xiang/favicon.ico" - return App.Public() + share.App.Icons["favicon"] = "/api/xiang/favicon.ico" + return share.App.Public() } // processFavicon 运行模型 MustCreate func processFavicon(process *gou.Process) interface{} { - return xfs.DecodeString(App.Icons["png"]) + return xfs.DecodeString(share.App.Icons["png"]) } // processFileContent 返回文件内容 diff --git a/go.mod b/go.mod index 5a451f5f..ae95994f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/yaoapp/xiang go 1.16 require ( + github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286 // indirect + github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible // indirect github.com/caarlos0/env/v6 v6.7.1 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect @@ -34,6 +36,7 @@ require ( golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/net v0.0.0-20211006190231-62292e806868 // indirect golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect google.golang.org/genproto v0.0.0-20211005153810-c76a74d43a8e // indirect google.golang.org/grpc v1.41.0 // indirect ) diff --git a/go.sum b/go.sum index 3f42e16d..4bd4bcf9 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,10 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286 h1:+Ah33S/QRnOFp4dM6VV2gYDbFu3Cj4HrRy1VpOhBzm8= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286/go.mod h1:9CMdKNL3ynIGPpfTcdwTvIm8SGuAZYYC4jFVSSvE1YQ= +github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible h1:D3gwOr9qUUmyyBRDbpnATqu+EkqqmigFd3Od6xO1QUU= +github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -119,6 +123,7 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= @@ -227,12 +232,15 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= @@ -640,6 +648,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -825,6 +835,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= diff --git a/share/app.go b/share/app.go new file mode 100644 index 00000000..69a50772 --- /dev/null +++ b/share/app.go @@ -0,0 +1,41 @@ +package share + +// App 应用信息 +var App AppInfo + +// AppInfo 应用信息 +type AppInfo struct { + Name string `json:"name,omitempty"` + Short string `json:"short,omitempty"` + Version string `json:"version,omitempty"` + Description string `json:"description,omitempty"` + Icons map[string]string `json:"icons,omitempty"` + Storage AppStorage `json:"storage,omitempty"` + Option map[string]interface{} `json:"option,omitempty"` +} + +// AppStorage 应用存储 +type AppStorage struct { + Default string `json:"default"` + Buckets map[string]string `json:"buckets,omitempty"` + S3 map[string]interface{} `json:"s3,omitempty"` + OSS *AppStorageOSS `json:"oss,omitempty"` + COS map[string]interface{} `json:"cos,omitempty"` +} + +// AppStorageOSS 阿里云存储 +type AppStorageOSS struct { + Endpoint string `json:"endpoint,omitempty"` + ID string `json:"id,omitempty"` + Secret string `json:"secret,omitempty"` + RoleArn string `json:"roleArn,omitempty"` + SessionName string `json:"sessionName,omitempty"` +} + +// Public 输出公共信息 +func (app AppInfo) Public() AppInfo { + app.Storage.COS = nil + app.Storage.OSS = nil + app.Storage.S3 = nil + return app +} diff --git a/xfs/process.go b/xfs/process.go index 429bffa6..ea4a5efb 100644 --- a/xfs/process.go +++ b/xfs/process.go @@ -9,9 +9,12 @@ import ( "strings" "time" + "github.com/aliyun/alibaba-cloud-sdk-go/services/sts" "github.com/yaoapp/gou" + "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/maps" + "github.com/yaoapp/xiang/share" "github.com/yaoapp/xun" ) @@ -76,7 +79,42 @@ func processReadFile(process *gou.Process) interface{} { // processGetToken 上传文件到腾讯云对象存储 COS func processGetToken(process *gou.Process) interface{} { - return nil + process.ValidateArgNums(2) + name := process.ArgsString(0, "oss") + // bucket := process.ArgsString(1) + if name != "oss" { + exception.New("暂时支持 oss 存储", 400).Throw() + } + + if share.App.Storage.OSS == nil { + exception.New("未配置 OSS 存储", 400).Throw() + } + + app := share.App.Storage.OSS + id := strings.Split(app.Endpoint, ".")[0] + client, err := sts.NewClientWithAccessKey(id, app.ID, app.Secret) + if err != nil { + exception.New("配置错误 %s", 400, err.Error()).Throw() + } + + //构建请求对象。 + request := sts.CreateAssumeRoleRequest() + request.Scheme = "https" + request.Domain = "sts.aliyuncs.com" + + //设置参数。关于参数含义和设置方法,请参见API参考。 + request.RoleArn = app.RoleArn + request.RoleSessionName = app.SessionName + + //发起请求,并得到响应。 + response, err := client.AssumeRole(request) + if err != nil { + exception.New("配置错误 %s", 400, err.Error()).Throw() + } + + res := any.Of(response.Credentials).Map() + res.Set("Endpoint", app.Endpoint) + return res } // processGetURL 返回文件CDN地址 diff --git a/xfs/process_test.go b/xfs/process_test.go new file mode 100644 index 00000000..55f6b434 --- /dev/null +++ b/xfs/process_test.go @@ -0,0 +1,62 @@ +package xfs + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/any" + "github.com/yaoapp/xiang/share" +) + +func init() { + share.App = share.AppInfo{ + Storage: share.AppStorage{ + Default: "oss", + OSS: &share.AppStorageOSS{ + Endpoint: "oss-cn-hangzhou.aliyuncs.com", + ID: "LTAI5tHXfWAMpcsobfqfeXsm", + Secret: "4NeGNw97nFrfmgBWZTlFE0XtYQdMQO", + RoleArn: "acs:ram::31524094:role/ramosstest", + SessionName: "SessionTest", + }, + }, + } +} +func TestProcessGetToken(t *testing.T) { + + args := []interface{}{ + "oss", + "hh", + } + process := gou.NewProcess("xiang.fs.GetToken", args...) + response := processGetToken(process) + assert.NotNil(t, response) + res := any.Of(response).Map() + assert.True(t, res.Has("AccessKeyId")) + assert.True(t, res.Has("AccessKeySecret")) + assert.True(t, res.Has("Expiration")) + assert.True(t, res.Has("SecurityToken")) + assert.True(t, res.Has("Endpoint")) + + // 使用token + client, err := oss.New( + res.Get("Endpoint").(string), + res.Get("AccessKeyId").(string), + res.Get("AccessKeySecret").(string), + oss.SecurityToken(res.Get("SecurityToken").(string)), + ) + assert.Nil(t, err) + + bucket, err := client.Bucket("image-appcook") + assert.Nil(t, err) + + // 上传字符串。 + now := fmt.Sprintf("%d", time.Now().UnixNano()) + err = bucket.PutObject("image-appcook/xiang/unit-test.txt", strings.NewReader(now)) + assert.Nil(t, err) +} diff --git a/xiang/apis/storage.http.json b/xiang/apis/storage.http.json index b309540e..8f7a8ad4 100644 --- a/xiang/apis/storage.http.json +++ b/xiang/apis/storage.http.json @@ -31,7 +31,7 @@ "path": "/token", "method": "GET", "guard": "-", - "process": "xiang.fs.processGetToken", + "process": "xiang.fs.GetToken", "in": ["$query.name", "$query.bucket"], "out": { "status": 200,