Compare commits
8 commits
main
...
v0.10.3-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80e15d5be7 | ||
|
|
868ca0132e | ||
|
|
5edc9e809f | ||
|
|
9649a1d234 | ||
|
|
fbd3fc4c3c | ||
|
|
b96e519981 | ||
|
|
d7d3d832cb | ||
|
|
3511a4bd73 |
9 changed files with 484 additions and 12 deletions
3
.github/workflows/unit-test.yml
vendored
3
.github/workflows/unit-test.yml
vendored
|
|
@ -85,7 +85,7 @@ jobs:
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: ${{ env.REPO_GOU }}
|
repository: ${{ env.REPO_GOU }}
|
||||||
ref: dc3d98411f61e21cf775e4c82d92d5ac2acfc03c
|
ref: v0.10.2-locked
|
||||||
path: gou
|
path: gou
|
||||||
|
|
||||||
- name: Checkout V8Go
|
- name: Checkout V8Go
|
||||||
|
|
@ -99,6 +99,7 @@ jobs:
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: yaoapp/yao-dev-app
|
repository: yaoapp/yao-dev-app
|
||||||
|
ref: fef08943afc6ff91ed7a9a44559118ad40074e6b
|
||||||
path: app
|
path: app
|
||||||
|
|
||||||
- name: Move Kun, Xun, Gou, V8Go
|
- name: Move Kun, Xun, Gou, V8Go
|
||||||
|
|
|
||||||
18
Makefile
18
Makefile
|
|
@ -171,10 +171,10 @@ artifacts-linux: clean
|
||||||
export NODE_ENV=production
|
export NODE_ENV=production
|
||||||
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
||||||
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
||||||
cd ../xgen-v1.0 && pnpm install && pnpm run build
|
cd ../xgen-v1.0 && pnpm install --no-frozen-lockfile && pnpm run build
|
||||||
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
cd ../xgen-v1.0/packages/setup && pnpm install && pnpm run build
|
cd ../xgen-v1.0/packages/setup && pnpm install --no-frozen-lockfile && pnpm run build
|
||||||
|
|
||||||
# Init Application
|
# Init Application
|
||||||
cd ../yao-init rm -rf .git
|
cd ../yao-init rm -rf .git
|
||||||
|
|
@ -197,6 +197,7 @@ artifacts-linux: clean
|
||||||
sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}-${NOW}\"/g" share/const.go
|
sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}-${NOW}\"/g" share/const.go
|
||||||
|
|
||||||
# Making artifacts
|
# Making artifacts
|
||||||
|
go mod tidy
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
CGO_ENABLED=1 CGO_LDFLAGS="-static" GOOS=linux GOARCH=amd64 go build -v -o dist/yao-${VERSION}-linux-amd64
|
CGO_ENABLED=1 CGO_LDFLAGS="-static" GOOS=linux GOARCH=amd64 go build -v -o dist/yao-${VERSION}-linux-amd64
|
||||||
CGO_ENABLED=1 CGO_LDFLAGS="-static" GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ go build -v -o dist/yao-${VERSION}-linux-arm64
|
CGO_ENABLED=1 CGO_LDFLAGS="-static" GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ go build -v -o dist/yao-${VERSION}-linux-arm64
|
||||||
|
|
@ -221,10 +222,10 @@ artifacts-macos: clean
|
||||||
export NODE_ENV=production
|
export NODE_ENV=production
|
||||||
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
rm -f ../xgen-v1.0/pnpm-lock.yaml
|
||||||
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
echo "BASE=__yao_admin_root" > ../xgen-v1.0/packages/xgen/.env
|
||||||
cd ../xgen-v1.0 && pnpm install && pnpm run build
|
cd ../xgen-v1.0 && pnpm install --no-frozen-lockfile && pnpm run build
|
||||||
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
cd ../xgen-v1.0/packages/setup && pnpm install && pnpm run build
|
cd ../xgen-v1.0/packages/setup && pnpm install --no-frozen-lockfile && pnpm run build
|
||||||
|
|
||||||
# Init Application
|
# Init Application
|
||||||
cd ../yao-init rm -rf .git
|
cd ../yao-init rm -rf .git
|
||||||
|
|
@ -247,6 +248,7 @@ artifacts-macos: clean
|
||||||
sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}-${NOW}\"/g" share/const.go
|
sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}-${NOW}\"/g" share/const.go
|
||||||
|
|
||||||
# Making artifacts
|
# Making artifacts
|
||||||
|
go mod tidy
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -v -o dist/yao-${VERSION}-darwin-amd64
|
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -v -o dist/yao-${VERSION}-darwin-amd64
|
||||||
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -v -o dist/yao-${VERSION}-darwin-arm64
|
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -v -o dist/yao-${VERSION}-darwin-arm64
|
||||||
|
|
@ -299,10 +301,10 @@ release: clean
|
||||||
git clone https://github.com/YaoApp/xgen.git .tmp/xgen/v1.0
|
git clone https://github.com/YaoApp/xgen.git .tmp/xgen/v1.0
|
||||||
# cd .tmp/xgen/v1.0 && git checkout 5002c3fded585aaa69a4366135b415ea3234964e
|
# cd .tmp/xgen/v1.0 && git checkout 5002c3fded585aaa69a4366135b415ea3234964e
|
||||||
echo "BASE=__yao_admin_root" > .tmp/xgen/v1.0/packages/xgen/.env
|
echo "BASE=__yao_admin_root" > .tmp/xgen/v1.0/packages/xgen/.env
|
||||||
cd .tmp/xgen/v1.0 && pnpm install && pnpm run build
|
cd .tmp/xgen/v1.0 && pnpm install --no-frozen-lockfile && pnpm run build
|
||||||
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
cd .tmp/xgen/v1.0/packages/setup && pnpm install && pnpm run build
|
cd .tmp/xgen/v1.0/packages/setup && pnpm install --no-frozen-lockfile && pnpm run build
|
||||||
|
|
||||||
|
|
||||||
# Checkout init
|
# Checkout init
|
||||||
|
|
@ -353,10 +355,10 @@ linux-release: clean
|
||||||
git clone https://github.com/YaoApp/xgen.git .tmp/xgen/v1.0
|
git clone https://github.com/YaoApp/xgen.git .tmp/xgen/v1.0
|
||||||
rm -f .tmp/xgen/v1.0/pnpm-lock.yaml
|
rm -f .tmp/xgen/v1.0/pnpm-lock.yaml
|
||||||
echo "BASE=__yao_admin_root" > .tmp/xgen/v1.0/packages/xgen/.env
|
echo "BASE=__yao_admin_root" > .tmp/xgen/v1.0/packages/xgen/.env
|
||||||
cd .tmp/xgen/v1.0 && pnpm install && pnpm run build
|
cd .tmp/xgen/v1.0 && pnpm install --no-frozen-lockfile && pnpm run build
|
||||||
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
cd .tmp/xgen/v1.0/packages/setup && pnpm install && pnpm run build
|
cd .tmp/xgen/v1.0/packages/setup && pnpm install --no-frozen-lockfile && pnpm run build
|
||||||
|
|
||||||
|
|
||||||
# Checkout init
|
# Checkout init
|
||||||
|
|
|
||||||
58
crypto/aes.go
Normal file
58
crypto/aes.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Base64AESEncode AES encode
|
||||||
|
func Base64AESEncode(key []byte, text string) (string, error) {
|
||||||
|
|
||||||
|
plaintext := []byte(text)
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The IV needs to be unique, but not secure. Therefore it's common to
|
||||||
|
// include it at the beginning of the ciphertext.
|
||||||
|
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
|
||||||
|
iv := ciphertext[:aes.BlockSize]
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := cipher.NewCFBEncrypter(block, iv)
|
||||||
|
stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
|
||||||
|
|
||||||
|
// convert to base64
|
||||||
|
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64AESDecode AES decode
|
||||||
|
func Base64AESDecode(key []byte, cryptoText string) (string, error) {
|
||||||
|
ciphertext, _ := base64.StdEncoding.DecodeString(cryptoText)
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The IV needs to be unique, but not secure. Therefore it's common to
|
||||||
|
// include it at the beginning of the ciphertext.
|
||||||
|
if len(ciphertext) < aes.BlockSize {
|
||||||
|
return "", fmt.Errorf("ciphertext too short")
|
||||||
|
}
|
||||||
|
iv := ciphertext[:aes.BlockSize]
|
||||||
|
ciphertext = ciphertext[aes.BlockSize:]
|
||||||
|
|
||||||
|
stream := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
|
||||||
|
// XORKeyStream can work in-place if the two arguments are the same.
|
||||||
|
stream.XORKeyStream(ciphertext, ciphertext)
|
||||||
|
return fmt.Sprintf("%s", ciphertext), nil
|
||||||
|
}
|
||||||
37
crypto/aes_test.go
Normal file
37
crypto/aes_test.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBase64AES(t *testing.T) {
|
||||||
|
|
||||||
|
originalText := "encrypt this golang"
|
||||||
|
key := []byte("example key 1234")
|
||||||
|
|
||||||
|
// encrypt value to base64
|
||||||
|
cryptoText, err := Base64AESEncode(key, originalText)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypt base64 crypto to original value
|
||||||
|
text, err := Base64AESDecode(key, cryptoText)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, "encrypt this golang", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBase64AESProcess(t *testing.T) {
|
||||||
|
|
||||||
|
args := []interface{}{"example key 1234", "encrypt this golang"}
|
||||||
|
cryptoText := gou.NewProcess("yao.crypto.AESBase64Encode", args...).Run()
|
||||||
|
|
||||||
|
args = []interface{}{"example key 1234", cryptoText}
|
||||||
|
text := gou.NewProcess("yao.crypto.AESBase64Decode", args...).Run()
|
||||||
|
assert.Equal(t, "encrypt this golang", text)
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,9 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
gou.RegisterProcessHandler("yao.crypto.hash", ProcessHash) // deprecated → crypto.Hash
|
gou.RegisterProcessHandler("yao.crypto.hash", ProcessHash) // deprecated → crypto.Hash
|
||||||
gou.RegisterProcessHandler("yao.crypto.hmac", ProcessHmac) // deprecated → crypto.Hash
|
gou.RegisterProcessHandler("yao.crypto.hmac", ProcessHmac) // deprecated → crypto.Hash
|
||||||
|
gou.RegisterProcessHandler("yao.crypto.AESBase64Encode", processBase64AESEncode)
|
||||||
|
gou.RegisterProcessHandler("yao.crypto.AESBase64Decode", processBase64AESDecode)
|
||||||
|
gou.RegisterProcessHandler("yao.crypto.WeworkDecrypt", processWeworkDecrypt)
|
||||||
|
|
||||||
gou.AliasProcess("yao.crypto.hash", "crypto.Hash")
|
gou.AliasProcess("yao.crypto.hash", "crypto.Hash")
|
||||||
gou.AliasProcess("yao.crypto.hmac", "crypto.Hmac")
|
gou.AliasProcess("yao.crypto.hmac", "crypto.Hmac")
|
||||||
|
|
@ -60,3 +63,44 @@ func ProcessHmac(process *gou.Process) interface{} {
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processBase64AESEncode(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(2)
|
||||||
|
key := process.ArgsString(0)
|
||||||
|
value := process.ArgsString(1)
|
||||||
|
res, err := Base64AESEncode([]byte(key), value)
|
||||||
|
if err != nil {
|
||||||
|
exception.New("error: %s value: %s", 400, err, value).Throw()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func processBase64AESDecode(process *gou.Process) interface{} {
|
||||||
|
process.ValidateArgNums(2)
|
||||||
|
key := process.ArgsString(0)
|
||||||
|
value := process.ArgsString(1)
|
||||||
|
res, err := Base64AESDecode([]byte(key), value)
|
||||||
|
if err != nil {
|
||||||
|
exception.New("error: %s value: %s", 400, err, value).Throw()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func processWeworkDecrypt(process *gou.Process) interface{} {
|
||||||
|
|
||||||
|
process.ValidateArgNums(2)
|
||||||
|
encodingAESKey := process.ArgsString(0)
|
||||||
|
msgEncrypt := process.ArgsString(1)
|
||||||
|
parseXML := false
|
||||||
|
|
||||||
|
if process.NumOfArgsIs(3) {
|
||||||
|
parseXML = process.ArgsBool(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := WeworkDecrypt(encodingAESKey, msgEncrypt, parseXML)
|
||||||
|
if err != nil {
|
||||||
|
exception.New("error: %s msgEncrypt: %s", 400, err, msgEncrypt).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
||||||
81
crypto/wework.go
Normal file
81
crypto/wework.go
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WeworkDecrypt wework msg Decrypt
|
||||||
|
func WeworkDecrypt(encodingAESKey string, msgEncrypt string, parse bool) (map[string]interface{}, error) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
aseKey, err := base64.StdEncoding.DecodeString(encodingAESKey + "=")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := base64.StdEncoding.DecodeString(msgEncrypt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
randMsg, err := aesDecrypt(ciphertext, aseKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
content := randMsg[16:]
|
||||||
|
buf := bytes.NewBuffer(content[0:4])
|
||||||
|
var len int32
|
||||||
|
binary.Read(buf, binary.BigEndian, &len)
|
||||||
|
msg := content[4 : len+4]
|
||||||
|
receiveid := content[len+4:]
|
||||||
|
|
||||||
|
data := map[string]interface{}{}
|
||||||
|
if parse {
|
||||||
|
data, err = parseXML(string(msg))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"message": string(msg),
|
||||||
|
"data": data,
|
||||||
|
"receiveid": string(receiveid),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseXML(data string) (map[string]interface{}, error) {
|
||||||
|
|
||||||
|
decoder := NewDecoder(strings.NewReader(data))
|
||||||
|
result, err := decoder.Decode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func aesDecrypt(crypted, key []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockSize := block.BlockSize()
|
||||||
|
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||||
|
origData := make([]byte, len(crypted))
|
||||||
|
blockMode.CryptBlocks(origData, crypted)
|
||||||
|
origData = pckS5UnPadding(origData)
|
||||||
|
return origData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pckS5UnPadding(origData []byte) []byte {
|
||||||
|
length := len(origData)
|
||||||
|
unpadding := int(origData[length-1])
|
||||||
|
return origData[:(length - unpadding)]
|
||||||
|
}
|
||||||
61
crypto/wework_test.go
Normal file
61
crypto/wework_test.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/kun/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWework(t *testing.T) {
|
||||||
|
|
||||||
|
msgEncrypt := "meqbMyPr58hNy0j0YDdG9UT60UJZSh/tb3KOZt3z2SCKr6uvmSLbEnUCM89iFXS0BLWn11FOrD/xXsGUlVUSBw=="
|
||||||
|
encodingAESKey := "RhH75tStMzrH8bMxkTw8BrBfr0ZWULL5himUaRWCs7H"
|
||||||
|
|
||||||
|
res, err := WeworkDecrypt(encodingAESKey, msgEncrypt, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "8446271472585838141", res["message"])
|
||||||
|
assert.Equal(t, "wwe146299c731e6301", res["receiveid"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWeworkProcess(t *testing.T) {
|
||||||
|
|
||||||
|
msgEncrypt := "meqbMyPr58hNy0j0YDdG9UT60UJZSh/tb3KOZt3z2SCKr6uvmSLbEnUCM89iFXS0BLWn11FOrD/xXsGUlVUSBw=="
|
||||||
|
encodingAESKey := "RhH75tStMzrH8bMxkTw8BrBfr0ZWULL5himUaRWCs7H"
|
||||||
|
|
||||||
|
args := []interface{}{encodingAESKey, msgEncrypt}
|
||||||
|
res := gou.NewProcess("yao.crypto.WeworkDecrypt", args...).Run().(map[string]interface{})
|
||||||
|
|
||||||
|
assert.Equal(t, "8446271472585838141", res["message"])
|
||||||
|
assert.Equal(t, "wwe146299c731e6301", res["receiveid"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWeworkParseXML(t *testing.T) {
|
||||||
|
|
||||||
|
xml := `
|
||||||
|
<xml>
|
||||||
|
<ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName>
|
||||||
|
<FromUserName><![CDATA[mycreate]]></FromUserName>
|
||||||
|
<CreateTime>1409659813</CreateTime>
|
||||||
|
<MsgType><![CDATA[text]]></MsgType>
|
||||||
|
<Content><![CDATA[hello]]></Content>
|
||||||
|
<MsgId>4561255354251345929</MsgId>
|
||||||
|
<AgentID>218</AgentID>
|
||||||
|
<Nest>
|
||||||
|
<Id>111</Id>
|
||||||
|
</Nest>
|
||||||
|
</xml>`
|
||||||
|
|
||||||
|
data, err := parseXML(xml)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := maps.Of(data).Dot()
|
||||||
|
assert.Equal(t, "218", res.Get("xml.AgentID"))
|
||||||
|
assert.Equal(t, "111", res.Get("xml.Nest.Id"))
|
||||||
|
}
|
||||||
180
crypto/xml.go
Normal file
180
crypto/xml.go
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
attrPrefix = "@"
|
||||||
|
textPrefix = "#text"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//ErrInvalidDocument invalid document err
|
||||||
|
ErrInvalidDocument = errors.New("invalid document")
|
||||||
|
|
||||||
|
//ErrInvalidRoot data at the root level is invalid err
|
||||||
|
ErrInvalidRoot = errors.New("data at the root level is invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
Parent *node
|
||||||
|
Value map[string]interface{}
|
||||||
|
Attrs []xml.Attr
|
||||||
|
Label string
|
||||||
|
Space string
|
||||||
|
Text string
|
||||||
|
HasMany bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder instance
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
attrPrefix string
|
||||||
|
textPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder create new decoder instance
|
||||||
|
func NewDecoder(reader io.Reader) *Decoder {
|
||||||
|
return NewDecoderWithPrefix(reader, attrPrefix, textPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoderWithPrefix create new decoder instance with custom attribute prefix and text prefix
|
||||||
|
func NewDecoderWithPrefix(reader io.Reader, attrPrefix, textPrefix string) *Decoder {
|
||||||
|
return &Decoder{r: reader, attrPrefix: attrPrefix, textPrefix: textPrefix}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode xml string to map[string]interface{}
|
||||||
|
func (d *Decoder) Decode() (map[string]interface{}, error) {
|
||||||
|
decoder := xml.NewDecoder(d.r)
|
||||||
|
n := &node{}
|
||||||
|
stack := make([]*node, 0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
token, err := decoder.Token()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if token == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tok := token.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
{
|
||||||
|
label := tok.Name.Local
|
||||||
|
if tok.Name.Space != "" {
|
||||||
|
label = fmt.Sprintf("%s:%s", strings.ToLower(path.Base(tok.Name.Space)), tok.Name.Local)
|
||||||
|
}
|
||||||
|
n = &node{
|
||||||
|
Label: label,
|
||||||
|
Space: tok.Name.Space,
|
||||||
|
Parent: n,
|
||||||
|
Value: map[string]interface{}{label: map[string]interface{}{}},
|
||||||
|
Attrs: tok.Attr,
|
||||||
|
}
|
||||||
|
|
||||||
|
setAttrs(n, &tok, d.attrPrefix)
|
||||||
|
stack = append(stack, n)
|
||||||
|
|
||||||
|
if n.Parent != nil {
|
||||||
|
n.Parent.HasMany = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case xml.CharData:
|
||||||
|
data := strings.TrimSpace(string(tok))
|
||||||
|
if len(stack) > 0 {
|
||||||
|
stack[len(stack)-1].Text = data
|
||||||
|
} else if len(data) > 0 {
|
||||||
|
return nil, ErrInvalidRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
case xml.EndElement:
|
||||||
|
{
|
||||||
|
length := len(stack)
|
||||||
|
stack, n = stack[:length-1], stack[length-1]
|
||||||
|
|
||||||
|
if !n.HasMany {
|
||||||
|
if len(n.Attrs) > 0 {
|
||||||
|
m := n.Value[n.Label].(map[string]interface{})
|
||||||
|
m[d.textPrefix] = n.Text
|
||||||
|
} else {
|
||||||
|
n.Value[n.Label] = n.Text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(stack) == 0 {
|
||||||
|
return n.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
setNodeValue(n)
|
||||||
|
n = n.Parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrInvalidDocument
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAttrs(n *node, tok *xml.StartElement, attrPrefix string) {
|
||||||
|
if len(tok.Attr) > 0 {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
for _, attr := range tok.Attr {
|
||||||
|
if len(attr.Name.Space) > 0 {
|
||||||
|
m[attrPrefix+attr.Name.Space+":"+attr.Name.Local] = attr.Value
|
||||||
|
} else {
|
||||||
|
m[attrPrefix+attr.Name.Local] = attr.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.Value[tok.Name.Local] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNodeValue(n *node) {
|
||||||
|
if v, ok := n.Parent.Value[n.Parent.Label]; ok {
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
if v, ok = m[n.Label]; ok {
|
||||||
|
switch item := v.(type) {
|
||||||
|
case string:
|
||||||
|
m[n.Label] = []string{item, n.Value[n.Label].(string)}
|
||||||
|
case []string:
|
||||||
|
m[n.Label] = append(item, n.Value[n.Label].(string))
|
||||||
|
case map[string]interface{}:
|
||||||
|
vm := getMap(n)
|
||||||
|
if vm != nil {
|
||||||
|
m[n.Label] = []map[string]interface{}{item, vm}
|
||||||
|
}
|
||||||
|
case []map[string]interface{}:
|
||||||
|
vm := getMap(n)
|
||||||
|
if vm != nil {
|
||||||
|
m[n.Label] = append(item, vm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m[n.Label] = n.Value[n.Label]
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
n.Parent.Value[n.Parent.Label] = n.Value[n.Label]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMap(node *node) map[string]interface{} {
|
||||||
|
if v, ok := node.Value[node.Label]; ok {
|
||||||
|
switch v.(type) {
|
||||||
|
case string:
|
||||||
|
return map[string]interface{}{node.Label: v}
|
||||||
|
case map[string]interface{}:
|
||||||
|
return node.Value[node.Label].(map[string]interface{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
|
"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/yaoapp/gou"
|
"github.com/yaoapp/gou"
|
||||||
"github.com/yaoapp/gou/session"
|
"github.com/yaoapp/gou/session"
|
||||||
"github.com/yaoapp/kun/any"
|
"github.com/yaoapp/kun/any"
|
||||||
|
|
@ -33,9 +34,16 @@ func init() {
|
||||||
// processUpload 上传文件到本地服务器
|
// processUpload 上传文件到本地服务器
|
||||||
func processUpload(process *gou.Process) interface{} {
|
func processUpload(process *gou.Process) interface{} {
|
||||||
process.ValidateArgNums(1)
|
process.ValidateArgNums(1)
|
||||||
tmpfile, ok := process.Args[0].(xun.UploadFile)
|
|
||||||
if !ok {
|
var tmpfile xun.UploadFile
|
||||||
exception.New("上传文件参数错误", 400, process.Args[0]).Throw()
|
data, err := jsoniter.Marshal(process.Args[0])
|
||||||
|
if err != nil {
|
||||||
|
exception.New("上传文件参数错误 %s %v", 400, err.Error(), process.Args[0]).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jsoniter.Unmarshal(data, &tmpfile)
|
||||||
|
if err != nil {
|
||||||
|
exception.New("上传文件参数错误 %s %v", 400, err.Error(), process.Args[0]).Throw()
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := md5.Sum([]byte(time.Now().Format("20060102-15:04:05")))
|
hash := md5.Sum([]byte(time.Now().Format("20060102-15:04:05")))
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue