Compare commits

..

30 Commits

Author SHA1 Message Date
Dot.L 9ba746ba7f add azure openai
Feature/liujian 1.9
2025-08-26 17:28:36 +08:00
Dot.L e010f00a19 Merge pull request #383 from APIParkLab/feature/liujian-1.9
fix bug: fail to get ai logs
2025-08-15 16:27:51 +08:00
JackLiu 3e4f4e1cff Update README.md 2025-08-15 09:33:53 +08:00
ningyv 3217ad2bba Merge pull request #380 from APIParkLab/feature/1.9-OAuth
fix: Fix department selection issue
2025-08-12 10:44:59 +08:00
lcx 1485b31226 fix: Fix department selection issue 2025-08-12 10:43:17 +08:00
ningyv 8968e1f961 Merge pull request #379 from APIParkLab/feature/1.9-OAuth
fix: Fix department selection issue.
2025-08-12 10:23:42 +08:00
lcx 4b8fa43c36 fix: Fix department selection issue. 2025-08-12 10:23:14 +08:00
ningyv c21d783c52 Merge pull request #378 from APIParkLab/feature/1.9-OAuth
fix: issue with adding user permissions
2025-08-11 19:00:31 +08:00
lcx e928cd84d7 fix: issue with adding user permissions 2025-08-11 18:59:39 +08:00
FreyLoong 412cb75bf0 Update readme-zh-cn.md 2025-08-08 18:42:46 +08:00
FreyLoong a13b2a8afe Update README.md 2025-08-08 18:40:14 +08:00
Dot.L c0472c8539 Merge pull request #375 from APIParkLab/feature/liujian-1.9
Feature/liujian 1.9
2025-08-08 12:06:44 +08:00
Dot.L 55f09f7542 Merge pull request #374 from APIParkLab/feature/liujian-1.9
fix: fail to delete model
2025-08-08 11:31:25 +08:00
Dot.L 7aad2174aa Merge pull request #368 from APIParkLab/feature/liujian-1.9
update service publish config
2025-07-23 20:06:32 +08:00
ningyv 2a951c2854 Merge pull request #366 from APIParkLab/feature/1.9-OAuth
feat: Add Feishu OAuth login & consumer-grade MCP
2025-07-23 15:40:50 +08:00
lcx e91a9e7726 feat: Add Feishu OAuth login & consumer-grade MCP 2025-07-23 15:40:13 +08:00
ningyv e0d97186b1 Merge pull request #363 from APIParkLab/feature/1.9-OAuth
feat: Add Feishu OAuth login & consumer-grade MCP
2025-07-22 10:45:53 +08:00
lcx dff6e722c0 feat: Add Feishu OAuth login & consumer-grade MCP 2025-07-22 10:45:16 +08:00
ningyv b8c92961c1 Merge pull request #362 from APIParkLab/feature/1.9-OAuth
feat: Add Feishu OAuth login & consumer-grade MCP
2025-07-22 10:30:18 +08:00
lcx 025bd4c6cc feat: Add Feishu OAuth login & consumer-grade MCP 2025-07-22 10:28:59 +08:00
ningyv 3d2ec67fc1 Merge pull request #361 from APIParkLab/feature/1.9-OAuth
feat: Add Feishu OAuth login & consumer-grade MCP
2025-07-22 09:55:00 +08:00
lcx b2a8c8d901 feat: Add Feishu OAuth login & consumer-grade MCP 2025-07-22 09:53:41 +08:00
ningyv 90226ac6af Merge pull request #360 from APIParkLab/feature/1.9-OAuth
feat: Add Feishu OAuth login & consumer-grade MCP
2025-07-22 09:41:09 +08:00
lcx 0e1efc9656 feat: Add Feishu OAuth login & consumer-grade MCP 2025-07-22 09:40:30 +08:00
Dot.L 061027aa36 Merge pull request #359 from APIParkLab/feature/liujian-1.9
update ap-account version
2025-07-21 18:51:07 +08:00
ningyv a083f994f4 Merge pull request #358 from APIParkLab/feature/1.9-OAuth
feat: Add Feishu OAuth login & consumer-grade MCP
2025-07-21 18:31:44 +08:00
lcx 5898337481 feat: Add Feishu OAuth login & consumer-grade MCP 2025-07-21 18:30:50 +08:00
ningyv 0f1496137d Merge pull request #357 from APIParkLab/feature/1.9-OAuth
feat: Add Feishu OAuth login & consumer-grade MCP
2025-07-21 18:14:47 +08:00
lcx aff2d1ce01 feat: Add Feishu OAuth login & consumer-grade MCP 2025-07-21 18:14:12 +08:00
Dot.L 78d10318ad Merge pull request #356 from APIParkLab/feature/liujian-1.9
Feature/liujian 1.9
2025-07-21 16:44:55 +08:00
28 changed files with 243 additions and 375 deletions
+54 -1
View File
@@ -156,6 +156,10 @@ curl -sSO https://download.apipark.com/install/quick-start.sh ; bash quick-sta
<br>
<br>
# 🚀 Use Cases
## Simplify AI Integration Costs
- Connect to 100+ major models from all mainstream AI vendors, with standardized API calls requiring no additional adaptation work.
@@ -199,11 +203,20 @@ To achieve this goal, we plan to add new features to APIPark, including:
# 📕 Documentation
Visit [APIPark Documentation](https://docs.apipark.com/docs/deploy) for detailed installation guides, API references, and usage instructions.
# 🧑‍🤝‍🧑Friendly Links
<a href="https://xroute.ai/">
<img width="1248" height="158" alt="新建 PPTX 演示文稿 (2)_03" src="https://github.com/user-attachments/assets/0ebd694c-410a-4e3f-a793-90f1140d15df" />
</a>
<br>
<br>
# 🧾 License
APIPark uses the Apache 2.0 License. For more details, please refer to the LICENSE file.
<br>
# 💌 Contact Us
@@ -212,6 +225,46 @@ For enterprise-level features and professional technical support, contact our pr
- Website: https://apipark.com
- Email: contact@apipark.com
🙏 A big thanks to everyone who helped shape APIPark. We are thrilled to hear the communitys thoughts! Lets make the world of APIs and AI stronger and more fun together. 🎉
<br>
🙏 A big thanks to everyone who helped shape APIPark. We are thrilled to hear the communitys thoughts! Lets make the world of APIs and AI stronger and more fun together. 🎉
# 🤝 Partner
- [Cursor](https://www.cursor.com/): Cursor is an AI-powered code editor that integrates artificial intelligence directly into the coding workflow, offering features like intelligent next edit suggestions, deep codebase understanding for relevant answers, and natural language editing to streamline development tasks and boost developer productivity.
- [Dify](https://dify.ai/): Dify is a leading Agentic AI Development Platform that provides a comprehensive suite of tools for building and extending AI applications, offering everything needed for agentic workflows, RAG pipelines, integrations, and observability, while allowing users to amplify their applications with various global Large Language Models (LLMs) and versatile plugins.
- [Trae](https://www.trae.ai/): Trae is an AI-native Integrated Development Environment (IDE) product that aims to embody the concept of “The Real AI Engineer” through intelligent productivity, seamlessly integrating into the development process to enhance quality and efficiency, featuring a chat-based interaction interface and supporting code generation and assistance.
- [Windsurf](https://windsurf.com/): Windsurf is an AI code editor designed to provide a seamless and limitless flow for developers, introducing a new purpose-built IDE that leverages AI to enhance coding with features like "Cascade" for deep codebase understanding, "Windsurf Tab" for intelligent autocompletion, and "Memories" for remembering important aspects of the codebase.
- [Coze](https://www.coze.com/): Coze is a next-generation AI application and chatbot development platform by ByteDance, empowering users to easily create and deploy powerful AI chatbots across various platforms with a no-code bot builder, integrated workflow logic, access to proprietary data, and simplified creation through pre-built plugins, knowledge bases, and workflows.
- [Claude Code](https://www.anthropic.com/claude-code): Claude Code is a command-line AI tool by Anthropic that embeds the Claude Opus 4 model directly into the users terminal, providing deep codebase awareness, the ability to edit files and execute commands, and making coordinated changes across multiple files, all while integrating seamlessly with popular IDEs and leveraging existing test suites.
- [Flowith](https://flowith.io/): Flowith is an AI creation workspace designed to revolutionize productivity and deep work by transforming knowledge and streamlining tasks through a multi-thread interface powered by advanced AI agents, offering an intuitive canvas-based user experience unlike traditional chat-based AI tools, and including a 24/7 operational version for complex tasks.
- [OpenManus](https://github.com/FoundationAgents/OpenManus): OpenManus is an open-source framework dedicated to building general AI agents, aiming to provide a platform where users can create and deploy their own agents without an invite code, supporting multi-agent capabilities, and requiring configuration for Large Language Model (LLM) APIs while integrating with browser automation tools.
- [Fellou](https://fellou.ai/): Fellou is an innovative Agentic Browser designed to transcend traditional web browsing by actively performing actions on behalf of the user, automating the entire process of information gathering and insight delivery, and excelling in in-depth research with seamless integrations with popular tools like Notion and LinkedIn.
- [Genspark](https://www.genspark.ai/): Genspark is an ultimate all-in-one AI companion offering a comprehensive suite of tools like AI Slides, AI Sheets, and AI Chat, designed to enhance various aspects of productivity and content creation, with personalized tools and AI Pods for generating content from diverse sources.
- [TEN](https://github.com/TEN-framework/ten-framework): TEN (The Embodied Narrator) is an open-source framework for building real-time, multimodal conversational voice AI agents, including components like TEN Framework, TEN Turn Detection, TEN Agent, TMAN Designer, and TEN Portal, offering features like Real-time Avatar, seamless MCP integration, real-time hardware communication, and vision/screenshare detection.
- [ChatGPT](https://chatgpt.com/): ChatGPT is an AI chatbot developed by OpenAI, built upon large language models like GPT-3.5 and GPT-4, designed to generate human-like conversational dialogue, understand context, answer follow-up questions, and integrate with various platforms for enhanced productivity through advanced language understanding, generation, and multilingual capabilities.
- [LangChain](https://www.langchain.com/): LangChain is a robust platform engineered for the development of reliable agents and Large Language Model (LLM) applications, offering a comprehensive product suite that seamlessly integrates various tools across the entire application development lifecycle, including LangGraph, LangSmith, and the LangGraph Platform, with functionalities for code generation, automation, and AI Search.
- [LEMON AI](https://lemonai.cc/): Lemon AI is the first Full-stack, Open-source, Agentic AI framework, offering a fully local alternative to platforms like Manus & Genspark AI. It features an integrated Code Interpreter VM sandbox for safe execution.
- [LobeChat](https://lobehub.com/): LobeHub offers LobeChat, a personal LLM productivity tool designed to elevate the user experience beyond traditional chatbots by empowering individuals to build personal AI agents and professional teams, supporting a wide array of LLMs, offering a simple chat interface, visual recognition, voice interaction, a rich plugin ecosystem, and knowledge base functionalities.
- [VS Code](https://code.visualstudio.com/): Visual Studio Code (VS Code) is a widely popular, free, and open-source code editor by Microsoft, renowned for its extensibility and customization, supporting vast programming languages, and integrating AI capabilities like intelligent next edit suggestions and an advanced “agent mode” for complex tasks, with broad compatibility with various AI models.
- [XRoute](https://xroute.ai): The Unified Interface For LLMs, provides better prices, better throughput, and no subscription.
- [XPack MCP Marketplace](https://github.com/xpack-ai/XPack-MCP-Marketplace): The world's first open-source MCP monetization platform, transform any OpenAPI into a monetizable MCP server and build your own API marketplace in just 10 minutes. Everything is open-source and ready for commercial use.
- [MemU](https://github.com/NevaMind-AI/memU): MemU is an open-source memory framework for AI companions
+1 -5
View File
@@ -113,13 +113,9 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
if input.AiModel.Type != "local" {
provider = input.AiModel.Provider
}
modelName := input.AiModel.Name
if modelName == "" {
modelName = input.AiModel.Id
}
proxy.Plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{
"model": modelName,
"model": input.AiModel.Name,
"provider": provider,
"config": input.AiModel.Config,
},
+32 -72
View File
@@ -21,14 +21,13 @@ import (
var _ IMcpController = (*imlMcpController)(nil)
type imlMcpController struct {
settingModule system.ISettingModule `autowired:""`
authorizationModule application_authorization.IAuthorizationModule `autowired:""`
appModule service.IAppModule `autowired:""`
mcpModule mcp.IMcpModule `autowired:""`
sessionKeys sync.Map
sseServers map[string]http.Handler
openSseServer http.Handler
openStreamableServer http.Handler
settingModule system.ISettingModule `autowired:""`
authorizationModule application_authorization.IAuthorizationModule `autowired:""`
appModule service.IAppModule `autowired:""`
mcpModule mcp.IMcpModule `autowired:""`
sessionKeys sync.Map
server map[string]http.Handler
openServer http.Handler
}
func (i *imlMcpController) AppMCPHandle(ctx *gin.Context) {
@@ -43,12 +42,12 @@ func (i *imlMcpController) AppMCPHandle(ctx *gin.Context) {
paths := strings.Split(req.URL.Path, "/")
req.URL.Path = fmt.Sprintf("/api/v1/%s/%s", mcp_server.GlobalBasePath, paths[len(paths)-1])
locale := utils.I18n(ctx)
if v, ok := i.sseServers[locale]; ok {
if v, ok := i.server[locale]; ok {
v.ServeHTTP(ctx.Writer, req)
return
}
i.sseServers[languageEnUs].ServeHTTP(ctx.Writer, req)
i.server[languageEnUs].ServeHTTP(ctx.Writer, req)
}
func (i *imlMcpController) AppHandleSSE(ctx *gin.Context) {
@@ -69,7 +68,7 @@ func (i *imlMcpController) AppHandleSSE(ctx *gin.Context) {
}
ctx.Request.URL.Path = fmt.Sprintf("/openapi/v1/%s/sse", mcp_server.GlobalBasePath)
i.handleSSE(ctx, i.openSseServer, SessionInfo{
i.handleSSE(ctx, i.openServer, SessionInfo{
Apikey: apikey,
App: appId,
})
@@ -82,29 +81,8 @@ func (i *imlMcpController) AppHandleMessage(ctx *gin.Context) {
return
}
ctx.Request.URL.Path = fmt.Sprintf("/openapi/v1/%s/message", mcp_server.GlobalBasePath)
//ctx.Request = ctx.Request.WithContext(utils.SetLabel(ctx.Request.Context(), "app", appId))
i.handleMessage(ctx, i.openSseServer)
}
func (i *imlMcpController) AppHandleStreamHTTP(ctx *gin.Context) {
apikey := ctx.Request.Header.Get("Authorization")
apikey = strings.TrimPrefix(apikey, "Bearer ")
if apikey == "" {
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid apikey", "success": "fail"})
return
}
appId := ctx.Request.Header.Get("X-Application-Id")
if appId == "" {
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid app id", "success": "fail"})
return
}
cfg := i.settingModule.Get(ctx)
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
req = req.WithContext(utils.SetLabel(req.Context(), "apikey", apikey))
req = req.WithContext(utils.SetLabel(req.Context(), "app", appId))
req.URL.Path = mcp_server.OpenGlobalMCPPath
i.openStreamableServer.ServeHTTP(ctx.Writer, req)
ctx.Request = ctx.Request.WithContext(utils.SetLabel(ctx.Request.Context(), "app", appId))
i.handleMessage(ctx, i.openServer)
}
func (i *imlMcpController) AppMCPConfig(ctx *gin.Context, appId string) (string, error) {
@@ -116,44 +94,36 @@ func (i *imlMcpController) AppMCPConfig(ctx *gin.Context, appId string) (string,
if err != nil {
return "", fmt.Errorf("get app info error: %v", err)
}
return mcp_server.NewMCPConfig(
mcp_server.TransportTypeStreamableHTTP,
fmt.Sprintf("%s%s", strings.TrimSuffix(cfg.SitePrefix, "/"), mcp_server.OpenAppMCPPath),
map[string]string{
"Authorization": "Bearer {your_api_key}",
"X-Application-Id": appId,
},
nil,
).ToString(appInfo.Name), nil
return fmt.Sprintf(mcpDefaultConfig, appInfo.Name, fmt.Sprintf("%s/openapi/v1/mcp/app/%s/sse?apikey={your_api_key}", strings.TrimSuffix(cfg.SitePrefix, "/"), appId)), nil
}
var mcpDefaultConfig = `{
"mcpServers": {
"%s": {
"url": "%s"
}
}
}
`
func (i *imlMcpController) GlobalMCPConfig(ctx *gin.Context) (string, error) {
cfg := i.settingModule.Get(ctx)
if cfg.SitePrefix == "" {
return "", fmt.Errorf("site prefix is empty")
}
return mcp_server.NewMCPConfig(
mcp_server.TransportTypeStreamableHTTP,
fmt.Sprintf("%s%s", strings.TrimSuffix(cfg.SitePrefix, "/"), mcp_server.OpenGlobalMCPPath),
map[string]string{
"Authorization": "Bearer {your_api_key}",
},
nil,
).ToString("APIPark-MCP-Server"), nil
return fmt.Sprintf(mcpDefaultConfig, "APIPark-MCP-Server", fmt.Sprintf("%s/openapi/v1/%s/sse?apikey={your_api_key}", strings.TrimSuffix(cfg.SitePrefix, "/"), mcp_server.GlobalBasePath)), nil
}
func (i *imlMcpController) OnComplete() {
i.sseServers = make(map[string]http.Handler)
i.server = make(map[string]http.Handler)
for language, tools := range mcpToolsByLanguage {
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
s.AddTool(tools[ToolServiceList], i.mcpModule.Services)
s.AddTool(tools[ToolOpenAPIDocument], i.mcpModule.APIs)
s.AddTool(tools[ToolInvokeAPI], i.mcpModule.Invoke)
i.sseServers[language] = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
i.server[language] = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
if language == languageEnUs {
i.openSseServer = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
i.openStreamableServer = server.NewStreamableHTTPServer(s, server.WithEndpointPath(mcp_server.OpenGlobalMCPPath))
i.openServer = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
}
}
}
@@ -162,16 +132,16 @@ func (i *imlMcpController) GlobalMCPHandle(ctx *gin.Context) {
cfg := i.settingModule.Get(ctx)
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
locale := utils.I18n(ctx)
if v, ok := i.sseServers[locale]; ok {
if v, ok := i.server[locale]; ok {
v.ServeHTTP(ctx.Writer, req)
return
}
i.sseServers[languageEnUs].ServeHTTP(ctx.Writer, req)
i.server[languageEnUs].ServeHTTP(ctx.Writer, req)
}
func (i *imlMcpController) GlobalHandleSSE(ctx *gin.Context) {
apikey := ctx.Request.URL.Query().Get("apikey")
i.handleSSE(ctx, i.openSseServer, SessionInfo{
i.handleSSE(ctx, i.openServer, SessionInfo{
Apikey: apikey,
})
}
@@ -197,16 +167,7 @@ func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler, sIn
}
func (i *imlMcpController) GlobalHandleMessage(ctx *gin.Context) {
i.handleMessage(ctx, i.openSseServer)
}
func (i *imlMcpController) GlobalHandleStreamHTTP(ctx *gin.Context) {
apikey := ctx.Request.Header.Get("Authorization")
apikey = strings.TrimPrefix(apikey, "Bearer ")
cfg := i.settingModule.Get(ctx)
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
req = req.WithContext(utils.SetLabel(req.Context(), "apikey", apikey))
i.openStreamableServer.ServeHTTP(ctx.Writer, req)
i.handleMessage(ctx, i.openServer)
}
func (i *imlMcpController) MCPHandle(ctx *gin.Context) {
@@ -243,13 +204,12 @@ func (i *imlMcpController) ServiceHandleMessage(ctx *gin.Context) {
}
func (i *imlMcpController) ServiceHandleStreamHTTP(ctx *gin.Context) {
apikey := ctx.Request.Header.Get("Authorization")
serviceId := ctx.Request.Header.Get("X-Service-Id")
apikey := ctx.Request.URL.Query().Get("apikey")
serviceId := ctx.Param("serviceId")
if serviceId == "" {
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid service id", "success": "fail"})
return
}
apikey = strings.TrimPrefix(apikey, "Bearer ")
ok, err := i.authorizationModule.CheckAPIKeyAuthorizationByService(ctx, serviceId, apikey)
if err != nil {
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": err.Error(), "success": "fail"})
-2
View File
@@ -13,13 +13,11 @@ type IMcpController interface {
GlobalMCPHandle(ctx *gin.Context)
GlobalHandleSSE(ctx *gin.Context)
GlobalHandleMessage(ctx *gin.Context)
GlobalHandleStreamHTTP(ctx *gin.Context)
GlobalMCPConfig(ctx *gin.Context) (string, error)
AppMCPHandle(ctx *gin.Context)
AppHandleSSE(ctx *gin.Context)
AppHandleMessage(ctx *gin.Context)
AppHandleStreamHTTP(ctx *gin.Context)
AppMCPConfig(ctx *gin.Context, appId string) (string, error)
ServiceHandleSSE(ctx *gin.Context)
+10 -16
View File
@@ -520,21 +520,16 @@ func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string,
modelId := ""
modelCfg := ""
modelType := "online"
if input.Model != nil {
modelId = *input.Model
}
if *input.Provider == ai_provider_local.ProviderLocal {
modelType = "local"
if modelId == "" {
list, err := i.aiLocalModel.SimpleList(ctx)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, fmt.Errorf("no local model")
}
modelId = list[0].Id
list, err := i.aiLocalModel.SimpleList(ctx)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, fmt.Errorf("no local model")
}
modelId = list[0].Id
modelCfg = ai_provider_local.LocalConfig
} else {
pv, err := i.providerModule.Provider(ctx, *input.Provider)
@@ -545,15 +540,14 @@ func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string,
if !has {
return nil, fmt.Errorf("provider not found")
}
if modelId == "" {
modelId = pv.DefaultLLM
}
m, has := p.GetModel(modelId)
m, has := p.GetModel(pv.DefaultLLM)
if !has {
return nil, fmt.Errorf("model %s not found", pv.DefaultLLM)
}
//modelId = m.ID()
modelId = m.Name()
modelCfg = m.DefaultConfig()
}
var info *service_dto.Service
+1 -1
View File
@@ -8,7 +8,7 @@
"description": "",
"scripts": {
"test": "jest",
"build": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=core --stream --verbose ",
"build": "set NODE_OPTIONS=--max-old-space-size=8192 && lerna run build --scope=core --stream --verbose ",
"serve": "lerna run preview --parallel",
"serve:remotes": "lerna run serve --scope=remote --parallel",
"dev": "lerna run dev --scope=core --stream",
@@ -19,7 +19,7 @@ export type MemberTableListItem = {
enable:boolean
departmentId:string
roles:EntityItem[]
form: string
from: string
};
export type AddToDepartmentProps = {
@@ -41,7 +41,7 @@ export type MemberDropdownModalFieldType = {
export type MemberDropdownModalProps = {
type:'addDep'|'addChild'|'addMember'|'editMember'|'rename'
entity?:(MemberTableListItem & {departmentIds:string[]}) | ({id?:string, departmentIds?:string[],name?:string,form?:string})
entity?:(MemberTableListItem & {departmentIds:string[]}) | ({id?:string, departmentIds?:string[],name?:string,from?:string})
selectedMemberGroupId?:string
}
+23 -3
View File
@@ -93,6 +93,11 @@ const Login: FC = () => {
const code = query.get('code')
if (code) {
feishuLogin(code)
setSpinning(false)
return
}
if (isInFeishuClient() && feishu) {
openFeishuLogin(feishu.config.client_id)
}
setSpinning(false)
}
@@ -161,10 +166,25 @@ const Login: FC = () => {
fetchLogin({ username: 'guest', password: '12345678' })
}
const isInFeishuClient = () => {
// 方法1:检查User-Agent
const ua = navigator.userAgent.toLowerCase();
const isLark = ua.includes('lark') || ua.includes('feishu');
// 方法2:检查全局对象
const hasSDK = typeof window.h5sdk !== 'undefined' || typeof window.tt !== 'undefined';
// 方法3:检查URL参数
const params = new URLSearchParams(window.location.search);
const hasFeishuParams = params.has('from') || params.has('required_launch_ability');
return isLark || hasSDK || hasFeishuParams;
}
// 打开飞书授权页面
const openFeishuLogin = () => {
const openFeishuLogin = (id?: string) => {
const href = window.location.origin + window.location.pathname
const authUrl = `https://accounts.feishu.cn/open-apis/authen/v1/authorize?client_id=${feishuAppId}&redirect_uri=${href}`
const authUrl = `https://accounts.feishu.cn/open-apis/authen/v1/authorize?client_id=${id || feishuAppId}&redirect_uri=${href}`
localStorage.setItem('feishuCallbackUrl', href)
window.location.href = authUrl
}
@@ -336,7 +356,7 @@ const Login: FC = () => {
loading={loading}
className="h-[40px] w-full inline-flex justify-center items-center"
type="default"
onClick={openFeishuLogin}
onClick={() => openFeishuLogin(feishuAppId)}
>
<img className="h-[30px]" src={FeishuLogo} />
{$t('飞书授权登录')}
@@ -14,8 +14,7 @@ export const MemberDropdownModal = forwardRef<MemberDropdownModalHandle,MemberDr
const {fetchData} = useFetch()
const [departmentList, setDepartmentList] = useState<DepartmentListItem[]>([])
const { state } = useGlobalContext()
const [disableEditMemberData] = useState<boolean>(entity?.form !== 'self-build')
const [disableEditMemberData] = useState<boolean>(entity?.from === 'feishu')
const save:()=>Promise<boolean | string> = ()=>{
let url:string
let method:string
@@ -95,14 +95,15 @@ const AddToDepartment = forwardRef<AddToDepartmentHandle, AddToDepartmentProps>(
treeData?.map((x: DataNode) => ({
...x,
name: $t((x as unknown as { name: string }).name),
checkable: false,
children: x.children?.map(y => ({ ...y, checkable: false }))
checkable: false, // 根节点不可选中
children: x.children?.map(y => ({ ...y, checkable: true })) // 子节点可以选中
})),
[state.language, treeData]
)
const onCheck: TreeProps['onCheck'] = (checkedKeys: string[]) => {
setSelectedKeys(checkedKeys.checked)
const onCheck: TreeProps['onCheck'] = (checkedKeys, info) => {
const selectedIds = Array.isArray(checkedKeys) ? checkedKeys : checkedKeys.checked || []
setSelectedKeys(selectedIds)
}
useEffect(() => {
@@ -153,11 +154,12 @@ const MemberList = () => {
const [tableHttpReload, setTableHttpReload] = useState(true)
const [tableListDataSource, setTableListDataSource] = useState<MemberTableListItem[]>([])
const pageListRef = useRef<ActionType>(null)
const { topGroupId, selectedDepartmentIds, refreshGroup } = useOutletContext<{
const { topGroupId, selectedDepartmentIds, refreshGroup, refreshTableCount } = useOutletContext<{
topGroupId: string
departmentList: DepartmentListItem[]
selectedDepartmentIds: string[]
refreshGroup: () => void
refreshTableCount: number
}>()
const AddMemberRef = useRef<MemberDropdownModalHandle>(null)
const EditMemberRef = useRef<MemberDropdownModalHandle>(null)
@@ -396,7 +398,7 @@ const MemberList = () => {
width: 600,
okText: $t('确认'),
okButtonProps: {
disabled: isActionAllowed(type) || (type === 'editMember' && entity?.form !== 'self-build')
disabled: isActionAllowed(type) || (type === 'editMember' && entity?.from === 'feishu')
},
cancelText: $t('取消'),
closable: true,
@@ -415,6 +417,13 @@ const MemberList = () => {
getDepartmentList()
}, [])
// 监听外部刷新触发器
useEffect(() => {
if (refreshTableCount > 0) {
manualReloadTable()
}
}, [refreshTableCount])
const getDepartmentList = async () => {
setDepartmentValueEnum([])
const { code, data, msg } = await fetchData<BasicResponse<{ department: DepartmentListItem }>>(
@@ -36,6 +36,11 @@ const MemberPage = ()=>{
const [selectedDepartmentId, setSelectedDepartmentId] = useState<string>('-1')
const {accessData,state} = useGlobalContext()
const [refreshMemberCount, setRefreshMemberCount] = useState<number>(0)
const [refreshTableCount, setRefreshTableCount] = useState<number>(0)
const refreshMemberTable = () => {
setRefreshTableCount(prev => prev + 1)
}
const onSearchWordChange = (e:string)=>{
setSearchWord(e || '')
}
@@ -90,7 +95,7 @@ const MemberPage = ()=>{
case 'addChild':
return AddChildRef.current?.save().then((res)=>{if(res === true)getDepartmentList()})
case 'addMember':
return AddMemberRef.current?.save().then((res)=>{if(res === true){getDepartmentList();setRefreshMemberCount(pre=>pre+1)}})
return AddMemberRef.current?.save().then((res)=>{if(res === true){getDepartmentList();setRefreshMemberCount(pre=>pre+1);refreshMemberTable()}})
case 'rename':
return RenameRef.current?.save().then((res)=>{if(res === true)getDepartmentList()})
case 'delete':
@@ -262,7 +267,7 @@ const MemberPage = ()=>{
</div>
</div>
<div className="flex-1 p-btnbase pr-PAGE_INSIDE_X overflow-x-hidden">
<Outlet context={{refreshMemberCount, selectedDepartmentIds,refreshGroup:()=>getDepartmentList()}}/>
<Outlet context={{refreshMemberCount, selectedDepartmentIds,refreshGroup:()=>getDepartmentList(), refreshTableCount}}/>
</div>
</div>
</InsidePage>);
@@ -20,7 +20,7 @@ export default function ManagementInsidePage() {
const { message } = App.useApp()
const { fetchData } = useFetch()
const { setBreadcrumb } = useBreadcrumb()
const [activeMenu, setActiveMenu] = useState<string>('service')
const [activeMenu, setActiveMenu] = useState<string>('authorization')
const { appId, teamId } = useParams<RouterParams>()
const navigateTo = useNavigate()
const currentUrl = useLocation().pathname
@@ -56,7 +56,7 @@ export default function ManagementInsidePage() {
}, [accessData, accessInit, TENANT_MANAGEMENT_APP_MENU])
useEffect(() => {
setActiveMenu(currentUrl.split('/').pop() || 'service')
setActiveMenu(currentUrl.split('/').pop() || 'authorization')
}, [currentUrl])
const onMenuClick: MenuProps['onClick'] = (node) => {
@@ -336,7 +336,7 @@ export default function ServiceHubManagement() {
setTableSearchWord={setTableSearchWord}
editApp={(row: ServiceHubAppListItem) => {
setAppName(row.name)
navigateTo(`/consumer/${row.team.id}/inside/${row.id}/service`)
navigateTo(`/consumer/${row.team.id}/inside/${row.id}/authorization`)
}}
/>
)}
-28
View File
@@ -1,28 +0,0 @@
package auth
func init() {
b := NewAKSK()
Register(b.Name(), b)
}
func NewAKSK() *AKSK {
return &AKSK{}
}
type AKSK struct {
}
func (a *AKSK) Name() string {
return "aksk"
}
func (a *AKSK) ToPattern(cfg map[string]interface{}) interface{} {
result := make(map[string]interface{})
result["ak"] = cfg["ak"]
result["sk"] = cfg["sk"]
return result
}
func (a *AKSK) ToConfig(cfg map[string]interface{}) interface{} {
return nil
}
-35
View File
@@ -1,35 +0,0 @@
package auth
func init() {
b := NewJWT()
Register(b.Name(), b)
}
func NewJWT() *JWT {
return &JWT{}
}
type JWT struct {
}
func (J *JWT) Name() string {
return "jwt"
}
func (J *JWT) ToPattern(cfg map[string]interface{}) interface{} {
result := make(map[string]interface{})
result["username"] = cfg["user"]
return result
}
func (J *JWT) ToConfig(cfg map[string]interface{}) interface{} {
result := make(map[string]interface{})
result["iss"] = cfg["iss"]
result["algorithm"] = cfg["algorithm"]
result["secret"] = cfg["secret"]
result["rsa_public_key"] = cfg["publicKey"]
result["path"] = cfg["userPath"]
result["claims_to_verify"] = cfg["claimsToVerify"]
result["signature_is_base_64"] = cfg["signatureIsBase64"]
return result
}
-29
View File
@@ -1,29 +0,0 @@
package auth
func init() {
b := NewOAuth2()
Register(b.Name(), b)
}
func NewOAuth2() *OAuth2 {
return &OAuth2{}
}
type OAuth2 struct {
}
func (o *OAuth2) Name() string {
return "oauth2"
}
func (o *OAuth2) ToPattern(cfg map[string]interface{}) interface{} {
result := make(map[string]interface{})
result["client_id"] = cfg["client_id"]
result["client_secret"] = cfg["client_secret"]
result["client_type"] = cfg["client_type"]
result["hash_secret"] = cfg["hash_secret"]
result["redirect_urls"] = cfg["redirect_urls"]
return result
}
func (o *OAuth2) ToConfig(cfg map[string]interface{}) interface{} {
return nil
}
+3 -5
View File
@@ -15,7 +15,7 @@ require (
github.com/go-sql-driver/mysql v1.7.0
github.com/google/uuid v1.6.0
github.com/influxdata/influxdb-client-go/v2 v2.14.0
github.com/mark3labs/mcp-go v0.42.0-beta.3
github.com/mark3labs/mcp-go v0.33.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nsqio/go-nsq v1.1.0
github.com/ollama/ollama v0.5.8
@@ -27,8 +27,6 @@ require (
require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
@@ -48,7 +46,6 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
@@ -70,7 +67,6 @@ require (
github.com/spf13/cast v1.7.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
go.uber.org/atomic v1.7.0 // indirect
@@ -91,4 +87,6 @@ require (
//)
//replace github.com/eolinker/ap-account => ../../eolinker/ap-account
//
//replace github.com/eolinker/go-common => ../../eolinker/go-common
+2 -10
View File
@@ -2,8 +2,6 @@ github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
@@ -11,8 +9,6 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
@@ -84,8 +80,6 @@ github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjw
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -107,8 +101,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mark3labs/mcp-go v0.42.0-beta.3 h1:nmOg1HFgSOgy0bZkAQ+E6qVpPMPmE8hIkM0BO94Ks9k=
github.com/mark3labs/mcp-go v0.42.0-beta.3/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/mark3labs/mcp-go v0.33.0 h1:naxhjnTIs/tyPZmWUZFuG0lDmdA6sUyYGGf3gsHvTCc=
github.com/mark3labs/mcp-go v0.33.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -165,8 +159,6 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=
github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg=
-36
View File
@@ -1,36 +0,0 @@
package mcp_server
import "encoding/json"
type TransportType string
const (
TransportTypeStreamableHTTP TransportType = "streamable-http"
TransportTypeSSE TransportType = "sse"
)
func NewMCPConfig(typ TransportType, url string, headers map[string]string, alwaysAllow []string) *MCPConfig {
return &MCPConfig{
Type: typ,
URL: url,
Headers: headers,
AlwaysAllow: alwaysAllow,
}
}
type MCPConfig struct {
Type TransportType `json:"type"`
URL string `json:"url"`
Headers map[string]string `json:"headers,omitempty"`
AlwaysAllow []string `json:"alwaysAllow,omitempty"`
}
func (c *MCPConfig) ToString(name string) string {
m := map[string]interface{}{
"mcpServers": map[string]interface{}{
name: c,
},
}
data, _ := json.MarshalIndent(m, "", "\t")
return string(data)
}
+6 -21
View File
@@ -17,10 +17,6 @@ var (
ServiceBasePath = "mcp/service"
GlobalBasePath = "mcp/global"
AppBasePath = "mcp/app"
OpenGlobalMCPPath = "/openapi/v1/global/mcp"
OpenAppMCPPath = "/openapi/v1/app/mcp"
OpenServiceMCPPath = "/openapi/v1/service/mcp"
)
func NewServer() *Server {
@@ -65,7 +61,7 @@ func (s *Server) Set(id string, ser *server.MCPServer) {
}
tmp.handlers["api-sse"] = server.NewSSEServer(ser, server.WithStaticBasePath(fmt.Sprintf("/api/v1/%s/%s", ServiceBasePath, id)))
tmp.handlers["openapi-sse"] = server.NewSSEServer(ser, server.WithStaticBasePath(fmt.Sprintf("/openapi/v1/%s/%s", ServiceBasePath, id)))
tmp.handlers["openapi-stream"] = server.NewStreamableHTTPServer(ser, server.WithEndpointPath(OpenServiceMCPPath))
tmp.handlers["openapi-stream"] = server.NewStreamableHTTPServer(ser, server.WithEndpointPath(fmt.Sprintf("/openapi/v1/%s/%s/mcp", ServiceBasePath, id)))
s.servers[id] = tmp
}
@@ -95,23 +91,12 @@ func (s *Server) Get(id string) (*Handler, bool) {
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var sid string
if r.URL.Path == OpenServiceMCPPath {
sid = r.Header.Get("X-Service-Id")
if sid == "" {
http.NotFound(w, r)
return
}
} else {
id, err := genPath(r.URL.Path)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
sid = id
sid, err := genPath(r.URL.Path)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
ser, has := s.Get(sid)
if has {
ser.ServeHTTP(w, r)
@@ -6,11 +6,11 @@ import (
"fmt"
"strconv"
"strings"
auth_driver "github.com/APIParkLab/APIPark/module/application-authorization/auth-driver"
"github.com/eolinker/go-common/utils"
application_authorization_dto "github.com/APIParkLab/APIPark/module/application-authorization/dto"
)
@@ -26,12 +26,12 @@ type Config struct {
Iss string `json:"iss"`
Algorithm string `json:"algorithm"`
Secret string `json:"secret"`
PublicKey string `json:"publicKey"`
PublicKey string `json:"public_key"`
User string `json:"user"`
UserPath string `json:"userPath"`
ClaimsToVerify []string `json:"claimsToVerify"`
UserPath string `json:"user_path"`
ClaimsToVerify []string `json:"claims_to_verify"`
Label map[string]string `json:"label"`
SignatureIsBase64 bool `json:"signatureIsBase64"`
SignatureIsBase64 bool `json:"signature_is_base64"`
}
func (cfg *Config) ID() string {
@@ -46,7 +46,7 @@ func (cfg *Config) ID() string {
for _, claim := range cfg.ClaimsToVerify {
builder.WriteString(strings.TrimSpace(claim))
}
case "RS256", "RS384", "RS512", "ES256", "ES384", "ES512":
builder.WriteString(strings.TrimSpace(cfg.Iss))
builder.WriteString(strings.TrimSpace(cfg.PublicKey))
@@ -81,7 +81,7 @@ func (cfg *Config) Valid() ([]byte, error) {
default:
return nil, fmt.Errorf("unsupport algorithm")
}
//校验 校验字段
for _, claim := range cfg.ClaimsToVerify {
switch claim {
@@ -94,7 +94,7 @@ func (cfg *Config) Valid() ([]byte, error) {
}
func (cfg *Config) Detail() []application_authorization_dto.DetailItem {
items := []application_authorization_dto.DetailItem{
{Key: "Iss", Value: cfg.Iss},
{Key: "签名算法", Value: cfg.Algorithm},
@@ -102,7 +102,7 @@ func (cfg *Config) Detail() []application_authorization_dto.DetailItem {
{Key: "用户名JsonPath", Value: cfg.UserPath},
{Key: "校验字段", Value: strings.Join(cfg.ClaimsToVerify, ",")},
}
switch cfg.Algorithm {
case "HS256", "HS384", "HS512":
items = append(items, application_authorization_dto.DetailItem{Key: "Secret", Value: cfg.Secret})
@@ -110,10 +110,10 @@ func (cfg *Config) Detail() []application_authorization_dto.DetailItem {
if cfg.SignatureIsBase64 {
base64 = "true"
}
items = append(items, application_authorization_dto.DetailItem{Key: "SignatureIsBase64", Value: base64})
items = append(items, application_authorization_dto.DetailItem{Key: "Secret", Value: base64})
default:
items = append(items, application_authorization_dto.DetailItem{Key: "RSA公钥", Value: cfg.PublicKey})
}
return items
}
+38 -44
View File
@@ -219,19 +219,19 @@ func (i *imlAuthorizationModule) initGateway(ctx context.Context, partitionId st
return clientDriver.Application().Online(ctx, applications...)
}
func (i *imlAuthorizationModule) getApplicationRelease(ctx context.Context, s *service.Service) (*gateway.ApplicationRelease, error) {
func (i *imlAuthorizationModule) online(ctx context.Context, s *service.Service) error {
clusters, err := i.clusterService.List(ctx)
if err != nil {
return err
}
if len(clusters) < 1 {
return nil
}
authorizations, err := i.authorizationService.ListByApp(ctx, s.Id)
if err != nil {
return nil, err
return err
}
if len(authorizations) < 1 {
return &gateway.ApplicationRelease{
BasicItem: &gateway.BasicItem{
ID: s.Id,
},
}, nil
}
return &gateway.ApplicationRelease{
app := &gateway.ApplicationRelease{
BasicItem: &gateway.BasicItem{
ID: s.Id,
Description: s.Description,
@@ -256,40 +256,10 @@ func (i *imlAuthorizationModule) getApplicationRelease(ctx context.Context, s *s
},
}
}),
}, nil
}
func (i *imlAuthorizationModule) doOffline(ctx context.Context, clusterId string, app *gateway.ApplicationRelease) error {
client, err := i.clusterService.GatewayClient(ctx, clusterId)
if err != nil {
return err
}
defer func() {
_ = client.Close(ctx)
}()
return client.Application().Offline(ctx, app)
}
func (i *imlAuthorizationModule) online(ctx context.Context, s *service.Service) error {
clusters, err := i.clusterService.List(ctx)
if err != nil {
return err
}
if len(clusters) < 1 {
return nil
}
release, err := i.getApplicationRelease(ctx, s)
if err != nil {
return err
}
for _, c := range clusters {
if len(release.Authorizations) < 1 {
err = i.doOffline(ctx, c.Uuid, release)
if err != nil {
log.Warnf("service authorization offline for cluster[%s] %v", c.Name, err)
}
continue
}
err = i.doOnline(ctx, c.Uuid, release)
err := i.doOnline(ctx, c.Uuid, app)
if err != nil {
log.Warnf("service authorization online for cluster[%s] %v", c.Name, err)
}
@@ -405,7 +375,7 @@ func (i *imlAuthorizationModule) EditAuthorization(ctx context.Context, appId st
}
func (i *imlAuthorizationModule) DeleteAuthorization(ctx context.Context, pid string, aid string) error {
s, err := i.serviceService.Get(ctx, pid)
_, err := i.serviceService.Get(ctx, pid)
if err != nil {
return err
}
@@ -415,11 +385,35 @@ func (i *imlAuthorizationModule) DeleteAuthorization(ctx context.Context, pid st
if err != nil {
return err
}
return i.online(ctx, s)
clusters, err := i.clusterService.List(ctx)
if err != nil {
return err
}
app := &gateway.ApplicationRelease{
BasicItem: &gateway.BasicItem{
ID: pid,
},
}
for _, c := range clusters {
err := i.doOffline(ctx, c.Uuid, app)
if err != nil {
log.Warnf("service authorization offline for cluster[%s] %v", c.Name, err)
}
}
return nil
})
}
func (i *imlAuthorizationModule) doOffline(ctx context.Context, clusterId string, app *gateway.ApplicationRelease) error {
client, err := i.clusterService.GatewayClient(ctx, clusterId)
if err != nil {
return err
}
defer func() {
_ = client.Close(ctx)
}()
return client.Application().Offline(ctx, app)
}
func (i *imlAuthorizationModule) Authorizations(ctx context.Context, pid string) ([]*application_authorization_dto.AuthorizationItem, error) {
_, err := i.serviceService.Get(ctx, pid)
if err != nil {
+13 -11
View File
@@ -9,10 +9,10 @@ import (
"strings"
"time"
mcp_server "github.com/APIParkLab/APIPark/mcp-server"
service_overview "github.com/APIParkLab/APIPark/service/service-overview"
mcp_server "github.com/APIParkLab/APIPark/mcp-server"
"github.com/APIParkLab/APIPark/module/monitor/driver"
"github.com/APIParkLab/APIPark/service/monitor"
@@ -326,6 +326,15 @@ func (i *imlCatalogueModule) Subscribe(ctx context.Context, subscribeInfo *catal
})
}
var mcpDefaultConfig = `{
"mcpServers": {
"%s": {
"url": "%s"
}
}
}
`
func (i *imlCatalogueModule) ServiceDetail(ctx context.Context, sid string) (*catalogue_dto.ServiceDetail, error) {
// 获取服务的基本信息
s, err := i.serviceService.Get(ctx, sid)
@@ -391,15 +400,8 @@ func (i *imlCatalogueModule) ServiceDetail(ctx context.Context, sid string) (*ca
mcpAccessConfig := ""
if s.EnableMCP {
if sitePrefix != "" {
mcpAccessConfig = mcp_server.NewMCPConfig(
mcp_server.TransportTypeStreamableHTTP,
fmt.Sprintf("%s/openapi/v1/service/mcp", strings.TrimSuffix(sitePrefix, "/")),
map[string]string{
"Authorization": "Bearer {your_api_key}",
"X-Service-Id": s.Id,
},
nil,
).ToString(fmt.Sprintf("APIPark/%s", s.Name))
mcpAccessAddress = fmt.Sprintf("%s/openapi/v1/%s/%s/sse?apikey={your_api_key}", strings.TrimSuffix(sitePrefix, "/"), mcp_server.ServiceBasePath, s.Id)
mcpAccessConfig = fmt.Sprintf(mcpDefaultConfig, fmt.Sprintf("APIPark/%s", s.Name), mcpAccessAddress)
}
}
invokeMap, err := i.ProviderStatistics(ctx, time.Now().Add(-24*30*time.Hour), time.Now(), s.Id)
-4
View File
@@ -17,16 +17,12 @@ func (p *plugin) mcpAPIs() []pm3.Api {
globalMessagePath := fmt.Sprintf("/api/v1/%s/message", mcp_server.GlobalBasePath)
appSSEPath := fmt.Sprintf("/api/v1/%s/sse", mcp_server.AppBasePath)
appMessagePath := fmt.Sprintf("/api/v1/%s/message", mcp_server.AppBasePath)
globalMcpPath := fmt.Sprintf("/api/v1/%s/mcp", mcp_server.GlobalBasePath)
appMcpPath := fmt.Sprintf("/api/v1/%s/mcp", mcp_server.AppBasePath)
ignore.IgnorePath("login", http.MethodGet, serviceSSEPath)
ignore.IgnorePath("login", http.MethodPost, serviceMessagePath)
ignore.IgnorePath("login", http.MethodGet, globalSSEPath)
ignore.IgnorePath("login", http.MethodPost, globalMessagePath)
ignore.IgnorePath("login", http.MethodGet, appSSEPath)
ignore.IgnorePath("login", http.MethodPost, appMessagePath)
ignore.IgnorePath("login", http.MethodGet, globalMcpPath)
ignore.IgnorePath("login", http.MethodGet, appMcpPath)
return []pm3.Api{
pm3.CreateApiSimple(http.MethodGet, serviceSSEPath, p.mcpController.MCPHandle),
pm3.CreateApiSimple(http.MethodPost, serviceMessagePath, p.mcpController.MCPHandle),
-1
View File
@@ -60,7 +60,6 @@ func (o *openapiCheck) Handler(ginCtx *gin.Context) {
}
authorization = apikey
}
authorization = strings.TrimPrefix(authorization, "Bearer ")
if authorization == o.apikey {
return
}
+8 -21
View File
@@ -19,22 +19,17 @@ func (p *plugin) mcpAPIs() []pm3.Api {
serviceSSEPath := fmt.Sprintf("/openapi/v1/%s/:serviceId/sse", strings.Trim(mcp_server.ServiceBasePath, "/"))
serviceMessagePath := fmt.Sprintf("/openapi/v1/%s/:serviceId/message", strings.Trim(mcp_server.ServiceBasePath, "/"))
//serviceStreamablePath := fmt.Sprintf("/openapi/v1/%s/:serviceId/mcp", strings.Trim(mcp_server.ServiceBasePath, "/"))
serviceStreamablePath := fmt.Sprintf("/openapi/v1/%s/:serviceId/mcp", strings.Trim(mcp_server.ServiceBasePath, "/"))
ignore.IgnorePath("openapi", http.MethodPost, globalMessagePath)
//ignore.IgnorePath("openapi", http.MethodGet, globalSSEPath)
ignore.IgnorePath("openapi", http.MethodGet, globalSSEPath)
ignore.IgnorePath("openapi", http.MethodPost, appMessagePath)
ignore.IgnorePath("openapi", http.MethodGet, appSSEPath)
ignore.IgnorePath("openapi", http.MethodGet, serviceSSEPath)
ignore.IgnorePath("openapi", http.MethodPost, serviceMessagePath)
ignore.IgnorePath("openapi", http.MethodGet, mcp_server.OpenAppMCPPath)
ignore.IgnorePath("openapi", http.MethodPost, mcp_server.OpenAppMCPPath)
ignore.IgnorePath("openapi", http.MethodDelete, mcp_server.OpenAppMCPPath)
ignore.IgnorePath("openapi", http.MethodGet, mcp_server.OpenServiceMCPPath)
ignore.IgnorePath("openapi", http.MethodPost, mcp_server.OpenServiceMCPPath)
ignore.IgnorePath("openapi", http.MethodDelete, mcp_server.OpenServiceMCPPath)
ignore.IgnorePath("openapi", http.MethodGet, serviceStreamablePath)
ignore.IgnorePath("openapi", http.MethodPost, serviceStreamablePath)
ignore.IgnorePath("openapi", http.MethodDelete, serviceStreamablePath)
return []pm3.Api{
pm3.CreateApiSimple(http.MethodGet, globalSSEPath, p.mcpController.GlobalHandleSSE),
@@ -46,17 +41,9 @@ func (p *plugin) mcpAPIs() []pm3.Api {
pm3.CreateApiSimple(http.MethodGet, serviceSSEPath, p.mcpController.ServiceHandleSSE),
pm3.CreateApiSimple(http.MethodPost, serviceMessagePath, p.mcpController.ServiceHandleMessage),
pm3.CreateApiSimple(http.MethodGet, mcp_server.OpenGlobalMCPPath, p.mcpController.GlobalHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodPost, mcp_server.OpenGlobalMCPPath, p.mcpController.GlobalHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodDelete, mcp_server.OpenGlobalMCPPath, p.mcpController.GlobalHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodGet, mcp_server.OpenAppMCPPath, p.mcpController.AppHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodPost, mcp_server.OpenAppMCPPath, p.mcpController.AppHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodDelete, mcp_server.OpenAppMCPPath, p.mcpController.AppHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodPost, mcp_server.OpenServiceMCPPath, p.mcpController.ServiceHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodDelete, mcp_server.OpenServiceMCPPath, p.mcpController.ServiceHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodGet, mcp_server.OpenServiceMCPPath, p.mcpController.ServiceHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodPost, serviceStreamablePath, p.mcpController.ServiceHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodDelete, serviceStreamablePath, p.mcpController.ServiceHandleStreamHTTP),
pm3.CreateApiSimple(http.MethodGet, serviceStreamablePath, p.mcpController.ServiceHandleStreamHTTP),
}
}
+10
View File
@@ -202,6 +202,16 @@ curl -sSO https://download.apipark.com/install/quick-start.sh; bash quick-start.
# 📕文档
访问 [APIPark文档](https://docs.apipark.com/docs/deploy) 获取详细的安装指南、API 参考和使用说明。
<br>
友情链接
<a href="https://xroute.ai/">
<img width="1248" height="158" alt="新建 PPTX 演示文稿 (2)_02(1)" src="https://github.com/user-attachments/assets/3e1cd0c1-c4c3-4f0c-8649-810d76f3166b" />
</a>
<br>
+1 -2
View File
@@ -3,11 +3,10 @@ package commit
import (
"context"
"errors"
"github.com/eolinker/go-common/utils"
"strings"
"time"
"github.com/eolinker/go-common/utils"
"github.com/eolinker/go-common/store"
"github.com/google/uuid"
"gorm.io/gorm"