>('catalogues', {
method: 'GET'
})
- .then(response => {
+ .then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setGData(data.catalogues)
@@ -308,11 +290,7 @@ export default function ServiceCategory() {
return (
- }
- spinning={loading}
- className=""
- >
+ } spinning={loading} className="">
((props, ref) => {
- const { message } = App.useApp()
- const [form] = Form.useForm()
- const { type, entity } = props
- const { fetchData } = useFetch()
+export const ServiceHubCategoryConfig = forwardRef(
+ (props, ref) => {
+ const { message } = App.useApp()
+ const [form] = Form.useForm()
+ const { type, entity } = props
+ const { fetchData } = useFetch()
- const save: () => Promise = () => {
- const url: string = 'catalogue'
- let method: string
- switch (type) {
- case 'addCate':
- case 'addChildCate':
- method = 'POST'
- break
- case 'renameCate':
- method = 'PUT'
- break
- }
- return new Promise((resolve, reject) => {
- if (!url || !method) {
- reject($t(RESPONSE_TIPS.error))
- return
+ const save: () => Promise = () => {
+ const url: string = 'catalogue'
+ let method: string
+ switch (type) {
+ case 'addCate':
+ case 'addChildCate':
+ method = 'POST'
+ break
+ case 'renameCate':
+ method = 'PUT'
+ break
}
- form
- .validateFields()
- .then(value => {
- fetchData>(url, {
- method,
- eoBody: value,
- eoParams: { ...(type === 'renameCate' ? { catalogue: value.id } : undefined) }
- })
- .then(response => {
- const { code, msg } = response
- if (code === STATUS_CODE.SUCCESS) {
- message.success(msg || $t(RESPONSE_TIPS.success))
- resolve(true)
- } else {
- message.error(msg || $t(RESPONSE_TIPS.error))
- reject(msg || $t(RESPONSE_TIPS.error))
- }
+ return new Promise((resolve, reject) => {
+ if (!url || !method) {
+ reject($t(RESPONSE_TIPS.error))
+ return
+ }
+ form
+ .validateFields()
+ .then((value) => {
+ fetchData>(url, {
+ method,
+ eoBody: value,
+ eoParams: { ...(type === 'renameCate' ? { catalogue: value.id } : undefined) }
})
- .catch(errorInfo => reject(errorInfo))
- })
- .catch(errorInfo => reject(errorInfo))
- })
- }
-
- useImperativeHandle(ref, () => ({
- save
- }))
-
- useEffect(() => {
- switch (type) {
- case 'addCate':
- form.setFieldsValue({})
- break
- case 'addChildCate':
- form.setFieldsValue({ parent: entity!.id })
- break
- case 'renameCate':
- form.setFieldsValue(entity)
- break
+ .then((response) => {
+ const { code, msg } = response
+ if (code === STATUS_CODE.SUCCESS) {
+ message.success(msg || $t(RESPONSE_TIPS.success))
+ resolve(true)
+ } else {
+ message.error(msg || $t(RESPONSE_TIPS.error))
+ reject(msg || $t(RESPONSE_TIPS.error))
+ }
+ })
+ .catch((errorInfo) => reject(errorInfo))
+ })
+ .catch((errorInfo) => reject(errorInfo))
+ })
}
- }, [])
- return (
- ({
+ save
+ }))
+
+ useEffect(() => {
+ switch (type) {
+ case 'addCate':
+ form.setFieldsValue({})
+ break
+ case 'addChildCate':
+ form.setFieldsValue({ parent: entity!.id })
+ break
+ case 'renameCate':
+ form.setFieldsValue(entity)
+ break
}
- >
-
- label={$t('ID')}
- name="id"
- hidden
- rules={[{ required: true, whitespace: true }]}
- >
-
-
- )}
- {(type === 'addCate' || type === 'renameCate') && (
-
- label={$t('分类名称')}
- name="name"
- rules={[{ required: true, whitespace: true }]}
- >
-
-
- )}
+ }, [])
- {type === 'addChildCate' && (
- <>
+ return (
+
+
- label={$t('父分类 ID')}
- name="parent"
+ label={$t('ID')}
+ name="id"
hidden
rules={[{ required: true, whitespace: true }]}
>
-
+ )}
+ {(type === 'addCate' || type === 'renameCate') && (
- label={$t('子分类名称')}
+ label={$t('分类名称')}
name="name"
rules={[{ required: true, whitespace: true }]}
>
- >
- )}
-
-
- )
-})
+ )}
+
+ {type === 'addChildCate' && (
+ <>
+
+ label={$t('父分类 ID')}
+ name="parent"
+ hidden
+ rules={[{ required: true, whitespace: true }]}
+ >
+
+
+
+
+ label={$t('子分类名称')}
+ name="name"
+ rules={[{ required: true, whitespace: true }]}
+ >
+
+
+ >
+ )}
+
+
+ )
+ }
+)
diff --git a/frontend/packages/market/src/const/serviceHub/type.ts b/frontend/packages/market/src/const/serviceHub/type.ts
index e973d88d..6e02a06b 100644
--- a/frontend/packages/market/src/const/serviceHub/type.ts
+++ b/frontend/packages/market/src/const/serviceHub/type.ts
@@ -1,7 +1,6 @@
-import { DefaultOptionType } from 'antd/es/select'
import { EntityItem } from '@common/const/type'
import { SubscribeEnum, SubscribeFromEnum } from '@core/const/system/const'
-import WithPermission from '@common/components/aoplatform/WithPermission'
+import { DefaultOptionType } from 'antd/es/select'
export type ServiceBasicInfoType = {
app: EntityItem
@@ -37,7 +36,6 @@ export type ServiceHubCategoryConfigFieldType = {
export type ServiceHubCategoryConfigProps = {
type: 'addCate' | 'addChildCate' | 'renameCate'
entity?: { [k: string]: unknown }
- WithPermission: typeof WithPermission
}
export type ServiceHubCategoryConfigHandle = {
diff --git a/frontend/packages/market/src/pages/serviceHub/management/ServiceHubManagement.tsx b/frontend/packages/market/src/pages/serviceHub/management/ServiceHubManagement.tsx
index ef5f5ea2..8f0fe92d 100644
--- a/frontend/packages/market/src/pages/serviceHub/management/ServiceHubManagement.tsx
+++ b/frontend/packages/market/src/pages/serviceHub/management/ServiceHubManagement.tsx
@@ -5,7 +5,7 @@ import WithPermission from '@common/components/aoplatform/WithPermission'
import { BasicResponse, DATA_SHOW_TYPE_OPTIONS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { SimpleTeamItem } from '@common/const/type'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
-import { GlobalProvider, useGlobalContext } from '@common/contexts/GlobalStateContext'
+import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales'
import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
@@ -158,28 +158,8 @@ export default function ServiceHubManagement() {
switch (type) {
case 'add':
title = $t('添加消费者')
- content = (
-
-
-
- )
+ content =
break
- // case 'edit':{
- // title='配置 Open Api'
- // message.loading('正在加载数据')
- // const {code,data,msg} = await fetchData>('external-app',{method:'GET',eoParams:{id:entity!.id}})
- // message.destroy()
- // if(code === STATUS_CODE.SUCCESS){
- // content=
- // }else{
- // message.error(msg || $t(RESPONSE_TIPS.error))
- // return
- // }
- // break;}
- // case 'delete':
- // title='删除'
- // content='该数据删除后将无法找回,请确认是否删除?'
- // break;
}
modal.confirm({
diff --git a/gateway/apinto/entity/router.go b/gateway/apinto/entity/router.go
index 45406b28..664f0eb3 100644
--- a/gateway/apinto/entity/router.go
+++ b/gateway/apinto/entity/router.go
@@ -161,7 +161,7 @@ func ToRouter(r *gateway.ApiRelease, version string, matches map[string]string)
labels = r.Labels
}
- return &Router{
+ router := &Router{
BasicInfo: &BasicInfo{
ID: fmt.Sprintf("%s@router", r.ID),
Name: r.ID,
@@ -174,13 +174,16 @@ func ToRouter(r *gateway.ApiRelease, version string, matches map[string]string)
Method: r.Methods,
Location: r.Path,
Rules: rules,
- Service: fmt.Sprintf("%s@service", r.Service),
Plugins: plugin,
Retry: r.Retry,
TimeOut: r.Timeout,
Labels: labels,
Protocols: []string{"http", "https"},
}
+ if r.Service != "" {
+ router.Service = fmt.Sprintf("%s@service", r.Service)
+ }
+ return router
}
// formatProxyPath 格式化转发路径上,用于转发重写插件正则替换 比如 请求路径 /path/{A}/{B} 原转发路径:/path/{B} 格式化后 新转发路径: /path/$2
diff --git a/gateway/apinto/plugin/apinto_plugin.yml b/gateway/apinto/plugin/apinto_plugin.yml
index 4bd3765a..b3c3c6d8 100644
--- a/gateway/apinto/plugin/apinto_plugin.yml
+++ b/gateway/apinto/plugin/apinto_plugin.yml
@@ -28,7 +28,7 @@
b: "subscription_service:#{application}"
response:
status_code: 403
- content_typ: "text/plan"
+ content_type: "text/plan"
charset: "utf-8"
body: "Forbidden"
diff --git a/gateway/profession.go b/gateway/profession.go
index 606a4904..16956421 100644
--- a/gateway/profession.go
+++ b/gateway/profession.go
@@ -8,6 +8,7 @@ const (
ProfessionStrategy = "strategy"
ProfessionService = "service"
ProfessionAIProvider = "ai-provider"
+ ProfessionAIResource = "ai-resource"
)
func RegisterDynamicResourceDriver(key string, worker Worker) {
@@ -61,6 +62,14 @@ var dynamicResourceMap = map[string]Worker{
Profession: ProfessionOutput,
Driver: "loki",
},
+ "ai-provider": {
+ Profession: ProfessionAIResource,
+ Driver: "ai-provider",
+ },
+ "ai-key": {
+ Profession: ProfessionAIResource,
+ Driver: "ai-key",
+ },
}
type Worker struct {
diff --git a/go.mod b/go.mod
index 1d48fab3..fb4396bf 100644
--- a/go.mod
+++ b/go.mod
@@ -7,12 +7,14 @@ go 1.21
require (
github.com/eolinker/ap-account v1.0.15
github.com/eolinker/eosc v0.18.3
- github.com/eolinker/go-common v1.1.1
+ github.com/eolinker/go-common v1.1.4
github.com/gabriel-vasile/mimetype v1.4.4
github.com/getkin/kin-openapi v0.127.0
github.com/gin-gonic/gin v1.10.0
+ 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/nsqio/go-nsq v1.1.0
github.com/urfave/cli v1.22.16
golang.org/x/crypto v0.24.0
gopkg.in/yaml.v3 v3.0.1
@@ -37,8 +39,8 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
- github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
@@ -81,4 +83,4 @@ require (
//replace github.com/eolinker/ap-account => ../aoaccount
//
-//replace github.com/eolinker/go-common => ../go-common
+//replace github.com/eolinker/go-common => ../../eolinker/go-common
diff --git a/go.sum b/go.sum
index 9bc1954c..01c9b855 100644
--- a/go.sum
+++ b/go.sum
@@ -32,8 +32,8 @@ github.com/eolinker/ap-account v1.0.15 h1:n6DJeL6RHZ8eLlZUcY2U3H4d/GPaA5oelAx3R0
github.com/eolinker/ap-account v1.0.15/go.mod h1:zm/Ivs6waJ/M/nEszhpPmM6g50y/MKO+5eABFAdeD0g=
github.com/eolinker/eosc v0.18.3 h1:3IK5HkAPnJRfLbQ0FR7kWsZr6Y/OiqqGazvN1q2BL5A=
github.com/eolinker/eosc v0.18.3/go.mod h1:O9PQQXFCpB6fjHf+oFt/LN6EOAv779ItbMixMKCfTfk=
-github.com/eolinker/go-common v1.1.1 h1:3WqqecGqcHDgpa8Ljp156c1uWeZKP1CKScdU+6sOfcc=
-github.com/eolinker/go-common v1.1.1/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4=
+github.com/eolinker/go-common v1.1.4 h1:U5AtQMr3RCudgeV6jcX5TemUGrTz8WqLu//KrZm3BzA=
+github.com/eolinker/go-common v1.1.4/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY=
@@ -64,6 +64,9 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -107,6 +110,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/nsqio/go-nsq v1.1.0 h1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=
+github.com/nsqio/go-nsq v1.1.0/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
diff --git a/init.go b/init.go
index 74592a3a..af7b6ca9 100644
--- a/init.go
+++ b/init.go
@@ -5,6 +5,7 @@ import (
_ "github.com/APIParkLab/APIPark/frontend"
_ "github.com/APIParkLab/APIPark/gateway/apinto"
_ "github.com/APIParkLab/APIPark/plugins/core"
+ _ "github.com/APIParkLab/APIPark/plugins/openapi"
_ "github.com/APIParkLab/APIPark/plugins/permit"
_ "github.com/APIParkLab/APIPark/plugins/publish_flow"
_ "github.com/APIParkLab/APIPark/resources/locale"
diff --git a/module/ai-api/dto/input.go b/module/ai-api/dto/input.go
index be94417e..fe71acac 100644
--- a/module/ai-api/dto/input.go
+++ b/module/ai-api/dto/input.go
@@ -5,7 +5,7 @@ type CreateAPI struct {
Name string `json:"name"`
Path string `json:"path"`
Description string `json:"description"`
- Disable bool `json:"disable"`
+ Disable bool `json:"disabled"`
AiPrompt *AiPrompt `json:"ai_prompt"`
AiModel *AiModel `json:"ai_model"`
Timeout int `json:"timeout"`
@@ -33,7 +33,7 @@ type EditAPI struct {
Name *string `json:"name"`
Path *string `json:"path"`
Description *string `json:"description"`
- Disable *bool `json:"disable"`
+ Disable *bool `json:"disabled"`
AiPrompt *AiPrompt `json:"ai_prompt"`
AiModel *AiModel `json:"ai_model"`
Timeout *int `json:"timeout"`
diff --git a/module/ai-api/dto/output.go b/module/ai-api/dto/output.go
index 912bb878..a00c6690 100644
--- a/module/ai-api/dto/output.go
+++ b/module/ai-api/dto/output.go
@@ -9,7 +9,7 @@ type API struct {
Name string `json:"name"`
Path string `json:"path"`
Description string `json:"description"`
- Disable bool `json:"disable"`
+ Disable bool `json:"disabled"`
AiPrompt *AiPrompt `json:"ai_prompt"`
AiModel *AiModel `json:"ai_model"`
Timeout int `json:"timeout"`
@@ -21,7 +21,7 @@ type APIItem struct {
Name string `json:"name"`
RequestPath string `json:"request_path"`
Description string `json:"description"`
- Disable bool `json:"disable"`
+ Disable bool `json:"disabled"`
Creator auto.Label `json:"creator" aolabel:"user"`
Updater auto.Label `json:"updater" aolabel:"user"`
CreateTime auto.TimeLabel `json:"create_time"`
diff --git a/module/ai-api/iml.go b/module/ai-api/iml.go
index 55a60fc2..12994aa2 100644
--- a/module/ai-api/iml.go
+++ b/module/ai-api/iml.go
@@ -8,6 +8,8 @@ import (
"net/http"
"strings"
+ "github.com/eolinker/eosc/log"
+
model_runtime "github.com/APIParkLab/APIPark/ai-provider/model-runtime"
ai_api_dto "github.com/APIParkLab/APIPark/module/ai-api/dto"
@@ -110,10 +112,12 @@ func (i *imlAPIModule) Create(ctx context.Context, serviceId string, input *ai_a
Name: input.Name,
Service: serviceId,
Path: input.Path,
+ Disable: input.Disable,
Description: input.Description,
Timeout: input.Timeout,
Retry: input.Retry,
Model: input.AiModel.Id,
+ Provider: input.AiModel.Provider,
AdditionalConfig: map[string]interface{}{
"ai_prompt": input.AiPrompt,
"ai_model": input.AiModel,
@@ -148,8 +152,10 @@ func (i *imlAPIModule) Edit(ctx context.Context, serviceId string, apiId string,
return err
}
var modelId *string
+ var providerId *string
if input.AiModel != nil {
modelId = &input.AiModel.Id
+ providerId = &input.AiModel.Provider
}
if input.AiPrompt != nil {
apiInfo.AdditionalConfig["ai_prompt"] = input.AiPrompt
@@ -164,7 +170,9 @@ func (i *imlAPIModule) Edit(ctx context.Context, serviceId string, apiId string,
Timeout: input.Timeout,
Retry: input.Retry,
Model: modelId,
+ Provider: providerId,
AdditionalConfig: &apiInfo.AdditionalConfig,
+ Disable: input.Disable,
})
})
}
@@ -304,3 +312,27 @@ func (i *imlAPIModule) Prefix(ctx context.Context, serviceId string) (string, er
}
return strings.TrimSuffix(pInfo.Prefix, "/"), nil
}
+
+func (i *imlAPIModule) OnInit() {
+ ctx := context.Background()
+ list, err := i.aiAPIService.List(ctx)
+ if err != nil {
+ return
+ }
+ for _, item := range list {
+ if item.Provider == "" {
+ aiModel, err := ConvertStruct[ai_api_dto.AiModel](item.AdditionalConfig["ai_model"])
+ if err != nil {
+ log.Errorf("convert ai model error:%v", err)
+ continue
+ }
+ err = i.aiAPIService.Save(ctx, item.ID, &ai_api.Edit{
+ Provider: &aiModel.Provider,
+ })
+ if err != nil {
+ log.Errorf("update ai api provider error:%v", err)
+ continue
+ }
+ }
+ }
+}
diff --git a/module/ai-api/module.go b/module/ai-api/module.go
index 1119a2e0..b42714f5 100644
--- a/module/ai-api/module.go
+++ b/module/ai-api/module.go
@@ -2,9 +2,10 @@ package ai_api
import (
"context"
+ "reflect"
+
ai_api_dto "github.com/APIParkLab/APIPark/module/ai-api/dto"
"github.com/eolinker/go-common/autowire"
- "reflect"
)
type IAPIModule interface {
diff --git a/module/ai-key/dto/enum.go b/module/ai-key/dto/enum.go
new file mode 100644
index 00000000..29a26280
--- /dev/null
+++ b/module/ai-key/dto/enum.go
@@ -0,0 +1,49 @@
+package ai_key_dto
+
+var (
+ KeyNormal KeyStatus = "normal"
+ KeyExceed KeyStatus = "exceeded"
+ KeyExpired KeyStatus = "expired"
+ KeyDisable KeyStatus = "disabled"
+ KeyError KeyStatus = "error"
+)
+
+type KeyStatus string
+
+func (s KeyStatus) String() string {
+ return string(s)
+}
+
+func (s KeyStatus) Int() int {
+ switch s {
+ case KeyDisable:
+ return 0
+ case KeyNormal:
+ return 1
+ case KeyError:
+ return 2
+ case KeyExceed:
+ return 3
+ case KeyExpired:
+ return 4
+ default:
+ return 0
+ }
+}
+
+func ToKeyStatus(status int) KeyStatus {
+ switch status {
+ case 0:
+ return KeyDisable
+ case 1:
+ return KeyNormal
+ case 2:
+ return KeyError
+ case 3:
+ return KeyExceed
+ case 4:
+ return KeyExpired
+ default:
+ return KeyDisable
+ }
+}
diff --git a/module/ai-key/dto/input.go b/module/ai-key/dto/input.go
new file mode 100644
index 00000000..2559a4ee
--- /dev/null
+++ b/module/ai-key/dto/input.go
@@ -0,0 +1,20 @@
+package ai_key_dto
+
+type Create struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Config string `json:"config"`
+ ExpireTime int `json:"expire_time"`
+}
+
+type Edit struct {
+ Name *string `json:"name"`
+ Config *string `json:"config"`
+ ExpireTime *int `json:"expire_time"`
+}
+
+type Sort struct {
+ Origin string `json:"origin"`
+ Target string `json:"target"`
+ Sort string `json:"sort"`
+}
diff --git a/module/ai-key/dto/output.go b/module/ai-key/dto/output.go
new file mode 100644
index 00000000..4e1da2c9
--- /dev/null
+++ b/module/ai-key/dto/output.go
@@ -0,0 +1,21 @@
+package ai_key_dto
+
+import "github.com/eolinker/go-common/auto"
+
+type Item struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Status KeyStatus `json:"status"`
+ UseToken int `json:"use_token"`
+ UpdateTime auto.TimeLabel `json:"update_time"`
+ ExpireTime int `json:"expire_time"`
+ Priority int `json:"priority"`
+ CanDelete bool `json:"can_delete"`
+}
+
+type Key struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Config string `json:"config"`
+ ExpireTime int `json:"expire_time"`
+}
diff --git a/module/ai-key/iml.go b/module/ai-key/iml.go
new file mode 100644
index 00000000..21cf216e
--- /dev/null
+++ b/module/ai-key/iml.go
@@ -0,0 +1,447 @@
+package ai_key
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/APIParkLab/APIPark/service/cluster"
+ "github.com/eolinker/eosc/log"
+
+ "github.com/APIParkLab/APIPark/gateway"
+
+ "github.com/eolinker/go-common/utils"
+
+ "gorm.io/gorm"
+
+ "github.com/eolinker/go-common/auto"
+
+ model_runtime "github.com/APIParkLab/APIPark/ai-provider/model-runtime"
+
+ "github.com/google/uuid"
+
+ "github.com/eolinker/go-common/store"
+
+ "github.com/APIParkLab/APIPark/service/ai"
+
+ ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
+ ai_key "github.com/APIParkLab/APIPark/service/ai-key"
+)
+
+var _ IKeyModule = &imlKeyModule{}
+
+type imlKeyModule struct {
+ providerService ai.IProviderService `autowired:""`
+ aiKeyService ai_key.IKeyService `autowired:""`
+ clusterService cluster.IClusterService `autowired:""`
+ transaction store.ITransaction `autowired:""`
+}
+
+func newKey(key *ai_key.Key) *gateway.DynamicRelease {
+
+ return &gateway.DynamicRelease{
+ BasicItem: &gateway.BasicItem{
+ ID: fmt.Sprintf("%s-%s", key.Provider, key.ID),
+ Description: key.Name,
+ Resource: "ai-key",
+ Version: time.Now().Format("20060102150405"),
+ MatchLabels: map[string]string{
+ "module": "ai-key",
+ },
+ },
+ Attr: map[string]interface{}{
+ "expired": key.ExpireTime,
+ "config": key.Config,
+ "provider": key.Provider,
+ "priority": key.Priority,
+ "disabled": key.Status == 0,
+ },
+ }
+}
+
+func (i *imlKeyModule) Create(ctx context.Context, providerId string, input *ai_key_dto.Create) error {
+ _, err := i.providerService.Get(ctx, providerId)
+ if err != nil {
+ return fmt.Errorf("provider not found: %w", err)
+ }
+ p, has := model_runtime.GetProvider(providerId)
+ if !has {
+ return fmt.Errorf("provider not found: %w", err)
+ }
+ p.URI()
+ err = p.Check(input.Config)
+ if err != nil {
+ return fmt.Errorf("config check failed: %w", err)
+ }
+ priority, err := i.aiKeyService.MaxPriority(ctx, providerId)
+ if err != nil {
+ return fmt.Errorf("get key error: %v", err)
+ }
+ return i.transaction.Transaction(ctx, func(ctx context.Context) error {
+ if input.Id == "" {
+ input.Id = uuid.NewString()
+ }
+ status := ai_key_dto.KeyNormal.Int()
+ if input.ExpireTime > 0 && time.Unix(int64(input.ExpireTime), 0).Before(time.Now()) {
+ status = ai_key_dto.KeyExpired.Int()
+ }
+
+ err = i.aiKeyService.Create(ctx, &ai_key.Create{
+ ID: input.Id,
+ Name: input.Name,
+ Config: input.Config,
+ Provider: providerId,
+ Status: status,
+ ExpireTime: input.ExpireTime,
+ Priority: priority + 1,
+ })
+
+ info, _ := i.aiKeyService.Get(ctx, input.Id)
+ releases := []*gateway.DynamicRelease{newKey(info)}
+ return i.syncGateway(ctx, cluster.DefaultClusterID, releases, true)
+ })
+}
+
+func (i *imlKeyModule) syncGateway(ctx context.Context, clusterId string, releases []*gateway.DynamicRelease, online bool) error {
+ client, err := i.clusterService.GatewayClient(ctx, clusterId)
+ if err != nil {
+ log.Errorf("get apinto client error: %v", err)
+ return nil
+ }
+ defer func() {
+ err := client.Close(ctx)
+ if err != nil {
+ log.Warn("close apinto client:", err)
+ }
+ }()
+ for _, releaseInfo := range releases {
+ dynamicClient, err := client.Dynamic(releaseInfo.Resource)
+ if err != nil {
+ return err
+ }
+ if online {
+ err = dynamicClient.Online(ctx, releaseInfo)
+ } else {
+ err = dynamicClient.Offline(ctx, releaseInfo)
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (i *imlKeyModule) Edit(ctx context.Context, providerId string, id string, input *ai_key_dto.Edit) error {
+ p, has := model_runtime.GetProvider(providerId)
+ if !has {
+ return fmt.Errorf("provider not found: %s", providerId)
+ }
+ _, err := i.providerService.Get(ctx, providerId)
+ if err != nil {
+ return fmt.Errorf("provider not found: %w", err)
+ }
+ return i.transaction.Transaction(ctx, func(ctx context.Context) error {
+ info, err := i.aiKeyService.Get(ctx, id)
+ if err != nil {
+ return fmt.Errorf("key not found: %w", err)
+ }
+ if input.Config != nil {
+ err = p.Check(*input.Config)
+ if err != nil {
+ return fmt.Errorf("config check failed: %w", err)
+ }
+ cfg, err := p.GenConfig(*input.Config, info.Config)
+ if err != nil {
+ return fmt.Errorf("config gen failed: %w", err)
+ }
+ input.Config = &cfg
+ if info.Default {
+ err = i.providerService.Save(ctx, info.Provider, &ai.SetProvider{
+ Config: input.Config,
+ })
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ status := ai_key_dto.KeyNormal.Int()
+ orgStatus := ai_key_dto.ToKeyStatus(info.Status)
+ switch orgStatus {
+ case ai_key_dto.KeyNormal, ai_key_dto.KeyError, ai_key_dto.KeyExpired:
+ if input.ExpireTime != nil {
+ expireTime := *input.ExpireTime
+ if expireTime > 0 && time.Unix(int64(expireTime), 0).Before(time.Now()) {
+ status = ai_key_dto.KeyExpired.Int()
+ }
+ } else if info.ExpireTime > 0 && time.Unix(int64(info.ExpireTime), 0).Before(time.Now()) {
+ // 如果过期时间未更改,且已过期,则设置为过期状态
+ status = ai_key_dto.KeyExpired.Int()
+ }
+ default:
+ // 停用、超额需要启用,所以维持原状态
+ status = orgStatus.Int()
+ }
+
+ err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{
+ Name: input.Name,
+ Config: input.Config,
+ ExpireTime: input.ExpireTime,
+ Status: &status,
+ })
+ if err != nil {
+ return err
+ }
+ if status == ai_key_dto.KeyNormal.Int() {
+ info, err = i.aiKeyService.Get(ctx, id)
+ if err != nil {
+ return err
+ }
+ releases := []*gateway.DynamicRelease{newKey(info)}
+ return i.syncGateway(ctx, cluster.DefaultClusterID, releases, true)
+ }
+ return nil
+ })
+
+}
+
+func (i *imlKeyModule) Delete(ctx context.Context, providerId string, id string) error {
+ _, err := i.providerService.Get(ctx, providerId)
+ if err != nil {
+ return fmt.Errorf("provider not found: %w", err)
+ }
+ return i.transaction.Transaction(ctx, func(ctx context.Context) error {
+ info, err := i.aiKeyService.Get(ctx, id)
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil
+ }
+ return err
+ }
+ if info.Default {
+ return fmt.Errorf("default key can't be deleted: %s", id)
+ }
+ keys, err := i.aiKeyService.KeysAfterPriority(ctx, providerId, info.Priority)
+ if err != nil {
+ return err
+ }
+ for _, key := range keys {
+ key.Priority--
+ err = i.aiKeyService.Save(ctx, key.ID, &ai_key.Edit{
+ Priority: &key.Priority,
+ })
+ if err != nil {
+ return err
+ }
+ }
+
+ err = i.aiKeyService.Delete(ctx, id)
+ if err != nil {
+ return err
+ }
+ return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{{
+ BasicItem: &gateway.BasicItem{
+ ID: fmt.Sprintf("%s-%s", providerId, id),
+ Resource: "ai-key",
+ },
+ Attr: nil,
+ },
+ }, false)
+ })
+}
+
+func (i *imlKeyModule) Get(ctx context.Context, providerId string, id string) (*ai_key_dto.Key, error) {
+ p, has := model_runtime.GetProvider(providerId)
+ if !has {
+ return nil, fmt.Errorf("provider not found: %s", providerId)
+ }
+ _, err := i.providerService.Get(ctx, providerId)
+ if err != nil {
+ return nil, fmt.Errorf("provider not found: %w", err)
+ }
+ info, err := i.aiKeyService.Get(ctx, id)
+ if err != nil {
+ return nil, fmt.Errorf("key not found: %w", err)
+ }
+
+ return &ai_key_dto.Key{
+ Id: info.ID,
+ Name: info.Name,
+ Config: p.MaskConfig(info.Config),
+ ExpireTime: info.ExpireTime,
+ }, nil
+}
+
+func (i *imlKeyModule) List(ctx context.Context, providerId string, keyword string, page, pageSize int, statuses []string) ([]*ai_key_dto.Item, int64, error) {
+ _, err := i.aiKeyService.DefaultKey(ctx, providerId)
+ if err != nil {
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, 0, fmt.Errorf("get default key failed: %w", err)
+ }
+ info, err := i.providerService.Get(ctx, providerId)
+ if err != nil {
+ return nil, 0, fmt.Errorf("provider is unconfigued,id is %s", providerId)
+ }
+ err = i.aiKeyService.Create(ctx, &ai_key.Create{
+ ID: info.Id,
+ Name: info.Name,
+ Config: info.Config,
+ Provider: info.Id,
+ Status: ai_key_dto.KeyNormal.Int(),
+ Priority: 1,
+ ExpireTime: 0,
+ UseToken: 0,
+ Default: true,
+ })
+ if err != nil {
+ return nil, 0, fmt.Errorf("create default key failed: %w", err)
+ }
+ }
+ w := map[string]interface{}{
+ "provider": providerId,
+ }
+ hasExpired := true
+ if len(statuses) > 0 {
+ hasExpired = false
+ statusConditions := make([]int, 0, len(statuses))
+ for _, s := range statuses {
+ status := ai_key_dto.KeyStatus(s)
+ if status == ai_key_dto.KeyExpired {
+ hasExpired = true
+ }
+ statusConditions = append(statusConditions, status.Int())
+ }
+ w["status"] = statusConditions
+ }
+ var list []*ai_key.Key
+ var total int64
+ if !hasExpired {
+ if keyword != "" {
+ list, err = i.aiKeyService.Search(ctx, keyword, w, "sort asc")
+ if err != nil {
+ return nil, 0, err
+ }
+ if len(list) == 0 {
+ return nil, 0, nil
+ }
+ uuids := utils.SliceToSlice(list, func(key *ai_key.Key) string {
+ return key.ID
+ })
+ w["uuid"] = uuids
+ }
+ list, total, err = i.aiKeyService.SearchUnExpiredByPage(ctx, w, page, pageSize, "sort asc")
+ if err != nil {
+ return nil, 0, err
+ }
+ } else {
+ list, total, err = i.aiKeyService.SearchByPage(ctx, keyword, w, page, pageSize, "sort asc")
+ if err != nil {
+ return nil, 0, err
+ }
+ }
+
+ var result []*ai_key_dto.Item
+ for _, item := range list {
+ status := ai_key_dto.ToKeyStatus(item.Status)
+ if item.ExpireTime > 0 && time.Unix(int64(item.ExpireTime), 0).Before(time.Now()) {
+ status = ai_key_dto.KeyExpired
+ }
+ result = append(result, &ai_key_dto.Item{
+ Id: item.ID,
+ Name: item.Name,
+ Status: status,
+ UseToken: item.UseToken,
+ UpdateTime: auto.TimeLabel(item.UpdateAt),
+ ExpireTime: item.ExpireTime,
+ CanDelete: !item.Default,
+ Priority: item.Priority,
+ })
+ }
+
+ return result, total, nil
+}
+
+func (i *imlKeyModule) UpdateKeyStatus(ctx context.Context, providerId string, id string, enable bool) error {
+ _, err := i.providerService.Get(ctx, providerId)
+ if err != nil {
+ return fmt.Errorf("provider not found: %w", err)
+ }
+ info, err := i.aiKeyService.Get(ctx, id)
+ if err != nil {
+ return fmt.Errorf("key not found: %w", err)
+ }
+ return i.transaction.Transaction(ctx, func(ctx context.Context) error {
+ if !enable {
+ status := ai_key_dto.KeyDisable.Int()
+ err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{
+ Status: &status,
+ })
+ if err != nil {
+ return err
+ }
+ releases := []*gateway.DynamicRelease{{
+ BasicItem: &gateway.BasicItem{
+ ID: id,
+ Resource: "ai-key",
+ },
+ Attr: nil,
+ }}
+ return i.syncGateway(ctx, providerId, releases, false)
+ }
+ if info.Status == ai_key_dto.KeyDisable.Int() || info.Status == ai_key_dto.KeyExceed.Int() {
+ // 超额 或 停用状态,可启用
+ if info.ExpireTime > 0 && time.Unix(int64(info.ExpireTime), 0).Before(time.Now()) {
+ // 如果过期时间未更改,且已过期,则设置为过期状态
+ status := ai_key_dto.KeyExpired.Int()
+ return i.aiKeyService.Save(ctx, id, &ai_key.Edit{
+ Status: &status,
+ })
+ }
+ status := ai_key_dto.KeyNormal.Int()
+ err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{
+ Status: &status,
+ })
+ if err != nil {
+ return err
+ }
+ info, err = i.aiKeyService.Get(ctx, id)
+ if err != nil {
+ return err
+ }
+ releases := []*gateway.DynamicRelease{newKey(info)}
+ return i.syncGateway(ctx, providerId, releases, true)
+ }
+ return nil
+ })
+}
+
+func (i *imlKeyModule) Sort(ctx context.Context, providerId string, input *ai_key_dto.Sort) error {
+ _, err := i.providerService.Get(ctx, providerId)
+ if err != nil {
+ return fmt.Errorf("provider not found: %w", err)
+ }
+ return i.transaction.Transaction(ctx, func(ctx context.Context) error {
+ switch input.Sort {
+ case "before":
+ _, err = i.aiKeyService.SortBefore(ctx, providerId, input.Origin, input.Target)
+ case "after":
+ _, err = i.aiKeyService.SortAfter(ctx, providerId, input.Origin, input.Target)
+ default:
+ return fmt.Errorf("invalid sort type: %s", input.Sort)
+ }
+ if err != nil {
+ return err
+ }
+ list, err := i.aiKeyService.KeysByProvider(ctx, providerId)
+ if err != nil {
+ return err
+ }
+ releases := make([]*gateway.DynamicRelease, 0, len(list))
+ for _, info := range list {
+ releases = append(releases, newKey(info))
+ }
+ return i.syncGateway(ctx, cluster.DefaultClusterID, releases, true)
+ })
+}
diff --git a/module/ai-key/module.go b/module/ai-key/module.go
new file mode 100644
index 00000000..30ddebbb
--- /dev/null
+++ b/module/ai-key/module.go
@@ -0,0 +1,25 @@
+package ai_key
+
+import (
+ "context"
+ "reflect"
+
+ ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
+ "github.com/eolinker/go-common/autowire"
+)
+
+type IKeyModule interface {
+ Create(ctx context.Context, providerId string, input *ai_key_dto.Create) error
+ Edit(ctx context.Context, providerId string, id string, input *ai_key_dto.Edit) error
+ Delete(ctx context.Context, providerId string, id string) error
+ Get(ctx context.Context, providerId string, id string) (*ai_key_dto.Key, error)
+ List(ctx context.Context, providerId string, keyword string, page, pageSize int, statuses []string) ([]*ai_key_dto.Item, int64, error)
+ UpdateKeyStatus(ctx context.Context, providerId string, id string, enable bool) error
+ Sort(ctx context.Context, providerId string, input *ai_key_dto.Sort) error
+}
+
+func init() {
+ autowire.Auto[IKeyModule](func() reflect.Value {
+ return reflect.ValueOf(new(imlKeyModule))
+ })
+}
diff --git a/module/ai/dto/enum.go b/module/ai/dto/enum.go
new file mode 100644
index 00000000..2cc76b6f
--- /dev/null
+++ b/module/ai/dto/enum.go
@@ -0,0 +1,39 @@
+package ai_dto
+
+var (
+ ProviderEnabled ProviderStatus = "enabled"
+ ProviderDisabled ProviderStatus = "disabled"
+ ProviderAbnormal ProviderStatus = "abnormal"
+)
+
+type ProviderStatus string
+
+func (p ProviderStatus) Int() int {
+ switch p {
+ case ProviderAbnormal:
+ return 2
+ case ProviderEnabled:
+ return 1
+ case ProviderDisabled:
+ return 0
+ default:
+ return 1
+ }
+}
+
+func (p ProviderStatus) String() string {
+ return string(p)
+}
+
+func ToProviderStatus(status int) ProviderStatus {
+ switch status {
+ case 2:
+ return ProviderAbnormal
+ case 0:
+ return ProviderDisabled
+ case 1:
+ return ProviderEnabled
+ default:
+ return ProviderEnabled
+ }
+}
diff --git a/module/ai/dto/input.go b/module/ai/dto/input.go
index 8f1b4c48..93a112f8 100644
--- a/module/ai/dto/input.go
+++ b/module/ai/dto/input.go
@@ -7,4 +7,10 @@ type UpdateLLM struct {
type UpdateConfig struct {
DefaultLLM string `json:"default_llm"`
Config string `json:"config"`
+ Priority *int `json:"priority"`
+ Enable *bool `json:"enable"`
+}
+
+type Sort struct {
+ Providers []string `json:"providers"`
}
diff --git a/module/ai/dto/output.go b/module/ai/dto/output.go
index c21f7ceb..83914ae6 100644
--- a/module/ai/dto/output.go
+++ b/module/ai/dto/output.go
@@ -1,33 +1,71 @@
package ai_dto
-import "time"
+import (
+ "github.com/eolinker/go-common/auto"
+)
+
+type SimpleProvider struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ DefaultConfig string `json:"default_config"`
+ Logo string `json:"logo"`
+ GetAPIKeyUrl string `json:"get_apikey_url"`
+}
type Provider struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Config string `json:"config"`
- GetAPIKeyUrl string `json:"get_apikey_url"`
- DefaultLLM string `json:"defaultLLM"`
- DefaultLLMConfig string `json:"-"`
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Config string `json:"config"`
+ GetAPIKeyUrl string `json:"get_apikey_url"`
+ DefaultLLM string `json:"default_llm"`
+ DefaultLLMConfig string `json:"-"`
+ Priority int `json:"priority"`
+ Status ProviderStatus `json:"status"`
+ Configured bool `json:"configured"`
+}
+
+type ConfiguredProviderItem struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Logo string `json:"logo"`
+ DefaultLLM string `json:"default_llm"`
+ Status ProviderStatus `json:"status"`
+ APICount int64 `json:"api_count"`
+ KeyCount int `json:"key_count"`
+ KeyStatus []*KeyStatus `json:"keys"`
+ Priority int `json:"priority"`
+}
+
+type KeyStatus struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Status string `json:"status"`
+ Priority int `json:"-"`
}
type ProviderItem struct {
- Id string `json:"id"`
- Name string `json:"name"`
- DefaultLLM string `json:"default_llm"`
- DefaultLLMLogo string `json:"default_llm_logo"`
- Logo string `json:"logo"`
- Configured bool `json:"configured"`
- Recommend bool `json:"recommend"`
- Sort int `json:"sort"`
- UpdateTime time.Time `json:"-"`
-}
-
-type SimpleProviderItem struct {
Id string `json:"id"`
Name string `json:"name"`
Logo string `json:"logo"`
- Configured bool `json:"configured"`
+ DefaultLLM string `json:"default_llm"`
+ Sort int `json:"-"`
+}
+
+type SimpleProviderItem struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Logo string `json:"logo"`
+ Configured bool `json:"configured"`
+ DefaultConfig string `json:"default_config"`
+ Status ProviderStatus `json:"status"`
+ Model *BasicInfo `json:"model,omitempty"`
+ Priority int `json:"-"`
+}
+
+type BackupProvider struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Model *BasicInfo `json:"model,omitempty"`
}
type LLMItem struct {
@@ -36,3 +74,26 @@ type LLMItem struct {
Config string `json:"config"`
Scopes []string `json:"scopes"`
}
+
+type APIItem struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Service auto.Label `json:"service" aolabel:"service"`
+ Team auto.Label `json:"team" aolabel:"team"`
+ Method string `json:"method"`
+ RequestPath string `json:"request_path"`
+ Model auto.Label `json:"model"`
+ UpdateTime auto.TimeLabel `json:"update_time"`
+ UseToken int `json:"use_token"`
+ Disable bool `json:"disable"`
+}
+
+type Condition struct {
+ Models []*BasicInfo `json:"models"`
+ Services []*BasicInfo `json:"services"`
+}
+
+type BasicInfo struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+}
diff --git a/module/ai/iml.go b/module/ai/iml.go
index f830a8ac..201ff398 100644
--- a/module/ai/iml.go
+++ b/module/ai/iml.go
@@ -2,12 +2,22 @@ package ai
import (
"context"
- "encoding/json"
"errors"
"fmt"
+ "net/http"
"sort"
"time"
+ "github.com/APIParkLab/APIPark/service/service"
+
+ ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
+
+ ai_key "github.com/APIParkLab/APIPark/service/ai-key"
+
+ "github.com/eolinker/go-common/auto"
+
+ ai_api "github.com/APIParkLab/APIPark/service/ai-api"
+
model_runtime "github.com/APIParkLab/APIPark/ai-provider/model-runtime"
"github.com/APIParkLab/APIPark/gateway"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
@@ -19,24 +29,24 @@ import (
"gorm.io/gorm"
)
-func newAIUpstream(provider string, uri model_runtime.IProviderURI) *gateway.DynamicRelease {
+func newKey(key *ai_key.Key) *gateway.DynamicRelease {
+
return &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
- ID: provider,
- Description: fmt.Sprintf("auto create by ai provider %s", provider),
- Resource: "service",
+ ID: fmt.Sprintf("%s-%s", key.Provider, key.ID),
+ Description: key.Name,
+ Resource: "ai-key",
Version: time.Now().Format("20060102150405"),
MatchLabels: map[string]string{
- "module": "service",
+ "module": "ai-key",
},
},
Attr: map[string]interface{}{
- "driver": "http",
- "balance": "round-robin",
- "nodes": []string{fmt.Sprintf("%s weight=100", uri.Host())},
- "pass_host": "node",
- "scheme": uri.Scheme(),
- "timeout": 300000,
+ "expired": key.ExpireTime,
+ "config": key.Config,
+ "provider": key.Provider,
+ "priority": key.Priority,
+ "disabled": key.Status == 0,
},
}
}
@@ -46,9 +56,192 @@ var _ IProviderModule = (*imlProviderModule)(nil)
type imlProviderModule struct {
providerService ai.IProviderService `autowired:""`
clusterService cluster.IClusterService `autowired:""`
+ aiAPIService ai_api.IAPIService `autowired:""`
+ aiKeyService ai_key.IKeyService `autowired:""`
transaction store.ITransaction `autowired:""`
}
+func (i *imlProviderModule) SimpleProvider(ctx context.Context, id string) (*ai_dto.SimpleProvider, error) {
+ p, has := model_runtime.GetProvider(id)
+ if !has {
+ return nil, fmt.Errorf("ai provider not found")
+ }
+ return &ai_dto.SimpleProvider{
+ Id: p.ID(),
+ Name: p.Name(),
+ Logo: p.Logo(),
+ DefaultConfig: p.DefaultConfig(),
+ GetAPIKeyUrl: p.HelpUrl(),
+ }, nil
+}
+
+func (i *imlProviderModule) Sort(ctx context.Context, input *ai_dto.Sort) error {
+ return i.transaction.Transaction(ctx, func(txCtx context.Context) error {
+ list, err := i.providerService.List(ctx)
+ if err != nil {
+ return err
+ }
+ providerMap := utils.SliceToMap(list, func(e *ai.Provider) string {
+ return e.Id
+ })
+ releases := make([]*gateway.DynamicRelease, 0, len(list))
+ offlineReleases := make([]*gateway.DynamicRelease, 0, len(list))
+ for index, id := range input.Providers {
+ p, has := model_runtime.GetProvider(id)
+ if !has {
+ continue
+ }
+
+ l, has := providerMap[id]
+ if !has {
+ continue
+ }
+ model, has := p.GetModel(l.DefaultLLM)
+ if !has {
+ continue
+ }
+ priority := index + 1
+ err = i.providerService.Save(txCtx, id, &ai.SetProvider{
+ Priority: &priority,
+ })
+ if err != nil {
+ return err
+ }
+ if ai_dto.ToProviderStatus(l.Status) == ai_dto.ProviderDisabled {
+ offlineReleases = append(offlineReleases, &gateway.DynamicRelease{
+ BasicItem: &gateway.BasicItem{
+ ID: l.Id,
+ Resource: "ai-provider",
+ }})
+ } else {
+ cfg := make(map[string]interface{})
+ cfg["provider"] = l.Id
+ cfg["model"] = l.DefaultLLM
+ cfg["model_config"] = model.DefaultConfig()
+ cfg["priority"] = l.Priority
+ cfg["base"] = fmt.Sprintf("%s://%s", p.URI().Scheme(), p.URI().Host())
+ releases = append(releases, &gateway.DynamicRelease{
+ BasicItem: &gateway.BasicItem{
+ ID: l.Id,
+ Description: l.Name,
+ Resource: "ai-provider",
+ Version: l.UpdateAt.Format("20060102150405"),
+ MatchLabels: map[string]string{
+ "module": "ai-provider",
+ },
+ },
+ Attr: cfg,
+ })
+ }
+ }
+ err = i.syncGateway(ctx, cluster.DefaultClusterID, releases, true)
+ if err != nil {
+ return err
+ }
+ return i.syncGateway(ctx, cluster.DefaultClusterID, offlineReleases, false)
+
+ })
+}
+
+func (i *imlProviderModule) ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error) {
+ // 获取已配置的AI服务商
+ list, err := i.providerService.List(ctx)
+ if err != nil {
+ return nil, nil, fmt.Errorf("get provider list error:%v", err)
+ }
+ aiAPIMap, err := i.aiAPIService.CountMapByProvider(ctx, "", nil)
+ if err != nil {
+ return nil, nil, fmt.Errorf("get ai api count error:%v", err)
+ }
+ providers := make([]*ai_dto.ConfiguredProviderItem, 0, len(list))
+ for _, l := range list {
+ // 检查是否有默认Key
+ _, err = i.aiKeyService.DefaultKey(ctx, l.Id)
+ if err != nil {
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, nil, err
+ }
+ err = i.aiKeyService.Create(ctx, &ai_key.Create{
+ ID: l.Id,
+ Name: l.Name,
+ Config: l.Config,
+ Provider: l.Id,
+ Priority: 1,
+ Status: 1,
+ ExpireTime: 0,
+ UseToken: 0,
+ Default: true,
+ })
+ if err != nil {
+ return nil, nil, fmt.Errorf("create default key error:%v", err)
+ }
+ }
+
+ p, has := model_runtime.GetProvider(l.Id)
+ if !has {
+ continue
+ }
+ keys, err := i.aiKeyService.KeysByProvider(ctx, l.Id)
+ if err != nil {
+ return nil, nil, fmt.Errorf("get provider keys error:%v", err)
+ }
+
+ keysStatus := make([]*ai_dto.KeyStatus, 0, len(keys))
+ for _, k := range keys {
+ status := ai_key_dto.ToKeyStatus(k.Status)
+ switch status {
+ case ai_key_dto.KeyNormal, ai_key_dto.KeyDisable, ai_key_dto.KeyError:
+ default:
+ status = ai_key_dto.KeyError
+ }
+ keysStatus = append(keysStatus, &ai_dto.KeyStatus{
+ Id: k.ID,
+ Name: k.Name,
+ Status: status.String(),
+ Priority: k.Priority,
+ })
+ }
+ sort.Slice(keysStatus, func(i, j int) bool {
+ return keysStatus[i].Priority < keysStatus[j].Priority
+ })
+
+ providers = append(providers, &ai_dto.ConfiguredProviderItem{
+ Id: l.Id,
+ Name: l.Name,
+ Logo: p.Logo(),
+ DefaultLLM: l.DefaultLLM,
+ Status: ai_dto.ToProviderStatus(l.Status),
+ APICount: aiAPIMap[l.Id],
+ KeyCount: len(keysStatus),
+ KeyStatus: keysStatus,
+ Priority: l.Priority,
+ })
+ }
+ sort.Slice(providers, func(i, j int) bool {
+ if providers[i].Priority != providers[j].Priority {
+ if providers[i].Priority == 0 {
+ return false
+ }
+ if providers[j].Priority == 0 {
+ return true
+ }
+ return providers[i].Priority < providers[j].Priority
+ }
+ return providers[i].Name < providers[j].Name
+ })
+ var backup *ai_dto.BackupProvider
+ for _, p := range providers {
+ if p.Status == ai_dto.ProviderEnabled {
+ backup = &ai_dto.BackupProvider{
+ Id: p.Id,
+ Name: p.Name,
+ }
+ break
+ }
+ }
+ return providers, backup, nil
+}
+
func (i *imlProviderModule) SimpleProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, error) {
list, err := i.providerService.List(ctx)
if err != nil {
@@ -62,19 +255,95 @@ func (i *imlProviderModule) SimpleProviders(ctx context.Context) ([]*ai_dto.Simp
items := make([]*ai_dto.SimpleProviderItem, 0, len(providers))
for _, v := range providers {
item := &ai_dto.SimpleProviderItem{
- Id: v.ID(),
- Name: v.Name(),
- Logo: v.Logo(),
+ Id: v.ID(),
+ Name: v.Name(),
+ Logo: v.Logo(),
+ DefaultConfig: v.DefaultConfig(),
+ Status: ai_dto.ProviderDisabled,
}
- if _, has := providerMap[v.ID()]; has {
+ if info, has := providerMap[v.ID()]; has {
item.Configured = true
+ item.Status = ai_dto.ToProviderStatus(info.Status)
+ item.Priority = info.Priority
}
items = append(items, item)
}
+ sort.Slice(items, func(i, j int) bool {
+ if items[i].Priority != items[j].Priority {
+ if items[i].Priority == 0 {
+ return false
+ }
+ if items[j].Priority == 0 {
+ return true
+ }
+ return items[i].Priority < items[j].Priority
+ }
+ return items[i].Name < items[j].Name
+ })
return items, nil
}
-func (i *imlProviderModule) Providers(ctx context.Context) ([]*ai_dto.ProviderItem, error) {
+func (i *imlProviderModule) SimpleConfiguredProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error) {
+ list, err := i.providerService.List(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+ items := make([]*ai_dto.SimpleProviderItem, 0, len(list))
+ var backup *ai_dto.BackupProvider
+ for _, l := range list {
+ p, has := model_runtime.GetProvider(l.Id)
+ if !has {
+ continue
+ }
+ model, has := p.GetModel(l.DefaultLLM)
+ if !has {
+ model, has = p.DefaultModel(model_runtime.ModelTypeLLM)
+ if !has {
+ continue
+ }
+ }
+ item := &ai_dto.SimpleProviderItem{
+ Id: l.Id,
+ Name: l.Name,
+ Logo: p.Logo(),
+ DefaultConfig: p.DefaultConfig(),
+ Status: ai_dto.ToProviderStatus(l.Status),
+ Priority: l.Priority,
+ Configured: true,
+ Model: &ai_dto.BasicInfo{
+ Id: model.ID(),
+ Name: model.ID(),
+ },
+ }
+
+ items = append(items, item)
+ }
+ sort.Slice(items, func(i, j int) bool {
+ if items[i].Priority != items[j].Priority {
+ if items[i].Priority == 0 {
+ return false
+ }
+ if items[j].Priority == 0 {
+ return true
+ }
+ return items[i].Priority < items[j].Priority
+ }
+ return items[i].Name < items[j].Name
+ })
+ for _, item := range items {
+ if item.Status == ai_dto.ProviderEnabled {
+ backup = &ai_dto.BackupProvider{
+ Id: item.Id,
+ Name: item.Name,
+ Model: item.Model,
+ }
+ break
+ }
+ }
+ return items, backup, nil
+}
+
+func (i *imlProviderModule) UnConfiguredProviders(ctx context.Context) ([]*ai_dto.ProviderItem, error) {
list, err := i.providerService.List(ctx)
if err != nil {
return nil, err
@@ -85,30 +354,21 @@ func (i *imlProviderModule) Providers(ctx context.Context) ([]*ai_dto.ProviderIt
})
items := make([]*ai_dto.ProviderItem, 0, len(providers))
for _, v := range providers {
-
- item := &ai_dto.ProviderItem{
- Id: v.ID(),
- Name: v.Name(),
- Logo: v.Logo(),
- Recommend: v.Recommend(),
- Sort: v.Sort(),
+ if _, has := providerMap[v.ID()]; has {
+ // 已配置,跳过
+ continue
}
- if info, has := providerMap[v.ID()]; has {
- defaultLLM, has := v.GetModel(info.DefaultLLM)
- if !has {
- continue
- }
- item.Configured = true
- item.DefaultLLM = defaultLLM.ID()
- item.DefaultLLMLogo = defaultLLM.Logo()
- item.UpdateTime = info.UpdateAt
+ defaultLLM, _ := v.DefaultModel(model_runtime.ModelTypeLLM)
+ item := &ai_dto.ProviderItem{
+ Id: v.ID(),
+ Name: v.Name(),
+ Logo: v.Logo(),
+ DefaultLLM: defaultLLM.ID(),
+ Sort: v.Sort(),
}
items = append(items, item)
}
sort.Slice(items, func(i, j int) bool {
- if items[i].Configured == items[j].Configured && items[i].Configured {
- return items[i].Name < items[j].Name
- }
if items[i].Sort != items[j].Sort {
if items[i].Sort == 0 {
return false
@@ -128,6 +388,13 @@ func (i *imlProviderModule) Provider(ctx context.Context, id string) (*ai_dto.Pr
if !has {
return nil, fmt.Errorf("ai provider not found")
}
+ maxPriority, err := i.providerService.MaxPriority(ctx)
+ if err != nil {
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, err
+ }
+ }
+ maxPriority = maxPriority + 1
info, err := i.providerService.Get(ctx, id)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
@@ -144,6 +411,8 @@ func (i *imlProviderModule) Provider(ctx context.Context, id string) (*ai_dto.Pr
GetAPIKeyUrl: p.HelpUrl(),
DefaultLLM: defaultLLM.ID(),
DefaultLLMConfig: defaultLLM.Logo(),
+ Status: ai_dto.ProviderDisabled,
+ Priority: maxPriority,
}, nil
}
defaultLLM, has := p.GetModel(info.DefaultLLM)
@@ -154,6 +423,10 @@ func (i *imlProviderModule) Provider(ctx context.Context, id string) (*ai_dto.Pr
}
defaultLLM = model
}
+ if info.Priority == 0 {
+ info.Priority = maxPriority
+ }
+
return &ai_dto.Provider{
Id: info.Id,
Name: info.Name,
@@ -161,6 +434,9 @@ func (i *imlProviderModule) Provider(ctx context.Context, id string) (*ai_dto.Pr
GetAPIKeyUrl: p.HelpUrl(),
DefaultLLM: defaultLLM.ID(),
DefaultLLMConfig: defaultLLM.DefaultConfig(),
+ Priority: info.Priority,
+ Status: ai_dto.ToProviderStatus(info.Status),
+ Configured: true,
}, nil
}
@@ -196,78 +472,19 @@ func (i *imlProviderModule) LLMs(ctx context.Context, driver string) ([]*ai_dto.
return nil, nil, fmt.Errorf("ai provider default llm not found")
}
return items, &ai_dto.ProviderItem{
- Id: p.ID(),
- Name: p.Name(),
- DefaultLLM: defaultLLM.ID(),
- DefaultLLMLogo: defaultLLM.Logo(),
- Logo: p.Logo(),
- Configured: false,
+ Id: p.ID(),
+ Name: p.Name(),
+ DefaultLLM: defaultLLM.ID(),
+ Logo: p.Logo(),
}, nil
}
- return items, &ai_dto.ProviderItem{Id: info.Id, Name: info.Name, DefaultLLM: info.DefaultLLM, Logo: p.Logo(), Configured: true}, nil
-}
-
-func (i *imlProviderModule) UpdateProviderStatus(ctx context.Context, id string, enable bool) error {
- driver, has := model_runtime.GetProvider(id)
- if !has {
- return fmt.Errorf("ai provider not found")
- }
- info, err := i.providerService.Get(ctx, id)
- if err != nil {
- if !errors.Is(err, gorm.ErrRecordNotFound) {
- return err
- }
- return fmt.Errorf("ai provider not found")
- }
-
- return i.transaction.Transaction(ctx, func(txCtx context.Context) error {
- err = i.providerService.Save(txCtx, id, &ai.SetProvider{
- Status: &enable,
- })
- if err != nil {
- return err
- }
- if enable {
- cfg := make(map[string]interface{})
- err = json.Unmarshal([]byte(info.Config), &cfg)
- if err != nil {
- log.Errorf("unmarshal ai provider config error,id is %s,err is %v", info.Id, err)
- return err
- }
- cfg["driver"] = info.Id
-
- return i.syncGateway(txCtx, cluster.DefaultClusterID, []*gateway.DynamicRelease{{
- BasicItem: &gateway.BasicItem{
- ID: info.Id,
- Description: info.Name,
- Version: info.UpdateAt.Format("20060102150405"),
- MatchLabels: map[string]string{
- "module": "ai-provider",
- },
- },
- Attr: cfg,
- }, newAIUpstream(info.Id, driver.URI()),
- }, enable)
- } else {
- return i.syncGateway(txCtx, cluster.DefaultClusterID, []*gateway.DynamicRelease{
- {
- BasicItem: &gateway.BasicItem{
- ID: info.Id,
- Resource: info.Id,
- },
- },
- {
- BasicItem: &gateway.BasicItem{
- ID: info.Id,
- Resource: "service",
- },
- },
- }, enable)
- }
-
- })
-
+ return items, &ai_dto.ProviderItem{
+ Id: info.Id,
+ Name: info.Name,
+ DefaultLLM: info.DefaultLLM,
+ Logo: p.Logo(),
+ }, nil
}
func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string, input *ai_dto.UpdateConfig) error {
@@ -294,6 +511,10 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string,
Config: input.Config,
}
}
+ model, has := p.GetModel(input.DefaultLLM)
+ if !has {
+ return fmt.Errorf("ai provider model not found")
+ }
err = p.Check(input.Config)
if err != nil {
return err
@@ -303,70 +524,120 @@ func (i *imlProviderModule) UpdateProviderConfig(ctx context.Context, id string,
return err
}
return i.transaction.Transaction(ctx, func(txCtx context.Context) error {
- err = i.providerService.Save(ctx, id, &ai.SetProvider{
+ status := 0
+ if input.Enable != nil && *input.Enable {
+ status = 1
+ }
+ pInfo := &ai.SetProvider{
Name: &info.Name,
- DefaultLLM: &info.DefaultLLM,
+ DefaultLLM: &input.DefaultLLM,
Config: &input.Config,
- })
+ Priority: input.Priority,
+ Status: &status,
+ }
+ _, err = i.aiKeyService.DefaultKey(ctx, id)
+ if err != nil {
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return err
+ }
+ err = i.aiKeyService.Create(ctx, &ai_key.Create{
+ ID: id,
+ Name: info.Name,
+ Config: info.Config,
+ Provider: id,
+ Status: 1,
+ ExpireTime: 0,
+ Default: true,
+ Priority: 1,
+ })
+ } else {
+ err = i.aiKeyService.Save(ctx, id, &ai_key.Edit{
+ Config: &info.Config,
+ })
+ }
+ if err != nil {
+ return err
+ }
+
+ //if input.Enable != nil {
+ // status = 0
+ // if *input.Enable {
+ // status = 1
+ // }
+ // pInfo.Status = &status
+ //}
+ err = i.providerService.Save(ctx, id, pInfo)
+ if err != nil {
+ return err
+ }
+
+ if *pInfo.Status == 0 {
+ return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{
+ {
+ BasicItem: &gateway.BasicItem{
+ ID: id,
+ Resource: "ai-provider",
+ },
+ },
+ }, false)
+ }
+ // 获取当前供应商所有Key信息
+ defaultKey, err := i.aiKeyService.DefaultKey(ctx, id)
if err != nil {
return err
}
cfg := make(map[string]interface{})
- err = json.Unmarshal([]byte(input.Config), &cfg)
- if err != nil {
- log.Errorf("unmarshal ai provider config error,id is %s,err is %v", id, err)
- return err
- }
-
+ cfg["provider"] = info.Id
+ cfg["model"] = info.DefaultLLM
+ cfg["model_config"] = model.DefaultConfig()
+ cfg["priority"] = info.Priority
+ cfg["base"] = fmt.Sprintf("%s://%s", p.URI().Scheme(), p.URI().Host())
return i.syncGateway(ctx, cluster.DefaultClusterID, []*gateway.DynamicRelease{
{
BasicItem: &gateway.BasicItem{
ID: id,
Description: info.Name,
- Resource: id,
+ Resource: "ai-provider",
Version: info.UpdateAt.Format("20060102150405"),
MatchLabels: map[string]string{
"module": "ai-provider",
},
},
Attr: cfg,
- }, newAIUpstream(id, p.URI()),
+ }, newKey(defaultKey),
}, true)
})
}
-func (i *imlProviderModule) UpdateProviderDefaultLLM(ctx context.Context, id string, input *ai_dto.UpdateLLM) error {
- _, err := i.providerService.Get(ctx, id)
- if err != nil {
- if !errors.Is(err, gorm.ErrRecordNotFound) {
- return err
- }
- return fmt.Errorf("ai provider not found")
- }
- return i.providerService.Save(ctx, id, &ai.SetProvider{
- DefaultLLM: &input.LLM,
- })
-}
-
func (i *imlProviderModule) getAiProviders(ctx context.Context) ([]*gateway.DynamicRelease, error) {
list, err := i.providerService.List(ctx)
if err != nil {
return nil, err
}
+
providers := make([]*gateway.DynamicRelease, 0, len(list))
- for _, p := range list {
- cfg := make(map[string]interface{})
- err = json.Unmarshal([]byte(p.Config), &cfg)
- if err != nil {
- log.Errorf("unmarshal ai provider config error,id is %s,err is %v", p.Id, err)
- continue
+ for _, l := range list {
+ // 获取当前供应商所有Key信息
+
+ driver, has := model_runtime.GetProvider(l.Id)
+ if !has {
+ return nil, fmt.Errorf("provider not found: %s", l.Id)
}
+ model, has := driver.GetModel(l.DefaultLLM)
+ if !has {
+ return nil, fmt.Errorf("model not found: %s", l.DefaultLLM)
+ }
+ cfg := make(map[string]interface{})
+ cfg["provider"] = l.Id
+ cfg["model"] = l.DefaultLLM
+ cfg["model_config"] = model.DefaultConfig()
+ cfg["priority"] = l.Priority
providers = append(providers, &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
- ID: p.Id,
- Description: p.Name,
- Resource: p.Id,
- Version: p.UpdateAt.Format("20060102150405"),
+ ID: l.Id,
+ Description: l.Name,
+ Resource: "ai-provider",
+ Version: l.UpdateAt.Format("20060102150405"),
MatchLabels: map[string]string{
"module": "ai-provider",
},
@@ -376,21 +647,15 @@ func (i *imlProviderModule) getAiProviders(ctx context.Context) ([]*gateway.Dyna
}
return providers, nil
}
+
func (i *imlProviderModule) initGateway(ctx context.Context, clusterId string, clientDriver gateway.IClientDriver) error {
providers, err := i.getAiProviders(ctx)
if err != nil {
return err
}
- serviceClient, err := clientDriver.Dynamic("service")
- if err != nil {
- return err
- }
+
for _, p := range providers {
- driver, has := model_runtime.GetProvider(p.ID)
- if !has {
- continue
- }
- client, err := clientDriver.Dynamic(p.ID)
+ client, err := clientDriver.Dynamic(p.Resource)
if err != nil {
return err
}
@@ -398,12 +663,6 @@ func (i *imlProviderModule) initGateway(ctx context.Context, clusterId string, c
if err != nil {
return err
}
-
- err = serviceClient.Online(ctx, newAIUpstream(p.ID, driver.URI()))
- if err != nil {
- return err
- }
-
}
return nil
@@ -429,7 +688,7 @@ func (i *imlProviderModule) syncGateway(ctx context.Context, clusterId string, r
if online {
err = dynamicClient.Online(ctx, releaseInfo)
} else {
- err = dynamicClient.Offline(ctx, releaseInfo)
+ dynamicClient.Offline(ctx, releaseInfo)
}
if err != nil {
return err
@@ -438,3 +697,125 @@ func (i *imlProviderModule) syncGateway(ctx context.Context, clusterId string, r
return nil
}
+
+var _ IAIAPIModule = (*imlAIApiModule)(nil)
+
+type imlAIApiModule struct {
+ aiAPIService ai_api.IAPIService `autowired:""`
+ aiAPIUseService ai_api.IAPIUseService `autowired:""`
+ serviceService service.IServiceService `autowired:""`
+}
+
+func (i *imlAIApiModule) APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool, models []string, serviceIds []string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error) {
+ p, has := model_runtime.GetProvider(providerId)
+ if !has {
+ return nil, nil, 0, fmt.Errorf("ai provider not found")
+ }
+ sortRule := "desc"
+ if asc {
+ sortRule = "asc"
+ }
+ services, err := i.serviceService.ServiceListByKind(ctx, service.AIService)
+ if err != nil {
+ return nil, nil, 0, err
+ }
+ serviceItems := make([]*ai_dto.BasicInfo, 0, len(services))
+ serviceTeamMap := make(map[string]string)
+ for _, s := range services {
+ serviceItems = append(serviceItems, &ai_dto.BasicInfo{
+ Id: s.Id,
+ Name: s.Name,
+ })
+ serviceTeamMap[s.Id] = s.Team
+
+ }
+
+ modelItems := utils.SliceToSlice(p.Models(), func(e model_runtime.IModel) *ai_dto.BasicInfo {
+ return &ai_dto.BasicInfo{
+ Id: e.ID(),
+ Name: e.ID(),
+ }
+ })
+ condition := &ai_dto.Condition{Services: serviceItems, Models: modelItems}
+ switch sortCondition {
+ default:
+ w := map[string]interface{}{
+ "provider": providerId,
+ }
+ if len(models) > 0 {
+ w["model"] = models
+ }
+ if len(serviceIds) > 0 {
+ w["service"] = serviceIds
+ }
+ apis, err := i.aiAPIService.Search(ctx, keyword, w, "update_at desc")
+ if err != nil {
+ return nil, nil, 0, err
+ }
+
+ if len(apis) <= 0 {
+ return nil, condition, 0, nil
+ }
+ apiMap := make(map[string]*ai_api.API)
+ apiIds := make([]string, 0, len(apis))
+ for _, a := range apis {
+ apiMap[a.ID] = a
+ apiIds = append(apiIds, a.ID)
+ }
+ offset := (page - 1) * pageSize
+ results, _, err := i.aiAPIUseService.SumByApisPage(ctx, providerId, start, end, offset, pageSize, fmt.Sprintf("total_token %s", sortRule), apiIds...)
+ if err != nil {
+ return nil, nil, 0, err
+ }
+
+ apiItems := utils.SliceToSlice(results, func(e *ai_api.APIUse) *ai_dto.APIItem {
+ info := apiMap[e.API]
+
+ delete(apiMap, e.API)
+ return &ai_dto.APIItem{
+ Id: e.API,
+ Name: info.Name,
+ Service: auto.UUID(info.Service),
+ Team: auto.UUID(serviceTeamMap[info.Service]),
+ Method: http.MethodPost,
+ RequestPath: info.Path,
+ Model: auto.Label{
+ Id: info.Model,
+ Name: info.Model,
+ },
+ UpdateTime: auto.TimeLabel(info.UpdateAt),
+ UseToken: e.TotalToken,
+ Disable: info.Disable,
+ }
+ })
+ sortApis := make([]*ai_dto.APIItem, 0, len(apiMap))
+ for _, a := range apiMap {
+ sortApis = append(sortApis, &ai_dto.APIItem{
+ Id: a.ID,
+ Name: a.Name,
+ Service: auto.UUID(a.Service),
+ Team: auto.UUID(serviceTeamMap[a.Service]),
+ Method: http.MethodPost,
+ RequestPath: a.Path,
+ Model: auto.Label{
+ Id: a.Model,
+ Name: a.Model,
+ },
+ UpdateTime: auto.TimeLabel(a.UpdateAt),
+ UseToken: 0,
+ Disable: a.Disable,
+ })
+ }
+ // 排序
+ sort.Slice(sortApis, func(i, j int) bool {
+ return time.Time(sortApis[i].UpdateTime).After(time.Time(sortApis[j].UpdateTime))
+ })
+ size := pageSize - len(apiItems)
+ for i := offset; i < offset+size && i < len(sortApis); i++ {
+ apiItems = append(apiItems, sortApis[i])
+ }
+
+ total := int64(len(apis))
+ return apiItems, condition, total, nil
+ }
+}
diff --git a/module/ai/module.go b/module/ai/module.go
index 4bc67358..99b6a56e 100644
--- a/module/ai/module.go
+++ b/module/ai/module.go
@@ -2,20 +2,29 @@ package ai
import (
"context"
+ "reflect"
+
"github.com/APIParkLab/APIPark/gateway"
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
"github.com/eolinker/go-common/autowire"
- "reflect"
)
type IProviderModule interface {
- Providers(ctx context.Context) ([]*ai_dto.ProviderItem, error)
+ ConfiguredProviders(ctx context.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error)
+ UnConfiguredProviders(ctx context.Context) ([]*ai_dto.ProviderItem, error)
SimpleProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, error)
+ SimpleConfiguredProviders(ctx context.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error)
Provider(ctx context.Context, id string) (*ai_dto.Provider, error)
+ SimpleProvider(ctx context.Context, id string) (*ai_dto.SimpleProvider, error)
LLMs(ctx context.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error)
- UpdateProviderStatus(ctx context.Context, id string, enable bool) error
+ //UpdateProviderStatus(ctx context.Context, id string, enable bool) error
UpdateProviderConfig(ctx context.Context, id string, input *ai_dto.UpdateConfig) error
- UpdateProviderDefaultLLM(ctx context.Context, id string, input *ai_dto.UpdateLLM) error
+ //UpdateProviderDefaultLLM(ctx context.Context, id string, input *ai_dto.UpdateLLM) error
+ Sort(ctx context.Context, input *ai_dto.Sort) error
+}
+
+type IAIAPIModule interface {
+ APIs(ctx context.Context, keyword string, providerId string, start int64, end int64, page int, pageSize int, sortCondition string, asc bool, models []string, services []string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error)
}
func init() {
@@ -24,4 +33,8 @@ func init() {
gateway.RegisterInitHandleFunc(module.initGateway)
return reflect.ValueOf(module)
})
+
+ autowire.Auto[IAIAPIModule](func() reflect.Value {
+ return reflect.ValueOf(new(imlAIApiModule))
+ })
}
diff --git a/module/catalogue/iml.go b/module/catalogue/iml.go
index 18b58def..c43de0c8 100644
--- a/module/catalogue/iml.go
+++ b/module/catalogue/iml.go
@@ -176,19 +176,6 @@ func (i *imlCatalogueModule) Subscribe(ctx context.Context, subscribeInfo *catal
// 修改订阅表状态
subscribers, err := i.subscribeService.ListByApplication(ctx, subscribeInfo.Service, appId)
if err != nil {
- //if !errors.Is(err, gorm.ErrRecordNotFound) {
- // return err
- //}
- //err = i.subscribeService.Create(ctx, &subscribe.CreateSubscribe{
- // Uuid: uuid.New().String(),
- // Service: subscribeInfo.Service,
- // Application: appId,
- // ApplyStatus: status,
- // From: subscribe.FromSubscribe,
- //})
- //if err != nil {
- // return err
- //}
return err
} else {
subscriberMap := utils.SliceToMap(subscribers, func(t *subscribe.Subscribe) string {
diff --git a/module/cluster/dto/input.go b/module/cluster/dto/input.go
index 9d690526..43036f37 100644
--- a/module/cluster/dto/input.go
+++ b/module/cluster/dto/input.go
@@ -17,12 +17,12 @@ package cluster_dto
//type SaveMonitorConfig struct {
// Driver string `json:"driver"`
-// Config map[string]interface{} `json:"config"`
+// DefaultConfig map[string]interface{} `json:"config"`
//}
//type MonitorConfig struct {
// Driver string `json:"driver"`
-// Config map[string]interface{} `json:"config"`
+// DefaultConfig map[string]interface{} `json:"config"`
//}
//type MonitorPartition struct {
diff --git a/module/publish/iml.go b/module/publish/iml.go
index 0eb17754..4226ef1f 100644
--- a/module/publish/iml.go
+++ b/module/publish/iml.go
@@ -124,6 +124,7 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
Version: version,
}
apis := make([]*gateway.ApiRelease, 0, len(apiInfos))
+ hasUpstream := len(upstreamCommitIds) > 0
for _, a := range apiInfos {
apiInfo := &gateway.ApiRelease{
BasicItem: &gateway.BasicItem{
@@ -133,7 +134,10 @@ func (m *imlPublishModule) getProjectRelease(ctx context.Context, projectID stri
},
Path: a.Path,
Methods: a.Methods,
- Service: a.Upstream,
+ //Service: a.Upstream,
+ }
+ if hasUpstream {
+ apiInfo.Service = a.Upstream
}
proxy, ok := proxyCommitMap[a.UUID]
if ok {
diff --git a/module/router/dto/input.go b/module/router/dto/input.go
index 02fcbf10..593a3647 100644
--- a/module/router/dto/input.go
+++ b/module/router/dto/input.go
@@ -30,7 +30,7 @@ type Create struct {
MatchRules []Match `json:"match"`
Upstream string `json:"upstream"`
Proxy *InputProxy `json:"proxy"`
- Disable bool `json:"disable"`
+ Disable bool `json:"disabled"`
}
type InputProxy struct {
@@ -69,7 +69,7 @@ type Edit struct {
Methods *[]string `json:"methods"`
Protocols *[]string `json:"protocols"`
MatchRules *[]Match `json:"match"`
- Disable *bool `json:"disable"`
+ Disable *bool `json:"disabled"`
Upstream *string `json:"upstream"`
}
diff --git a/module/router/dto/output.go b/module/router/dto/output.go
index 9f8276ed..50611785 100644
--- a/module/router/dto/output.go
+++ b/module/router/dto/output.go
@@ -15,7 +15,7 @@ type Item struct {
Protocols []string `json:"protocols"`
Path string `json:"request_path"`
Description string `json:"description"`
- Disable bool `json:"disable"`
+ Disable bool `json:"disabled"`
Creator auto.Label `json:"creator" aolabel:"user"`
Updater auto.Label `json:"updater" aolabel:"user"`
CreateTime auto.TimeLabel `json:"create_time"`
@@ -34,7 +34,7 @@ type Detail struct {
SimpleDetail
Proxy *Proxy `json:"proxy"`
Protocols []string `json:"protocols"`
- Disable bool `json:"disable"`
+ Disable bool `json:"disabled"`
//Doc map[string]interface{} `json:"doc"`
}
diff --git a/module/service/iml.go b/module/service/iml.go
index 377990bd..8edf26d3 100644
--- a/module/service/iml.go
+++ b/module/service/iml.go
@@ -92,11 +92,6 @@ func (i *imlServiceModule) ExportAll(ctx context.Context) ([]*service_dto.Export
serviceTagMap[st.Sid] = append(serviceTagMap[st.Sid], tagMap[st.Tid].Name)
}
- //docMap, err := i.serviceDocService.Map(ctx, serviceIds...)
- //if err != nil {
- // return nil, err
- //}
-
items := make([]*service_dto.ExportService, 0, len(services))
for _, s := range services {
info := &service_dto.ExportService{
@@ -171,24 +166,6 @@ func (i *imlServiceModule) SearchMyServices(ctx context.Context, teamId string,
return items, nil
}
-//func (i *imlServiceModule) SimpleAPPS(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
-// w := make(map[string]interface{})
-// w["as_app"] = true
-// services, err := i.serviceService.SearchByDriver(ctx, keyword, w)
-// if err != nil {
-// return nil, err
-// }
-// return utils.SliceToSlice(services, func(p *service.Service) *service_dto.SimpleServiceItem {
-// return &service_dto.SimpleServiceItem{
-// Id: p.Id,
-// Name: p.Name,
-// Description: p.Description,
-//
-// Team: auto.UUID(p.Team),
-// }
-// }), nil
-//}
-
func (i *imlServiceModule) Simple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) {
w := make(map[string]interface{})
w["as_server"] = true
diff --git a/module/subscribe/iml.go b/module/subscribe/iml.go
index 70e43bd2..03dc7935 100644
--- a/module/subscribe/iml.go
+++ b/module/subscribe/iml.go
@@ -218,33 +218,46 @@ func (i *imlSubscribeModule) AddSubscriber(ctx context.Context, serviceId string
if err != nil {
return err
}
- _, err = i.subscribeService.GetByServiceAndApplication(ctx, serviceId, input.Application)
- if err == nil {
- // 订阅方已存在
- return fmt.Errorf("subscriber is already exists")
+ clusters, err := i.clusterService.List(ctx)
+ if err != nil {
+ return err
}
-
sub := &gateway.SubscribeRelease{
Service: serviceId,
Application: input.Application,
Expired: "0",
}
- clusters, err := i.clusterService.List(ctx)
- if err != nil {
- return err
- }
-
return i.transaction.Transaction(ctx, func(ctx context.Context) error {
- err = i.subscribeService.Create(ctx, &subscribe.CreateSubscribe{
- Uuid: uuid.New().String(),
- Service: serviceId,
- Application: input.Application,
- ApplyStatus: subscribe.ApplyStatusSubscribe,
- From: subscribe.FromUser,
- })
- if err != nil {
- return err
+ info, err := i.subscribeService.GetByServiceAndApplication(ctx, serviceId, input.Application)
+ if err == nil {
+ // 订阅方已存在
+ if info.ApplyStatus != subscribe.ApplyStatusSubscribe {
+ // 更新订阅方状态
+ status := subscribe.ApplyStatusSubscribe
+ from := subscribe.FromUser
+ err = i.subscribeService.Save(ctx, info.Id, &subscribe.UpdateSubscribe{
+ ApplyStatus: &status,
+ From: &from,
+ })
+ if err != nil {
+ return err
+ }
+ } else {
+ return nil
+ }
+ } else {
+ err = i.subscribeService.Create(ctx, &subscribe.CreateSubscribe{
+ Uuid: uuid.New().String(),
+ Service: serviceId,
+ Application: input.Application,
+ ApplyStatus: subscribe.ApplyStatusSubscribe,
+ From: subscribe.FromUser,
+ })
+ if err != nil {
+ return err
+ }
}
+
for _, c := range clusters {
err = i.onlineSubscriber(ctx, c.Uuid, sub)
if err != nil {
diff --git a/plugins/core/ai.go b/plugins/core/ai.go
index c73e8173..e42a09db 100644
--- a/plugins/core/ai.go
+++ b/plugins/core/ai.go
@@ -10,13 +10,29 @@ import (
func (p *plugin) aiAPIs() []pm3.Api {
return []pm3.Api{
- pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/providers", []string{"context"}, []string{"providers"}, p.aiProviderController.Providers, access.SystemSettingsAiProviderView),
+ pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/providers/unconfigured", []string{"context"}, []string{"providers"}, p.aiProviderController.UnConfiguredProviders, access.SystemSettingsAiProviderView),
+ pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/providers/configured", []string{"context"}, []string{"providers", "backup"}, p.aiProviderController.ConfiguredProviders, access.SystemSettingsAiProviderView),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/ai/providers", []string{"context"}, []string{"providers"}, p.aiProviderController.SimpleProviders),
+ pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/ai/providers/configured", []string{"context"}, []string{"providers", "backup"}, p.aiProviderController.SimpleConfiguredProviders),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/provider/config", []string{"context", "query:provider"}, []string{"provider"}, p.aiProviderController.Provider, access.SystemSettingsAiProviderView),
+ pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/ai/provider", []string{"context", "query:provider"}, []string{"provider"}, p.aiProviderController.SimpleProvider),
pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/provider/llms", []string{"context", "query:provider"}, []string{"llms", "provider"}, p.aiProviderController.LLMs),
- //pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/provider/enable", []string{"context", "query:provider"}, nil, p.aiProviderController.isStop),
- //pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/provider/disable", []string{"context", "query:provider"}, nil, p.aiProviderController.Disable),
+ pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/provider/sort", []string{"context", "body"}, nil, p.aiProviderController.Sort),
pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/provider/config", []string{"context", "query:provider", "body"}, nil, p.aiProviderController.UpdateProviderConfig, access.SystemSettingsAiProviderManager),
- //pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/provider/default-llm", []string{"context", "query:provider", "body"}, nil, p.aiProviderController.UpdateProviderDefaultLLM),
+
+ pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/apis", []string{"context", "query:keyword", "query:provider", "query:start", "query:end", "query:page", "query:page_size", "query:sort", "query:asc", "query:models", "query:services"}, []string{"apis", "condition", "total"}, p.aiStatisticController.APIs),
+ }
+}
+
+func (p *plugin) aiKeyApis() []pm3.Api {
+ return []pm3.Api{
+ pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/resource/key", []string{"context", "query:provider", "query:id"}, []string{"info"}, p.aiKeyController.Get),
+ pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/ai/resource/keys", []string{"context", "query:provider", "query:keyword", "query:page", "query:page_size", "query:statuses"}, []string{"keys", "total"}, p.aiKeyController.List),
+ pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/ai/resource/key", []string{"context", "query:provider", "body"}, nil, p.aiKeyController.Create),
+ pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/resource/key", []string{"context", "query:provider", "query:id", "body"}, nil, p.aiKeyController.Edit),
+ pm3.CreateApiWidthDoc(http.MethodDelete, "/api/v1/ai/resource/key", []string{"context", "query:provider", "query:id"}, nil, p.aiKeyController.Delete),
+ pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/resource/key/enable", []string{"context", "query:provider", "query:id"}, nil, p.aiKeyController.Enable),
+ pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/resource/key/disable", []string{"context", "query:provider", "query:id"}, nil, p.aiKeyController.Disable),
+ pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/ai/resource/key/sort", []string{"context", "query:provider", "body"}, nil, p.aiKeyController.Sort),
}
}
diff --git a/plugins/core/core.go b/plugins/core/core.go
index 7a3e57d6..55d8c3f2 100644
--- a/plugins/core/core.go
+++ b/plugins/core/core.go
@@ -3,6 +3,8 @@ package core
import (
"net/http"
+ ai_key "github.com/APIParkLab/APIPark/controller/ai-key"
+
"github.com/APIParkLab/APIPark/controller/log"
"github.com/APIParkLab/APIPark/controller/strategy"
@@ -74,6 +76,8 @@ type plugin struct {
upstreamController upstream.IUpstreamController `autowired:""`
routerController router.IRouterController `autowired:""`
aiAPIController ai_api.IAPIController `autowired:""`
+ aiStatisticController ai.IStatisticController `autowired:""`
+ aiKeyController ai_key.IKeyController `autowired:""`
apiDocController router.IAPIDocController `autowired:""`
subscribeController subscribe.ISubscribeController `autowired:""`
strategyController strategy.IStrategyController `autowired:""`
@@ -111,6 +115,7 @@ func (p *plugin) OnComplete() {
p.apis = append(p.apis, p.commonApis()...)
p.apis = append(p.apis, p.systemApis()...)
p.apis = append(p.apis, p.aiAPIs()...)
+ p.apis = append(p.apis, p.aiKeyApis()...)
p.apis = append(p.apis, p.strategyApis()...)
p.apis = append(p.apis, p.logApis()...)
}
diff --git a/plugins/openapi/authorization.go b/plugins/openapi/authorization.go
new file mode 100644
index 00000000..d6f37e8a
--- /dev/null
+++ b/plugins/openapi/authorization.go
@@ -0,0 +1,18 @@
+package openapi
+
+import (
+ "net/http"
+
+ "github.com/eolinker/go-common/pm3"
+)
+
+func (p *plugin) appAuthorizationApis() []pm3.Api {
+ return []pm3.Api{
+ pm3.CreateApiWidthDoc(http.MethodPost, "/openapi/v1/app/authorization", []string{"context", "query:app", "body"}, []string{"authorization"}, p.authorizationController.AddAuthorization),
+ pm3.CreateApiWidthDoc(http.MethodPut, "/openapi/v1/app/authorization", []string{"context", "query:app", "query:authorization", "body"}, []string{"authorization"}, p.authorizationController.EditAuthorization),
+ pm3.CreateApiWidthDoc(http.MethodDelete, "/openapi/v1/app/authorization", []string{"context", "query:app", "query:authorization"}, nil, p.authorizationController.DeleteAuthorization),
+ pm3.CreateApiWidthDoc(http.MethodGet, "/openapi/v1/app/authorization", []string{"context", "query:app", "query:authorization"}, []string{"authorization"}, p.authorizationController.Info),
+ pm3.CreateApiWidthDoc(http.MethodGet, "/openapi/v1/app/authorizations", []string{"context", "query:app"}, []string{"authorizations"}, p.authorizationController.Authorizations),
+ pm3.CreateApiWidthDoc(http.MethodGet, "/openapi/v1/app/authorization/details", []string{"context", "query:app", "query:authorization"}, []string{"details"}, p.authorizationController.Detail),
+ }
+}
diff --git a/plugins/openapi/check.go b/plugins/openapi/check.go
new file mode 100644
index 00000000..4f5f50f7
--- /dev/null
+++ b/plugins/openapi/check.go
@@ -0,0 +1,45 @@
+package openapi
+
+import (
+ "strings"
+
+ "github.com/eolinker/eosc/env"
+
+ "github.com/gin-gonic/gin"
+)
+
+var (
+ defaultAPIKey = "37eb0ebf"
+ openCheck = newOpenapiCheck()
+)
+
+type openapiCheck struct {
+ apikey string
+}
+
+func newOpenapiCheck() *openapiCheck {
+ apikey, has := env.GetEnv("API_KEY")
+ if !has {
+ apikey = defaultAPIKey
+ }
+ return &openapiCheck{apikey: apikey}
+}
+
+func (o *openapiCheck) Check(method string, path string) (bool, []gin.HandlerFunc) {
+ if strings.HasPrefix(path, "/openapi/") {
+ return true, []gin.HandlerFunc{o.Handler}
+ }
+ return false, nil
+}
+
+func (o *openapiCheck) Sort() int {
+ return -1
+}
+
+func (o *openapiCheck) Handler(ginCtx *gin.Context) {
+ authorization := ginCtx.GetHeader("Authorization")
+ if authorization == "" {
+ ginCtx.AbortWithStatusJSON(403, gin.H{"code": -8, "msg": "invalid token", "success": "fail"})
+ return
+ }
+}
diff --git a/plugins/openapi/driver.go b/plugins/openapi/driver.go
new file mode 100644
index 00000000..9e9997a6
--- /dev/null
+++ b/plugins/openapi/driver.go
@@ -0,0 +1,19 @@
+package openapi
+
+import (
+ "github.com/eolinker/go-common/autowire"
+ "github.com/eolinker/go-common/pm3"
+)
+
+func init() {
+ pm3.Register("openapi", new(Driver))
+}
+
+type Driver struct {
+}
+
+func (d *Driver) Create() (pm3.IPlugin, error) {
+ p := new(plugin)
+ autowire.Autowired(p)
+ return p, nil
+}
diff --git a/plugins/openapi/plugin.go b/plugins/openapi/plugin.go
new file mode 100644
index 00000000..3082fb84
--- /dev/null
+++ b/plugins/openapi/plugin.go
@@ -0,0 +1,33 @@
+package openapi
+
+import (
+ application_authorization "github.com/APIParkLab/APIPark/controller/application-authorization"
+ "github.com/eolinker/go-common/pm3"
+)
+
+var (
+ _ pm3.IPlugin = (*plugin)(nil)
+ _ pm3.IPluginMiddleware = (*plugin)(nil)
+)
+
+type plugin struct {
+ apis []pm3.Api
+ authorizationController application_authorization.IAuthorizationController `autowired:""`
+}
+
+func (p *plugin) Middlewares() []pm3.IMiddleware {
+ return []pm3.IMiddleware{
+ openCheck,
+ }
+}
+
+func (p *plugin) APis() []pm3.Api {
+ return p.apis
+}
+
+func (p *plugin) Name() string {
+ return "openapi"
+}
+func (p *plugin) OnComplete() {
+ p.apis = p.appAuthorizationApis()
+}
diff --git a/readme/readme-jp.md b/readme/readme-jp.md
index 6977a313..7f1602d0 100644
--- a/readme/readme-jp.md
+++ b/readme/readme-jp.md
@@ -211,7 +211,7 @@ APIParkはApache 2.0ライセンスの下で提供されています。詳細に
エンタープライズ機能や専門的な技術サポートについては、プリセールスの専門家に連絡し、個別デモ、カスタムソリューション、価格情報を入手してください。
- ウェブサイト: https://apipark.com
-- メール: dev@apipark.com
+- メール: contact@apipark.com
diff --git a/readme/readme-zh-cn.md b/readme/readme-zh-cn.md
index 9384a30a..ae7c9e8d 100644
--- a/readme/readme-zh-cn.md
+++ b/readme/readme-zh-cn.md
@@ -215,7 +215,7 @@ APIPark 使用 Apache 2.0 许可证。更多详情请查看 LICENSE 文件。
对于企业级功能和专业技术支持,请联系售前专家进行个性化演示、定制方案和获取报价。
- 网站: https://apipark.com
-- 电子邮件: dev@apipark.com
+- 电子邮件: contact@apipark.com
diff --git a/readme/readme-zh-tw.md b/readme/readme-zh-tw.md
index c6ddc025..ab4dd255 100644
--- a/readme/readme-zh-tw.md
+++ b/readme/readme-zh-tw.md
@@ -212,7 +212,7 @@ APIPark 使用 Apache 2.0 授權條款。更多詳情請參閱 LICENSE 文件。
如需企業級功能與專業技術支援,請聯絡我們的售前專家,獲取個性化演示、定制方案和報價。
- 網站: https://apipark.com
-- 電子郵件: dev@apipark.com
+- 電子郵件: contact@apipark.com
diff --git a/resources/access/access.yaml b/resources/access/access.yaml
index 6b24896f..504b01d1 100644
--- a/resources/access/access.yaml
+++ b/resources/access/access.yaml
@@ -100,6 +100,36 @@ system:
value: 'manager'
dependents:
- system.settings.ai_provider.view
+ - name: ai key resource
+ value: 'ai_key_resource'
+ children:
+ - name: view
+ value: 'view'
+ guest_allow: true
+ - name: manager
+ value: 'manager'
+ dependents:
+ - system.settings.ai_key_resource.view
+ - name: ai api
+ value: 'ai_api'
+ children:
+ - name: view
+ value: 'view'
+ guest_allow: true
+ - name: manager
+ value: 'manager'
+ dependents:
+ - system.settings.ai_api.view
+ - name: ai log
+ value: 'ai_log'
+ children:
+ - name: view
+ value: 'view'
+ guest_allow: true
+ - name: manager
+ value: 'manager'
+ dependents:
+ - system.settings.ai_log.view
- name: ssl certificate
cname: 证书
value: 'ssl_certificate'
diff --git a/resources/access/role.yaml b/resources/access/role.yaml
index 9b9613db..b9f3ebe4 100644
--- a/resources/access/role.yaml
+++ b/resources/access/role.yaml
@@ -6,6 +6,12 @@ system:
- system.api_portal.api_portal.view
- system.settings.account.manager
- system.settings.account.view
+ - system.settings.ai_api.manager
+ - system.settings.ai_api.view
+ - system.settings.ai_key_resource.manager
+ - system.settings.ai_key_resource.view
+ - system.settings.ai_log.manager
+ - system.settings.ai_log.view
- system.settings.ai_provider.manager
- system.settings.ai_provider.view
- system.settings.api_gateway.manager
@@ -39,6 +45,12 @@ system:
permits:
- system.analysis.run_view.view
- system.api_portal.api_portal.view
+ - system.settings.ai_api.manager
+ - system.settings.ai_api.view
+ - system.settings.ai_key_resource.manager
+ - system.settings.ai_key_resource.view
+ - system.settings.ai_log.manager
+ - system.settings.ai_log.view
- system.settings.ai_provider.manager
- system.settings.ai_provider.view
- system.settings.api_gateway.manager
diff --git a/resources/plugin/plugin.yml b/resources/plugin/plugin.yml
index f731a53f..73aa40bb 100644
--- a/resources/plugin/plugin.yml
+++ b/resources/plugin/plugin.yml
@@ -1,4 +1,4 @@
-version: v7
+version: v8
sort:
- "access_log"
- "monitor"
@@ -41,7 +41,7 @@ plugin:
b: "subscription_service:#{application}"
response:
status_code: 403
- content_typ: "text/plan"
+ content_type: "text/plan"
charset: "utf-8"
body: "Forbidden"
diff --git a/scripts/Dockerfile b/scripts/Dockerfile
index f78900c8..6e712a47 100755
--- a/scripts/Dockerfile
+++ b/scripts/Dockerfile
@@ -8,6 +8,9 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ARG APP
+ENV NSQ_ADDR=${APP}-nsq:4150
+ENV NSQ_TOPIC_PREFIX=${APP}
+
RUN mkdir -p /${APP}
COPY cmd/* /${APP}/
diff --git a/scripts/build.sh b/scripts/build.sh
index 47747583..0b6c85dc 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -8,6 +8,8 @@ source ./scripts/common.sh
OUTPUT_DIR=$(mkdir_output "$1")
APP="apipark"
OUTPUT_BIN="${OUTPUT_DIR}/${APP}"
+AI_EVENT_LISTEN_APP="apipark_ai_event_listen"
+AI_EVENT_LISTEN_BIN="${OUTPUT_DIR}/${AI_EVENT_LISTEN_APP}"
VERSION=$(gen_version "$2")
BUILD_TYPE=$3
ARCH=$4
@@ -104,6 +106,10 @@ build_backend() {
# -ldflags="-w -s" means omit DWARF symbol table and the symbol table and debug information
echo "GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build $Tags -ldflags \"-w -s $flags\" -o \"${OUTPUT_BIN}\""
GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build ${Tags} -ldflags "-w -s $flags" -o ${OUTPUT_BIN}
+
+ echo "Build backend successfully..."
+ echo "GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build -ldflags \"-w -s\" -o \"${AI_EVENT_LISTEN_BIN}\" ./app/ai-event-handler"
+ GOOS=linux GOARCH=$ARCH CGO_ENABLED=0 go build -ldflags "-w -s" -o "${AI_EVENT_LISTEN_BIN}" ./app/ai-event-handler
return
}
@@ -123,6 +129,9 @@ package() {
cp "${OUTPUT_BIN}" "${PACKAGE_DIR}"
+ echo "cp ${AI_EVENT_LISTEN_BIN} ${PACKAGE_DIR}"
+ cp "${AI_EVENT_LISTEN_BIN}" "${PACKAGE_DIR}"
+
echo "tar -czvf ${PACKAGE_DIR}_linux_${ARCH}.tar.gz -C ${PACKAGE_DIR}/ ./"
tar -czvf "${PACKAGE_DIR}_linux_${ARCH}.tar.gz" -C "${PACKAGE_DIR}/" "./"
# rm -fr "${PACKAGE_DIR}"
diff --git a/scripts/docker_build.sh b/scripts/docker_build.sh
index 2a43ba84..5c51a097 100755
--- a/scripts/docker_build.sh
+++ b/scripts/docker_build.sh
@@ -17,7 +17,7 @@ source ./scripts/common.sh
APP="apipark"
-mkdir -p scripts/cmd/ && cp cmd/${APP} scripts/cmd/
+mkdir -p scripts/cmd/ && cp cmd/${APP} scripts/cmd/ && cp cmd/apipark_ai_event_listen scripts/cmd/
VERSION=$(gen_version)
diff --git a/scripts/resource/docker_run.sh b/scripts/resource/docker_run.sh
index 53d3d711..255f7243 100755
--- a/scripts/resource/docker_run.sh
+++ b/scripts/resource/docker_run.sh
@@ -25,6 +25,9 @@ for s in ${arr[@]}
do
echo -e " - $s" >> config.yml
done
+echo -e "nsq:" >> config.yml
+echo -e " addr: ${NSQ_ADDR}" >> config.yml
+echo -e " topic_prefix: ${NSQ_TOPIC_PREFIX}" >> config.yml
echo -e "port: 8288" >> config.yml
echo -e "error_log:" >> config.yml
echo -e " dir: ${ERROR_DIR}" >> config.yml
@@ -34,4 +37,6 @@ echo -e " log_expire: ${ERROR_EXPIRE}" >> config.yml
echo -e " log_period: ${ERROR_PERIOD}" >> config.yml
cat config.yml
-./apipark
\ No newline at end of file
+nohup ./apipark >> run.log 2>&1 &
+nohup ./apipark_ai_event_listen >> run.log 2>&1 &
+tail -F run.log
\ No newline at end of file
diff --git a/scripts/resource/run.sh b/scripts/resource/run.sh
index cc998bd9..6aff686b 100755
--- a/scripts/resource/run.sh
+++ b/scripts/resource/run.sh
@@ -42,7 +42,8 @@ start() {
cat "$LOG_FILE"
exit 1
fi
-
+ # 启动ai事件监听程序
+# nohup ./apipark_ai_event_listen >> "$LOG_FILE" 2>&1 &
}
# 停止函数
diff --git a/service/ai-api/iml.go b/service/ai-api/iml.go
index 4971dcbf..b59a004e 100644
--- a/service/ai-api/iml.go
+++ b/service/ai-api/iml.go
@@ -1,10 +1,15 @@
package ai_api
import (
+ "context"
"encoding/json"
+ "errors"
+ "time"
+
+ "gorm.io/gorm"
+
"github.com/APIParkLab/APIPark/service/universally"
"github.com/APIParkLab/APIPark/stores/api"
- "time"
)
var _ IAPIService = (*imlAPIService)(nil)
@@ -18,6 +23,10 @@ type imlAPIService struct {
universally.IServiceDelete
}
+func (i *imlAPIService) CountMapByProvider(ctx context.Context, keyword string, conditions map[string]interface{}) (map[string]int64, error) {
+ return i.store.CountByGroup(ctx, keyword, conditions, "provider")
+}
+
func (i *imlAPIService) OnComplete() {
i.IServiceGet = universally.NewGetSoftDelete[API, api.AiAPIInfo](i.store, FromEntity)
i.IServiceCreate = universally.NewCreatorSoftDelete[Create, api.AiAPIInfo](i.store, "ai_api_info", createEntityHandler, uniquestHandler, labelHandler)
@@ -26,7 +35,7 @@ func (i *imlAPIService) OnComplete() {
}
func labelHandler(e *api.AiAPIInfo) []string {
- return []string{e.Name, e.Uuid}
+ return []string{e.Name, e.Path}
}
func uniquestHandler(i *Create) []map[string]interface{} {
return []map[string]interface{}{{"uuid": i.ID}}
@@ -43,6 +52,8 @@ func createEntityHandler(i *Create) *api.AiAPIInfo {
Timeout: i.Timeout,
Retry: i.Retry,
Model: i.Model,
+ Provider: i.Provider,
+ Disable: i.Disable,
CreateAt: now,
UpdateAt: now,
AdditionalConfig: string(cfg),
@@ -67,9 +78,89 @@ func updateHandler(e *api.AiAPIInfo, i *Edit) {
if i.Model != nil {
e.Model = *i.Model
}
+ if i.Provider != nil {
+ e.Provider = *i.Provider
+ }
if i.AdditionalConfig != nil {
cfg, _ := json.Marshal(i.AdditionalConfig)
e.AdditionalConfig = string(cfg)
}
+ if i.Disable != nil {
+ e.Disable = *i.Disable
+ }
+ if i.UseToken != nil {
+ e.UseToken = *i.UseToken
+ }
e.UpdateAt = time.Now()
}
+
+var _ IAPIUseService = (*imlAPIUseService)(nil)
+
+type imlAPIUseService struct {
+ store api.IAiAPIUseStore `autowired:""`
+}
+
+func (i *imlAPIUseService) Incr(ctx context.Context, incr *IncrAPIUse) error {
+ info, err := i.store.First(ctx, map[string]interface{}{
+ "api": incr.API,
+ "service": incr.Service,
+ "provider": incr.Provider,
+ "model": incr.Model,
+ "day": incr.Day,
+ "hour": incr.Hour,
+ "minute": incr.Minute,
+ })
+ if err != nil {
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return err
+ }
+ info = &api.AiAPIUse{
+ API: incr.API,
+ Service: incr.Service,
+ Provider: incr.Provider,
+ Model: incr.Model,
+ Day: incr.Day,
+ Hour: incr.Hour,
+ Minute: incr.Minute,
+ }
+ }
+ info.InputToken += incr.InputToken
+ info.OutputToken += incr.OutputToken
+ info.TotalToken += incr.TotalToken
+ return i.store.Save(ctx, info)
+}
+
+func (i *imlAPIUseService) SumByApisPage(ctx context.Context, providerId string, start, end int64, offset, limit int, order string, apiIds ...string) ([]*APIUse, int64, error) {
+ list, total, err := i.store.SumByGroupPage(ctx, "api", order, offset, limit, "api,sum(input_token) as input_token,sum(output_token) as output_token,sum(total_token) as total_token", "provider = ? and api in (?) and minute >= ? and minute <= ?", providerId, apiIds, start, end)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ result := make([]*APIUse, 0, len(list))
+ for _, v := range list {
+ result = append(result, &APIUse{
+ API: v.API,
+ InputToken: v.InputToken,
+ OutputToken: v.OutputToken,
+ TotalToken: v.TotalToken,
+ })
+ }
+ return result, total, nil
+}
+
+func (i *imlAPIUseService) SumByApis(ctx context.Context, providerId string, start, end int64, apiIds ...string) ([]*APIUse, error) {
+ //list, err := i.store.SumByGroup(ctx, "api", "api,sum(input_token) as input_token,sum(output_token) as output_token,sum(total_token) as total_token", "provider = ? and api in (?) and minute >= ? and minute <= ?", providerId, apiIds, start, end)
+ //if err != nil {
+ // return nil, err
+ //}
+ //
+ //return utils.SliceToSlice(list, func(v *api.AiAPIUse) *APIUse {
+ // return &APIUse{
+ // API: v.API,
+ // InputToken: v.InputToken,
+ // OutputToken: v.OutputToken,
+ // TotalToken: v.TotalToken,
+ // }
+ //}), nil
+ return nil, nil
+}
diff --git a/service/ai-api/model.go b/service/ai-api/model.go
index bf9e7d41..b8ee9cb7 100644
--- a/service/ai-api/model.go
+++ b/service/ai-api/model.go
@@ -16,8 +16,10 @@ type API struct {
Timeout int
Retry int
Model string
+ Provider string
CreateAt time.Time
UpdateAt time.Time
+ UseToken int
Creator string
Updater string
AdditionalConfig map[string]interface{}
@@ -33,7 +35,9 @@ type Create struct {
Timeout int
Retry int
Model string
+ Provider string
AdditionalConfig map[string]interface{}
+ Disable bool
}
type Edit struct {
@@ -42,7 +46,10 @@ type Edit struct {
Description *string
Timeout *int
Retry *int
+ Provider *string
Model *string
+ Disable *bool
+ UseToken *int
AdditionalConfig *map[string]interface{}
}
@@ -64,6 +71,28 @@ func FromEntity(e *api.AiAPIInfo) *API {
UpdateAt: e.UpdateAt,
Creator: e.Creator,
Updater: e.Updater,
+ Disable: e.Disable,
+ UseToken: e.UseToken,
AdditionalConfig: cfg,
}
}
+
+type APIUse struct {
+ API string
+ InputToken int
+ OutputToken int
+ TotalToken int
+}
+
+type IncrAPIUse struct {
+ API string
+ Service string
+ Provider string
+ Model string
+ Day int64
+ Hour int64
+ Minute int64
+ InputToken int
+ OutputToken int
+ TotalToken int
+}
diff --git a/service/ai-api/service.go b/service/ai-api/service.go
index 6cf068ee..de841fda 100644
--- a/service/ai-api/service.go
+++ b/service/ai-api/service.go
@@ -1,9 +1,11 @@
package ai_api
import (
+ "context"
+ "reflect"
+
"github.com/APIParkLab/APIPark/service/universally"
"github.com/eolinker/go-common/autowire"
- "reflect"
)
type IAPIService interface {
@@ -11,12 +13,20 @@ type IAPIService interface {
universally.IServiceCreate[Create]
universally.IServiceEdit[Edit]
universally.IServiceDelete
+ CountMapByProvider(ctx context.Context, keyword string, conditions map[string]interface{}) (map[string]int64, error)
+}
- //ListByServices(ctx context.Context, serviceIds ...string) ([]*API, error)
+type IAPIUseService interface {
+ SumByApis(ctx context.Context, providerId string, start, end int64, apiIds ...string) ([]*APIUse, error)
+ SumByApisPage(ctx context.Context, providerId string, start, end int64, page, pageSize int, order string, apiIds ...string) ([]*APIUse, int64, error)
+ Incr(ctx context.Context, incr *IncrAPIUse) error
}
func init() {
autowire.Auto[IAPIService](func() reflect.Value {
return reflect.ValueOf(new(imlAPIService))
})
+ autowire.Auto[IAPIUseService](func() reflect.Value {
+ return reflect.ValueOf(new(imlAPIUseService))
+ })
}
diff --git a/service/ai-key/iml.go b/service/ai-key/iml.go
new file mode 100644
index 00000000..302ec182
--- /dev/null
+++ b/service/ai-key/iml.go
@@ -0,0 +1,244 @@
+package ai_key
+
+import (
+ "context"
+ "fmt"
+ "sort"
+ "time"
+
+ "github.com/eolinker/go-common/store"
+
+ "github.com/APIParkLab/APIPark/service/universally"
+ "github.com/APIParkLab/APIPark/stores/ai"
+)
+
+var _ IKeyService = &imlAIKeyService{}
+
+type imlAIKeyService struct {
+ store ai.IKeyStore `autowired:""`
+ transaction store.ITransaction `autowired:""`
+ universally.IServiceGet[Key]
+ universally.IServiceCreate[Create]
+ universally.IServiceEdit[Edit]
+ universally.IServiceDelete
+}
+
+func (i *imlAIKeyService) IncrUseToken(ctx context.Context, id string, useToken int) error {
+ info, err := i.store.GetByUUID(ctx, id)
+ if err != nil {
+ return err
+ }
+
+ info.UseToken += useToken
+ return i.store.Save(ctx, info)
+}
+
+func (i *imlAIKeyService) SearchUnExpiredByPage(ctx context.Context, w map[string]interface{}, page, pageSize int, order string) ([]*Key, int64, error) {
+ sql := "(expire_time = 0 || expire_time > ?)"
+ args := []interface{}{time.Now().Unix()}
+ for k, v := range w {
+ switch v.(type) {
+ case []int:
+ sql += fmt.Sprintf(" and `%s` in (?)", k)
+ default:
+ sql += fmt.Sprintf(" and `%s` = ?", k)
+ }
+ args = append(args, v)
+ }
+ list, total, err := i.store.ListPage(ctx, sql, page, pageSize, args, order)
+ if err != nil {
+ return nil, 0, err
+ }
+ var result []*Key
+ for _, item := range list {
+ result = append(result, FromEntity(item))
+ }
+ return result, total, nil
+}
+
+func (i *imlAIKeyService) KeysAfterPriority(ctx context.Context, providerId string, priority int) ([]*Key, error) {
+ list, err := i.store.ListQuery(ctx, "sort > ? and provider = ?", []interface{}{priority, providerId}, "sort asc")
+ if err != nil {
+ return nil, err
+ }
+ var result []*Key
+ for _, item := range list {
+ result = append(result, FromEntity(item))
+ }
+ return result, nil
+}
+
+func (i *imlAIKeyService) MaxPriority(ctx context.Context, providerId string) (int, error) {
+ info, err := i.store.First(ctx, map[string]interface{}{"provider": providerId}, "sort desc")
+ if err != nil {
+ return 0, err
+ }
+ return info.Sort, nil
+}
+
+func (i *imlAIKeyService) DefaultKey(ctx context.Context, providerId string) (*Key, error) {
+ info, err := i.store.First(ctx, map[string]interface{}{"provider": providerId, "default": true})
+ if err != nil {
+ return nil, err
+ }
+ return FromEntity(info), nil
+}
+
+func (i *imlAIKeyService) KeysByProvider(ctx context.Context, providerId string) ([]*Key, error) {
+ list, err := i.store.List(ctx, map[string]interface{}{"provider": providerId})
+ if err != nil {
+ return nil, err
+ }
+ var result []*Key
+ for _, item := range list {
+ result = append(result, FromEntity(item))
+ }
+ return result, nil
+}
+
+func (i *imlAIKeyService) SortBefore(ctx context.Context, provider string, originID string, targetID string) ([]*Key, error) {
+ originKey, err := i.store.GetByUUID(ctx, originID)
+ if err != nil {
+ return nil, fmt.Errorf("get key error: %v,id is %s", err, originID)
+ }
+ targetKey, err := i.store.GetByUUID(ctx, targetID)
+ if err != nil {
+ return nil, fmt.Errorf("get key error: %v,id is %s", err, targetID)
+ }
+ originKeySort, targetKeySort := originKey.Sort, targetKey.Sort
+ // 初始化顺序,假设原始Key在目标Key之后,中间的key往后移动,原始Key移动到`targetKeySort`位置
+ originKey.Sort = targetKeySort
+ fn := func(priority int) int {
+ return priority + 1
+ }
+ sql := "sort < ? and sort >= ?"
+ if originKeySort < targetKeySort {
+ // 如果原始Key在目标Key之前,中间的key往前移动,原始Key移动到`targetKeySort - 1`位置
+ sql = "sort > ? and sort < ?"
+ originKey.Sort = targetKeySort - 1
+ fn = func(priority int) int {
+ return priority - 1
+ }
+ }
+ list, err := i.store.ListQuery(ctx, sql, []interface{}{originKeySort, targetKeySort}, "sort asc")
+ if err != nil {
+ return nil, err
+ }
+ result := make([]*Key, 0, len(list)+1)
+ err = i.transaction.Transaction(ctx, func(txCtx context.Context) error {
+ for _, l := range list {
+ l.Sort = fn(l.Sort)
+ _, err := i.store.Update(ctx, l)
+ if err != nil {
+ return err
+ }
+ result = append(result, FromEntity(l))
+ }
+ _, err = i.store.Update(ctx, originKey)
+ return err
+ })
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, FromEntity(originKey))
+ sort.Slice(list, func(i, j int) bool { return list[i].Sort < list[j].Sort })
+ return result, nil
+}
+
+func (i *imlAIKeyService) SortAfter(ctx context.Context, provider string, originID string, targetID string) ([]*Key, error) {
+ originKey, err := i.store.GetByUUID(ctx, originID)
+ if err != nil {
+ return nil, fmt.Errorf("get key error: %v,id is %s", err, originID)
+ }
+ targetKey, err := i.store.GetByUUID(ctx, targetID)
+ if err != nil {
+ return nil, fmt.Errorf("get key error: %v,id is %s", err, targetID)
+ }
+ originKeySort, targetKeySort := originKey.Sort, targetKey.Sort
+ // 初始化顺序,假设原始Key在目标Key之后,中间的Key往后移动,原始Key移动到`targetKeySort + 1`位置
+ originKey.Sort = targetKeySort + 1
+ fn := func(priority int) int {
+ return priority + 1
+ }
+ sql := "sort < ? and sort > ?"
+ if originKeySort < targetKeySort {
+ // 如果原始Key在目标Key之前,中间的Key往前移动,原始Key移动到`targetKeySort`位置
+ sql = "sort > ? and sort <= ?"
+ originKey.Sort = targetKeySort
+ fn = func(priority int) int {
+ return priority - 1
+ }
+ }
+ list, err := i.store.ListQuery(ctx, sql, []interface{}{originKeySort, targetKeySort}, "sort asc")
+ if err != nil {
+ return nil, err
+ }
+ result := make([]*Key, 0, len(list)+1)
+ err = i.transaction.Transaction(ctx, func(txCtx context.Context) error {
+ for _, l := range list {
+ l.Sort = fn(l.Sort)
+ _, err := i.store.Update(ctx, l)
+ if err != nil {
+ return err
+ }
+ result = append(result, FromEntity(l))
+ }
+ _, err = i.store.Update(ctx, originKey)
+ return err
+ })
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, FromEntity(originKey))
+ sort.Slice(list, func(i, j int) bool { return list[i].Sort < list[j].Sort })
+ return result, nil
+}
+
+func (i *imlAIKeyService) OnComplete() {
+ i.IServiceGet = universally.NewGet[Key, ai.Key](i.store, FromEntity)
+ i.IServiceCreate = universally.NewCreator[Create, ai.Key](i.store, "ai_api_info", createEntityHandler, uniquestHandler, labelHandler)
+ i.IServiceEdit = universally.NewEdit[Edit, ai.Key](i.store, updateHandler)
+ i.IServiceDelete = universally.NewDelete[ai.Key](i.store)
+}
+
+func labelHandler(e *ai.Key) []string {
+ return []string{e.Name}
+}
+func uniquestHandler(i *Create) []map[string]interface{} {
+ return []map[string]interface{}{{"uuid": i.ID}}
+}
+func createEntityHandler(i *Create) *ai.Key {
+ now := time.Now()
+ return &ai.Key{
+ Uuid: i.ID,
+ Name: i.Name,
+ Config: i.Config,
+ Provider: i.Provider,
+ Status: i.Status,
+ ExpireTime: i.ExpireTime,
+ Sort: i.Priority,
+ UseToken: 0,
+ CreateAt: now,
+ UpdateAt: now,
+ Default: i.Default,
+ }
+}
+func updateHandler(e *ai.Key, i *Edit) {
+ if i.Name != nil {
+ e.Name = *i.Name
+ }
+ if i.Config != nil {
+ e.Config = *i.Config
+ }
+ if i.Status != nil {
+ e.Status = *i.Status
+ }
+ if i.ExpireTime != nil {
+ e.ExpireTime = *i.ExpireTime
+ }
+ if i.Priority != nil {
+ e.Sort = *i.Priority
+ }
+
+ e.UpdateAt = time.Now()
+}
diff --git a/service/ai-key/model.go b/service/ai-key/model.go
new file mode 100644
index 00000000..61148922
--- /dev/null
+++ b/service/ai-key/model.go
@@ -0,0 +1,61 @@
+package ai_key
+
+import (
+ "time"
+
+ "github.com/APIParkLab/APIPark/stores/ai"
+)
+
+type Key struct {
+ ID string
+ Name string
+ Config string
+ Provider string
+ Status int
+ ExpireTime int
+ UseToken int
+ Creator string
+ Updater string
+ Priority int
+ CreateAt time.Time
+ UpdateAt time.Time
+ Default bool
+}
+
+func FromEntity(e *ai.Key) *Key {
+ return &Key{
+ ID: e.Uuid,
+ Name: e.Name,
+ Config: e.Config,
+ Provider: e.Provider,
+ Status: e.Status,
+ ExpireTime: e.ExpireTime,
+ UseToken: e.UseToken,
+ Creator: e.Creator,
+ Updater: e.Updater,
+ CreateAt: e.CreateAt,
+ UpdateAt: e.UpdateAt,
+ Priority: e.Sort,
+ Default: e.Default,
+ }
+}
+
+type Create struct {
+ ID string
+ Name string
+ Config string
+ Provider string
+ Priority int
+ Status int
+ ExpireTime int
+ UseToken int
+ Default bool
+}
+
+type Edit struct {
+ Name *string
+ Config *string
+ Status *int
+ ExpireTime *int
+ Priority *int
+}
diff --git a/service/ai-key/service.go b/service/ai-key/service.go
new file mode 100644
index 00000000..8bbe6d83
--- /dev/null
+++ b/service/ai-key/service.go
@@ -0,0 +1,31 @@
+package ai_key
+
+import (
+ "context"
+ "reflect"
+
+ "github.com/eolinker/go-common/autowire"
+
+ "github.com/APIParkLab/APIPark/service/universally"
+)
+
+type IKeyService interface {
+ universally.IServiceGet[Key]
+ universally.IServiceCreate[Create]
+ universally.IServiceEdit[Edit]
+ universally.IServiceDelete
+ DefaultKey(ctx context.Context, providerId string) (*Key, error)
+ KeysByProvider(ctx context.Context, providerId string) ([]*Key, error)
+ MaxPriority(ctx context.Context, providerId string) (int, error)
+ SortBefore(ctx context.Context, provider string, originID string, targetID string) ([]*Key, error)
+ SortAfter(ctx context.Context, provider string, originID string, targetID string) ([]*Key, error)
+ KeysAfterPriority(ctx context.Context, providerId string, priority int) ([]*Key, error)
+ SearchUnExpiredByPage(ctx context.Context, w map[string]interface{}, page, pageSize int, order string) ([]*Key, int64, error)
+ IncrUseToken(ctx context.Context, id string, useToken int) error
+}
+
+func init() {
+ autowire.Auto[IKeyService](func() reflect.Value {
+ return reflect.ValueOf(new(imlAIKeyService))
+ })
+}
diff --git a/service/ai/iml.go b/service/ai/iml.go
index 2eea283f..453d63a7 100644
--- a/service/ai/iml.go
+++ b/service/ai/iml.go
@@ -4,12 +4,13 @@ import (
"context"
"encoding/base64"
"errors"
+ "time"
+
"github.com/APIParkLab/APIPark/service/universally"
"github.com/APIParkLab/APIPark/stores/ai"
"github.com/eolinker/go-common/auto"
"github.com/eolinker/go-common/utils"
"gorm.io/gorm"
- "time"
)
var _ IProviderService = (*imlProviderService)(nil)
@@ -19,6 +20,14 @@ type imlProviderService struct {
store ai.IProviderStore `autowired:""`
}
+func (i *imlProviderService) MaxPriority(ctx context.Context) (int, error) {
+ t, err := i.store.First(ctx, nil, "priority desc")
+ if err != nil {
+ return 0, err
+ }
+ return t.Priority, nil
+}
+
func (i *imlProviderService) Save(ctx context.Context, id string, cfg *SetProvider) error {
userId := utils.UserId(ctx)
now := time.Now()
@@ -30,10 +39,20 @@ func (i *imlProviderService) Save(ctx context.Context, id string, cfg *SetProvid
if cfg.Name == nil || cfg.Config == nil || cfg.DefaultLLM == nil {
return errors.New("invalid params")
}
- status := false
+ status := 1
if cfg.Status != nil {
status = *cfg.Status
}
+ priority := 1
+ if cfg.Priority == nil {
+ count, err := i.store.Count(ctx, "", nil)
+ if err != nil {
+ return err
+ }
+ priority = int(count) + 1
+ } else {
+ priority = *cfg.Priority
+ }
info = &ai.Provider{
UUID: id,
Name: *cfg.Name,
@@ -42,6 +61,7 @@ func (i *imlProviderService) Save(ctx context.Context, id string, cfg *SetProvid
Status: status,
Creator: userId,
Updater: userId,
+ Priority: priority,
CreateAt: now,
UpdateAt: now,
}
@@ -58,6 +78,9 @@ func (i *imlProviderService) Save(ctx context.Context, id string, cfg *SetProvid
if cfg.Status != nil {
info.Status = *cfg.Status
}
+ if cfg.Priority != nil {
+ info.Priority = *cfg.Priority
+ }
info.Updater = userId
info.UpdateAt = now
}
diff --git a/service/ai/model.go b/service/ai/model.go
index 6865f5bf..80c6b43f 100644
--- a/service/ai/model.go
+++ b/service/ai/model.go
@@ -2,8 +2,9 @@ package ai
import (
"encoding/base64"
- "github.com/APIParkLab/APIPark/stores/ai"
"time"
+
+ "github.com/APIParkLab/APIPark/stores/ai"
)
type Provider struct {
@@ -13,7 +14,8 @@ type Provider struct {
Config string
Creator string
Updater string
- Status bool
+ Status int
+ Priority int
CreateAt time.Time
UpdateAt time.Time
}
@@ -22,7 +24,8 @@ type SetProvider struct {
Name *string
DefaultLLM *string
Config *string
- Status *bool
+ Status *int
+ Priority *int
}
func FromEntity(e *ai.Provider) *Provider {
@@ -40,5 +43,6 @@ func FromEntity(e *ai.Provider) *Provider {
CreateAt: e.CreateAt,
UpdateAt: e.UpdateAt,
Status: e.Status,
+ Priority: e.Priority,
}
}
diff --git a/service/ai/service.go b/service/ai/service.go
index 39dd9c7b..83760314 100644
--- a/service/ai/service.go
+++ b/service/ai/service.go
@@ -2,14 +2,16 @@ package ai
import (
"context"
+ "reflect"
+
"github.com/APIParkLab/APIPark/service/universally"
"github.com/eolinker/go-common/autowire"
- "reflect"
)
type IProviderService interface {
universally.IServiceGet[Provider]
Save(ctx context.Context, id string, cfg *SetProvider) error
+ MaxPriority(ctx context.Context) (int, error)
}
func init() {
diff --git a/service/service/iml.go b/service/service/iml.go
index c3090ea0..719e55e7 100644
--- a/service/service/iml.go
+++ b/service/service/iml.go
@@ -46,7 +46,7 @@ func (i *imlServiceService) ServiceListByKind(ctx context.Context, kind Kind, se
w["uuid"] = serviceIds
}
w["as_server"] = true
- w["kind"] = kind
+ w["kind"] = kind.Int()
w["is_delete"] = false
list, err := i.store.List(ctx, w)
if err != nil {
diff --git a/service/subscribe/iml.go b/service/subscribe/iml.go
index dd3fd460..92d0b4f6 100644
--- a/service/subscribe/iml.go
+++ b/service/subscribe/iml.go
@@ -200,6 +200,9 @@ func (i *imlSubscribeService) updateHandler(e *subscribe.Subscribe, t *UpdateSub
if t.ApplyStatus != nil {
e.ApplyStatus = *t.ApplyStatus
}
+ if t.From != nil {
+ e.From = *t.From
+ }
}
var (
diff --git a/service/subscribe/model.go b/service/subscribe/model.go
index d9882145..365a9d4d 100644
--- a/service/subscribe/model.go
+++ b/service/subscribe/model.go
@@ -2,14 +2,14 @@ package subscribe
import (
"time"
-
+
"github.com/APIParkLab/APIPark/stores/subscribe"
)
type Subscribe struct {
Id string
Service string
-
+
// 订阅方相关
Application string
From int
@@ -28,6 +28,7 @@ type CreateSubscribe struct {
}
type UpdateSubscribe struct {
+ From *int
ApplyStatus *int
}
diff --git a/stores/access_log/model.go b/stores/access_log/model.go
new file mode 100644
index 00000000..369ecef5
--- /dev/null
+++ b/stores/access_log/model.go
@@ -0,0 +1,30 @@
+package access_log
+
+import "time"
+
+type Log struct {
+ Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"`
+ UUID string `gorm:"type:varchar(36);not null;column:uuid;uniqueIndex:uuid;comment:UUID;"`
+ Cluster string `gorm:"column:cluster;type:varchar(36);NOT NULL;comment:集群ID"`
+ Node string `gorm:"column:node;type:varchar(36);NOT NULL;comment:节点ID"`
+ Service string `gorm:"column:service;type:varchar(36);NOT NULL;comment:服务ID"`
+ API string `gorm:"column:api;type:varchar(36);NOT NULL;comment:API ID"`
+ Application string `gorm:"column:application;type:varchar(36);NOT NULL;comment:应用ID"`
+ Auth string `gorm:"column:auth;type:varchar(36);NOT NULL;comment:认证ID"`
+ Type string `gorm:"column:type;type:varchar(36);NOT NULL;comment:日志类型;index:idx_type"`
+ Target string `gorm:"column:target;type:varchar(36);NOT NULL;comment:目标ID"`
+ IP string `gorm:"column:ip;type:varchar(36);NOT NULL;comment:IP"`
+ RequestPath string `gorm:"column:request_path;type:varchar(255);NOT NULL;comment:请求路径"`
+ Method string `gorm:"column:method;type:varchar(36);NOT NULL;comment:请求方法"`
+ ResponseTime float64 `gorm:"column:response_time;type:float;NOT NULL;comment:响应时间"`
+ StatusCode int64 `gorm:"column:status_code;type:BIGINT(20);NOT NULL;comment:响应状态码"`
+ ReportTime time.Time `gorm:"column:report_time;type:timestamp;NOT NULL;comment:上报时间;index:idx_report_time"`
+}
+
+func (c *Log) IdValue() int64 {
+ return c.Id
+}
+
+func (c *Log) TableName() string {
+ return "access_log"
+}
diff --git a/stores/access_log/store.go b/stores/access_log/store.go
new file mode 100644
index 00000000..a702d7db
--- /dev/null
+++ b/stores/access_log/store.go
@@ -0,0 +1,22 @@
+package access_log
+
+import (
+ "reflect"
+
+ "github.com/eolinker/go-common/autowire"
+ "github.com/eolinker/go-common/store"
+)
+
+type ILogStore interface {
+ store.ISearchStore[Log]
+}
+
+type imlLogStore struct {
+ store.SearchStore[Log]
+}
+
+func init() {
+ autowire.Auto[ILogStore](func() reflect.Value {
+ return reflect.ValueOf(new(imlLogStore))
+ })
+}
diff --git a/stores/ai/model.go b/stores/ai/model.go
index 7b82e702..b25f4c29 100644
--- a/stores/ai/model.go
+++ b/stores/ai/model.go
@@ -8,7 +8,8 @@ type Provider struct {
Name string `gorm:"type:varchar(100);not null;column:name;comment:name"`
DefaultLLM string `gorm:"type:varchar(255);not null;column:default_llm;comment:默认模型ID"`
Config string `gorm:"type:text;not null;column:config;comment:配置信息"`
- Status bool `gorm:"type:tinyint(1);not null;column:status;comment:状态"`
+ Status int `gorm:"type:tinyint(1);not null;column:status;comment:状态,0:停用;1:启用,2:异常;default:1"`
+ Priority int `gorm:"type:int;not null;column:priority;comment:优先级,值越小优先级越高"`
Creator string `gorm:"size:36;not null;column:creator;comment:创建人;index:creator" aovalue:"creator"` // 创建人
Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"` // 更新人
CreateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:create_at;comment:创建时间"`
@@ -22,3 +23,48 @@ func (i *Provider) TableName() string {
func (i *Provider) IdValue() int64 {
return i.Id
}
+
+type LogMetrics struct {
+ Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"`
+ UUID string `gorm:"type:varchar(36);not null;column:uuid;uniqueIndex:uuid;comment:UUID;"`
+ Provider string `gorm:"type:varchar(36);not null;column:provider;comment:供应商ID"`
+ Model string `gorm:"type:varchar(36);not null;column:model;comment:模型ID"`
+ InputToken int `gorm:"type:int;not null;column:input_token;comment:输入token"`
+ OutputToken int `gorm:"type:int;not null;column:output_token;comment:输出token"`
+ TotalToken int `gorm:"type:int;not null;column:total_token;comment:总token"`
+ Cost float64 `gorm:"type:int;not null;column:cost;comment:费用"`
+ Per float64 `gorm:"type:int;not null;column:per;comment:每个token的价格"`
+}
+
+func (i *LogMetrics) TableName() string {
+ return "ai_log_metrics"
+}
+
+func (i *LogMetrics) IdValue() int64 {
+ return i.Id
+}
+
+type Key struct {
+ Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"`
+ Uuid string `gorm:"type:varchar(36);not null;column:uuid;uniqueIndex:uuid;comment:UUID;"`
+ Name string `gorm:"type:varchar(100);not null;column:name;comment:名称"`
+ Config string `gorm:"type:text;not null;column:config;comment:配置"`
+ Provider string `gorm:"type:varchar(36);not null;column:provider;comment:供应商ID"`
+ Status int `gorm:"type:tinyint(1);not null;column:status;comment:状态,0:停用;1:启用,2:错误;3:超额;4:过期"`
+ ExpireTime int `gorm:"type:int;not null;column:expire_time;comment:过期时间"`
+ UseToken int `gorm:"type:int;not null;column:use_token;comment:使用token数"`
+ Sort int `gorm:"type:int;not null;column:sort;comment:排序"`
+ Creator string `gorm:"size:36;not null;column:creator;comment:创建人;index:creator" aovalue:"creator"` // 创建人
+ Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"` // 更新人
+ CreateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:create_at;comment:创建时间"`
+ UpdateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:update_at;comment:更新时间"`
+ Default bool `gorm:"type:tinyint(1);not null;column:default;comment:是否默认"`
+}
+
+func (i *Key) TableName() string {
+ return "ai_key"
+}
+
+func (i *Key) IdValue() int64 {
+ return i.Id
+}
diff --git a/stores/ai/store.go b/stores/ai/store.go
index 107f625c..4afa193f 100644
--- a/stores/ai/store.go
+++ b/stores/ai/store.go
@@ -1,9 +1,10 @@
package ai
import (
+ "reflect"
+
"github.com/eolinker/go-common/autowire"
"github.com/eolinker/go-common/store"
- "reflect"
)
type IProviderStore interface {
@@ -14,8 +15,32 @@ type imlProviderStore struct {
store.SearchStore[Provider]
}
+type ILogMetricsStore interface {
+ store.ISearchStore[LogMetrics]
+}
+
+type imlLogMetricsStore struct {
+ store.SearchStore[LogMetrics]
+}
+
+type IKeyStore interface {
+ store.ISearchStore[Key]
+}
+
+type imlKeyStore struct {
+ store.SearchStore[Key]
+}
+
func init() {
autowire.Auto[IProviderStore](func() reflect.Value {
return reflect.ValueOf(new(imlProviderStore))
})
+
+ autowire.Auto[ILogMetricsStore](func() reflect.Value {
+ return reflect.ValueOf(new(imlLogMetricsStore))
+ })
+
+ autowire.Auto[IKeyStore](func() reflect.Value {
+ return reflect.ValueOf(new(imlKeyStore))
+ })
}
diff --git a/stores/api/api.go b/stores/api/api.go
index 4eab2d83..26fd5ba4 100644
--- a/stores/api/api.go
+++ b/stores/api/api.go
@@ -31,6 +31,14 @@ type imlAiAPIInfoStore struct {
store.SearchStoreSoftDelete[AiAPIInfo]
}
+type IAiAPIUseStore interface {
+ store.IStatisticsStore[AiAPIUse]
+}
+
+type imlAiAPIUseStore struct {
+ store.StatisticsStore[AiAPIUse]
+}
+
func init() {
autowire.Auto[IApiBaseStore](func() reflect.Value {
@@ -48,4 +56,8 @@ func init() {
autowire.Auto[IAiAPIInfoStore](func() reflect.Value {
return reflect.ValueOf(new(imlAiAPIInfoStore))
})
+
+ autowire.Auto[IAiAPIUseStore](func() reflect.Value {
+ return reflect.ValueOf(new(imlAiAPIUseStore))
+ })
}
diff --git a/stores/api/model.go b/stores/api/model.go
index 0c81e9db..d530cf3f 100644
--- a/stores/api/model.go
+++ b/stores/api/model.go
@@ -55,7 +55,7 @@ type Doc struct {
Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"`
UUID string `gorm:"type:varchar(36);not null;column:uuid;uniqueIndex:uuid;comment:UUID;"`
Service string `gorm:"size:36;not null;column:service;comment:服务;index:service"`
- Content string `gorm:"type:text;null;column:content;comment:文档内容"`
+ Content string `gorm:"type:longtext;null;column:content;comment:文档内容"`
Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"`
UpdateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:update_at;comment:更新时间"`
APICount int64 `gorm:"type:int(11);not null;column:api_count;comment:接口数量"`
@@ -79,11 +79,14 @@ type AiAPIInfo struct {
Timeout int `gorm:"type:int(11);not null;column:timeout;comment:超时时间"`
Retry int `gorm:"type:int(11);not null;column:retry;comment:重试次数"`
Model string `gorm:"size:255;not null;column:model;comment:模型"`
+ Provider string `gorm:"size:36;not null;column:provider;comment:提供者;index:provider"`
Creator string `gorm:"size:36;not null;column:creator;comment:创建人;index:creator" aovalue:"creator"`
CreateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:create_at;comment:创建时间"`
Updater string `gorm:"size:36;not null;column:updater;comment:更新人;index:updater" aovalue:"updater"`
UpdateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:update_at;comment:更新时间"`
AdditionalConfig string `gorm:"type:text;null;column:additional_config;comment:额外配置"`
+ UseToken int `gorm:"type:int(11);not null;column:use_token;comment:使用token"`
+ Disable bool `gorm:"type:tinyint(1);not null;column:disable;comment:是否禁用 0:否 1:是"`
IsDelete bool `gorm:"type:tinyint(1);not null;column:is_delete;comment:是否删除 0:否 1:是"`
}
@@ -94,3 +97,25 @@ func (a *AiAPIInfo) TableName() string {
func (a *AiAPIInfo) IdValue() int64 {
return a.Id
}
+
+type AiAPIUse struct {
+ Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"`
+ API string `gorm:"size:36;not null;column:api;comment:API;index:api"`
+ Service string `gorm:"size:36;not null;column:service;comment:服务;index:service"`
+ Provider string `gorm:"size:36;not null;column:provider;comment:提供者;index:provider"`
+ Model string `gorm:"size:255;not null;column:model;comment:模型"`
+ Day int64 `gorm:"type:int(11);not null;column:day;comment:当前日期"`
+ Hour int64 `gorm:"type:int(11);not null;column:hour;comment:当前小时"`
+ Minute int64 `gorm:"type:int(11);not null;column:minute;comment:当前分钟"`
+ InputToken int `gorm:"type:int(11);not null;column:input_token;comment:输入token"`
+ OutputToken int `gorm:"type:int(11);not null;column:output_token;comment:输出token"`
+ TotalToken int `gorm:"type:int(11);not null;column:total_token;comment:总token"`
+}
+
+func (a *AiAPIUse) TableName() string {
+ return "ai_api_use"
+}
+
+func (a *AiAPIUse) IdValue() int64 {
+ return a.Id
+}
diff --git a/stores/universally/commit/commit.go b/stores/universally/commit/commit.go
index d6850960..700fb722 100644
--- a/stores/universally/commit/commit.go
+++ b/stores/universally/commit/commit.go
@@ -7,7 +7,7 @@ type Commit[H any] struct {
UUID string `gorm:"size:36;not null;column:uuid;comment:uuid;uniqueIndex:uuid;"`
Target string `gorm:"column:target;type:varchar(36);NOT NULL;comment:目标id;index:target;"`
Key string `gorm:"size:50;not null;column:key;comment:类型;index:key;"`
- Data *H `gorm:"type:text;not null;column:data;comment:数据;charset=utf8mb4;serializer:json"`
+ Data *H `gorm:"type:longtext;not null;column:data;comment:数据;charset=utf8mb4;serializer:json"`
CreateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:create_at;comment:创建时间"`
Operator string `gorm:"size:36;not null;column:operator;comment:操作人;index:operator;"`
}