From 275512e4927c863aa517c771be9bebb6797feeb7 Mon Sep 17 00:00:00 2001 From: Star Date: Mon, 1 Dec 2025 00:30:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=B8=E5=A4=9A=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API.go | 197 +++++++++++++++++++++++++++++++++--------------- ai_test.js | 174 +++++++++++++++++++++++++----------------- commonSigner.go | 6 +- go.mod | 21 +++--- plugin.go | 193 ++++++++++++++++++++++++++++++++++------------- plugin_test.go | 1 + 6 files changed, 397 insertions(+), 195 deletions(-) diff --git a/API.go b/API.go index 0c0b664..451ddd7 100644 --- a/API.go +++ b/API.go @@ -16,12 +16,22 @@ import ( "sync" "time" + "apigo.cc/gojs" + "apigo.cc/gojs/goja" xml2json "github.com/basgys/goxml2json" "github.com/ssgo/httpclient" "github.com/ssgo/u" ) -var Signers = map[string]func(req *Request, cfg *SignerConfig) error{ +type SignerCode struct { + Code string + Version uint64 +} + +var jsSigners = map[string]*SignerCode{} +var actionConfigs = map[string]*ApiConfig{} + +var goSigners = map[string]func(req *Request, cfg *ApiConfig) error{ // 基础认证 "basic": makeBasicAuthSign, "bearer": makeBearerSign, @@ -30,79 +40,134 @@ var Signers = map[string]func(req *Request, cfg *SignerConfig) error{ // "cos": makeCOSSign, // "hmac": makeHmacSign, } -var signersLock = sync.RWMutex{} +var actionsLock = sync.RWMutex{} -func GetSigner(name string) func(req *Request, cfg *SignerConfig) error { - if name == "" { - return nil - } - signersLock.RLock() - defer signersLock.RUnlock() - return Signers[name] -} - -func RegisterSigner(name string, f func(req *Request, cfg *SignerConfig) error) { - signersLock.Lock() - defer signersLock.Unlock() - Signers[name] = f -} - -type SignerConfig struct { +type ApiConfig struct { data map[string]any } -var signerConfig = map[string]*map[string]any{} +var apiConfigs = map[string]*map[string]any{} var confLock = sync.RWMutex{} -func GetSignerConfig(signer string) *SignerConfig { +func GetSigner(name string, vm *goja.Runtime) func(req *Request, cfg *ApiConfig) error { + if name == "" { + return nil + } + var jsi *SignerCode + actionsLock.RLock() + si := goSigners[name] + if si == nil { + jsi = jsSigners[name] + } + actionsLock.RUnlock() + + // 获取当前VM的Signer + if jsi != nil { + if jsi.Version != u.Uint64(vm.GetData("__api_signer_version_"+name)) { + // 从代码更新 + vm.SetData("__api_signer_version_"+name, jsi.Version) + if fnV, err := vm.RunString("(" + jsi.Code + ")"); err == nil { + if fn, ok := goja.AssertFunction(fnV); ok { + si = func(req *Request, cfg *ApiConfig) error { + _, err := fn(nil, vm.ToValue(gojs.ToMap(req)), vm.ToValue(gojs.ToMap(cfg))) + return err + } + vm.SetData("__signer_"+name, si) + } + } + } else { + // 从vm中直接获取 + if si1, ok := vm.GetData("__signer_" + name).(func(req *Request, cfg *ApiConfig) error); ok { + si = si1 + } + } + } + return si +} + +func GetSignerVersion(name string) uint64 { + actionsLock.Lock() + defer actionsLock.Unlock() + si := jsSigners[name] + if si == nil { + return 0 + } + return si.Version +} + +// func RegisterSigner(name string, f func(req *Request, cfg *SignerConfig) error) { +func RegisterSigner(name string, fnCode string, fnVersion *uint64, vm *goja.Runtime) error { + version := u.Uint64(fnVersion) + if version == 0 { + version = 1 + } + // fmt.Println(name, fnCode, version) + if fnV, err := vm.RunString("(" + fnCode + ")"); err == nil { + if _, ok := goja.AssertFunction(fnV); !ok { + return gojs.Err("signer must be a function") + } + } else { + return gojs.Err(err) + } + + actionsLock.Lock() + defer actionsLock.Unlock() + jsSigners[name] = &SignerCode{ + Code: fnCode, + Version: version, + } + return nil +} + +func GetSignerConfig(signer string) *ApiConfig { confLock.RLock() - cfg := signerConfig[signer] + cfg := apiConfigs[signer] confLock.RUnlock() if cfg == nil { cfg = &map[string]any{} } - return &SignerConfig{data: *cfg} + return &ApiConfig{data: *cfg} } // func (cfg *SignerConfig) Signer() string { // return u.String(cfg.data["signer"]) // } -func (cfg *SignerConfig) Get(k string, defaultValue any) any { +func (cfg *ApiConfig) Get(k string, defaultValue any) any { if v, ok := cfg.data[k]; ok { return v } return defaultValue } -func (cfg *SignerConfig) Set(k string, v any) { +func (cfg *ApiConfig) Set(k string, v any) { cfg.data[k] = v } -func (cfg *SignerConfig) String(k string, defaultValue string) string { +func (cfg *ApiConfig) String(k string, defaultValue string) string { return u.String(cfg.Get(k, defaultValue)) } -func (cfg *SignerConfig) Int(k string, defaultValue int64) int64 { +func (cfg *ApiConfig) Int(k string, defaultValue int64) int64 { return u.Int64(cfg.Get(k, defaultValue)) } -func (cfg *SignerConfig) Float(k string, defaultValue float64) float64 { +func (cfg *ApiConfig) Float(k string, defaultValue float64) float64 { return u.Float64(cfg.Get(k, defaultValue)) } -func (cfg *SignerConfig) Bool(k string, defaultValue bool) bool { +func (cfg *ApiConfig) Bool(k string, defaultValue bool) bool { return u.Bool(cfg.Get(k, defaultValue)) } -func (cfg *SignerConfig) To(k string, to any) { +func (cfg *ApiConfig) To(k string, to any) { from := cfg.Get(k, nil) if from != nil { u.Convert(from, to) } } -func (cfg *SignerConfig) Map(k string) map[string]any { +func (cfg *ApiConfig) Map(k string) map[string]any { m := map[string]any{} cfg.To(k, &m) return m @@ -374,7 +439,7 @@ func (req *Request) MakeBody() { } } -func MakeSign(cfg *SignerConfig, req *Request) error { +func MakeSign(cfg *ApiConfig, req *Request, vm *goja.Runtime) error { if req.RequestType == "" { if req.Data != nil { req.RequestType = RequestType.Json @@ -426,7 +491,7 @@ func MakeSign(cfg *SignerConfig, req *Request) error { } if cfg == nil { - cfg = &SignerConfig{} + cfg = &ApiConfig{} } // 从signer中设置header @@ -453,7 +518,7 @@ func MakeSign(cfg *SignerConfig, req *Request) error { req.MakeQuery() req.MakeBody() - if signer := GetSigner(u.String(cfg.data["signer"])); signer != nil { + if signer := GetSigner(u.String(cfg.data["signer"]), vm); signer != nil { // fmt.Println(111, req.Method, 111) if err := signer(req, cfg); err != nil { // fmt.Println(333, err) @@ -466,38 +531,52 @@ func MakeSign(cfg *SignerConfig, req *Request) error { return nil } -func Get(cfg *SignerConfig, req *Request) (any, *http.Response, error) { - req.Method = "GET" - return Do(cfg, req) -} +// func Get(cfg *SignerConfig, req *Request, vm *goja.Runtime) (any, *http.Response, error) { +// req.Method = "GET" +// return Do(cfg, req, vm) +// } -func Post(cfg *SignerConfig, req *Request) (any, *http.Response, error) { - req.Method = "POST" - return Do(cfg, req) -} +// func Post(cfg *SignerConfig, req *Request, vm *goja.Runtime) (any, *http.Response, error) { +// req.Method = "POST" +// return Do(cfg, req, vm) +// } -func Put(cfg *SignerConfig, req *Request) (any, *http.Response, error) { - req.Method = "PUT" - return Do(cfg, req) -} +// func Put(cfg *SignerConfig, req *Request, vm *goja.Runtime) (any, *http.Response, error) { +// req.Method = "PUT" +// return Do(cfg, req, vm) +// } -func Delete(cfg *SignerConfig, req *Request) (any, *http.Response, error) { - req.Method = "DELETE" - return Do(cfg, req) -} +// func Delete(cfg *SignerConfig, req *Request, vm *goja.Runtime) (any, *http.Response, error) { +// req.Method = "DELETE" +// return Do(cfg, req, vm) +// } -func Head(cfg *SignerConfig, req *Request) (any, *http.Response, error) { - req.Method = "HEAD" - return Do(cfg, req) -} +// func Head(cfg *SignerConfig, req *Request, vm *goja.Runtime) (any, *http.Response, error) { +// req.Method = "HEAD" +// return Do(cfg, req, vm) +// } -func Options(cfg *SignerConfig, req *Request) (any, *http.Response, error) { - req.Method = "OPTIONS" - return Do(cfg, req) -} +// func Options(cfg *SignerConfig, req *Request, vm *goja.Runtime) (any, *http.Response, error) { +// req.Method = "OPTIONS" +// return Do(cfg, req, vm) +// } -func Do(cfg *SignerConfig, req *Request) (any, *http.Response, error) { - err := MakeSign(cfg, req) +func Do(action string, req *Request, vm *goja.Runtime) (any, *http.Response, error) { + var cfg *ApiConfig + if action != "" { + action1 := u.SplitTrimN(action, ".", 2)[0] + actionsLock.RLock() + cfg = actionConfigs[action] + if cfg == nil { + cfg = actionConfigs[action1] + } + actionsLock.RUnlock() + } + if cfg == nil { + cfg = &ApiConfig{data: map[string]any{}} + } + + err := MakeSign(cfg, req, vm) if err != nil { return nil, nil, err } diff --git a/ai_test.js b/ai_test.js index d80b309..f419108 100644 --- a/ai_test.js +++ b/ai_test.js @@ -1,97 +1,129 @@ import co from 'apigo.cc/gojs/console' import u from 'apigo.cc/gojs/util' import api from 'apigo.cc/gojs/api' +import file from 'apigo.cc/gojs/file' // 注册腾讯云TC3签名器 -api.registerSigner("tc3", (req, cfg) => { - let action = cfg.string("action", "") - let service = cfg.string("service", "") - let timestamp = u.timestamp() - let contentType = "application/json; charset=utf-8" - let canonicalHeaders = "content-type:" + contentType + "\nhost:" + req.finalHost + "\nx-tc-action:" + action.toLowerCase() + "\n" - let signedHeaders = "content-type;host;x-tc-action" - let hashedRequestPayload = u.hex(u.sha256(req.finalBody)) - let canonicalRequest = req.method + "\n" + req.finalPath + "\n" + req.finalQuery + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload - let date = u.formatDate("2006-01-02", timestamp) - let credentialScope = date + "/" + service + "/tc3_request" - let string2sign = "TC3-HMAC-SHA256\n" + timestamp + "\n" + credentialScope + "\n" + u.hex(u.sha256(canonicalRequest)) - let secretDate = u.hmacSHA256("TC3" + cfg.string("secretKey", ""), date) - let secretService = u.hmacSHA256(secretDate, service) - let secretSigning = u.hmacSHA256(secretService, "tc3_request") - let signature = u.hex(u.hmacSHA256(secretSigning, string2sign)) - let authorization = "TC3-HMAC-SHA256 Credential=" + cfg.string("secretId", "") + "/" + credentialScope + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature - req.setHeader("Content-Type", contentType) - req.setHeader("X-TC-Action", action) - req.setHeader("X-TC-Timestamp", u.string(timestamp)) - req.setHeader("X-TC-Version", cfg.string("version", "")) - req.setHeader("X-TC-Region", cfg.string("region", "")) - req.setHeader("Authorization", authorization) -}) +api.registerSigner("tc3", ((req, cfg) => { + import u from 'apigo.cc/gojs/util' + let action = cfg.string("action", "") + let service = cfg.string("service", "") + let timestamp = u.timestamp() + let contentType = "application/json; charset=utf-8" + let canonicalHeaders = "content-type:" + contentType + "\nhost:" + req.finalHost + "\nx-tc-action:" + action.toLowerCase() + "\n" + let signedHeaders = "content-type;host;x-tc-action" + let hashedRequestPayload = u.hex(u.sha256(req.finalBody)) + let canonicalRequest = req.method + "\n" + req.finalPath + "\n" + req.finalQuery + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload + let date = u.formatDate("2006-01-02", timestamp) + let credentialScope = date + "/" + service + "/tc3_request" + let string2sign = "TC3-HMAC-SHA256\n" + timestamp + "\n" + credentialScope + "\n" + u.hex(u.sha256(canonicalRequest)) + let secretDate = u.hmacSHA256("TC3" + cfg.string("secretKey", ""), date) + let secretService = u.hmacSHA256(secretDate, service) + let secretSigning = u.hmacSHA256(secretService, "tc3_request") + let signature = u.hex(u.hmacSHA256(secretSigning, string2sign)) + let authorization = "TC3-HMAC-SHA256 Credential=" + cfg.string("secretId", "") + "/" + credentialScope + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature + req.setHeader("Content-Type", contentType) + req.setHeader("X-TC-Action", action) + req.setHeader("X-TC-Timestamp", u.string(timestamp)) + req.setHeader("X-TC-Version", cfg.string("version", "")) + req.setHeader("X-TC-Region", cfg.string("region", "")) + req.setHeader("Authorization", authorization) +}).toString()) // 腾讯云COS签名 -api.registerSigner("cos", (req, cfg) => { - let startTimestamp = u.timestamp() - let keyTime = startTimestamp + ";" + startTimestamp + cfg.int("expiredTime", 600) - let signKey = u.hex(u.hmacSHA1(cfg.string("secretKey", ""), keyTime)) - let [urlParamList, httpParameters] = api.sortParams(req.query) - let [headerList, httpHeaders] = api.sortParams(req.headers) - let hashedHttpString = u.hex(u.sha1(req.method.toLowerCase() + "\n" + req.finalPath + "\n" + httpParameters + "\n" + httpHeaders + "\n")) - let signature = u.hex(u.hmacSHA1(signKey, "sha1\n" + keyTime + "\n" + hashedHttpString + "\n")) - let authorization = "q-sign-algorithm=sha1&q-ak=" + cfg.string("secretId", "") + "&q-sign-time=" + keyTime + "&q-key-time=" + keyTime + "&q-header-list=" + headerList + "&q-url-param-list=" + urlParamList + "&q-signature=" + signature - req.setHeader("Authorization", authorization) -}) +api.registerSigner("cos", ((req, cfg) => { + import u from 'apigo.cc/gojs/util' + let startTimestamp = u.timestamp() + let keyTime = startTimestamp + ";" + startTimestamp + cfg.int("expiredTime", 600) + let signKey = u.hex(u.hmacSHA1(cfg.string("secretKey", ""), keyTime)) + let [urlParamList, httpParameters] = api.sortParams(req.query) + let [headerList, httpHeaders] = api.sortParams(req.headers) + let hashedHttpString = u.hex(u.sha1(req.method.toLowerCase() + "\n" + req.finalPath + "\n" + httpParameters + "\n" + httpHeaders + "\n")) + let signature = u.hex(u.hmacSHA1(signKey, "sha1\n" + keyTime + "\n" + hashedHttpString + "\n")) + let authorization = "q-sign-algorithm=sha1&q-ak=" + cfg.string("secretId", "") + "&q-sign-time=" + keyTime + "&q-key-time=" + keyTime + "&q-header-list=" + headerList + "&q-url-param-list=" + urlParamList + "&q-signature=" + signature + req.setHeader("Authorization", authorization) +}).toString()) try { - // let r = api.tencent.smsPackagesStatistics({ data: { SmsSdkAppId: '1400624676', BeginTime: '2025010100', EndTime: '2045010100' } }) - // let r = api.tencent.getBucketList() - // let r = api.laoCos.get({ url: "/?max-keys=5" }) - // let r = api.laoCos.get({ url: "user/9VKQpY2RH7Ks/avatar.jpg", responseType: 'text/plain' }) - // let r = api.laoCos.put({ url: "test/aaa.txt", text: 'hello world' }) - // let r = api.laoCos.get({ url: "test/aaa.txt" }) - // let r = api.tencent.getCosToken({ config: { path: '/aaa/111' } }) - // co.info(r) + // let r = api.tencent.smsPackagesStatistics({ data: { SmsSdkAppId: '1400624676', BeginTime: '2025010100', EndTime: '2045010100' } }) + let r = api.tencent.getBucketList() + // let r = api.laoCos.get({ url: "/?max-keys=5" }) + // let r = api.laoCos.get({ url: "user/9VKQpY2RH7Ks/avatar.jpg", responseType: 'text/plain' }) + // let r = api.laoCos.put({ url: "test/aaa.txt", text: 'hello world' }) + // let r = api.laoCos.get({ url: "test/aaa.txt" }) + // let r = api.tencent.getCosToken({ config: { path: '/aaa/111' } }) + co.info(r) - // 测试llm接口 - // let r = api.zhipujwt.do({ url: "https://open.bigmodel.cn/api/paas/v4/chat/completions", data: { model: 'GLM-4.5-Flash', messages: [{ role: 'user', content: '你好' }], thinking: { type: 'disabled' } } }) - // test('智谱(jwt):简单对话(.do)', r.statusCode == 200 && !!r.data, r?.data?.choices[0]?.message?.content, r) + // 测试llm接口 + // let r = api.zhipujwt.do({ url: "https://open.bigmodel.cn/api/paas/v4/chat/completions", data: { model: 'GLM-4.5-Flash', messages: [{ role: 'user', content: '你好' }], thinking: { type: 'disabled' } } }) + // test('智谱(jwt):简单对话(.do)', r.statusCode == 200 && !!r.data, r?.data?.choices[0]?.message?.content, r) - // testChat('PPIO(openai)', api.openai) - // testChat('智谱', api.zhipu) - testChat('豆包', api.doubao) + testChat('PPIO(openai)', api.openai) + // testChat('智谱', api.zhipu) + // testChat('豆包', api.doubao) - // let r = api.zhipu.image({ data: { prompt: '一张猫在沙发上喝咖啡' } }) - // test('智谱:图片生成', r.statusCode == 200 && !!r.data, r?.data?.data[0]?.url, r) - // let img = r?.data?.data[0]?.url + // let r = api.zhipu.image({ data: { prompt: '一张猫在沙发上喝咖啡' } }) + // test('智谱:图片生成', r.statusCode == 200 && !!r.data, r?.data?.data[0]?.url, r) + // let img = r?.data?.data[0]?.url - // let img = 'https://aigc-files.bigmodel.cn/api/cogview/20250816103236c2331606d04c460a_0.png' - // let r = api.zhipu.chat({ data: { model: 'GLM-4V-Flash', messages: [{ role: 'user', content: [{ type: 'text', text: '看图说话,写一个简短生动的狗血爱情故事' }, { type: 'image_url', image_url: { url: img } }] }] } }) - // test('智谱:看图说话', r.statusCode == 200 && !!r.data, r?.data?.choices[0]?.message?.content, r) + // let img = 'https://aigc-files.bigmodel.cn/api/cogview/20250816103236c2331606d04c460a_0.png' + // let r = api.zhipu.chat({ data: { model: 'GLM-4V-Flash', messages: [{ role: 'user', content: [{ type: 'text', text: '看图说话,写一个简短生动的狗血爱情故事' }, { type: 'image_url', image_url: { url: img } }] }] } }) + // test('智谱:看图说话', r.statusCode == 200 && !!r.data, r?.data?.choices[0]?.message?.content, r) - // 测试文件扫描 - // let r = api.textin.scan({ binary: fs.readBytes('testRes/table.jpeg') }) - // test('Textin:表格识别', r.statusCode == 200 && !!r.data, r?.data, r) + // 测试文件扫描 + // let r = api.textin.scan({ binary: fs.readBytes('testRes/table.jpeg') }) + // test('Textin:表格识别', r.statusCode == 200 && !!r.data, r?.data, r) - return true + + // 测试动态配置 + let zhipuKey = file.read('env.yml').match(/zhipu:.*?key: (.*?)\n/s)[1] + let zp = { + url: 'https://open.bigmodel.cn/api/paas/v4', + key: zhipuKey, + headers: { + 'Authorization': 'Bearer {{.key}}', + }, + actions: { + chat: { + url: '/chat/completions', + data: { + model: 'GLM-4.5-Flash', + thinking: { + type: 'disabled', + }, + }, + }, + }, + } + api.config('zp', zp) + let r1 = api.zp.chat({ callback: v => { co.println(co.yellow(v.choices[0].delta.content) + ' ') }, data: { messages: [{ role: 'user', content: '1+1=多少?说人话简单点' }], stream: true } }) + test('动态配置(v1)', r1.statusCode == 200 && !!r1.data, r1?.data?.choices[0]?.message?.content, r1) + zp.actions.chat.data.thinking.type = 'enabled' + api.config('zp', zp, 2) + r1 = api.zp.chat({ callback: v => { co.println(co.yellow(v.choices[0]?.delta?.reasoning_content || v.choices[0]?.delta?.content) + ' ') }, data: { messages: [{ role: 'user', content: '1+2=多少?说人话简单点' }], stream: true } }) + test('动态配置(v2)', r1.statusCode == 200 && !!r1.data, r1?.data?.choices[0]?.message, r1) + + return true } catch (ex) { - return ex.message + return ex.message } function testChat(title, llm) { - let r = llm.chat({ data: { messages: [{ role: 'user', content: '你好' }] } }) - test(title + ':简单对话', r.statusCode == 200 && !!r.data, r?.data?.choices[0]?.message?.content, r) + let r = llm.chat({ data: { messages: [{ role: 'user', content: '你好' }] } }) + test(title + ':简单对话', r.statusCode == 200 && !!r.data, r?.data?.choices[0]?.message?.content, r) - r = llm.chat({ callback: v => { co.println(co.yellow(v.choices[0].delta.content) + ' ') }, data: { messages: [{ role: 'user', content: '你好' }], stream: true } }) - test(title + ':流式对话', r.statusCode == 200 && !!r.data, r?.data?.usage?.total_tokens, r) + r = llm.chat({ callback: v => { co.println(co.yellow(v.choices[0].delta.content) + ' ') }, data: { messages: [{ role: 'user', content: '你好' }], stream: true } }) + test(title + ':流式对话', r.statusCode == 200 && !!r.data, r?.data?.usage?.total_tokens, r) - // r = llm.embeddings({ data: { input: '今天天气真好' } }) - // test(title + ':向量化', r.statusCode == 200 && !!r.data, r?.data?.data[0]?.embedding?.length, r) + // r = llm.embeddings({ data: { input: '今天天气真好' } }) + // test(title + ':向量化', r.statusCode == 200 && !!r.data, r?.data?.data[0]?.embedding?.length, r) } function test(title, condition, successMessage, failedMessage) { - if (!condition) { - co.info(title, co.bRed('失败'), co.red(u.jsonP(failedMessage))) - throw new Error(title + '失败') - } - co.info(title, co.bGreen('通过'), co.yellow(u.json(successMessage))) + if (!condition) { + co.info(title, co.bRed('失败'), co.red(u.jsonP(failedMessage))) + throw new Error(title + '失败') + } + co.info(title, co.bGreen('通过'), co.yellow(u.json(successMessage))) } diff --git a/commonSigner.go b/commonSigner.go index c479900..287ca39 100644 --- a/commonSigner.go +++ b/commonSigner.go @@ -14,7 +14,7 @@ import ( ) // 基本认证签名器 -func makeBasicAuthSign(req *Request, cfg *SignerConfig) error { +func makeBasicAuthSign(req *Request, cfg *ApiConfig) error { // 1. 获取用户名和密码 username := cfg.String("username", "") password := cfg.String("password", "") @@ -30,13 +30,13 @@ func makeBasicAuthSign(req *Request, cfg *SignerConfig) error { } // Bearer Token认证 -func makeBearerSign(req *Request, cfg *SignerConfig) error { +func makeBearerSign(req *Request, cfg *ApiConfig) error { req.Headers["Authorization"] = "Bearer " + cfg.String("key", "") return nil } // 通用JWT签名器 -func makeJWTSign(req *Request, cfg *SignerConfig) error { +func makeJWTSign(req *Request, cfg *ApiConfig) error { // 1. 获取JWT配置 secret := cfg.String("secret", "") algorithm := cfg.String("algorithm", "HS256") diff --git a/go.mod b/go.mod index db12962..241288d 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module apigo.cc/gojs/api go 1.24.0 require ( - apigo.cc/gojs v0.0.26 - apigo.cc/gojs/console v0.0.2 - apigo.cc/gojs/file v0.0.5 - apigo.cc/gojs/runtime v0.0.3 - apigo.cc/gojs/util v0.0.14 + apigo.cc/gojs v0.0.30 + apigo.cc/gojs/console v0.0.4 + apigo.cc/gojs/file v0.0.7 + apigo.cc/gojs/runtime v0.0.4 + apigo.cc/gojs/util v0.0.16 github.com/basgys/goxml2json v1.1.0 github.com/ssgo/config v1.7.10 github.com/ssgo/httpclient v1.7.8 @@ -22,19 +22,18 @@ require ( require ( github.com/ZZMarquis/gm v1.3.2 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect - github.com/emmansun/gmsm v0.32.0 // indirect + github.com/emmansun/gmsm v0.40.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/pprof v0.0.0-20250903194437-c28834ac2320 // indirect - github.com/kr/text v0.2.0 // indirect github.com/obscuren/ecies v0.0.0-20150213224233-7c0f4a9b18d9 // indirect github.com/ssgo/log v1.7.9 // indirect github.com/ssgo/standard v1.7.7 // indirect github.com/ssgo/tool v0.4.29 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/net v0.44.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/crypto v0.44.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/plugin.go b/plugin.go index 5d2ce29..94634fa 100644 --- a/plugin.go +++ b/plugin.go @@ -24,44 +24,51 @@ type Result struct { } func init() { - config.LoadConfig(pluginName, &signerConfig) - Config(signerConfig) - - obj := map[string]any{ - "config": Config, - "registerSigner": RegisterSigner, - "sortParams": SortParams, + config.LoadConfig(pluginName, &apiConfigs) + for k, v := range apiConfigs { + Config(k, *v, nil) } - for signer := range signerConfig { - cfg := signerConfig[signer] - o := map[string]any{} - o["do"] = MakeAction(signer, nil) - o["get"] = MakeAction(signer, map[string]any{ - "method": "GET", - }) - o["post"] = MakeAction(signer, map[string]any{ - "method": "POST", - }) - o["put"] = MakeAction(signer, map[string]any{ - "method": "PUT", - }) - o["delete"] = MakeAction(signer, map[string]any{ - "method": "DELETE", - }) - o["head"] = MakeAction(signer, map[string]any{ - "method": "HEAD", - }) - o["options"] = MakeAction(signer, map[string]any{ - "method": "OPTIONS", - }) + obj := map[string]any{ + "config": Config, + "getConfigVersion": GetConfigVersion, + "makeApi": MakeApi, + "registerSigner": RegisterSigner, + "getSignerVersion": GetSignerVersion, + "sortParams": SortParams, + } - actions := map[string]map[string]any{} - u.Convert((*cfg)["actions"], &actions) - for k, v := range actions { - o[k] = MakeAction(signer, v) - } - obj[signer] = o + // TODO 动态更新配置时无法重新生成obj,预处理配置生成Action,增加通用的Call方法 + for apiName := range apiConfigs { + // cfg := signerConfig[signer] + // o := map[string]any{} + // o["do"] = MakeAction(signer, nil) + // o["get"] = MakeAction(signer, map[string]any{ + // "method": "GET", + // }) + // o["post"] = MakeAction(signer, map[string]any{ + // "method": "POST", + // }) + // o["put"] = MakeAction(signer, map[string]any{ + // "method": "PUT", + // }) + // o["delete"] = MakeAction(signer, map[string]any{ + // "method": "DELETE", + // }) + // o["head"] = MakeAction(signer, map[string]any{ + // "method": "HEAD", + // }) + // o["options"] = MakeAction(signer, map[string]any{ + // "method": "OPTIONS", + // }) + // o["version"] = (*cfg)["_version"] + + // actions := map[string]map[string]any{} + // u.Convert((*cfg)["actions"], &actions) + // for k, v := range actions { + // o[k] = MakeAction(signer, v) + // } + obj[apiName] = MakeApi(apiName) } tsCode := gojs.MakeTSCode(obj) @@ -72,12 +79,61 @@ func init() { }, TsCode: tsCode, Desc: pluginName, + JsCode: ` + let $MOD$_obj = $MOD$ + $MOD$ = new Proxy($MOD$_obj, { + get(target, prop) { + if(['config', 'getConfigVersion', 'registerSigner', 'getSignerVersion', 'sortParams'].indexOf(prop) >= 0) { + return target[prop] + } + let o = target[prop] + let curVer = o && o.version || 0 + if(curVer == 0 || curVer != $MOD$_obj.getConfigVersion(prop)){ + o = $MOD$_obj.makeApi(prop) + target[prop] = o + } + return o + } + }) + `, }) } -func MakeAction(signer string, actionCfg map[string]any) func(req *Request) (*Result, error) { - cfg := &SignerConfig{data: map[string]any{}} - if c := signerConfig[signer]; c != nil { +func MakeApi(apiName string) map[string]any { + cfg := apiConfigs[apiName] + o := map[string]any{} + o["do"] = MakeAction(apiName, "", nil) + o["get"] = MakeAction(apiName, "get", map[string]any{ + "method": "GET", + }) + o["post"] = MakeAction(apiName, "post", map[string]any{ + "method": "POST", + }) + o["put"] = MakeAction(apiName, "put", map[string]any{ + "method": "PUT", + }) + o["delete"] = MakeAction(apiName, "delete", map[string]any{ + "method": "DELETE", + }) + o["head"] = MakeAction(apiName, "head", map[string]any{ + "method": "HEAD", + }) + o["options"] = MakeAction(apiName, "options", map[string]any{ + "method": "OPTIONS", + }) + o["version"] = (*cfg)["_version"] + + actions := map[string]map[string]any{} + u.Convert((*cfg)["actions"], &actions) + for k, v := range actions { + o[k] = MakeAction(apiName, k, v) + } + return o +} + +func MakeAction(apiName, actionName string, actionCfg map[string]any) func(req *Request, vm *goja.Runtime) (*Result, error) { + cfg := &ApiConfig{data: map[string]any{}} + if c := apiConfigs[apiName]; c != nil { u.Convert(c, cfg.data) delete(cfg.data, "actions") } @@ -104,7 +160,15 @@ func MakeAction(signer string, actionCfg map[string]any) func(req *Request) (*Re defaultUrl = cfg.String("url", "") } - return func(req *Request) (*Result, error) { + actionFullName := apiName + if actionName != "" { + actionFullName += "." + actionName + } + actionsLock.Lock() + actionConfigs[actionFullName] = cfg + actionsLock.Unlock() + + return func(req *Request, vm *goja.Runtime) (*Result, error) { if req == nil { req = &Request{} } @@ -199,7 +263,7 @@ func MakeAction(signer string, actionCfg map[string]any) func(req *Request) (*Re } } - data, resp, err := Do(cfg, req1) + data, resp, err := Do(actionFullName, req1, vm) headers := map[string]string{} statusCode := 0 status := "" @@ -219,19 +283,46 @@ func MakeAction(signer string, actionCfg map[string]any) func(req *Request) (*Re } } -func Config(cfg map[string]*map[string]any) { - for k, v := range cfg { - if u.String((*v)["signer"]) == "" { - (*v)["signer"] = k - } - // 尝试解密配置 - decryptConfig(reflect.ValueOf(v)) - makeConfigVar((*v), reflect.ValueOf(v)) +// func Config(cfg map[string]*map[string]any) { +// for k, v := range cfg { +// if u.String((*v)["signer"]) == "" { +// (*v)["signer"] = k +// } +// // 尝试解密配置 +// decryptConfig(reflect.ValueOf(v)) +// makeConfigVar((*v), reflect.ValueOf(v)) - confLock.Lock() - signerConfig[k] = v - confLock.Unlock() +// confLock.Lock() +// signerConfig[k] = v +// confLock.Unlock() +// } +// } + +func Config(name string, cfg map[string]any, version *uint64) { + ver := u.Uint64(version) + if ver == 0 { + ver = 1 } + cfg["_version"] = ver + + // 尝试解密配置 + cfgV := reflect.ValueOf(cfg) + decryptConfig(cfgV) + makeConfigVar(cfg, cfgV) + + confLock.Lock() + apiConfigs[name] = &cfg + confLock.Unlock() +} + +func GetConfigVersion(name string) uint64 { + confLock.RLock() + defer confLock.RUnlock() + cfg := apiConfigs[name] + if cfg != nil { + return u.Uint64((*cfg)["_version"]) + } + return 0 } func decryptConfig(v reflect.Value) { diff --git a/plugin_test.go b/plugin_test.go index 2988946..b3a10ae 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -14,6 +14,7 @@ import ( ) func TestPlugin(t *testing.T) { + // fmt.Println(filepath.Join("1", "2", "", "", "3", "", "4")) gojs.ExportForDev() for _, f := range u.ReadDirN(".") { if strings.HasSuffix(f.Name, "_test.js") {