Browse Source

Merge branch 'feature/sync-wx' into debug

* feature/sync-wx:
  自动同步联系人改为每 30 分钟执行一次
  GetWeChatsReq 接口适配非 dev 模式
  fixbug
  还原定时任务
  还原 service_context.go 文件
  自动同步个微
  增加工作ID和tokend的对照表
  临时提交
  去除JWT验证/修正丢失flush的bug
  全部合并远端分支origin/feature/api-key
  部分结合api_key的调整
  第一次本地可运行版提交
  防止处理消息的方法异常panic
  fix:修改发送信息失败时记录日志方式

# Conflicts:
#	desc/all.api
#	etc/wechat.yaml
#	internal/config/config.go
#	internal/handler/routes.go
#	internal/pkg/wechat_ws/test/main.go
#	internal/pkg/wechat_ws/wechat_ws_client.go
#	internal/svc/service_context.go
#	internal/types/types.go
boweniac 1 month ago
parent
commit
9052da6cfc

+ 5 - 0
crontask/init.go

@@ -42,4 +42,9 @@ func ScheduleRun(c *cron.Cron, serverCtx *svc.ServiceContext) {
 	c.AddFunc("0 * * * *", func() {
 		computeStatistic.computeStatistic()
 	})
+
+	syncWx := NewCronTask(context.Background(), serverCtx)
+	c.AddFunc("*/30 * * * *", func() {
+		syncWx.syncWx()
+	})
 }

+ 104 - 0
crontask/sync_wx.go

@@ -0,0 +1,104 @@
+package crontask
+
+import (
+	"encoding/json"
+	"github.com/imroc/req/v3"
+	"strconv"
+	"time"
+	"wechat-api/ent"
+	"wechat-api/ent/wx"
+	"wechat-api/internal/types"
+)
+
+func (l *CronTask) syncWx() {
+	// 获取微信列表
+	var result types.WorkPhoneGetWeChatsResp
+	client := req.C().DevMode()
+	client.SetCommonRetryCount(2).
+		SetCommonRetryBackoffInterval(1*time.Second, 5*time.Second).
+		SetCommonRetryFixedInterval(2 * time.Second).SetTimeout(30 * time.Second)
+	res, err := client.R().SetSuccessResult(&result).Post("http://chat.gkscrm.com:13086/pc/GetWeChatsReq?id=0")
+	if err != nil {
+		l.Error("syncWx: ", err)
+		return
+	}
+	if !res.IsSuccessState() {
+		l.Error("GetWeChats failed with status code: ", res.StatusCode)
+		return
+	}
+
+	// 遍历微信列表
+	for _, account := range result.Data {
+		if account.Wechatid == "" {
+			continue
+		}
+		wxinfo, err := l.svcCtx.DB.Wx.Query().
+			Where(
+				wx.And(
+					wx.Or(
+						wx.WxidEQ(account.Wechatid),
+						wx.PortEQ(account.Deviceid),
+					),
+					wx.CtypeEQ(1),
+				),
+			).
+			Only(l.ctx)
+
+		if err != nil && !ent.IsNotFound(err) {
+			l.Error("syncWx: ", err)
+			return
+		}
+		var status uint8
+		if account.Isonline == 0 {
+			status = 1
+		} else {
+			status = 0
+		}
+		if wxinfo != nil {
+			err = l.svcCtx.DB.Wx.UpdateOneID(wxinfo.ID).
+				SetServerID(0).
+				SetPort(account.Deviceid).
+				SetProcessID(strconv.FormatInt(account.Cid, 10)).
+				SetAccount(account.Wechatno).
+				SetNickname(account.Wechatnick).
+				SetHeadBig(account.Avatar).
+				SetStatus(status).
+				Exec(l.ctx)
+
+			if err != nil {
+				l.Error("syncWx: ", err)
+				return
+			}
+		} else {
+			l.Debug("wxinfo is nil")
+			_, err := l.svcCtx.DB.Wx.Create().
+				SetServerID(0).
+				SetPort(account.Deviceid).
+				SetProcessID(strconv.FormatInt(account.Cid, 10)).
+				SetWxid(account.Wechatid).
+				SetAccount(account.Wechatno).
+				SetHeadBig(account.Avatar).
+				SetNickname(account.Wechatnick).
+				SetStatus(status).
+				SetAllowList([]string{}).SetBlockList([]string{}).SetGroupAllowList([]string{}).SetGroupBlockList([]string{}).
+				Save(l.ctx)
+			if err != nil {
+				l.Error("syncWx: ", err)
+				return
+			}
+		}
+
+		data := map[string]interface{}{
+			"MsgType": "TriggerFriendPushTask",
+			"Content": map[string]interface{}{
+				"WeChatId": account.Wechatid,
+			},
+		}
+		jsonStr, err := json.Marshal(data)
+		err = l.svcCtx.WechatWs["default"].SendMsg([]byte(jsonStr))
+		if err != nil {
+			l.Error("syncWx: ", err)
+			return
+		}
+	}
+}

+ 3 - 1
desc/all.api

@@ -14,6 +14,7 @@ import "./wechat/message_records.api"
 import "./wechat/chatroom_member.api"
 import "./wechat/user.api"
 import "./openapi/contact.api"
+import "./openapi/chat.api"
 import "./wechat/batch_msg.api"
 import "./wechat/msg.api"
 import "./wechat/agent.api"
@@ -47,4 +48,5 @@ import "./wechat/xunji_service.api"
 import "./wp_wecom/send_msg.api"
 import "./wechat/fastgpt.api"
 import "./wechat/department.api"
-import "./wechat/api_key.api"
+import "./wechat/api_key.api"
+import "./wechat/department.api"

+ 188 - 0
desc/openapi/chat.api

@@ -0,0 +1,188 @@
+import "../base.api"
+
+type (
+    
+    //以下是API请求类型
+
+	CompApiReq {
+        CompCtlReq
+		StdCompApiReq
+        FastGptSpecReq
+    }
+
+	//FastGpt Completions请求信息
+	FastGptApiReq {
+		StdCompApiReq
+        FastGptSpecReq
+	}
+
+	//标准Completions请求信息
+	StdCompApiReq {
+        //model,like 'gpt-4o'
+        Model string `json:"model,optional"`
+        //Message list
+        Messages []StdCompMessage `json:"messages"`
+        //Stream 是否流式输出
+        Stream bool `json:"stream,default=false"`
+    }
+
+	//关于工作流配置的请求信息
+	CompCtlReq {
+		//EventType事件类型
+        EventType string `json:"event_type,default=fastgpt"`
+        //WorkId工作流ID
+        WorkId string `json:"work_id"`
+		//IsBatch 是同步还是异步,默认及取值false表明同步
+        IsBatch bool `json:"is_batch,default=false"`
+        //异步回调地址
+        Callback string `json:"callback,optional"`
+	}
+
+	FastGptSpecReq {
+        //ChatId
+        ChatId string `json:"chat_id,optional"`
+        //ResponseChatItemId
+        ResponseChatItemId string `json:"response_chat_item_id,optional"`
+        //Detail 详情开关
+        Detail bool `json:"detail,default=false"`
+        //Variables
+        Variables map[string]string `json:"variables,optional"`
+	}
+	
+	StdCompMessage {
+        Role string `json:"role"`
+        Content string `json:"content"`
+    }
+
+    //以下是API响应类型
+	CompOpenApiResp {
+        StdCompApiResp
+		FastgptSpecResp
+    }
+
+	StdCompApiResp {
+        // A unique identifier for the chat completion.
+	    ID string `json:"id"`
+	    // A list of chat completion choices. Can be more than one if `n` is greater
+	    // than 1.
+    	Choices []ChatCompletionChoice `json:"choices"`
+	    // The Unix timestamp (in seconds) of when the chat completion was created.
+	    Created int64 `json:"created"`
+	    // The model used for the chat completion.
+	    Model string `json:"model"`
+	    // The object type, which is always `chat.completion`.
+	    Object string `json:"object"`
+	    // The service tier used for processing the request.
+	    ServiceTier string `json:"service_tier,omitempty"`
+	    // This fingerprint represents the backend configuration that the model runs with.
+	    //
+    	// Can be used in conjunction with the `seed` request parameter to understand when
+	    // backend changes have been made that might impact determinism.
+	    SystemFingerprint string `json:"system_fingerprint"`
+	    // Usage statistics for the completion request.
+	    Usage CompletionUsage    `json:"usage,omitempty"`
+    }
+
+	FastgptSpecResp {
+		ResponseData []map[string]string `json:"responseData,omitempty"`
+		NewVariables map[string]string `json:"newVariables,omitempty"`
+	}
+	
+	ChatCompletionAudio {
+	    // Unique identifier for this audio response.
+	    ID string `json:"id"`
+        //TODO
+    }
+
+    ChatCompletionMessage {
+	    // The contents of the message.
+	    Content string `json:"content"`
+        //The contents of the reasoning message
+        ReasoningContent string `json:"reasoning_content,omitempty"`
+	    // The refusal message generated by the model.
+	    Refusal string `json:"refusal"`
+	    // The role of the author of this message.
+	    Role string `json:"role"`
+	    // If the audio output modality is requested, this object contains data about the
+	    // audio response from the model.
+	    // [Learn more](https://platform.openai.com/docs/guides/audio).
+	    Audio ChatCompletionAudio `json:"audio,omitempty"`
+	}
+
+    ChatCompletionChoice {
+	    // The reason the model stopped generating tokens. This will be `stop` if the model
+	    // hit a natural stop point or a provided stop sequence, `length` if the maximum
+	    // number of tokens specified in the request was reached, `content_filter` if
+	    // content was omitted due to a flag from our content filters, `tool_calls` if the
+	    // model called a tool, or `function_call` (deprecated) if the model called a
+	    // function.
+	    FinishReason string `json:"finish_reason"`
+	    // The index of the choice in the list of choices.
+	    Index int64 `json:"index"`
+	    // A chat completion message generated by the model.
+	    Message ChatCompletionMessage  `json:"message,omitempty"`
+        // A chat completion message generated by the model stream mode.
+	    Delta ChatCompletionMessage  `json:"delta,omitempty"`
+    }
+
+    CompletionUsageCompletionTokensDetails {
+	    // When using Predicted Outputs, the number of tokens in the prediction that
+	    // appeared in the completion.
+	    AcceptedPredictionTokens int64 `json:"accepted_prediction_tokens"`
+	    // Audio input tokens generated by the model.
+	    AudioTokens int64 `json:"audio_tokens"`
+	    // Tokens generated by the model for reasoning.
+	    ReasoningTokens int64 `json:"reasoning_tokens"`
+	    // When using Predicted Outputs, the number of tokens in the prediction that did
+	    // not appear in the completion. However, like reasoning tokens, these tokens are
+	    // still counted in the total completion tokens for purposes of billing, output,
+	    // and context window limits.
+	    RejectedPredictionTokens int64 `json:"rejected_prediction_tokens"`
+    }
+
+    CompletionUsagePromptTokensDetails {
+	    // Audio input tokens present in the prompt.
+	    AudioTokens int64 `json:"audio_tokens"`
+	    // Cached tokens present in the prompt.
+	    CachedTokens int64 `json:"cached_tokens"`
+    }
+
+    CompletionUsage {
+	    // Number of tokens in the generated completion.
+	    CompletionTokens int64 `json:"completion_tokens,required"`
+	    // Number of tokens in the prompt.
+	    PromptTokens int64 `json:"prompt_tokens,required"`
+	    // Total number of tokens used in the request (prompt + completion).
+	    TotalTokens int64 `json:"total_tokens,required"`
+	    // Breakdown of tokens used in a completion.
+	    CompletionTokensDetails CompletionUsageCompletionTokensDetails `json:"completion_tokens_details"`
+	    // Breakdown of tokens used in the prompt.
+	    PromptTokensDetails CompletionUsagePromptTokensDetails `json:"prompt_tokens_details"`
+    }   
+
+)
+
+@server(
+    group: chat
+    prefix: /v1
+)
+
+service Wechat {
+    
+    @handler getAuth
+    get /chat/getauth () returns (BaseMsgResp)
+}
+
+@server(
+	
+    group: chat
+    prefix: /v1
+	//jwt: Auth
+	middleware: OpenAuthority
+)
+
+service Wechat {
+    
+    @handler chatCompletions
+    post /chat/completions (CompApiReq) returns (CompOpenApiResp)
+}

BIN
ent.zip


+ 10 - 15
etc/wechat.yaml

@@ -28,8 +28,8 @@ DatabaseConf:
   Host: mysql-server
   Port: 3306
   DBName: wechat
-  Username: root
-  Password: simple-admin.
+  Username: wallet
+  Password: wallet
   MaxOpenConn: 100
   SSLMode: disable
   CacheTime: 5
@@ -47,9 +47,9 @@ CasbinDatabaseConf:
   Type: mysql
   Host: mysql-server
   Port: 3306
-  DBName: wechat_admin
-  Username: root
-  Password: simple-admin.
+  DBName: wechat-admin
+  Username: wallet
+  Password: wallet
   MaxOpenConn: 100
   SSLMode: disable
   CacheTime: 5
@@ -90,17 +90,12 @@ OpenAI:
   BaseUrl: https://api.openai.com/v1
   ApiKey: sk-ZQRNypQOC8ID5WbpCdF263C58dF44271842e86D408Bb3848
 
-WebSocket:
-    -
-      Type: wechat
-      Name: default
-      Url: ws://chat.gkscrm.com:13088
-
 FastgptMongoConf:
   Url: mongodb://myusername:mypassword@47.251.25.21:27017/?connect=direct
   DBName: fastgpt
 
-WeChatWs:
-  -
-    Url: ws://chat.gkscrm.com:15088
-    Appid: wechat
+WebSocket:
+    -
+      Type: wechat
+      Name: default
+      Url: ws://chat.gkscrm.com:13088

+ 1 - 0
go.mod

@@ -16,6 +16,7 @@ require (
 	github.com/deckarep/golang-set/v2 v2.8.0
 	github.com/go-resty/resty/v2 v2.14.0
 	github.com/gofrs/uuid/v5 v5.0.0
+	github.com/golang-jwt/jwt/v4 v4.3.0
 	github.com/golang-jwt/jwt/v5 v5.2.1
 	github.com/gorilla/websocket v1.5.0
 	github.com/imroc/req/v3 v3.43.1

+ 2 - 4
go.sum

@@ -244,6 +244,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
+github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
 github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
@@ -470,7 +472,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -509,7 +510,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
 github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@@ -611,8 +611,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
 github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
 github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
-github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
-github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=

+ 44 - 0
internal/handler/chat/chat_completions_handler.go

@@ -0,0 +1,44 @@
+package chat
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/chat"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /v1/chat/completions chat ChatCompletions
+//
+
+//
+
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: CompApiReq
+//
+// Responses:
+//  200: CompOpenApiResp
+
+func ChatCompletionsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.CompApiReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := chat.NewChatCompletionsLogic(r.Context(), svcCtx)
+		resp, err := l.ChatCompletions(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 31 - 0
internal/handler/chat/get_auth_handler.go

@@ -0,0 +1,31 @@
+package chat
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/chat"
+	"wechat-api/internal/svc"
+)
+
+// swagger:route get /v1/chat/getauth chat GetAuth
+//
+
+//
+
+//
+// Responses:
+//  200: BaseMsgResp
+
+func GetAuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		l := chat.NewGetAuthLogic(r.Context(), svcCtx)
+		resp, err := l.GetAuth()
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 28 - 0
internal/handler/routes.go

@@ -1,4 +1,6 @@
 // Code generated by goctl. DO NOT EDIT.
+// goctls v1.10.1
+
 package handler
 
 import (
@@ -23,6 +25,7 @@ import (
 	base "wechat-api/internal/handler/base"
 	batch_msg "wechat-api/internal/handler/batch_msg"
 	category "wechat-api/internal/handler/category"
+	chat "wechat-api/internal/handler/chat"
 	chatrecords "wechat-api/internal/handler/chatrecords"
 	chatsession "wechat-api/internal/handler/chatsession"
 	contact "wechat-api/internal/handler/contact"
@@ -856,6 +859,31 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 	)
 
 	server.AddRoutes(
+		[]rest.Route{
+			{
+				Method:  http.MethodGet,
+				Path:    "/chat/getauth",
+				Handler: chat.GetAuthHandler(serverCtx),
+			},
+		},
+		rest.WithPrefix("/v1"),
+	)
+
+	server.AddRoutes(
+		rest.WithMiddlewares(
+			[]rest.Middleware{serverCtx.OpenAuthority},
+			[]rest.Route{
+				{
+					Method:  http.MethodPost,
+					Path:    "/chat/completions",
+					Handler: chat.ChatCompletionsHandler(serverCtx),
+				},
+			}...,
+		),
+		rest.WithPrefix("/v1"),
+	)
+
+	server.AddRoutes(
 		rest.WithMiddlewares(
 			[]rest.Middleware{serverCtx.Authority},
 			[]rest.Route{

+ 3 - 2
internal/logic/api_key/create_api_key_logic.go

@@ -5,12 +5,13 @@ import (
 	"crypto/rand"
 	"encoding/base64"
 	"fmt"
-	"github.com/suyuan32/simple-admin-common/msg/errormsg"
-	"github.com/zeromicro/go-zero/core/errorx"
+
 	"wechat-api/internal/svc"
 	"wechat-api/internal/types"
 	"wechat-api/internal/utils/dberrorhandler"
 
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"github.com/zeromicro/go-zero/core/errorx"
 	"github.com/zeromicro/go-zero/core/logx"
 )
 

+ 67 - 0
internal/logic/chat/chat_completions_logic.go

@@ -0,0 +1,67 @@
+package chat
+
+import (
+	"context"
+	"errors"
+
+	"wechat-api/ent"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/compapi"
+	"wechat-api/internal/utils/contextkey"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type ChatCompletionsLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewChatCompletionsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ChatCompletionsLogic {
+	return &ChatCompletionsLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *ChatCompletionsLogic) ChatCompletions(req *types.CompApiReq) (resp *types.CompOpenApiResp, err error) {
+	// todo: add your logic here and delete this line
+
+	/*
+	   1.鉴权获得token
+	   2.必要参数检测及转换
+	   3. 根据event_type选择不同处理路由
+	*/
+	var (
+		apiKeyObj *ent.ApiKey
+		ok        bool
+	)
+	workToken := compapi.GetWorkTokenByID(req.EventType, req.WorkId)
+	apiKeyObj, ok = contextkey.AuthTokenInfoKey.GetValue(l.ctx)
+	if !ok {
+		return nil, errors.New("content get token err")
+	}
+	/*
+		fmt.Println("=========================================")
+		fmt.Printf("In ChatCompletion Get Token Info:\nKey:'%s'\n", apiKeyObj.Key)
+		fmt.Printf("Title:'%s'\n", apiKeyObj.Title)
+		fmt.Printf("OpenaiBase:'%s'\n", apiKeyObj.OpenaiBase)
+		fmt.Printf("OpenaiKey:'%s'\n", apiKeyObj.OpenaiKey)
+		fmt.Printf("workToken:'%s' because %s/%s\n", workToken, req.EventType, req.WorkId)
+		fmt.Println("=========================================")
+	*/
+
+	if len(apiKeyObj.OpenaiBase) == 0 || len(workToken) == 0 {
+		return nil, errors.New("not auth info")
+	}
+	return l.workForFastgpt(req, workToken, apiKeyObj.OpenaiBase)
+}
+
+func (l *ChatCompletionsLogic) workForFastgpt(req *types.CompApiReq, apiKey string, apiBase string) (resp *types.CompOpenApiResp, err error) {
+
+	//apiKey := "fastgpt-d2uehCb2T40h9chNGjf4bpFrVKmMkCFPbrjfVLZ6DAL2zzqzOFJWP"
+	return compapi.NewFastgptChatCompletions(l.ctx, apiKey, apiBase, req)
+
+}

+ 64 - 0
internal/logic/chat/get_auth_logic.go

@@ -0,0 +1,64 @@
+package chat
+
+import (
+	"context"
+	"fmt"
+	"math/rand"
+	"time"
+
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+
+	myjwt "github.com/golang-jwt/jwt/v4"
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type GetAuthLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewGetAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAuthLogic {
+	return &GetAuthLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *GetAuthLogic) GetAuth() (resp *types.BaseMsgResp, err error) {
+	// todo: add your logic here and delete this line
+
+	nowSec := time.Now().Unix()
+	accessExpire := l.svcCtx.Config.Auth.AccessExpire
+	accessSecret := l.svcCtx.Config.Auth.AccessSecret
+	Id := RangeRandom(1, 99999)
+
+	accessToken, err := getToken(accessSecret, nowSec, accessExpire, Id)
+	if err != nil {
+		accessToken = ""
+	}
+	msg := fmt.Sprintf("gen Token:'%s' by Id:%d and unixtime:%d \n", accessToken, Id, nowSec)
+	return &types.BaseMsgResp{
+		Code: 100,
+		Msg:  msg,
+	}, err
+}
+
+func getToken(secretKey string, iat, seconds, uid int64) (string, error) {
+	claims := make(myjwt.MapClaims)
+	claims["exp"] = iat + seconds
+	claims["iat"] = iat
+	claims["uid"] = uid
+	token := myjwt.New(myjwt.SigningMethodHS256)
+	token.Claims = claims
+	return token.SignedString([]byte(secretKey))
+}
+
+// RangeRandom 返回指定范围内的随机整数。[min,max),不包含max
+func RangeRandom(min, max int64) (number int64) {
+	//创建随机种子
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	number = r.Int63n(max-min) + min
+	return number
+}

+ 141 - 0
internal/middleware/openauthority_middleware.go

@@ -0,0 +1,141 @@
+package middleware
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"reflect"
+
+	"wechat-api/ent"
+	"wechat-api/ent/apikey"
+	"wechat-api/ent/predicate"
+	"wechat-api/internal/utils/compapi"
+	"wechat-api/internal/utils/contextkey"
+
+	"wechat-api/internal/config"
+
+	"github.com/redis/go-redis/v9"
+	"github.com/suyuan32/simple-admin-common/utils/jwt"
+	"github.com/zeromicro/go-zero/core/errorx"
+	"github.com/zeromicro/go-zero/rest/httpx"
+)
+
+/*
+//"wechat-api/internal/types/payload"
+var p types.payload.SendWxPayload
+*/
+
+type OpenAuthorityMiddleware struct {
+	DB     *ent.Client
+	Rds    redis.UniversalClient
+	Config config.Config
+}
+
+func NewOpenAuthorityMiddleware(db *ent.Client, rds redis.UniversalClient, c config.Config) *OpenAuthorityMiddleware {
+	return &OpenAuthorityMiddleware{
+		DB:     db,
+		Rds:    rds,
+		Config: c,
+	}
+}
+
+func (m *OpenAuthorityMiddleware) checkTokenUserInfo(ctx context.Context, authToken string) (*ent.ApiKey, int, error) {
+	var (
+		rc  int
+		err error
+		val *ent.ApiKey
+	)
+	val, rc, err = m.getTokenUserInfoByDb(ctx, authToken)
+	return val, rc, err
+
+	/*
+		r, e = m.getTokenUserInfoByRds(ctx, loginToken)
+		fmt.Println("redis:", "code-", r, "err-", e)
+	*/
+	/*
+		//首先从redis取数据
+		_, rc, err = m.getTokenUserInfoByRds(ctx, authToken)
+		fmt.Printf("++++++++++++++++++++++++get authinfo from rds out:%d/err:%s\n", rc, err)
+		if rc <= 0 || err != nil { //无法获得后再从数据库获得
+			rc = 0
+			err = nil
+			val, rc, err = m.getTokenUserInfoByDb(ctx, authToken)
+			fmt.Println("----------------------After m.getTokenUserInfoByDb:", val)
+			err = m.saveTokenUserInfoToRds(ctx, authToken, val)
+			fmt.Println("------------save saveTokenUserInfoToRd err:", err)
+		}
+
+		_ = rc
+		if err != nil {
+			return nil, 0, err
+		}
+		return val, 0, nil
+	*/
+}
+
+func (m *OpenAuthorityMiddleware) saveTokenUserInfoToRds(ctx context.Context, authToken string, saveInfo *ent.ApiKey) error {
+	if bs, err := json.Marshal(saveInfo); err == nil {
+		return err
+	} else {
+		rc, err := m.Rds.HSet(ctx, compapi.APIAuthInfoKey, authToken, string(bs)).Result()
+		fmt.Printf("#~~~~~~~~~~~~~~~++~~~~~~~~~~~~~HSet Val:%s get Result:%d/%s\n", string(bs), rc, err)
+		return err
+	}
+}
+func (m *OpenAuthorityMiddleware) getTokenUserInfoByRds(ctx context.Context, authToken string) (*ent.ApiKey, int, error) {
+
+	rcode := -1
+	val, err := m.Rds.HGet(ctx, compapi.APIAuthInfoKey, authToken).Result()
+	if err == redis.Nil {
+		rcode = 0
+	} else if err == nil {
+		rcode = 1
+	}
+	fmt.Printf("#####################From Redis By Key:'%s' Get '%s'(%s/%T)\n", authToken, val, reflect.TypeOf(val), val)
+	fmt.Println(val)
+	return nil, rcode, err
+}
+
+func (m *OpenAuthorityMiddleware) getTokenUserInfoByDb(ctx context.Context, loginToken string) (*ent.ApiKey, int, error) {
+	rcode := -1
+
+	var predicates []predicate.ApiKey
+	predicates = append(predicates, apikey.KeyEQ(loginToken))
+	val, err := m.DB.ApiKey.Query().Where(predicates...).WithAgent().Only(ctx)
+	if err != nil {
+		return nil, rcode, err
+	}
+	return val, 1, nil
+}
+
+func (m *OpenAuthorityMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+
+		ctx := r.Context()
+		ctx = contextkey.HttpResponseWriterKey.WithValue(ctx, w) //context存入http.ResponseWriter
+		authToken := jwt.StripBearerPrefixFromToken(r.Header.Get("Authorization"))
+		if len(authToken) == 0 {
+			httpx.Error(w, errorx.NewApiError(http.StatusForbidden, "无法获取token"))
+			return
+		}
+		apiKeyObj, _, err := m.checkTokenUserInfo(ctx, authToken)
+		if err != nil {
+			httpx.Error(w, errorx.NewApiError(http.StatusForbidden, "无法获取合适的授权信息"))
+			return
+		}
+		//ctx = contextkey.OpenapiTokenKey.WithValue(ctx, apiToken)
+		ctx = contextkey.AuthTokenInfoKey.WithValue(ctx, apiKeyObj)
+		/*
+			fmt.Println("=========================================")
+			fmt.Printf("In Middleware Get Token Info:\nKey:'%s'\n", apiKeyObj.Key)
+			fmt.Printf("Title:'%s'\n", apiKeyObj.Title)
+			fmt.Printf("OpenaiBase:'%s'\n", apiKeyObj.OpenaiBase)
+			fmt.Printf("OpenaiKey:'%s'\n", apiKeyObj.OpenaiKey)
+			fmt.Println("=========================================")
+		*/
+		newReq := r.WithContext(ctx)
+		// Passthrough to next handler if need
+		next(w, newReq)
+	}
+}

+ 1 - 0
internal/pkg/wechat_ws/test/main.go

@@ -24,6 +24,7 @@ func main() {
 
 	go client.WritePump()
 
+	//client.SendMsg([]byte(`{"msgType":"text","message":"你好"}`))
 	client.SendMsg([]byte(`{
     "MsgType": "TalkToFriendTask",
     "Content": {

+ 6 - 0
internal/pkg/wechat_ws/wechat_ws_client.go

@@ -230,6 +230,12 @@ func (c *WechatWsClient) ReadPump() {
 				for _, handler := range c.MessageHandler {
 					// todo 重启时这里会丢消息,回头需要用wg来进行平滑处理
 					go func() {
+						defer func() {
+							// 捕获panic, 防止程序崩溃
+							if r := recover(); r != nil {
+								logx.Error("Recovered in f", r)
+							}
+						}()
 						err = handler(&msg)
 						if err != nil {
 							logx.Error(err)

+ 68 - 0
internal/service/MessageHandlers/friend_push_notice.go

@@ -0,0 +1,68 @@
+package MessageHandlers
+
+import (
+	"context"
+	"encoding/json"
+	"github.com/zeromicro/go-zero/core/logx"
+	"wechat-api/ent/wx"
+	"wechat-api/hook"
+	"wechat-api/internal/pkg/wechat_ws"
+	"wechat-api/internal/svc"
+	"wechat-api/workphone"
+)
+
+type FriendPushNoticeHandler struct {
+	svcCtx *svc.ServiceContext
+}
+
+func NewFriendPushNoticeHandler(svcCtx *svc.ServiceContext) *FriendPushNoticeHandler {
+	return &FriendPushNoticeHandler{
+		svcCtx: svcCtx,
+	}
+}
+
+func (f *FriendPushNoticeHandler) Handler(msg *wechat_ws.MsgJsonObject) error {
+	if msg.MsgType == "FriendPushNotice" {
+		message := workphone.FriendPushNoticeMessage{}
+		err := json.Unmarshal([]byte(msg.Message), &message)
+		if err != nil {
+			return err
+		}
+		// 拿到租户 id
+		wx_info, err := f.svcCtx.DB.Wx.Query().
+			Where(
+				wx.WxidEQ(message.WeChatId), // Additional filter by organizationId
+			).
+			Only(context.TODO())
+		hookClient := hook.NewHook("", "", "")
+		for _, friend := range message.Friends {
+			friendType := 1
+			if friend.Type == 1 {
+				friendType = 2
+				_ = hookClient.RequestChatRoomInfo(friend.FriendId, message.WeChatId)
+			} else {
+				friendType = 1
+			}
+			_, err = f.svcCtx.DB.Contact.Create().
+				SetWxWxid(message.WeChatId).
+				SetType(friendType).
+				SetWxid(friend.FriendId).
+				SetAccount(friend.FriendNo).
+				SetNickname(friend.FriendNick).
+				SetMarkname(friend.Memo).
+				SetHeadimg(friend.Avatar).
+				//SetSex(cast.ToInt(friend.Gender)).
+				SetOrganizationID(wx_info.OrganizationID).
+				OnConflict().
+				UpdateNewValues().
+				SetOrganizationID(wx_info.OrganizationID).
+				ID(context.TODO())
+
+			if err != nil {
+				logx.Error("Contact.Create: ", wx_info.OrganizationID)
+				return err
+			}
+		}
+	}
+	return nil
+}

+ 39 - 34
internal/svc/service_context.go

@@ -9,33 +9,36 @@ import (
 	"time"
 	"wechat-api/database"
 	"wechat-api/database/dao/wechat/query"
+	"wechat-api/internal/config"
+	"wechat-api/internal/middleware"
+	"wechat-api/internal/pkg/wechat_ws"
+	"wechat-api/mongo_model"
 
 	"github.com/suyuan32/simple-admin-core/rpc/coreclient"
 	"github.com/zeromicro/go-zero/core/logx"
-	"github.com/zeromicro/go-zero/rest"
-	"github.com/zeromicro/go-zero/zrpc"
+
 	"wechat-api/ent"
 	_ "wechat-api/ent/runtime"
-	"wechat-api/internal/config"
-	"wechat-api/internal/middleware"
-	"wechat-api/internal/pkg/wechat_ws"
-	mongo_model "wechat-api/mongo_model"
+
+	"github.com/zeromicro/go-zero/rest"
+	"github.com/zeromicro/go-zero/zrpc"
 )
 
 type ServiceContext struct {
-	Config      config.Config
-	Casbin      *casbin.Enforcer
-	Authority   rest.Middleware
-	Miniprogram rest.Middleware
-	DB          *ent.Client //大家都不爱用,后边可以慢慢过渡到gorm的库
-	WechatDB    *gorm.DB
-	WechatQ     *query.Query
-	CoreRpc     coreclient.Core
-	Rds         redis.UniversalClient
-	WechatWs    map[string]*wechat_ws.WechatWsClient
-	Cache       *collection.Cache
-	NodeID      *snowflake.Node
-	MongoModel  *mongo_model.AllMongoModel
+	Config        config.Config
+	Casbin        *casbin.Enforcer
+	Authority     rest.Middleware
+	OpenAuthority rest.Middleware
+	Miniprogram   rest.Middleware
+	DB            *ent.Client
+	WechatDB      *gorm.DB
+	WechatQ       *query.Query
+	CoreRpc       coreclient.Core
+	Rds           redis.UniversalClient
+	WechatWs      map[string]*wechat_ws.WechatWsClient
+	Cache         *collection.Cache
+	NodeID        *snowflake.Node
+	MongoModel    *mongo_model.AllMongoModel
 }
 
 func NewServiceContext(c config.Config) *ServiceContext {
@@ -50,6 +53,9 @@ func NewServiceContext(c config.Config) *ServiceContext {
 		ent.Debug(), // debug mode
 	)
 
+	// 初始化 MongoDB 客户端
+	all_mongo_model := mongo_model.SetupMongoModel(c.FastgptMongoConf.Url, c.FastgptMongoConf.DBName)
+
 	// gorm 数据库连接
 	wechatDb, err := database.InitWechatDB(c.DatabaseConf, c.Mode)
 	if err != nil {
@@ -58,11 +64,10 @@ func NewServiceContext(c config.Config) *ServiceContext {
 	}
 	wechatQ := query.Use(wechatDb)
 
-	// 初始化 MongoDB 客户端
-	all_mongo_model := mongo_model.SetupMongoModel(c.FastgptMongoConf.Url, c.FastgptMongoConf.DBName)
-
 	coreRpc := coreclient.NewCore(zrpc.NewClientIfEnable(c.CoreRpc))
 
+	// 初始化微信ws客户端
+	// todo 现在配置是从 config.yaml中读取的,后续需要改成从数据库中读取,以便匹配不同的微信号
 	wechatWs := make(map[string]*wechat_ws.WechatWsClient)
 
 	for _, ws := range c.WebSocket {
@@ -92,17 +97,17 @@ func NewServiceContext(c config.Config) *ServiceContext {
 	}
 
 	return &ServiceContext{
-		Config:      c,
-		Authority:   middleware.NewAuthorityMiddleware(cbn, rds, coreRpc).Handle,
-		Miniprogram: middleware.NewMiniprogramMiddleware(cbn, rds, coreRpc, c).Handle,
-		DB:          db,
-		WechatDB:    wechatDb,
-		WechatQ:     wechatQ,
-		CoreRpc:     coreRpc,
-		Rds:         rds,
-		WechatWs:    wechatWs,
-		Cache:       cache,
-		NodeID:      node,
-		MongoModel:  all_mongo_model,
+		Config:        c,
+		Authority:     middleware.NewAuthorityMiddleware(cbn, rds, coreRpc).Handle,
+		OpenAuthority: middleware.NewOpenAuthorityMiddleware(db, rds, c).Handle, Miniprogram: middleware.NewMiniprogramMiddleware(cbn, rds, coreRpc, c).Handle,
+		DB:         db,
+		WechatDB:   wechatDb,
+		WechatQ:    wechatQ,
+		CoreRpc:    coreRpc,
+		Rds:        rds,
+		WechatWs:   wechatWs,
+		Cache:      cache,
+		NodeID:     node,
+		MongoModel: all_mongo_model,
 	}
 }

+ 200 - 35
internal/types/types.go

@@ -1895,6 +1895,170 @@ type WxidReq struct {
 	Wxid string `json:"wxid"`
 }
 
+// 以下是API请求类型
+// swagger:model CompApiReq
+type CompApiReq struct {
+	CompCtlReq
+	StdCompApiReq
+	FastGptSpecReq
+}
+
+// FastGpt Completions请求信息
+// swagger:model FastGptApiReq
+type FastGptApiReq struct {
+	StdCompApiReq
+	FastGptSpecReq
+}
+
+// 标准Completions请求信息
+// swagger:model StdCompApiReq
+type StdCompApiReq struct {
+	//model,like 'gpt-4o'
+	Model string `json:"model,optional"`
+	//Message list
+	Messages []StdCompMessage `json:"messages"`
+	//Stream 是否流式输出
+	Stream bool `json:"stream,default=false"`
+}
+
+// 关于工作流配置的请求信息
+// swagger:model CompCtlReq
+type CompCtlReq struct {
+	//EventType事件类型
+	EventType string `json:"event_type,default=fastgpt"`
+	//WorkId工作流ID
+	WorkId string `json:"work_id"`
+	//IsBatch 是同步还是异步,默认及取值false表明同步
+	IsBatch bool `json:"is_batch,default=false"`
+	//异步回调地址
+	Callback string `json:"callback,optional"`
+}
+
+// swagger:model FastGptSpecReq
+type FastGptSpecReq struct {
+	//ChatId
+	ChatId string `json:"chat_id,optional"`
+	//ResponseChatItemId
+	ResponseChatItemId string `json:"response_chat_item_id,optional"`
+	//Detail 详情开关
+	Detail bool `json:"detail,default=false"`
+	//Variables
+	Variables map[string]string `json:"variables,optional"`
+}
+
+type StdCompMessage struct {
+	Role    string `json:"role"`
+	Content string `json:"content"`
+}
+
+// 以下是API响应类型
+// swagger:model CompOpenApiResp
+type CompOpenApiResp struct {
+	StdCompApiResp
+	FastgptSpecResp
+}
+
+// swagger:model StdCompApiResp
+type StdCompApiResp struct {
+	// A unique identifier for the chat completion.
+	ID string `json:"id"`
+	// A list of chat completion choices. Can be more than one if `n` is greater
+	// than 1.
+	Choices []ChatCompletionChoice `json:"choices"`
+	// The Unix timestamp (in seconds) of when the chat completion was created.
+	Created int64 `json:"created"`
+	// The model used for the chat completion.
+	Model string `json:"model"`
+	// The object type, which is always `chat.completion`.
+	Object string `json:"object"`
+	// The service tier used for processing the request.
+	ServiceTier string `json:"service_tier,omitempty"`
+	// This fingerprint represents the backend configuration that the model runs with.
+	//
+	// Can be used in conjunction with the `seed` request parameter to understand when
+	// backend changes have been made that might impact determinism.
+	SystemFingerprint string `json:"system_fingerprint"`
+	// Usage statistics for the completion request.
+	Usage CompletionUsage `json:"usage,omitempty"`
+}
+
+// swagger:model FastgptSpecResp
+type FastgptSpecResp struct {
+	ResponseData []map[string]string `json:"responseData,omitempty"`
+	NewVariables map[string]string   `json:"newVariables,omitempty"`
+}
+
+type ChatCompletionAudio struct {
+	// Unique identifier for this audio response.
+	ID string `json:"id"`
+}
+
+type ChatCompletionMessage struct {
+	// The contents of the message.
+	Content string `json:"content"`
+	//The contents of the reasoning message
+	ReasoningContent string `json:"reasoning_content,omitempty"`
+	// The refusal message generated by the model.
+	Refusal string `json:"refusal"`
+	// The role of the author of this message.
+	Role string `json:"role"`
+	// If the audio output modality is requested, this object contains data about the
+	// audio response from the model.
+	// [Learn more](https://platform.openai.com/docs/guides/audio).
+	Audio ChatCompletionAudio `json:"audio,omitempty"`
+}
+
+type ChatCompletionChoice struct {
+	// The reason the model stopped generating tokens. This will be `stop` if the model
+	// hit a natural stop point or a provided stop sequence, `length` if the maximum
+	// number of tokens specified in the request was reached, `content_filter` if
+	// content was omitted due to a flag from our content filters, `tool_calls` if the
+	// model called a tool, or `function_call` (deprecated) if the model called a
+	// function.
+	FinishReason string `json:"finish_reason"`
+	// The index of the choice in the list of choices.
+	Index int64 `json:"index"`
+	// A chat completion message generated by the model.
+	Message ChatCompletionMessage `json:"message,omitempty"`
+	// A chat completion message generated by the model stream mode.
+	Delta ChatCompletionMessage `json:"delta,omitempty"`
+}
+
+type CompletionUsageCompletionTokensDetails struct {
+	// When using Predicted Outputs, the number of tokens in the prediction that
+	// appeared in the completion.
+	AcceptedPredictionTokens int64 `json:"accepted_prediction_tokens"`
+	// Audio input tokens generated by the model.
+	AudioTokens int64 `json:"audio_tokens"`
+	// Tokens generated by the model for reasoning.
+	ReasoningTokens int64 `json:"reasoning_tokens"`
+	// When using Predicted Outputs, the number of tokens in the prediction that did
+	// not appear in the completion. However, like reasoning tokens, these tokens are
+	// still counted in the total completion tokens for purposes of billing, output,
+	// and context window limits.
+	RejectedPredictionTokens int64 `json:"rejected_prediction_tokens"`
+}
+
+type CompletionUsagePromptTokensDetails struct {
+	// Audio input tokens present in the prompt.
+	AudioTokens int64 `json:"audio_tokens"`
+	// Cached tokens present in the prompt.
+	CachedTokens int64 `json:"cached_tokens"`
+}
+
+type CompletionUsage struct {
+	// Number of tokens in the generated completion.
+	CompletionTokens int64 `json:"completion_tokens,required"`
+	// Number of tokens in the prompt.
+	PromptTokens int64 `json:"prompt_tokens,required"`
+	// Total number of tokens used in the request (prompt + completion).
+	TotalTokens int64 `json:"total_tokens,required"`
+	// Breakdown of tokens used in a completion.
+	CompletionTokensDetails CompletionUsageCompletionTokensDetails `json:"completion_tokens_details"`
+	// Breakdown of tokens used in the prompt.
+	PromptTokensDetails CompletionUsagePromptTokensDetails `json:"prompt_tokens_details"`
+}
+
 // The data of batch msg information | BatchMsg信息
 // swagger:model BatchMsgInfo
 type BatchMsgInfo struct {
@@ -4188,41 +4352,6 @@ type DepartmentInfo struct {
 	ParentId *uint64 `json:"parentId,optional"`
 }
 
-// The response data of department list | 部门列表数据
-// swagger:model DepartmentListResp
-type DepartmentListResp struct {
-	BaseDataInfo
-	// Department list data | 部门列表数据
-	Data DepartmentListInfo `json:"data"`
-}
-
-// Department list data | 部门列表数据
-// swagger:model DepartmentListInfo
-type DepartmentListInfo struct {
-	BaseListInfo
-	// The API list data | 部门列表数据
-	Data []DepartmentInfo `json:"data"`
-}
-
-// Get department list request params | 部门列表请求参数
-// swagger:model DepartmentListReq
-type DepartmentListReq struct {
-	PageInfo
-	// Name | 部门名称
-	// max length : 50
-	Name *string `json:"name,optional" validate:"omitempty,max=50"`
-	// Leader | 部门负责人
-	// max length : 20
-	Leader *string `json:"leader,optional" validate:"omitempty,max=20"`
-}
-
-// Department information response | 部门信息返回体
-// swagger:model DepartmentInfoResp
-type DepartmentInfoResp struct {
-	BaseDataInfo
-	// Department information | 部门数据
-	Data DepartmentInfo `json:"data"`
-}
 
 // The data of api_key information | ApiKey信息
 // swagger:model ApiKeyInfo
@@ -4266,3 +4395,39 @@ type ApiKeyListReq struct {
 	Key            *string `json:"key,optional"`
 	OrganizationId *uint64 `json:"organization_id,optional"`
 }
+
+// The response data of department list | 部门列表数据
+// swagger:model DepartmentListResp
+type DepartmentListResp struct {
+	BaseDataInfo
+	// Department list data | 部门列表数据
+	Data DepartmentListInfo `json:"data"`
+}
+
+// Department list data | 部门列表数据
+// swagger:model DepartmentListInfo
+type DepartmentListInfo struct {
+	BaseListInfo
+	// The API list data | 部门列表数据
+	Data []DepartmentInfo `json:"data"`
+}
+
+// Get department list request params | 部门列表请求参数
+// swagger:model DepartmentListReq
+type DepartmentListReq struct {
+	PageInfo
+	// Name | 部门名称
+	// max length : 50
+	Name *string `json:"name,optional" validate:"omitempty,max=50"`
+	// Leader | 部门负责人
+	// max length : 20
+	Leader *string `json:"leader,optional" validate:"omitempty,max=20"`
+}
+
+// Department information response | 部门信息返回体
+// swagger:model DepartmentInfoResp
+type DepartmentInfoResp struct {
+	BaseDataInfo
+	// Department information | 部门数据
+	Data DepartmentInfo `json:"data"`
+}

+ 121 - 0
internal/utils/compapi/compsteam.go

@@ -0,0 +1,121 @@
+package compapi
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/json"
+	"errors"
+	"io"
+	"net/http"
+	"strings"
+	"wechat-api/internal/types"
+
+	"github.com/openai/openai-go/packages/ssestream"
+)
+
+type ChatCompSteamChunk struct {
+	types.StdCompApiResp
+	RAW string `json:"-"`
+}
+
+type ApiRespStreamChunk struct {
+	Event string             `json:"event"`
+	Data  ChatCompSteamChunk `json:"data"`
+}
+
+type myStreamDecoder struct {
+	evt    ssestream.Event
+	rc     io.ReadCloser
+	scn    *bufio.Scanner
+	err    error
+	closed bool
+	// 用于处理多行事件
+	pendingEvent string
+}
+
+func (r *ChatCompSteamChunk) UnmarshalJSON(data []byte) (err error) {
+	r.RAW = string(data)
+	type Alias ChatCompSteamChunk
+	return json.Unmarshal(data, (*Alias)(r))
+}
+
+func ApiRespStreamDecoder(res any) ssestream.Decoder {
+	var rc io.ReadCloser
+	switch v := res.(type) {
+	case *http.Response:
+		rc = v.Body
+	case []byte:
+		rc = io.NopCloser(bytes.NewReader(v))
+	case string:
+		rc = io.NopCloser(bytes.NewReader([]byte(v)))
+	default:
+		rc = io.NopCloser(strings.NewReader(""))
+	}
+	return &myStreamDecoder{rc: rc, scn: bufio.NewScanner(rc)}
+}
+
+func (s *myStreamDecoder) Event() ssestream.Event {
+	return s.evt
+}
+
+func (s *myStreamDecoder) Close() error {
+
+	s.closed = true
+	if closer, ok := s.rc.(io.Closer); ok {
+		return closer.Close()
+	}
+	return nil
+}
+
+func (s *myStreamDecoder) Err() error {
+	return s.err
+}
+
+func (s *myStreamDecoder) Next() bool {
+	if s.err != nil {
+		return false
+	}
+	eventType := ""
+	dataBuffer := bytes.NewBuffer(nil)
+
+	for s.scn.Scan() {
+		line := strings.TrimSpace(s.scn.Text())
+		if len(line) == 0 {
+			continue //跳过空行
+		}
+
+		// 处理事件类型行
+		if strings.HasPrefix(line, "event:") {
+			s.pendingEvent = strings.TrimSpace(line[len("event:"):])
+			continue
+		}
+
+		// 处理数据行
+		if strings.HasPrefix(line, "data:") {
+			tmpdata := strings.TrimSpace(line[len("data:"):])
+
+			//确定事件类型
+			if s.pendingEvent != "" {
+				eventType = s.pendingEvent
+				s.pendingEvent = ""
+			} else {
+				eventType = "answer" // 默认类型
+			}
+
+			_, s.err = dataBuffer.WriteString(tmpdata)
+			break
+		}
+		//忽略无法识别的行
+	}
+	if dataBuffer.Len() > 0 {
+		s.evt = ssestream.Event{
+			Type: eventType,
+			Data: dataBuffer.Bytes(),
+		}
+		return true
+	}
+	if err := s.scn.Err(); err != nil && !errors.Is(err, io.EOF) {
+		s.err = s.scn.Err()
+	}
+	return false
+}

+ 26 - 0
internal/utils/compapi/config.go

@@ -0,0 +1,26 @@
+package compapi
+
+import (
+	"github.com/openai/openai-go"
+)
+
+const (
+	ChatModelDeepSeekV3 openai.ChatModel = "deepseek-chat"
+	ChatModelDeepSeekR1 openai.ChatModel = "deepseek-reasoner"
+
+	APIAuthInfoKey string = "COMPAPI_AUTHINFO"
+)
+
+var fastgptWorkIdMap = map[string]string{
+	"default":              "fastgpt-jcDATa9aH4vtUsjDpCU773BxmLU50IxKUX9nUT0mCTLQkEoo1hPxPEdNQeOEWGTn",
+	"OPTIMIZE_CALL_SCRIPT": "fastgpt-bcQ9cWKd6y9a2LfizweeWEnukkQi1Oq46yoiRg9yDNLm8NPTWXsyFwcB",
+}
+
+// 获取workToken
+func GetWorkTokenByID(eventType string, workId string) string {
+	val, exist := fastgptWorkIdMap[workId]
+	if !exist {
+		val = fastgptWorkIdMap["default"]
+	}
+	return val
+}

+ 190 - 0
internal/utils/compapi/func.go

@@ -0,0 +1,190 @@
+package compapi
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/contextkey"
+
+	openai "github.com/openai/openai-go"
+	"github.com/openai/openai-go/option"
+	"github.com/openai/openai-go/packages/ssestream"
+	"github.com/zeromicro/go-zero/rest/httpx"
+)
+
+func NewAiClient(apiKey string, apiBase string) *openai.Client {
+	return openai.NewClient(option.WithAPIKey(apiKey),
+		option.WithBaseURL(apiBase))
+}
+
+func NewFastgptClient(apiKey string) *openai.Client {
+	//http://fastgpt.ascrm.cn/api/v1/
+	return openai.NewClient(option.WithAPIKey(apiKey),
+		option.WithBaseURL("http://fastgpt.ascrm.cn/api/v1/"))
+}
+
+func NewDeepSeekClient(apiKey string) *openai.Client {
+	return openai.NewClient(option.WithAPIKey(apiKey),
+		option.WithBaseURL("https://api.deepseek.com"))
+}
+
+func DoChatCompletions(ctx context.Context, client *openai.Client, chatInfo *types.CompApiReq) (*types.CompOpenApiResp, error) {
+
+	var (
+		jsonBytes []byte
+		err       error
+	)
+	emptyParams := openai.ChatCompletionNewParams{}
+	if jsonBytes, err = json.Marshal(chatInfo); err != nil {
+		return nil, err
+	}
+	customResp := types.CompOpenApiResp{}
+	reqBodyOps := option.WithRequestBody("application/json", jsonBytes)
+	respBodyOps := option.WithResponseBodyInto(&customResp)
+	if _, err = client.Chat.Completions.New(ctx, emptyParams, reqBodyOps, respBodyOps); err != nil {
+		return nil, err
+	}
+	return &customResp, nil
+
+}
+
+func DoChatCompletionsStream(ctx context.Context, client *openai.Client, chatInfo *types.CompApiReq) (res *types.CompOpenApiResp, err error) {
+
+	var (
+		jsonBytes []byte
+		raw       *http.Response
+		//raw []byte
+		ok bool
+		hw http.ResponseWriter
+	)
+
+	hw, ok = contextkey.HttpResponseWriterKey.GetValue(ctx) //context取出http.ResponseWriter
+	if !ok {
+		return nil, errors.New("content get http writer err")
+	}
+	flusher, ok := (hw).(http.Flusher)
+	if !ok {
+		http.Error(hw, "Streaming unsupported!", http.StatusInternalServerError)
+	}
+
+	emptyParams := openai.ChatCompletionNewParams{}
+	if jsonBytes, err = json.Marshal(chatInfo); err != nil {
+		return nil, err
+	}
+	reqBodyOps := option.WithRequestBody("application/json", jsonBytes)
+	respBodyOps := option.WithResponseBodyInto(&raw)
+	if _, err = client.Chat.Completions.New(ctx, emptyParams, reqBodyOps, respBodyOps, option.WithJSONSet("stream", true)); err != nil {
+		return nil, err
+	}
+
+	//设置流式输出头 http1.1
+	hw.Header().Set("Content-Type", "text/event-stream;charset=utf-8")
+	hw.Header().Set("Connection", "keep-alive")
+	hw.Header().Set("Cache-Control", "no-cache")
+
+	chatStream := ssestream.NewStream[ApiRespStreamChunk](ApiRespStreamDecoder(raw), err)
+	defer chatStream.Close()
+	for chatStream.Next() {
+		chunk := chatStream.Current()
+		fmt.Fprintf(hw, "event:%s\ndata:%s\n\n", chunk.Event, chunk.Data.RAW)
+		flusher.Flush()
+		//time.Sleep(1 * time.Millisecond)
+	}
+	fmt.Fprintf(hw, "event:%s\ndata:%s\n\n", "answer", "[DONE]")
+	flusher.Flush()
+	httpx.Ok(hw)
+
+	return nil, nil
+}
+
+func NewChatCompletions(ctx context.Context, client *openai.Client, chatInfo *types.CompApiReq) (*types.CompOpenApiResp, error) {
+	if chatInfo.Stream {
+		return DoChatCompletionsStream(ctx, client, chatInfo)
+	} else {
+		return DoChatCompletions(ctx, client, chatInfo)
+	}
+}
+
+func NewFastgptChatCompletions(ctx context.Context, apiKey string, apiBase string, chatInfo *types.CompApiReq) (*types.CompOpenApiResp, error) {
+
+	client := NewAiClient(apiKey, apiBase)
+	return NewChatCompletions(ctx, client, chatInfo)
+}
+
+func NewDeepSeekChatCompletions(ctx context.Context, apiKey string, chatInfo *types.CompApiReq, chatModel openai.ChatModel) (res *types.CompOpenApiResp, err error) {
+	client := NewDeepSeekClient(apiKey)
+	if chatModel != ChatModelDeepSeekV3 {
+		chatModel = ChatModelDeepSeekR1
+	}
+	chatInfo.Model = chatModel
+	return NewChatCompletions(ctx, client, chatInfo)
+}
+
+func DoChatCompletionsStreamOld(ctx context.Context, client *openai.Client, chatInfo *types.CompApiReq) (res *types.CompOpenApiResp, err error) {
+	var (
+		jsonBytes []byte
+	)
+	emptyParams := openai.ChatCompletionNewParams{}
+	if jsonBytes, err = json.Marshal(chatInfo); err != nil {
+		return nil, err
+	}
+
+	reqBodyOps := option.WithRequestBody("application/json", jsonBytes)
+	//customResp := types.CompOpenApiResp{}
+	//respBodyOps := option.WithResponseBodyInto(&customResp)
+	//chatStream := client.Chat.Completions.NewStreaming(ctx, emptyParams, reqBodyOps, respBodyOps)
+	chatStream := client.Chat.Completions.NewStreaming(ctx, emptyParams, reqBodyOps)
+
+	// optionally, an accumulator helper can be used
+	acc := openai.ChatCompletionAccumulator{}
+
+	httpWriter, ok := ctx.Value("HttpResp-Writer").(http.ResponseWriter)
+	if !ok {
+		return nil, errors.New("content get writer err")
+	}
+
+	//httpWriter.Header().Set("Content-Type", "text/event-stream;charset=utf-8")
+	//httpWriter.Header().Set("Connection", "keep-alive")
+	//httpWriter.Header().Set("Cache-Control", "no-cache")
+
+	idx := 0
+	for chatStream.Next() {
+		chunk := chatStream.Current()
+		acc.AddChunk(chunk)
+
+		fmt.Printf("=====>get %d chunk:%v\n", idx, chunk)
+		if _, err := fmt.Fprintf(httpWriter, "%v", chunk); err != nil {
+			fmt.Printf("Error writing to client:%v \n", err)
+			break
+		}
+
+		if content, ok := acc.JustFinishedContent(); ok {
+			println("Content stream finished:", content)
+		}
+
+		// if using tool calls
+		if tool, ok := acc.JustFinishedToolCall(); ok {
+			println("Tool call stream finished:", tool.Index, tool.Name, tool.Arguments)
+		}
+
+		if refusal, ok := acc.JustFinishedRefusal(); ok {
+			println("Refusal stream finished:", refusal)
+		}
+
+		// it's best to use chunks after handling JustFinished events
+		if len(chunk.Choices) > 0 {
+			idx++
+			fmt.Printf("idx:%d get =>'%s'\n", idx, chunk.Choices[0].Delta.Content)
+		}
+
+	}
+
+	if err := chatStream.Err(); err != nil {
+		return nil, err
+	}
+	return nil, nil
+}

+ 13 - 0
internal/utils/contextkey/config.go

@@ -0,0 +1,13 @@
+package contextkey
+
+import (
+	"net/http"
+	"wechat-api/ent"
+)
+
+// 在包内定义全局键(通常在 vars 块或 init 函数中初始化)
+var (
+	HttpResponseWriterKey = NewCtxKey[http.ResponseWriter]() // 管理http.ResponseWriter
+	OpenapiTokenKey       = NewCtxKey[string]()              // 管理openai token
+	AuthTokenInfoKey      = NewCtxKey[*ent.ApiKey]()
+)

+ 35 - 0
internal/utils/contextkey/func.go

@@ -0,0 +1,35 @@
+package contextkey
+
+import (
+	"context"
+)
+
+// Key 泛型键对象,封装特定类型的上下文存取
+type CtxKey[T any] struct {
+	key any // 实际存储的唯一键(通过空结构体保证类型唯一)
+}
+
+// NewKey 创建管理特定类型值的键对象(每个调用生成唯一键)
+func NewCtxKey[T any]() CtxKey[T] {
+	type uniqueKey struct{} // 闭包内部类型确保唯一性
+	return CtxKey[T]{key: uniqueKey{}}
+}
+
+// WithValue 将值存入context,返回新context
+func (k CtxKey[T]) WithValue(ctx context.Context, value T) context.Context {
+	//fmt.Printf("WithValue=====>key:%v|addr:%p\n", k.key, &k.key)
+	return context.WithValue(ctx, k.key, value)
+}
+
+// GetValue 从context中提取值(带类型安全校验)
+func (k CtxKey[T]) GetValue(ctx context.Context) (T, bool) {
+	//fmt.Printf("GetValue#1=====>key:%v|addr:%p\n", k.key, &k.key)
+	v := ctx.Value(k.key)
+	if v == nil {
+		var zero T
+		return zero, false
+	}
+	//fmt.Printf("GetValue#2=====>key:%v|addr:%p\n", k.key, &k.key)
+	t, ok := v.(T)
+	return t, ok
+}

+ 2 - 2
proto/FriendAddNotice.proto

@@ -1,14 +1,14 @@
 syntax = "proto3";
 package Jubo.JuLiao.IM.Wx.Proto; //命名空间约定
 option go_package = "./workphone";
-import "TransportMessage.proto";
+//import "TransportMessage.proto";
 
 message FriendMessage {
     string FriendId = 1; // wxid
     string FriendNo = 2; // 微信号
     string FriendNick = 3; // 昵称
     string Memo = 4; // 备注
-    EnumGender Gender = 5; //性别
+    string Gender = 5; //性别
     string Country = 6; //国家 (非必传)
     string Province = 7; //省份
     string City = 8; //城市

+ 0 - 12
teams/error.go

@@ -1,12 +0,0 @@
-package model
-
-import (
-	"errors"
-
-	"github.com/zeromicro/go-zero/core/stores/mon"
-)
-
-var (
-	ErrNotFound        = mon.ErrNotFound
-	ErrInvalidObjectId = errors.New("invalid objectId")
-)

+ 0 - 25
teams/teamsmodel.go

@@ -1,25 +0,0 @@
-package model
-
-import "github.com/zeromicro/go-zero/core/stores/mon"
-
-var _ TeamsModel = (*customTeamsModel)(nil)
-
-type (
-	// TeamsModel is an interface to be customized, add more methods here,
-	// and implement the added methods in customTeamsModel.
-	TeamsModel interface {
-		teamsModel
-	}
-
-	customTeamsModel struct {
-		*defaultTeamsModel
-	}
-)
-
-// NewTeamsModel returns a model for the mongo.
-func NewTeamsModel(url, db, collection string) TeamsModel {
-	conn := mon.MustNewModel(url, db, collection)
-	return &customTeamsModel{
-		defaultTeamsModel: newDefaultTeamsModel(conn),
-	}
-}

+ 0 - 76
teams/teamsmodelgen.go

@@ -1,76 +0,0 @@
-// Code generated by goctl. DO NOT EDIT.
-// goctl 1.8.1
-
-package model
-
-import (
-	"context"
-	"time"
-
-	"github.com/zeromicro/go-zero/core/stores/mon"
-	"go.mongodb.org/mongo-driver/bson"
-	"go.mongodb.org/mongo-driver/bson/primitive"
-	"go.mongodb.org/mongo-driver/mongo"
-)
-
-type teamsModel interface {
-	Insert(ctx context.Context, data *Teams) error
-	FindOne(ctx context.Context, id string) (*Teams, error)
-	Update(ctx context.Context, data *Teams) (*mongo.UpdateResult, error)
-	Delete(ctx context.Context, id string) (int64, error)
-}
-
-type defaultTeamsModel struct {
-	conn *mon.Model
-}
-
-func newDefaultTeamsModel(conn *mon.Model) *defaultTeamsModel {
-	return &defaultTeamsModel{conn: conn}
-}
-
-func (m *defaultTeamsModel) Insert(ctx context.Context, data *Teams) error {
-	if data.ID.IsZero() {
-		data.ID = primitive.NewObjectID()
-		data.CreateAt = time.Now()
-		data.UpdateAt = time.Now()
-	}
-
-	_, err := m.conn.InsertOne(ctx, data)
-	return err
-}
-
-func (m *defaultTeamsModel) FindOne(ctx context.Context, id string) (*Teams, error) {
-	oid, err := primitive.ObjectIDFromHex(id)
-	if err != nil {
-		return nil, ErrInvalidObjectId
-	}
-
-	var data Teams
-
-	err = m.conn.FindOne(ctx, &data, bson.M{"_id": oid})
-	switch err {
-	case nil:
-		return &data, nil
-	case mon.ErrNotFound:
-		return nil, ErrNotFound
-	default:
-		return nil, err
-	}
-}
-
-func (m *defaultTeamsModel) Update(ctx context.Context, data *Teams) (*mongo.UpdateResult, error) {
-	data.UpdateAt = time.Now()
-
-	res, err := m.conn.UpdateOne(ctx, bson.M{"_id": data.ID}, bson.M{"$set": data})
-	return res, err
-}
-
-func (m *defaultTeamsModel) Delete(ctx context.Context, id string) (int64, error) {
-	oid, err := primitive.ObjectIDFromHex(id)
-	if err != nil {
-		return 0, ErrInvalidObjectId
-	}
-
-	res, err := m.conn.DeleteOne(ctx, bson.M{"_id": oid})
-	return res, err
-}

+ 0 - 14
teams/teamstypes.go

@@ -1,14 +0,0 @@
-package model
-
-import (
-	"time"
-
-	"go.mongodb.org/mongo-driver/bson/primitive"
-)
-
-type Teams struct {
-	ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
-	// TODO: Fill your own fields
-	UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"`
-	CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"`
-}

+ 17 - 13
wechat.go

@@ -25,9 +25,11 @@ package main
 import (
 	"flag"
 	"fmt"
+	"github.com/zeromicro/go-zero/core/logx"
 	"wechat-api/crontask"
 	"wechat-api/internal/config"
 	"wechat-api/internal/handler"
+	"wechat-api/internal/service/MessageHandlers"
 	"wechat-api/internal/svc"
 
 	"github.com/robfig/cron/v3"
@@ -47,22 +49,24 @@ func main() {
 	defer server.Stop()
 
 	ctx := svc.NewServiceContext(c)
+
 	handler.RegisterHandlers(server, ctx)
 
 	//个微处理程序 没有彻底完成之前不能开,和cow重复了
-	//if len(ctx.WechatWs) == 0 {
-	//	fmt.Println("wechat ws is nil")
-	//} else if ctx.WechatWs["default"] != nil {
-	//	var ic channel.IChannel
-	//	for _, ws := range ctx.WechatWs {
-	//		switch ws.CTypes {
-	//		case "wechat":
-	//			ic = channel.NewWechatChannel(ws, ctx)
-	//		}
-	//		ws.RegisterMessageHandler(ic.OnMessage)
-	//	}
-	//	logx.Info("注册个微处理通道~")
-	//}
+	if len(ctx.WechatWs) == 0 {
+		fmt.Println("wechat ws is nil")
+	} else if ctx.WechatWs["default"] != nil {
+		//var ic channel.IChannel
+		for _, ws := range ctx.WechatWs {
+			//switch ws.CTypes {
+			//case "wechat":
+			//	ic = channel.NewWechatChannel(ws, ctx)
+			//}
+			//ws.RegisterMessageHandler(ic.OnMessage)
+			ws.RegisterMessageHandler(MessageHandlers.NewFriendPushNoticeHandler(ctx).Handler)
+		}
+		logx.Info("注册个微处理通道~")
+	}
 	//for {
 	//	time.Sleep(time.Second * 10)
 	//}

+ 72 - 81
workphone/FriendAddNotice.pb.go

@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.35.2
-// 	protoc        v5.28.3
+// 	protoc-gen-go v1.36.5
+// 	protoc        v3.21.12
 // source: FriendAddNotice.proto
 
 package workphone
@@ -11,6 +11,7 @@ import (
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	reflect "reflect"
 	sync "sync"
+	unsafe "unsafe"
 )
 
 const (
@@ -21,26 +22,25 @@ const (
 )
 
 type FriendMessage struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	FriendId      string                 `protobuf:"bytes,1,opt,name=FriendId,proto3" json:"FriendId,omitempty"`     // wxid
+	FriendNo      string                 `protobuf:"bytes,2,opt,name=FriendNo,proto3" json:"FriendNo,omitempty"`     // 微信号
+	FriendNick    string                 `protobuf:"bytes,3,opt,name=FriendNick,proto3" json:"FriendNick,omitempty"` // 昵称
+	Memo          string                 `protobuf:"bytes,4,opt,name=Memo,proto3" json:"Memo,omitempty"`             // 备注
+	Gender        string                 `protobuf:"bytes,5,opt,name=Gender,proto3" json:"Gender,omitempty"`         //性别
+	Country       string                 `protobuf:"bytes,6,opt,name=Country,proto3" json:"Country,omitempty"`       //国家 (非必传)
+	Province      string                 `protobuf:"bytes,7,opt,name=Province,proto3" json:"Province,omitempty"`     //省份
+	City          string                 `protobuf:"bytes,8,opt,name=City,proto3" json:"City,omitempty"`             //城市
+	Avatar        string                 `protobuf:"bytes,9,opt,name=Avatar,proto3" json:"Avatar,omitempty"`         //头像
+	Remark        string                 `protobuf:"bytes,10,opt,name=Remark,proto3" json:"Remark,omitempty"`        // 业务备注
+	Type          int32                  `protobuf:"varint,11,opt,name=Type,proto3" json:"Type,omitempty"`           // 联系人类型,参考 (&512 消息免打扰)
+	LabelIds      string                 `protobuf:"bytes,12,opt,name=LabelIds,proto3" json:"LabelIds,omitempty"`    // 标签Id
+	Phone         string                 `protobuf:"bytes,13,opt,name=Phone,proto3" json:"Phone,omitempty"`          // 手机号
+	Desc          string                 `protobuf:"bytes,14,opt,name=Desc,proto3" json:"Desc,omitempty"`            // 描述
+	Source        int32                  `protobuf:"varint,15,opt,name=Source,proto3" json:"Source,omitempty"`       // 好友来源
+	SourceExt     string                 `protobuf:"bytes,16,opt,name=SourceExt,proto3" json:"SourceExt,omitempty"`  //来源扩展信息
 	unknownFields protoimpl.UnknownFields
-
-	FriendId   string     `protobuf:"bytes,1,opt,name=FriendId,proto3" json:"FriendId,omitempty"`                                      // wxid
-	FriendNo   string     `protobuf:"bytes,2,opt,name=FriendNo,proto3" json:"FriendNo,omitempty"`                                      // 微信号
-	FriendNick string     `protobuf:"bytes,3,opt,name=FriendNick,proto3" json:"FriendNick,omitempty"`                                  // 昵称
-	Memo       string     `protobuf:"bytes,4,opt,name=Memo,proto3" json:"Memo,omitempty"`                                              // 备注
-	Gender     EnumGender `protobuf:"varint,5,opt,name=Gender,proto3,enum=Jubo.JuLiao.IM.Wx.Proto.EnumGender" json:"Gender,omitempty"` //性别
-	Country    string     `protobuf:"bytes,6,opt,name=Country,proto3" json:"Country,omitempty"`                                        //国家 (非必传)
-	Province   string     `protobuf:"bytes,7,opt,name=Province,proto3" json:"Province,omitempty"`                                      //省份
-	City       string     `protobuf:"bytes,8,opt,name=City,proto3" json:"City,omitempty"`                                              //城市
-	Avatar     string     `protobuf:"bytes,9,opt,name=Avatar,proto3" json:"Avatar,omitempty"`                                          //头像
-	Remark     string     `protobuf:"bytes,10,opt,name=Remark,proto3" json:"Remark,omitempty"`                                         // 业务备注
-	Type       int32      `protobuf:"varint,11,opt,name=Type,proto3" json:"Type,omitempty"`                                            // 联系人类型,参考 (&512 消息免打扰)
-	LabelIds   string     `protobuf:"bytes,12,opt,name=LabelIds,proto3" json:"LabelIds,omitempty"`                                     // 标签Id
-	Phone      string     `protobuf:"bytes,13,opt,name=Phone,proto3" json:"Phone,omitempty"`                                           // 手机号
-	Desc       string     `protobuf:"bytes,14,opt,name=Desc,proto3" json:"Desc,omitempty"`                                             // 描述
-	Source     int32      `protobuf:"varint,15,opt,name=Source,proto3" json:"Source,omitempty"`                                        // 好友来源
-	SourceExt  string     `protobuf:"bytes,16,opt,name=SourceExt,proto3" json:"SourceExt,omitempty"`                                   //来源扩展信息
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *FriendMessage) Reset() {
@@ -101,11 +101,11 @@ func (x *FriendMessage) GetMemo() string {
 	return ""
 }
 
-func (x *FriendMessage) GetGender() EnumGender {
+func (x *FriendMessage) GetGender() string {
 	if x != nil {
 		return x.Gender
 	}
-	return EnumGender_UnknownGender
+	return ""
 }
 
 func (x *FriendMessage) GetCountry() string {
@@ -186,12 +186,11 @@ func (x *FriendMessage) GetSourceExt() string {
 }
 
 type FriendAddNoticeMessage struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	WeChatId      string                 `protobuf:"bytes,1,opt,name=WeChatId,proto3" json:"WeChatId,omitempty"` // 全局唯一识别码
+	Friend        *FriendMessage         `protobuf:"bytes,3,opt,name=Friend,proto3" json:"Friend,omitempty"`     // 好友信息
 	unknownFields protoimpl.UnknownFields
-
-	WeChatId string         `protobuf:"bytes,1,opt,name=WeChatId,proto3" json:"WeChatId,omitempty"` // 全局唯一识别码
-	Friend   *FriendMessage `protobuf:"bytes,3,opt,name=Friend,proto3" json:"Friend,omitempty"`     // 好友信息
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *FriendAddNoticeMessage) Reset() {
@@ -240,59 +239,55 @@ func (x *FriendAddNoticeMessage) GetFriend() *FriendMessage {
 
 var File_FriendAddNotice_proto protoreflect.FileDescriptor
 
-var file_FriendAddNotice_proto_rawDesc = []byte{
+var file_FriendAddNotice_proto_rawDesc = string([]byte{
 	0x0a, 0x15, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x63,
 	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x4a, 0x75, 0x62, 0x6f, 0x2e, 0x4a, 0x75,
 	0x4c, 0x69, 0x61, 0x6f, 0x2e, 0x49, 0x4d, 0x2e, 0x57, 0x78, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f,
-	0x1a, 0x16, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61,
-	0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc2, 0x03, 0x0a, 0x0d, 0x46, 0x72, 0x69,
-	0x65, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x46, 0x72,
-	0x69, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x46, 0x72,
-	0x69, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64,
-	0x4e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64,
-	0x4e, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x4e, 0x69, 0x63, 0x6b,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x4e, 0x69,
-	0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x4d, 0x65, 0x6d, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x04, 0x4d, 0x65, 0x6d, 0x6f, 0x12, 0x3b, 0x0a, 0x06, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72,
-	0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x4a, 0x75, 0x62, 0x6f, 0x2e, 0x4a, 0x75,
+	0x22, 0x9d, 0x03, 0x0a, 0x0d, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1a,
+	0x0a, 0x08, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x4e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x08, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x4e, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x46, 0x72,
+	0x69, 0x65, 0x6e, 0x64, 0x4e, 0x69, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
+	0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x4e, 0x69, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x4d, 0x65,
+	0x6d, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4d, 0x65, 0x6d, 0x6f, 0x12, 0x16,
+	0x0a, 0x06, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
+	0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72,
+	0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79,
+	0x12, 0x1a, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04,
+	0x43, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x43, 0x69, 0x74, 0x79,
+	0x12, 0x16, 0x0a, 0x06, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x06, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x61,
+	0x72, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x52, 0x65, 0x6d, 0x61, 0x72, 0x6b,
+	0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04,
+	0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x49, 0x64, 0x73,
+	0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x49, 0x64, 0x73,
+	0x12, 0x14, 0x0a, 0x05, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x05, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x65, 0x73, 0x63, 0x18, 0x0e,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x44, 0x65, 0x73, 0x63, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x53, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x78, 0x74, 0x18,
+	0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x78, 0x74,
+	0x22, 0x74, 0x0a, 0x16, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x4e, 0x6f, 0x74,
+	0x69, 0x63, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x57, 0x65,
+	0x43, 0x68, 0x61, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x57, 0x65,
+	0x43, 0x68, 0x61, 0x74, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x06, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x4a, 0x75, 0x62, 0x6f, 0x2e, 0x4a, 0x75,
 	0x4c, 0x69, 0x61, 0x6f, 0x2e, 0x49, 0x4d, 0x2e, 0x57, 0x78, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f,
-	0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x06, 0x47, 0x65, 0x6e,
-	0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x06,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1a, 0x0a,
-	0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x43, 0x69, 0x74,
-	0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x43, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a,
-	0x06, 0x41, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x41,
-	0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18,
-	0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x52, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x12, 0x0a,
-	0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x54, 0x79, 0x70,
-	0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x49, 0x64, 0x73, 0x18, 0x0c, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x08, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x49, 0x64, 0x73, 0x12, 0x14, 0x0a,
-	0x05, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x50, 0x68,
-	0x6f, 0x6e, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x44, 0x65, 0x73, 0x63, 0x18, 0x0e, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x04, 0x44, 0x65, 0x73, 0x63, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63,
-	0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,
-	0x1c, 0x0a, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x78, 0x74, 0x18, 0x10, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x09, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x78, 0x74, 0x22, 0x74, 0x0a,
-	0x16, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65,
-	0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x57, 0x65, 0x43, 0x68, 0x61,
-	0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x57, 0x65, 0x43, 0x68, 0x61,
-	0x74, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x06, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x4a, 0x75, 0x62, 0x6f, 0x2e, 0x4a, 0x75, 0x4c, 0x69, 0x61,
-	0x6f, 0x2e, 0x49, 0x4d, 0x2e, 0x57, 0x78, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x72,
-	0x69, 0x65, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x06, 0x46, 0x72, 0x69,
-	0x65, 0x6e, 0x64, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x70, 0x68, 0x6f,
-	0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
+	0x2e, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x06,
+	0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x42, 0x0d, 0x5a, 0x0b, 0x2e, 0x2f, 0x77, 0x6f, 0x72, 0x6b,
+	0x70, 0x68, 0x6f, 0x6e, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+})
 
 var (
 	file_FriendAddNotice_proto_rawDescOnce sync.Once
-	file_FriendAddNotice_proto_rawDescData = file_FriendAddNotice_proto_rawDesc
+	file_FriendAddNotice_proto_rawDescData []byte
 )
 
 func file_FriendAddNotice_proto_rawDescGZIP() []byte {
 	file_FriendAddNotice_proto_rawDescOnce.Do(func() {
-		file_FriendAddNotice_proto_rawDescData = protoimpl.X.CompressGZIP(file_FriendAddNotice_proto_rawDescData)
+		file_FriendAddNotice_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_FriendAddNotice_proto_rawDesc), len(file_FriendAddNotice_proto_rawDesc)))
 	})
 	return file_FriendAddNotice_proto_rawDescData
 }
@@ -301,16 +296,14 @@ var file_FriendAddNotice_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
 var file_FriendAddNotice_proto_goTypes = []any{
 	(*FriendMessage)(nil),          // 0: Jubo.JuLiao.IM.Wx.Proto.FriendMessage
 	(*FriendAddNoticeMessage)(nil), // 1: Jubo.JuLiao.IM.Wx.Proto.FriendAddNoticeMessage
-	(EnumGender)(0),                // 2: Jubo.JuLiao.IM.Wx.Proto.EnumGender
 }
 var file_FriendAddNotice_proto_depIdxs = []int32{
-	2, // 0: Jubo.JuLiao.IM.Wx.Proto.FriendMessage.Gender:type_name -> Jubo.JuLiao.IM.Wx.Proto.EnumGender
-	0, // 1: Jubo.JuLiao.IM.Wx.Proto.FriendAddNoticeMessage.Friend:type_name -> Jubo.JuLiao.IM.Wx.Proto.FriendMessage
-	2, // [2:2] is the sub-list for method output_type
-	2, // [2:2] is the sub-list for method input_type
-	2, // [2:2] is the sub-list for extension type_name
-	2, // [2:2] is the sub-list for extension extendee
-	0, // [0:2] is the sub-list for field type_name
+	0, // 0: Jubo.JuLiao.IM.Wx.Proto.FriendAddNoticeMessage.Friend:type_name -> Jubo.JuLiao.IM.Wx.Proto.FriendMessage
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
 }
 
 func init() { file_FriendAddNotice_proto_init() }
@@ -318,12 +311,11 @@ func file_FriendAddNotice_proto_init() {
 	if File_FriendAddNotice_proto != nil {
 		return
 	}
-	file_TransportMessage_proto_init()
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_FriendAddNotice_proto_rawDesc,
+			RawDescriptor: unsafe.Slice(unsafe.StringData(file_FriendAddNotice_proto_rawDesc), len(file_FriendAddNotice_proto_rawDesc)),
 			NumEnums:      0,
 			NumMessages:   2,
 			NumExtensions: 0,
@@ -334,7 +326,6 @@ func file_FriendAddNotice_proto_init() {
 		MessageInfos:      file_FriendAddNotice_proto_msgTypes,
 	}.Build()
 	File_FriendAddNotice_proto = out.File
-	file_FriendAddNotice_proto_rawDesc = nil
 	file_FriendAddNotice_proto_goTypes = nil
 	file_FriendAddNotice_proto_depIdxs = nil
 }