fix: 解决go.mod 冲突
This commit is contained in:
commit
637cad4298
File diff suppressed because it is too large
Load Diff
@ -18,9 +18,9 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
package accountFiee;
|
package accountFiee;
|
||||||
import "github.com/mwitkow/go-proto-validators@v0.3.2/validator.proto";
|
import "github.com/mwitkow/go-proto-validators@v0.3.2/validator.proto";
|
||||||
|
|
||||||
option go_package = "./;accountFiee";
|
option go_package = "./;accountFiee";
|
||||||
|
|
||||||
|
//protoc -I . -I C:\Users\lenovo\go\src --go_out=. --go-triple_out=. ./accountFiee.proto
|
||||||
service AccountFiee {
|
service AccountFiee {
|
||||||
rpc Login (LoginRequest) returns (TokenInfo) {}
|
rpc Login (LoginRequest) returns (TokenInfo) {}
|
||||||
rpc RefreshToken (RefreshTokenRequest) returns (TokenInfo) {} //刷新token
|
rpc RefreshToken (RefreshTokenRequest) returns (TokenInfo) {} //刷新token
|
||||||
@ -62,6 +62,37 @@ service AccountFiee {
|
|||||||
rpc VerifySliderCaptcha(VerifySliderCaptchaRequest) returns (VerifySliderCaptchaResponse) {}// 验证滑块验证码位置
|
rpc VerifySliderCaptcha(VerifySliderCaptchaRequest) returns (VerifySliderCaptchaResponse) {}// 验证滑块验证码位置
|
||||||
rpc SendNationMsg (SendNationMsgRequest) returns (SendMsgStatusResponse) {} //发送境外国际短信验证码 --艺术商城
|
rpc SendNationMsg (SendNationMsgRequest) returns (SendMsgStatusResponse) {} //发送境外国际短信验证码 --艺术商城
|
||||||
rpc VerifySliderStatus(VerifySliderStatusRequest) returns (VerifySliderStatusResponse) {}// 验证滑块验证码状态
|
rpc VerifySliderStatus(VerifySliderStatusRequest) returns (VerifySliderStatusResponse) {}// 验证滑块验证码状态
|
||||||
|
|
||||||
|
// submit info
|
||||||
|
rpc SaveSubmitInfo(SubmitInfoRequest) returns (CommonResponse);
|
||||||
|
|
||||||
|
//-----------------------------客服聊天系统--------------------------------
|
||||||
|
rpc CreateChatUser ( ChatUserData )returns( CreateChatUserResp ){} //创建聊天用户
|
||||||
|
rpc UpdateChatUser ( ChatUserData )returns( CommonMsg ){} //更新聊天用户
|
||||||
|
rpc SaveChatUser ( ChatUserData )returns( CommonMsg ){} //覆盖聊天用户
|
||||||
|
rpc DeleteChatUser ( DeleteChatUserRequest )returns( CommonMsg ){} //删除聊天用户
|
||||||
|
rpc GetChatUserDetail ( GetChatUserByIdRequest )returns( ChatUserData ){} //查询聊天用户详情
|
||||||
|
rpc GetChatUserList ( GetChatUserListRequest )returns( GetChatUserListResp ){} //查询聊天用户列表
|
||||||
|
rpc GetChatUserList2 ( GetChatUserListRequest2 )returns( GetChatUserListResp2 ){} //查询聊天用户列表2
|
||||||
|
rpc RegisterWaiter ( RegisterWaiterRequest )returns( RegisterWaiterResp ){} //注册客服账号
|
||||||
|
rpc CreateChatRecord ( ChatRecordData )returns( CreateChatRecordResp ){} //创建ChatRecord
|
||||||
|
rpc UpdateChatRecord ( ChatRecordData )returns( CommonMsg ){} //更新ChatRecord
|
||||||
|
rpc SaveChatRecord ( ChatRecordData )returns( CommonMsg ){} //覆盖ChatRecord
|
||||||
|
rpc DeleteChatRecord ( DeleteChatRecordRequest )returns( CommonMsg ){} //删除ChatRecord
|
||||||
|
rpc GetChatRecordDetail ( GetChatRecordByIdRequest )returns( ChatRecordData ){} //查询ChatRecord详情
|
||||||
|
rpc GetChatRecordList ( GetChatRecordListRequest )returns( GetChatRecordListResp ){} //查询ChatRecord列表
|
||||||
|
rpc CreateChatMedia ( ChatMediaData )returns( CreateChatMediaResp ){} //创建ChatMedia
|
||||||
|
rpc UpdateChatMedia ( ChatMediaData )returns( CommonMsg ){} //更新ChatMedia
|
||||||
|
rpc SaveChatMedia ( ChatMediaData )returns( CommonMsg ){} //覆盖ChatMedia
|
||||||
|
rpc DeleteChatMedia ( DeleteChatMediaRequest )returns( CommonMsg ){} //删除ChatMedia
|
||||||
|
rpc GetChatMediaDetail ( GetChatMediaByIdRequest )returns( ChatMediaData ){} //查询ChatMedia详情
|
||||||
|
rpc GetChatMediaList ( GetChatMediaListRequest )returns( GetChatMediaListResp ){} //查询ChatMedia列表
|
||||||
|
rpc CreateChatAutoReplyRuler ( ChatAutoReplyRulerData )returns( CreateChatAutoReplyRulerResp ){} //创建自动回复规则
|
||||||
|
rpc UpdateChatAutoReplyRuler ( ChatAutoReplyRulerData )returns( CommonMsg ){} //更新自动回复规则
|
||||||
|
rpc SaveChatAutoReplyRuler ( ChatAutoReplyRulerData )returns( CommonMsg ){} //覆盖自动回复规则
|
||||||
|
rpc DeleteChatAutoReplyRuler ( DeleteChatAutoReplyRulerRequest )returns( CommonMsg ){} //删除自动回复规则
|
||||||
|
rpc GetChatAutoReplyRulerDetail ( GetChatAutoReplyRulerByIdRequest )returns( ChatAutoReplyRulerData ){} //查询自动回复规则详情
|
||||||
|
rpc GetChatAutoReplyRulerList ( GetChatAutoReplyRulerListRequest )returns( GetChatAutoReplyRulerListResp ){} //查询自动回复规则列表
|
||||||
}
|
}
|
||||||
|
|
||||||
message VerifySliderStatusRequest {
|
message VerifySliderStatusRequest {
|
||||||
@ -818,3 +849,206 @@ message ClockLogListResponse{
|
|||||||
repeated ClockLogInfo data =1;
|
repeated ClockLogInfo data =1;
|
||||||
uint64 count = 2;
|
uint64 count = 2;
|
||||||
}
|
}
|
||||||
|
message SubmitInfoRequest{
|
||||||
|
string firstName = 1;
|
||||||
|
string lastName = 2;
|
||||||
|
string email = 3;
|
||||||
|
string company = 4;
|
||||||
|
string phone = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CommonMsg{
|
||||||
|
string msg = 1;
|
||||||
|
}
|
||||||
|
enum MsgType{
|
||||||
|
UnknownMsgType = 0 ;//未知类型
|
||||||
|
TextMsgType = 1 ;//文本
|
||||||
|
ImageMsgType = 2 ;//图片
|
||||||
|
AudioMsgType = 3 ;//音频
|
||||||
|
VideoMsgType = 4 ;//视频
|
||||||
|
FileType = 5 ;//文件
|
||||||
|
}
|
||||||
|
message ChatRecordData{
|
||||||
|
int64 ID=1;
|
||||||
|
string createdAt=2;
|
||||||
|
string updatedAt=3;
|
||||||
|
int64 deletedAt=4;
|
||||||
|
string sessionId = 5; //会话UID
|
||||||
|
int64 userId = 6; //用户ID
|
||||||
|
string name = 7; //名称
|
||||||
|
string avatar = 8; //头像
|
||||||
|
MsgType msgType = 9; //消息类型
|
||||||
|
string content = 10; //消息内容
|
||||||
|
repeated ChatMediaData medias = 11; //媒体
|
||||||
|
int32 waiterRead=12;//客服是否已读 1=已读 2=未读 (被任意客服读取过均为已读)
|
||||||
|
int64 localStamp = 13; //本地时间戳 用户端的消息唯一值,用于用户本地的一些逻辑处理
|
||||||
|
string domain =14;//域
|
||||||
|
}
|
||||||
|
message CreateChatRecordResp{
|
||||||
|
ChatRecordData data=1;
|
||||||
|
string msg=2;
|
||||||
|
}
|
||||||
|
message DeleteChatRecordRequest{
|
||||||
|
int64 id=1; //二选一,数据id
|
||||||
|
repeated int64 ids=2;//二选一,数据id列表
|
||||||
|
}
|
||||||
|
message GetChatRecordByIdRequest{
|
||||||
|
int64 id=1; //数据id
|
||||||
|
}
|
||||||
|
message GetChatRecordListRequest{
|
||||||
|
ChatRecordData query =1;
|
||||||
|
int64 page=2;
|
||||||
|
int64 pageSize=3;
|
||||||
|
string where=4;
|
||||||
|
string order=5;
|
||||||
|
}
|
||||||
|
message GetChatRecordListResp{
|
||||||
|
repeated ChatRecordData list=1;
|
||||||
|
int64 page=2;
|
||||||
|
int64 pageSize=3;
|
||||||
|
int64 Total=4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegisterWaiterRequest{
|
||||||
|
string origin=1; //来源
|
||||||
|
int64 originId=2; //来源对应的用户ID
|
||||||
|
string nickName=3; //名称
|
||||||
|
string avatar=4; //头像
|
||||||
|
string telNum=5; //电话
|
||||||
|
string invitationCode=6; //邀请码
|
||||||
|
string account=7;
|
||||||
|
}
|
||||||
|
message RegisterWaiterResp{
|
||||||
|
int64 userId=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ChatMediaData{
|
||||||
|
int64 ID=1;
|
||||||
|
string createdAt=2;
|
||||||
|
string updatedAt=3;
|
||||||
|
int64 deletedAt=4;
|
||||||
|
string url = 5; //url
|
||||||
|
string md5 = 6; //md5值
|
||||||
|
string size = 7; //尺寸
|
||||||
|
string ext = 8; //后缀格式
|
||||||
|
string convText=9; //语音转文字内容
|
||||||
|
int64 duration=10;//时长
|
||||||
|
}
|
||||||
|
message CreateChatMediaResp{
|
||||||
|
ChatMediaData data=1;
|
||||||
|
string msg=2;
|
||||||
|
}
|
||||||
|
message DeleteChatMediaRequest{
|
||||||
|
int64 id=1; //二选一,数据id
|
||||||
|
repeated int64 ids=2;//二选一,数据id列表
|
||||||
|
}
|
||||||
|
message GetChatMediaByIdRequest{
|
||||||
|
int64 id=1; //数据id
|
||||||
|
}
|
||||||
|
message GetChatMediaListRequest{
|
||||||
|
ChatMediaData query =1;
|
||||||
|
int64 page=2;
|
||||||
|
int64 pageSize=3;
|
||||||
|
string where=4;
|
||||||
|
string order=5;
|
||||||
|
}
|
||||||
|
message GetChatMediaListResp{
|
||||||
|
repeated ChatMediaData list=1;
|
||||||
|
int64 page=2;
|
||||||
|
int64 pageSize=3;
|
||||||
|
int64 Total=4;
|
||||||
|
}
|
||||||
|
message GetChatUserListRequest2{
|
||||||
|
int64 page=1;
|
||||||
|
int64 pageSize=2;
|
||||||
|
string where=3;
|
||||||
|
string name=4;
|
||||||
|
repeated int64 userIdIn=5;
|
||||||
|
}
|
||||||
|
message ChatUser2{
|
||||||
|
int64 userId=1;
|
||||||
|
string name=2;
|
||||||
|
string avatar=3;
|
||||||
|
string origin=4;
|
||||||
|
string originId=5;
|
||||||
|
}
|
||||||
|
message GetChatUserListResp2{
|
||||||
|
repeated ChatUser2 list=1;
|
||||||
|
int64 page=2;
|
||||||
|
int64 pageSize=3;
|
||||||
|
int64 Total=4;
|
||||||
|
string where=5;
|
||||||
|
}
|
||||||
|
message ChatAutoReplyRulerData{
|
||||||
|
int64 ID = 1; //
|
||||||
|
string createdAt = 2; //
|
||||||
|
string updatedAt = 3; //
|
||||||
|
int64 deletedAt = 4; //
|
||||||
|
string title = 5; //标题
|
||||||
|
string ruler = 6; //规则内容
|
||||||
|
int32 rulerStatus = 7; //规则状态: 1=启用 2=禁用
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateChatAutoReplyRulerResp{
|
||||||
|
ChatAutoReplyRulerData data=1;
|
||||||
|
string msg=2;
|
||||||
|
}
|
||||||
|
message DeleteChatAutoReplyRulerRequest{
|
||||||
|
int64 id=1; //二选一,数据id
|
||||||
|
repeated int64 ids=2;//二选一,数据id列表
|
||||||
|
}
|
||||||
|
message GetChatAutoReplyRulerByIdRequest{
|
||||||
|
int64 id=1; //数据id
|
||||||
|
}
|
||||||
|
message GetChatAutoReplyRulerListRequest{
|
||||||
|
ChatAutoReplyRulerData query =1;
|
||||||
|
int64 page=2;
|
||||||
|
int64 pageSize=3;
|
||||||
|
string where=4;
|
||||||
|
string order=5;
|
||||||
|
}
|
||||||
|
message GetChatAutoReplyRulerListResp{
|
||||||
|
repeated ChatAutoReplyRulerData list=1;
|
||||||
|
int64 page=2;
|
||||||
|
int64 pageSize=3;
|
||||||
|
int64 Total=4;
|
||||||
|
}
|
||||||
|
message ChatUserData{
|
||||||
|
int64 ID = 1; //
|
||||||
|
string createdAt = 2; //
|
||||||
|
string updatedAt = 3; //
|
||||||
|
int64 deletedAt = 4; //
|
||||||
|
string nickName = 5; //昵称
|
||||||
|
string account = 6; //账号
|
||||||
|
int32 role = 7; //聊天角色 1=用户 2=客服
|
||||||
|
string origin = 8; //数据来源
|
||||||
|
int64 originId = 9; //数据来源对应的用户ID
|
||||||
|
string avatar = 10; //头像
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateChatUserResp{
|
||||||
|
ChatUserData data=1;
|
||||||
|
string msg=2;
|
||||||
|
}
|
||||||
|
message DeleteChatUserRequest{
|
||||||
|
int64 id=1; //二选一,数据id
|
||||||
|
repeated int64 ids=2;//二选一,数据id列表
|
||||||
|
}
|
||||||
|
message GetChatUserByIdRequest{
|
||||||
|
int64 id=1; //数据id
|
||||||
|
}
|
||||||
|
message GetChatUserListRequest{
|
||||||
|
ChatUserData query =1;
|
||||||
|
int64 page=2;
|
||||||
|
int64 pageSize=3;
|
||||||
|
string where=4;
|
||||||
|
string order=5;
|
||||||
|
}
|
||||||
|
message GetChatUserListResp{
|
||||||
|
repeated ChatUserData list=1;
|
||||||
|
int64 page=2;
|
||||||
|
int64 pageSize=3;
|
||||||
|
int64 Total=4;
|
||||||
|
}
|
@ -522,3 +522,6 @@ func (this *ClockLogListResponse) Validate() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (this *SubmitInfoRequest) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
15
go.mod
15
go.mod
@ -1,11 +1,14 @@
|
|||||||
module fonchain-fiee
|
module fonchain-fiee
|
||||||
|
|
||||||
go 1.18
|
go 1.21.3
|
||||||
|
|
||||||
|
toolchain go1.23.6
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
|
//github.com/fonchain_enterprise/utils/objstorage => ../../tyfon-新/utils/objstorage
|
||||||
|
github.com/fonchain/utils/voice => ../utils/voice
|
||||||
github.com/fonchain_enterprise/utils/aes => ../utils/aes
|
github.com/fonchain_enterprise/utils/aes => ../utils/aes
|
||||||
github.com/fonchain_enterprise/utils/objstorage => ../utils/objstorage
|
github.com/fonchain_enterprise/utils/objstorage => ../utils/objstorage
|
||||||
//github.com/fonchain_enterprise/utils/objstorage => ../../tyfon-新/utils/objstorage
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -46,11 +49,11 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/jinzhu/copier v0.3.5 // indirect
|
github.com/jinzhu/copier v0.3.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/k0kubun/pp v3.0.1+incompatible // indirect
|
github.com/k0kubun/pp v3.0.1+incompatible // indirect
|
||||||
@ -102,6 +105,7 @@ require (
|
|||||||
github.com/PuerkitoBio/goquery v1.8.1
|
github.com/PuerkitoBio/goquery v1.8.1
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0
|
github.com/envoyproxy/protoc-gen-validate v0.1.0
|
||||||
|
github.com/fonchain/utils/voice v0.0.0-00010101000000-000000000000
|
||||||
github.com/fonchain_enterprise/utils/objstorage v0.0.0-00010101000000-000000000000
|
github.com/fonchain_enterprise/utils/objstorage v0.0.0-00010101000000-000000000000
|
||||||
github.com/gin-contrib/pprof v1.4.0
|
github.com/gin-contrib/pprof v1.4.0
|
||||||
github.com/go-redis/redis v6.15.9+incompatible
|
github.com/go-redis/redis v6.15.9+incompatible
|
||||||
@ -117,7 +121,7 @@ require (
|
|||||||
cloud.google.com/go v0.65.0 // indirect
|
cloud.google.com/go v0.65.0 // indirect
|
||||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 // indirect
|
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 // indirect
|
||||||
github.com/alibaba/sentinel-golang v1.0.4 // indirect
|
github.com/alibaba/sentinel-golang v1.0.4 // indirect
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 // indirect
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1376 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.38.20 // indirect
|
github.com/aws/aws-sdk-go v1.38.20 // indirect
|
||||||
github.com/baidubce/bce-sdk-go v0.9.123 // indirect
|
github.com/baidubce/bce-sdk-go v0.9.123 // indirect
|
||||||
@ -129,6 +133,7 @@ require (
|
|||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect
|
||||||
github.com/coreos/go-semver v0.3.0 // indirect
|
github.com/coreos/go-semver v0.3.0 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||||
|
github.com/dorlolo/simpleRequest v1.2.7 // indirect
|
||||||
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5 // indirect
|
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.7.4 // indirect
|
github.com/emicklei/go-restful/v3 v3.7.4 // indirect
|
||||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
|
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
|
||||||
|
7
go.sum
7
go.sum
@ -71,8 +71,9 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
|
|||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/alibaba/sentinel-golang v1.0.4 h1:i0wtMvNVdy7vM4DdzYrlC4r/Mpk1OKUUBurKKkWhEo8=
|
github.com/alibaba/sentinel-golang v1.0.4 h1:i0wtMvNVdy7vM4DdzYrlC4r/Mpk1OKUUBurKKkWhEo8=
|
||||||
github.com/alibaba/sentinel-golang v1.0.4/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk=
|
github.com/alibaba/sentinel-golang v1.0.4/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk=
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA=
|
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
|
||||||
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1376 h1:lExo7heZgdFn5AbaNJEllbA0KSJ/Z8T7MphvMREJOOo=
|
||||||
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1376/go.mod h1:9CMdKNL3ynIGPpfTcdwTvIm8SGuAZYYC4jFVSSvE1YQ=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.4+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.4+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible h1:KXeJoM1wo9I/6xPTyt6qCxoSZnmASiAjlrr0dyTUKt8=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible h1:KXeJoM1wo9I/6xPTyt6qCxoSZnmASiAjlrr0dyTUKt8=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
@ -187,6 +188,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/dorlolo/simpleRequest v1.2.7 h1:I6AlEhMBSZPNQ4QjpCevhpxsPRDa3lgDOxJYYfmPTU8=
|
||||||
|
github.com/dorlolo/simpleRequest v1.2.7/go.mod h1:koVT8DQu+JK40UoMNBQjt+zomlCW8FqE0ffEzjTOWYY=
|
||||||
github.com/dubbogo/go-zookeeper v1.0.3/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c=
|
github.com/dubbogo/go-zookeeper v1.0.3/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c=
|
||||||
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5 h1:XoR8SSVziXe698dt4uZYDfsmHpKLemqAgFyndQsq5Kw=
|
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5 h1:XoR8SSVziXe698dt4uZYDfsmHpKLemqAgFyndQsq5Kw=
|
||||||
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c=
|
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c=
|
||||||
@ -284,6 +287,7 @@ github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
|||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
@ -1365,6 +1369,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
|
77
pkg/common/jwt/common.go
Normal file
77
pkg/common/jwt/common.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Package jwt -----------------------------
|
||||||
|
// @file : common.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2025/6/12 18:07
|
||||||
|
// -------------------------------------------
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fonchain-fiee/api/account"
|
||||||
|
"fonchain-fiee/api/accountFiee"
|
||||||
|
"fonchain-fiee/pkg/config"
|
||||||
|
"fonchain-fiee/pkg/e"
|
||||||
|
"fonchain-fiee/pkg/service"
|
||||||
|
"fonchain-fiee/pkg/utils/secret"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseToChatUser 将token信息转换为聊天室用户信息
|
||||||
|
func ParseToChatUser(c *gin.Context) (chatUserInfo *accountFiee.ChatUserData, code e.ErrorCodeType) {
|
||||||
|
domain := config.Domain
|
||||||
|
domainAny, exist := c.Get("domain")
|
||||||
|
if exist {
|
||||||
|
domain = domainAny.(string)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
token := c.GetHeader(e.Authorization)
|
||||||
|
if token == "" {
|
||||||
|
code = e.NotLogin
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
var originId int64 = 0
|
||||||
|
switch domain {
|
||||||
|
case config.Domain:
|
||||||
|
//fiee token校验
|
||||||
|
token, err = secret.GetJwtFromStr(token)
|
||||||
|
if err != nil {
|
||||||
|
code = e.NotLogin
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var fieeJwtInfo *accountFiee.DecryptJwtResponse
|
||||||
|
fieeJwtInfo, err = service.AccountFieeProvider.DecryptJwt(ctx, &accountFiee.DecryptJwtRequest{Token: token, Domain: domain})
|
||||||
|
if err != nil || fieeJwtInfo.IsOffline {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originId = int64(fieeJwtInfo.ID)
|
||||||
|
case "fontree":
|
||||||
|
//erp token校验
|
||||||
|
token, err = secret.GetJwtFromStr(token)
|
||||||
|
if err != nil {
|
||||||
|
code = e.NotLogin
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var fontreeJwtInfo *account.DecryptJwtResponse
|
||||||
|
fontreeJwtInfo, err = service.AccountProvider.DecryptJwt(ctx, &account.DecryptJwtRequest{Token: token, Domain: domain})
|
||||||
|
if err != nil || fontreeJwtInfo.IsOffline {
|
||||||
|
code = e.NotLogin
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originId = int64(fontreeJwtInfo.ID)
|
||||||
|
}
|
||||||
|
var userQueryRes *accountFiee.GetChatUserListResp
|
||||||
|
userQueryRes, err = service.AccountFieeProvider.GetChatUserList(c, &accountFiee.GetChatUserListRequest{
|
||||||
|
Query: &accountFiee.ChatUserData{Origin: domain, OriginId: originId},
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 1,
|
||||||
|
})
|
||||||
|
if err != nil || userQueryRes.Total == 0 {
|
||||||
|
code = e.ErrorNotExistUser
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chatUserInfo = userQueryRes.List[0]
|
||||||
|
return
|
||||||
|
}
|
178
pkg/common/ws/base.go
Normal file
178
pkg/common/ws/base.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// Package ws -----------------------------
|
||||||
|
// @file : hertzWSUpgrade.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2022/6/28 14:14
|
||||||
|
// -------------------------------------------
|
||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fonchain-fiee/pkg/e"
|
||||||
|
"fonchain-fiee/pkg/serializer"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WsType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
RegisterType WsType = iota
|
||||||
|
ErrorType
|
||||||
|
TestType
|
||||||
|
ChatType
|
||||||
|
NewChatMsgType //新消息通知
|
||||||
|
AuthorizationType //token校验通知
|
||||||
|
)
|
||||||
|
|
||||||
|
// 消息结构
|
||||||
|
type WSMessage struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// websocket消息内容
|
||||||
|
type WsInfo struct {
|
||||||
|
Type WsType `json:"type"` //消息类型
|
||||||
|
Content interface{} `json:"content"` //消息内容
|
||||||
|
From string `json:"from"` //发送者 0为服务端,客户端填写clientId
|
||||||
|
To string `json:"to"` //接收者 接收消息的用户id
|
||||||
|
Mark string `json:"mark"`
|
||||||
|
//Conn *websocket.Conn `json:"-"` //客户端发送消息使用
|
||||||
|
}
|
||||||
|
type WsSessionInfo struct {
|
||||||
|
Type WsType `json:"type"` //消息类型
|
||||||
|
//SessionId string `json:"sessionId"` //会话Id
|
||||||
|
Content interface{} `json:"content"` //消息内容
|
||||||
|
}
|
||||||
|
|
||||||
|
// 身份认证消息
|
||||||
|
type AuthorizationInfo struct {
|
||||||
|
Type WsType `json:"type"` //消息类型
|
||||||
|
Content AuthInfo `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthInfo struct {
|
||||||
|
Auth string `json:"auth"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册消息
|
||||||
|
type WsRegisterInfo struct {
|
||||||
|
Type WsType `json:"type"` //消息类型
|
||||||
|
Content UserInfo `json:"content"` //消息内容
|
||||||
|
From string `json:"from"` //发送者 0为服务端,客户端填写clientId
|
||||||
|
To string `json:"to"` //接收者 接收消息的用户id
|
||||||
|
//Conn *websocket.Conn `json:"-"` //客户端发送消息使用
|
||||||
|
}
|
||||||
|
type UserInfo struct {
|
||||||
|
Uuid string `json:"uuid"` //画家uid
|
||||||
|
UserId int64 `json:"userId"` //用户id
|
||||||
|
ClientId string `json:"clientId,omitempty"` //服务端临时签发的客户端uid
|
||||||
|
}
|
||||||
|
|
||||||
|
type TempClientInfo struct {
|
||||||
|
ClientId string `json:"clientId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func WsMessageRegisterCallback(clientId string) []byte {
|
||||||
|
var errMsg = WsInfo{
|
||||||
|
Type: RegisterType,
|
||||||
|
Content: map[string]string{
|
||||||
|
"clientId": clientId,
|
||||||
|
},
|
||||||
|
From: "0",
|
||||||
|
To: clientId,
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(errMsg)
|
||||||
|
return byteMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func WsErrorMessage(wsType WsType, clientId string, code e.ErrorCodeType, err error) []byte {
|
||||||
|
var ers string
|
||||||
|
if err != nil {
|
||||||
|
ers = err.Error()
|
||||||
|
}
|
||||||
|
var content = serializer.Response{
|
||||||
|
Code: code,
|
||||||
|
Err: ers,
|
||||||
|
Msg: code.String(),
|
||||||
|
}
|
||||||
|
var errMsg = WsInfo{
|
||||||
|
Type: wsType,
|
||||||
|
Content: content,
|
||||||
|
From: "0",
|
||||||
|
To: clientId,
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(errMsg)
|
||||||
|
return byteMsg
|
||||||
|
}
|
||||||
|
func WsErrorPermissionDenied(wsType WsType, clientId string) []byte {
|
||||||
|
var content = serializer.Response{
|
||||||
|
Code: e.PermissionDenied,
|
||||||
|
Err: "Permission Denied",
|
||||||
|
Msg: "拒绝访问",
|
||||||
|
}
|
||||||
|
var errMsg = WsInfo{
|
||||||
|
Type: wsType,
|
||||||
|
Content: content,
|
||||||
|
From: "0",
|
||||||
|
To: clientId,
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(errMsg)
|
||||||
|
return byteMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func WsErrorInvalidDataFormat(clientId string) []byte {
|
||||||
|
var content = serializer.Response{
|
||||||
|
Status: e.Failed,
|
||||||
|
Code: e.Failed,
|
||||||
|
Err: "Invalid Data Format",
|
||||||
|
Msg: "发送失败",
|
||||||
|
}
|
||||||
|
var errMsg = WsInfo{
|
||||||
|
Type: ErrorType,
|
||||||
|
Content: content,
|
||||||
|
From: "0",
|
||||||
|
To: clientId,
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(errMsg)
|
||||||
|
return byteMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func WsErrorUnknownMessageType(clientId string) []byte {
|
||||||
|
var errMsg = WsInfo{
|
||||||
|
Type: ErrorType,
|
||||||
|
Content: "Unknown notice type",
|
||||||
|
From: "0",
|
||||||
|
To: clientId,
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(errMsg)
|
||||||
|
return byteMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func WsErrorConnection(clientId string, err string, marks ...string) []byte {
|
||||||
|
mark := ""
|
||||||
|
if marks != nil {
|
||||||
|
mark = strings.Join(marks, ";")
|
||||||
|
}
|
||||||
|
var errMsg = WsInfo{
|
||||||
|
Type: ErrorType,
|
||||||
|
Content: "Connection error:" + err,
|
||||||
|
From: "0",
|
||||||
|
To: clientId,
|
||||||
|
Mark: mark,
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(errMsg)
|
||||||
|
return byteMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func WsChatMessage(clientId string, targetClientId string, msg string) []byte {
|
||||||
|
var errMsg = WsInfo{
|
||||||
|
Type: ChatType,
|
||||||
|
Content: msg,
|
||||||
|
From: clientId,
|
||||||
|
To: targetClientId,
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(errMsg)
|
||||||
|
return byteMsg
|
||||||
|
}
|
315
pkg/common/ws/chatRoom.go
Normal file
315
pkg/common/ws/chatRoom.go
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
// Package ws -----------------------------
|
||||||
|
// @file : chatRoom.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2022/10/21 18:17:17
|
||||||
|
// -------------------------------------------
|
||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"fonchain-fiee/pkg/utils"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Time allowed to write a notice to the peer.
|
||||||
|
writeWait = 10 * time.Second
|
||||||
|
|
||||||
|
// Time allowed to read the next pong notice from the peer.
|
||||||
|
pongWait = 60 * time.Second
|
||||||
|
|
||||||
|
// Send pings to peer with this period. Must be less than pongWait.
|
||||||
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
|
||||||
|
// Maximum notice size allowed from peer.
|
||||||
|
maxMessageSize = 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewChatRoom() *ChatRoom {
|
||||||
|
var room = ChatRoom{
|
||||||
|
clientsRwLocker: &sync.RWMutex{},
|
||||||
|
clients: make(map[int64]map[string]*Client),
|
||||||
|
register: make(clientChan),
|
||||||
|
UnRegister: make(clientChan),
|
||||||
|
broadcast: make(broadcastChan),
|
||||||
|
}
|
||||||
|
go room.Run()
|
||||||
|
return &room
|
||||||
|
}
|
||||||
|
|
||||||
|
type broadcastMessage struct {
|
||||||
|
UserIds []int64
|
||||||
|
message []byte
|
||||||
|
}
|
||||||
|
type (
|
||||||
|
// []byte类型管道 用于客户端接收消息数据
|
||||||
|
messageChan chan []byte
|
||||||
|
|
||||||
|
//
|
||||||
|
wsConnChan chan *websocket.Conn
|
||||||
|
|
||||||
|
// Client类型数据管道
|
||||||
|
clientChan chan *Client
|
||||||
|
|
||||||
|
broadcastChan chan *broadcastMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChatRoom struct {
|
||||||
|
clientsRwLocker *sync.RWMutex
|
||||||
|
//clients 客户端信息存储
|
||||||
|
//// 支持多客户端连接 map[userId]map[clientId]*Client
|
||||||
|
clients map[int64]map[string]*Client
|
||||||
|
|
||||||
|
//会话 map[sessionId][]*Client
|
||||||
|
Session map[string][]*Client
|
||||||
|
|
||||||
|
//register register 注册管道
|
||||||
|
register clientChan
|
||||||
|
|
||||||
|
//unRegister 注销管道 接收需要注销的客户端
|
||||||
|
UnRegister clientChan
|
||||||
|
|
||||||
|
broadcast broadcastChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ChatRoom) Run() {
|
||||||
|
//消息分发
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
// 注册事件
|
||||||
|
case newClient := <-o.register:
|
||||||
|
////删除临时map中的客户户端
|
||||||
|
//delete(o.tempClient, client.clientId)
|
||||||
|
o.clientsRwLocker.Lock()
|
||||||
|
//添加到客户端集合中
|
||||||
|
if o.clients[newClient.UserId] == nil {
|
||||||
|
o.clients[newClient.UserId] = make(map[string]*Client)
|
||||||
|
}
|
||||||
|
o.clients[newClient.UserId][newClient.ClientId] = newClient
|
||||||
|
//添加到会话集合中
|
||||||
|
if o.Session == nil {
|
||||||
|
o.Session = make(map[string][]*Client)
|
||||||
|
}
|
||||||
|
//if _, ok := o.Session[newClient.SessionId]; ok {
|
||||||
|
// for i, client := range o.Session[newClient.SessionId] {
|
||||||
|
// if client.ClientId == newClient.ClientId {
|
||||||
|
// //将之前的客户端注销
|
||||||
|
// o.UnRegister <- client
|
||||||
|
// }
|
||||||
|
// o.Session[newClient.SessionId][i] = newClient
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
if newClient.Waiter {
|
||||||
|
//客服人员没有默认会话窗口,而是自动加入所有用户的会话
|
||||||
|
for sessionId, _ := range o.Session {
|
||||||
|
sessionId := sessionId
|
||||||
|
if sessionId != newClient.SessionId {
|
||||||
|
for _, client := range o.clients[newClient.UserId] {
|
||||||
|
o.Session[sessionId] = append(o.Session[sessionId], client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//画家添加会话的逻辑
|
||||||
|
_, ok := o.Session[newClient.SessionId]
|
||||||
|
if !ok {
|
||||||
|
o.Session[newClient.SessionId] = make([]*Client, 0)
|
||||||
|
//把客服拉入会话
|
||||||
|
for userId, clientInfo := range o.clients {
|
||||||
|
if userId == newClient.UserId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i, client := range clientInfo {
|
||||||
|
if client != nil && client.Waiter {
|
||||||
|
//把客服人员客户端加入会话中
|
||||||
|
o.Session[newClient.SessionId] = append(o.Session[newClient.SessionId], clientInfo[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//再把自己的客户端加入会话
|
||||||
|
o.Session[newClient.SessionId] = append(o.Session[newClient.SessionId], newClient)
|
||||||
|
}
|
||||||
|
o.clientsRwLocker.Unlock()
|
||||||
|
//注销事件
|
||||||
|
case client := <-o.UnRegister:
|
||||||
|
//panic 恢复
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != "" {
|
||||||
|
const size = 64 << 10
|
||||||
|
buf := make([]byte, size)
|
||||||
|
buf = buf[:runtime.Stack(buf, false)]
|
||||||
|
err, ok := r.(error)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
log.Fatal("close webosocket connection occured panic , recovered!", zap.Any("client", client), zap.Error(err), zap.String("stack", string(buf)))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fmt.Println("ws客户端注销事件触发")
|
||||||
|
//从客户端集合中删除
|
||||||
|
if _, ok := o.clients[client.UserId]; ok {
|
||||||
|
if client != nil && client.Conn != nil {
|
||||||
|
//_ = client.Conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||||
|
_ = client.Conn.Close()
|
||||||
|
}
|
||||||
|
o.clients[client.UserId][client.ClientId] = nil
|
||||||
|
delete(o.clients[client.UserId], client.ClientId)
|
||||||
|
fmt.Printf("ws客户端%s 被注销\n", client.ClientId)
|
||||||
|
}
|
||||||
|
// 消息群发事件
|
||||||
|
case messageInfo := <-o.broadcast:
|
||||||
|
o.Broadcast(messageInfo.message, messageInfo.UserIds...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (o *ChatRoom) Register(c *Client) (sessionId string) {
|
||||||
|
if c.SessionId == "" && !c.Waiter {
|
||||||
|
//这里的c经常拿不到sessionId,所以使用userId固定死
|
||||||
|
//c.SessionId = fmt.Sprintf("%d-%d", c.UserId, time.Now().Unix())
|
||||||
|
c.SessionId = fmt.Sprintf("%d", c.UserId)
|
||||||
|
}
|
||||||
|
o.register <- c
|
||||||
|
return c.SessionId
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendSessionMessage
|
||||||
|
// sendUserId: 发送消息的用户id,消息提醒时,此用户将会被排除
|
||||||
|
// sessionId: 会话id
|
||||||
|
// msgType: 消息类型
|
||||||
|
// message: 消息内容
|
||||||
|
func (o *ChatRoom) SendSessionMessage(sendUserId int64, sessionId string, msgType WsType, message any) (userIdInSession []int64, err error) {
|
||||||
|
o.clientsRwLocker.Lock()
|
||||||
|
defer o.clientsRwLocker.Unlock()
|
||||||
|
var msg = WsSessionInfo{
|
||||||
|
Type: msgType,
|
||||||
|
Content: message,
|
||||||
|
}
|
||||||
|
msgBytes, _ := json.Marshal(msg)
|
||||||
|
if o.Session[sessionId] == nil {
|
||||||
|
err = fmt.Errorf("该会话不存在或已失效")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("ChatRoom.SendSessionMessage - 1")
|
||||||
|
usableClients := []*Client{}
|
||||||
|
fmt.Printf("sessionId:[%s],客户端数量%d\n", sessionId, len(o.Session[sessionId]))
|
||||||
|
for i, client := range o.Session[sessionId] {
|
||||||
|
if client != nil {
|
||||||
|
_, exist := o.clients[client.UserId][client.ClientId]
|
||||||
|
if exist {
|
||||||
|
usableClients = append(usableClients, o.Session[sessionId][i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("client:%+v\n", client)
|
||||||
|
if client != nil && client.UserId != sendUserId {
|
||||||
|
client.Send <- msgBytes
|
||||||
|
userIdInSession = append(userIdInSession, client.UserId)
|
||||||
|
}
|
||||||
|
//client.Send <- msgBytes
|
||||||
|
}
|
||||||
|
o.Session[sessionId] = usableClients
|
||||||
|
fmt.Printf("sessionId:[%s],客户端数量%d\n", sessionId, len(o.Session[sessionId]))
|
||||||
|
fmt.Println("userIdInSession", userIdInSession)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (o *ChatRoom) GetUserIdInSession(sessionId string, withoutUserId ...int64) (userIds []int64) {
|
||||||
|
fmt.Printf("sessionId:%s withoutUserId:%d\n", sessionId, withoutUserId)
|
||||||
|
//o.clientsRwLocker.RLock()
|
||||||
|
//defer o.clientsRwLocker.RUnlock()
|
||||||
|
fmt.Println("GetUserIdInSession 1")
|
||||||
|
if o.Session[sessionId] != nil {
|
||||||
|
fmt.Printf("GetUserIdInSession 2,o.Session[sessionId]:%+v", o.Session[sessionId])
|
||||||
|
for _, client := range o.Session[sessionId] {
|
||||||
|
fmt.Println("session one of userId", client.UserId)
|
||||||
|
var jump bool
|
||||||
|
if withoutUserId != nil {
|
||||||
|
for _, userId := range withoutUserId {
|
||||||
|
if client.UserId == userId {
|
||||||
|
jump = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !jump {
|
||||||
|
fmt.Println("ADD USER", client.UserId)
|
||||||
|
userId := client.UserId
|
||||||
|
userIds = append(userIds, userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//针对app没有连接上websocket(聊天室没有检查到用户的客户端,此时websocket无法发送通知),但是需要推送app通知给用户的情况进行优化
|
||||||
|
fmt.Println("GetUserIdInSession 3,userIds:", userIds)
|
||||||
|
if len(userIds) == 0 {
|
||||||
|
sessionUserId, _ := strconv.Atoi(sessionId)
|
||||||
|
add := true
|
||||||
|
if sessionUserId != 0 {
|
||||||
|
for _, v := range withoutUserId {
|
||||||
|
if v == int64(sessionUserId) {
|
||||||
|
add = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if add {
|
||||||
|
userIds = append(userIds, int64(sessionUserId))
|
||||||
|
}
|
||||||
|
fmt.Println("GetUserIdInSession 4,userIds:", userIds)
|
||||||
|
}
|
||||||
|
userIds = utils.Unique(userIds)
|
||||||
|
fmt.Println("GetUserIdInSession 5,userIds:", userIds)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (o ChatRoom) RegisterClient(c *Client) {
|
||||||
|
// o.register <- c
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (o ChatRoom) DeleteClient(c *Client) {
|
||||||
|
// o.unRegister <- c
|
||||||
|
// }
|
||||||
|
func (o ChatRoom) Broadcast(message []byte, userIds ...int64) {
|
||||||
|
// 如果userIds为空则群发,否则找到这个用户的ws对象
|
||||||
|
if userIds == nil {
|
||||||
|
for _, userClients := range o.clients {
|
||||||
|
for _, cli := range userClients {
|
||||||
|
if cli == nil {
|
||||||
|
o.UnRegister <- cli
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
err := cli.Conn.WriteMessage(websocket.TextMessage, message)
|
||||||
|
if err != nil {
|
||||||
|
o.UnRegister <- cli
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, userId := range userIds {
|
||||||
|
userClients, ok := o.clients[userId]
|
||||||
|
if ok == false {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, cli := range userClients {
|
||||||
|
if cli == nil {
|
||||||
|
o.UnRegister <- cli
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
err := cli.Conn.WriteMessage(websocket.TextMessage, message)
|
||||||
|
if err != nil {
|
||||||
|
o.UnRegister <- cli
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
pkg/common/ws/client.go
Normal file
111
pkg/common/ws/client.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Package ws -----------------------------
|
||||||
|
// @file : client.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2022/10/21 18:18:05
|
||||||
|
// -------------------------------------------
|
||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// 注册事件最大等待时间
|
||||||
|
limitRegisterWaitTime = time.Second * 6
|
||||||
|
limitReadTime = time.Second * 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewClient 创建客户端实例
|
||||||
|
//
|
||||||
|
// param userId 用户id
|
||||||
|
// param uid 用户uuid
|
||||||
|
// param conn 客户端websocket连接对象
|
||||||
|
// return *Client
|
||||||
|
func NewClient(userId int64, uid string, conn *websocket.Conn, room *ChatRoom) *Client {
|
||||||
|
uidobj, _ := uuid.NewV4()
|
||||||
|
var v = &Client{
|
||||||
|
Room: room,
|
||||||
|
UserId: userId,
|
||||||
|
Uuid: uid,
|
||||||
|
ClientId: strings.Replace(uidobj.String(), "-", "", -1),
|
||||||
|
Conn: conn,
|
||||||
|
Send: make(chan []byte, 500),
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Room *ChatRoom `json:"-" `
|
||||||
|
UserId int64 `json:"userId" ` //用户id
|
||||||
|
Uuid string `json:"uuid"` //画家uid
|
||||||
|
ClientId string `json:"clientId"` //为用户不同设备分配不同的客户端ID
|
||||||
|
Conn *websocket.Conn `json:"-"`
|
||||||
|
Send chan []byte
|
||||||
|
SessionId string `json:"sessionId"` //会话ID,同一个用户多客户端登录,会话ID相同
|
||||||
|
Waiter bool `json:"waiter"` //是否是客服
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Reading(ctx context.Context, handleFunc ...func(sourceData []byte, cli *Client)) {
|
||||||
|
defer func() {
|
||||||
|
c.Room.UnRegister <- c
|
||||||
|
ctx.Done()
|
||||||
|
return
|
||||||
|
}()
|
||||||
|
//c.Conn.SetReadLimit(maxMessageSize)
|
||||||
|
c.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
//接收到ping命令后,更新读取时间
|
||||||
|
c.Conn.SetPongHandler(func(string) error {
|
||||||
|
c.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
for {
|
||||||
|
msgType, byteData, err := c.Conn.ReadMessage()
|
||||||
|
if msgType == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||||
|
log.Println("ws连接已关闭", zap.Error(err))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if handleFunc != nil {
|
||||||
|
handleFunc[0](byteData, c)
|
||||||
|
} else {
|
||||||
|
HandleMessage(byteData, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (c *Client) WriteWait() {
|
||||||
|
ticker := time.NewTicker(pingPeriod)
|
||||||
|
defer func() {
|
||||||
|
ticker.Stop()
|
||||||
|
c.Conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg, ok := <-c.Send:
|
||||||
|
fmt.Printf("发送消息:%+v\n", string(msg))
|
||||||
|
c.Conn.WriteMessage(websocket.TextMessage, msg)
|
||||||
|
fmt.Println("发送消息结束")
|
||||||
|
if !ok {
|
||||||
|
// 聊天室关闭了管道
|
||||||
|
c.Conn.WriteControl(websocket.CloseMessage, []byte{}, time.Now().Add(writeWait))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := c.Conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(pongWait)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
pkg/common/ws/ginWSUpgrade.go
Normal file
21
pkg/common/ws/ginWSUpgrade.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Package utils -----------------------------
|
||||||
|
// @file : hertzWSUpgrade.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2022/6/28 14:19
|
||||||
|
// -------------------------------------------
|
||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var UpGrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
// 检查请求的来源是否允许websocket连接,可根据需求自行实现
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
53
pkg/common/ws/readme.md
Normal file
53
pkg/common/ws/readme.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# wsscoket 对接说明
|
||||||
|
## 客户端对接测试页面
|
||||||
|
[{{服务端地址}}/ws/client](http://127.0.0.1:8088/ws/client)
|
||||||
|
|
||||||
|
## 客户端对接websocket流程
|
||||||
|
### websocket的连接
|
||||||
|
1. 客户端登录后获取uuid
|
||||||
|
2. 连接服务端websocket后,在10s内发送一下格式的数据,否则websocket连接将断开。
|
||||||
|
uuid请添加登录后获取的,如果uuid不正确,连接也会断开
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "register",
|
||||||
|
"from": "",
|
||||||
|
"to": "0",
|
||||||
|
"content": {
|
||||||
|
"uuid":"用户的uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
注册成功后,服务端将返回客户端临时id
|
||||||
|
```json
|
||||||
|
{"clientId":"02de5759-3f0a-47fa-a79f-afe61c39c5aa"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### weboscket 数据发送测试
|
||||||
|
消息类型`type="test"`时,客户端将会把`content`内容原路返回,以此来测试最基本的通讯功能。
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"from": "用户clientId",
|
||||||
|
"to": "0",
|
||||||
|
"content": {
|
||||||
|
"demo":"testdemo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### websocket消息类型说明
|
||||||
|
#### 错误消息
|
||||||
|
在websocket通讯过程中,服务端会对客户端发送过来的消息进行验证。
|
||||||
|
|
||||||
|
| type字段 | content字段 | 说明 |
|
||||||
|
|--------|----------------------|------------------------------|
|
||||||
|
| Error | Permission denied | 拒绝访问。 此报错一般出现在首次连接,验证uuid的时候 |
|
||||||
|
| Error | Invalid data format | 无效的数据格式。消息内容未按照指定格式书写 |
|
||||||
|
| Error | Unknown message type | 未知的消息类型。接收到了未定义的type |
|
||||||
|
|
||||||
|
**错误消息示例:**
|
||||||
|
```json
|
||||||
|
{"type":"Error","content":"Permission denied","from":"0","to":"tempId"}
|
||||||
|
//{"type":"Error","content":"Invalid data format","from":"0","to":""}
|
||||||
|
//{"type":"Error","content":"Unknown notice type","from":"0","to":"0"}
|
||||||
|
```
|
145
pkg/common/ws/wsMessageHandle.go
Normal file
145
pkg/common/ws/wsMessageHandle.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// Package ws -----------------------------
|
||||||
|
// @file : handler.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2022/10/23 11:13:43
|
||||||
|
// -------------------------------------------
|
||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fonchain-fiee/api/account"
|
||||||
|
"fonchain-fiee/api/accountFiee"
|
||||||
|
"fonchain-fiee/pkg/config"
|
||||||
|
"fonchain-fiee/pkg/e"
|
||||||
|
"fonchain-fiee/pkg/service"
|
||||||
|
"fonchain-fiee/pkg/utils/secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AuthorizationVerify(sourceData []byte) (userInfo *accountFiee.ChatUserData, ok bool, err error) {
|
||||||
|
var msg AuthorizationInfo
|
||||||
|
err = json.Unmarshal(sourceData, &msg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msg.Type != AuthorizationType {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msg.Content.Auth == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var ctx = context.Background()
|
||||||
|
var accountInfo accountFiee.ChatUserData
|
||||||
|
switch msg.Content.Domain {
|
||||||
|
case config.Domain:
|
||||||
|
//fiee token校验
|
||||||
|
msg.Content.Auth, err = secret.GetJwtFromStr(msg.Content.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var fieeJwtInfo *accountFiee.DecryptJwtResponse
|
||||||
|
fieeJwtInfo, err = service.AccountFieeProvider.DecryptJwt(ctx, &accountFiee.DecryptJwtRequest{Token: msg.Content.Auth, Domain: msg.Content.Domain})
|
||||||
|
if err != nil || fieeJwtInfo.IsOffline {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accountInfo.Origin = msg.Content.Domain
|
||||||
|
accountInfo.OriginId = int64(fieeJwtInfo.ID)
|
||||||
|
accountInfo.Account = fieeJwtInfo.Account
|
||||||
|
accountInfo.NickName = fieeJwtInfo.NickName
|
||||||
|
case "fontree":
|
||||||
|
//erp token校验
|
||||||
|
msg.Content.Auth, err = secret.GetJwtFromStr(msg.Content.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var fontreeJwtInfo *account.DecryptJwtResponse
|
||||||
|
fontreeJwtInfo, err = service.AccountProvider.DecryptJwt(ctx, &account.DecryptJwtRequest{Token: msg.Content.Auth, Domain: msg.Content.Domain})
|
||||||
|
if err != nil || fontreeJwtInfo.IsOffline {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accountInfo.Origin = msg.Content.Domain
|
||||||
|
accountInfo.OriginId = int64(fontreeJwtInfo.ID)
|
||||||
|
accountInfo.Account = fontreeJwtInfo.Account
|
||||||
|
accountInfo.NickName = fontreeJwtInfo.NickName
|
||||||
|
}
|
||||||
|
|
||||||
|
//查询是否已经注册
|
||||||
|
var chatUserQuery *accountFiee.GetChatUserListResp
|
||||||
|
chatUserQuery, err = service.AccountFieeProvider.GetChatUserList(ctx, &accountFiee.GetChatUserListRequest{
|
||||||
|
Query: &accountFiee.ChatUserData{OriginId: int64(accountInfo.ID), Origin: msg.Content.Domain},
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 1,
|
||||||
|
})
|
||||||
|
//如果找不到聊天用户则创建
|
||||||
|
if err != nil || chatUserQuery.Total > 0 {
|
||||||
|
//注册客服
|
||||||
|
var createUserRes *accountFiee.CreateChatUserResp
|
||||||
|
createUserRes, err = service.AccountFieeProvider.CreateChatUser(ctx, &accountFiee.ChatUserData{
|
||||||
|
NickName: accountInfo.NickName,
|
||||||
|
Account: accountInfo.Account,
|
||||||
|
Role: 2,
|
||||||
|
Origin: msg.Content.Domain,
|
||||||
|
OriginId: int64(accountInfo.ID),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userInfo = createUserRes.GetData()
|
||||||
|
}
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func HandleMessage(sourceData []byte, cli *Client) {
|
||||||
|
var msg WsInfo
|
||||||
|
err := json.Unmarshal(sourceData, &msg)
|
||||||
|
if err != nil {
|
||||||
|
cli.Send <- WsErrorInvalidDataFormat(msg.From)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msg.Type {
|
||||||
|
default:
|
||||||
|
cli.Send <- WsErrorUnknownMessageType(msg.From)
|
||||||
|
case TestType:
|
||||||
|
var newMsg = WsInfo{
|
||||||
|
Type: TestType,
|
||||||
|
Content: msg.Content,
|
||||||
|
From: "0",
|
||||||
|
To: msg.From,
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(newMsg)
|
||||||
|
cli.Send <- byteMsg
|
||||||
|
case ChatType:
|
||||||
|
if msg.From == "" {
|
||||||
|
//客户端id不能为空
|
||||||
|
cli.Send <- WsErrorMessage(ChatType, "null", e.ErrInvalidClientId, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var chatInfo ChatInfo
|
||||||
|
_ = json.Unmarshal(sourceData, &chatInfo)
|
||||||
|
//解析Content
|
||||||
|
if clients, ok := cli.Room.clients[chatInfo.Content.TargetUserId]; ok {
|
||||||
|
for _, targetObj := range clients {
|
||||||
|
if targetObj != nil {
|
||||||
|
targetObj.Send <- WsChatMessage(msg.From, chatInfo.Content.TargetClientId, chatInfo.Content.Msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//对方不在线
|
||||||
|
cli.Send <- WsErrorMessage(ChatType, msg.From, e.ErrTargetOutLine, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatInfo struct {
|
||||||
|
Type WsType `json:"type"` //消息类型
|
||||||
|
Content ChatContent `json:"content"` //消息内容
|
||||||
|
From string `json:"from"` //发送者 0为服务端,客户端填写clientId
|
||||||
|
To string `json:"to"` //接收者 接收消息的用户id
|
||||||
|
}
|
||||||
|
type ChatContent struct {
|
||||||
|
TargetUuid string `json:"targetUuid"`
|
||||||
|
TargetUserId int64 `json:"targetUserId"`
|
||||||
|
TargetClientId string `json:"targetClientId"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
127
pkg/common/ws/wsRoom.html
Normal file
127
pkg/common/ws/wsRoom.html
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Chat Example</title>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.onload = function () {
|
||||||
|
var conn;
|
||||||
|
var msg = document.getElementById("msg");
|
||||||
|
var log = document.getElementById("log");
|
||||||
|
|
||||||
|
function appendLog(item) {
|
||||||
|
var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;
|
||||||
|
log.appendChild(item);
|
||||||
|
if (doScroll) {
|
||||||
|
log.scrollTop = log.scrollHeight - log.clientHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//时间格式化
|
||||||
|
Date.prototype.Format = function (fmt) { // author: meizz
|
||||||
|
var o = {
|
||||||
|
"M+": this.getMonth() + 1, // 月份
|
||||||
|
"d+": this.getDate(), // 日
|
||||||
|
"h+": this.getHours(), // 小时
|
||||||
|
"m+": this.getMinutes(), // 分
|
||||||
|
"s+": this.getSeconds(), // 秒
|
||||||
|
"q+": Math.floor((this.getMonth() + 3) / 3), // 季度
|
||||||
|
"S": this.getMilliseconds() // 毫秒
|
||||||
|
};
|
||||||
|
if (/(y+)/.test(fmt))
|
||||||
|
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
|
||||||
|
for (var k in o)
|
||||||
|
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
|
||||||
|
return fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("form").onsubmit = function () {
|
||||||
|
if (!conn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!msg.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
conn.send(msg.value);
|
||||||
|
|
||||||
|
|
||||||
|
var item = document.createElement("div");
|
||||||
|
var now = new Date().Format("yyyy-MM-dd hh:mm:ss:S")
|
||||||
|
item.innerText = "客户端发送消息:\t"+now+"\n\t\t"+msg.value+"\n\n";
|
||||||
|
appendLog(item);
|
||||||
|
|
||||||
|
|
||||||
|
msg.value = "";
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (window["WebSocket"]) {
|
||||||
|
conn = new WebSocket("ws://" + document.location.host + "/ws");
|
||||||
|
conn.onclose = function (evt) {
|
||||||
|
var item = document.createElement("div");
|
||||||
|
item.innerHTML = "<b>Connection closed.</b>";
|
||||||
|
appendLog(item);
|
||||||
|
};
|
||||||
|
conn.onmessage = function (evt) {
|
||||||
|
var messages = evt.data.split('\n');
|
||||||
|
var now = new Date().Format("yyyy-MM-dd hh:mm:ss:S")
|
||||||
|
for (var i = 0; i < messages.length; i++) {
|
||||||
|
var item = document.createElement("div");
|
||||||
|
item.innerText = "服务端回复消息:\t"+now+"\n\t\t"+messages[i]+"\n\n";
|
||||||
|
appendLog(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
var item = document.createElement("div");
|
||||||
|
item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
|
||||||
|
appendLog(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
background: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
left: 0.5em;
|
||||||
|
right: 0.5em;
|
||||||
|
bottom: 3em;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form {
|
||||||
|
padding: 0 0.5em 0 0.5em;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1em;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
input{
|
||||||
|
height: 50px;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="log"></div>
|
||||||
|
<form id="form">
|
||||||
|
<input type="submit" value="Send" />
|
||||||
|
<input type="text" id="msg" size="64" autofocus />
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
63
pkg/e/chatCode.go
Normal file
63
pkg/e/chatCode.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Package e -----------------------------
|
||||||
|
// @file : chatCode.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2025/6/12 16:57
|
||||||
|
// -------------------------------------------
|
||||||
|
package e
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type ErrorCodeType int
|
||||||
|
|
||||||
|
func (e ErrorCodeType) String() string {
|
||||||
|
return GetCodeMsg(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrorCodeType) Error() string {
|
||||||
|
return GetCodeMsg(e)
|
||||||
|
}
|
||||||
|
func (e ErrorCodeType) Int() int {
|
||||||
|
return int(e)
|
||||||
|
}
|
||||||
|
func GetCodeMsg(e ErrorCodeType) string {
|
||||||
|
v, ok := msgFlags[e]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Sprintf("未知错误:[%d]", e)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgFlags = map[ErrorCodeType]string{
|
||||||
|
SUCCESS: "操作成功",
|
||||||
|
UpdatePasswordSuccess: "修改密码成功",
|
||||||
|
NotExistInentifier: "该第三方账号未绑定",
|
||||||
|
ERROR: "fail",
|
||||||
|
InvalidParams: "请求参数错误",
|
||||||
|
BindError: "参数绑定错误,类型不一致",
|
||||||
|
JsonUnmarshal: "Json解析错误",
|
||||||
|
|
||||||
|
ErrorDatabase: "数据库操作出错,请重试",
|
||||||
|
|
||||||
|
ErrorOss: "OSS配置错误",
|
||||||
|
|
||||||
|
InvalidToken: "Token验证失败",
|
||||||
|
|
||||||
|
ErrorUploadFile: "上传失败",
|
||||||
|
ErrorUploadVideoCover: "视频截取封面错误",
|
||||||
|
ErrorUploadValidParam: "上传参数非法",
|
||||||
|
ErrorFileReadErr: "读取文件错误",
|
||||||
|
ErrorFileNotExists: "文件不存在",
|
||||||
|
ErrorChunkNotGt: "分块数量不一致",
|
||||||
|
ErrorChunk: "读取分块错误",
|
||||||
|
ErrorUploadBos: "上传bos错误",
|
||||||
|
ErrorFileCreate: "文件创建错误",
|
||||||
|
ErrInvalidDataFormat: "无效的数据格式",
|
||||||
|
ErrInvalidClientId: "无效的客户端ID",
|
||||||
|
ErrRegisterFailed: "注册失败",
|
||||||
|
ErrUnRegistered: "未注册客户端",
|
||||||
|
PermissionDenied: "拒绝访问",
|
||||||
|
ErrChatSendErr: "消息发送失败",
|
||||||
|
NotLogin: "请先登录",
|
||||||
|
ErrorNotExistUser: "用户不存在",
|
||||||
|
}
|
@ -139,6 +139,15 @@ const (
|
|||||||
ERROR_Text_Irregularity = 90018
|
ERROR_Text_Irregularity = 90018
|
||||||
ERROR_Text_Length = 90019
|
ERROR_Text_Length = 90019
|
||||||
ERROR_NoPermission = 90020
|
ERROR_NoPermission = 90020
|
||||||
|
|
||||||
|
//聊天室
|
||||||
|
ErrInvalidDataFormat = 80100 //无效的数据格式
|
||||||
|
ErrInvalidClientId = 80101 //无效的客户端id
|
||||||
|
ErrRegisterFailed = 80102 //注册失败
|
||||||
|
ErrUnRegistered = 80103 //未注册
|
||||||
|
PermissionDenied = 80104 //拒绝访问
|
||||||
|
ErrChatSendErr = 80105 //聊天记录发送失败
|
||||||
|
ErrTargetOutLine = 80106 //目标离线
|
||||||
)
|
)
|
||||||
const (
|
const (
|
||||||
Push = 1
|
Push = 1
|
||||||
|
56
pkg/e/fileType.go
Normal file
56
pkg/e/fileType.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package e
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type FileType int
|
||||||
|
|
||||||
|
// 定义文件类型值
|
||||||
|
const (
|
||||||
|
Video FileType = 1
|
||||||
|
Audio FileType = 2
|
||||||
|
Image FileType = 3
|
||||||
|
File FileType = 4
|
||||||
|
Other FileType = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// 根据扩展名映射到文件类型值
|
||||||
|
var extensionToType = map[string]FileType{
|
||||||
|
// 视频文件
|
||||||
|
".3g2": Video, ".3gp": Video, ".asf": Video, ".avi": Video, ".divx": Video, ".drc": Video,
|
||||||
|
".flv": Video, ".h261": Video, ".h264": Video, ".mkv": Video, ".mov": Video, ".mp4": Video, ".mpg": Video,
|
||||||
|
".mpeg": Video, ".mpv": Video, ".mxf": Video, ".nuv": Video, ".qt": Video, ".rm": Video,
|
||||||
|
".rmvb": Video, ".srt": Video, ".swf": Video, ".vob": Video, ".vp6": Video, ".vp8": Video, ".webm": Video,
|
||||||
|
".wmv": Video, ".xesc": Video,
|
||||||
|
|
||||||
|
// 音频文件
|
||||||
|
".aac": Audio, ".aax": Audio, ".ac3": Audio, ".act": Audio, ".au": Audio, ".flac": Audio,
|
||||||
|
".m4a": Audio, ".m4p": Audio, ".m4r": Audio, ".mid": Audio, ".midi": Audio, ".mp2": Audio,
|
||||||
|
".mp3": Audio, ".mpa": Audio, ".mpc": Audio, ".ogg": Audio, ".wav": Audio, ".wma": Audio, ".wv": Audio,
|
||||||
|
|
||||||
|
// 图像文件
|
||||||
|
".bmp": Image, ".gif": Image, ".ico": Image, ".jpeg": Image, ".jpg": Image, ".jpe": Image,
|
||||||
|
".png": Image, ".psd": Image, ".tiff": Image, ".webp": Image,
|
||||||
|
|
||||||
|
// 普通文件
|
||||||
|
".a": File, ".abw": File, ".azw": File, ".bin": File, ".bz2": File, ".c": File, ".cab": File,
|
||||||
|
".class": File, ".conf": File, ".crt": File, ".css": File, ".csv": File, ".dat": File, ".deb": File,
|
||||||
|
".dll": File, ".dms": File, ".doc": File, ".docx": File, ".eot": File, ".eps": File, ".exe": File,
|
||||||
|
".gz": File, ".h": File, ".htm": File, ".html": File, ".iso": File, ".jar": File,
|
||||||
|
".js": File, ".json": File, ".log": File, ".m3u": File, ".m3u8": File, ".md": File, ".msi": File,
|
||||||
|
".otf": File, ".pcap": File, ".pdf": File, ".ppt": File, ".pptx": File, ".rar": File, ".rpm": File,
|
||||||
|
".rss": File, ".run": File, ".sh": File, ".sql": File, ".svg": File, ".tar": File, ".tgz": File,
|
||||||
|
".ttf": File, ".txt": File, ".vsd": File, ".weba": File,
|
||||||
|
".wps": File, ".xml": File, ".xpi": File, ".zip": File, ".z": File,
|
||||||
|
|
||||||
|
// 未知文件扩展名
|
||||||
|
"": Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectFileTypeByExtension 通过文件扩展名判断文件类型并返回对应的值
|
||||||
|
func DetectFileTypeByExtension(extension string) FileType {
|
||||||
|
extension = strings.ToLower(extension)
|
||||||
|
if fileType, exists := extensionToType[extension]; exists {
|
||||||
|
return fileType
|
||||||
|
}
|
||||||
|
return Other
|
||||||
|
}
|
@ -146,6 +146,8 @@ var MsgFlags = map[int]string{
|
|||||||
ERROR_Text_Irregularity: "文字内容不合规",
|
ERROR_Text_Irregularity: "文字内容不合规",
|
||||||
ERROR_Text_Length: "文本长度超出限制",
|
ERROR_Text_Length: "文本长度超出限制",
|
||||||
ERROR_NoPermission: "您暂无权限,请联系客服",
|
ERROR_NoPermission: "您暂无权限,请联系客服",
|
||||||
|
|
||||||
|
ErrInvalidClientId: "无效的客户端ID",
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fonchain-fiee/pkg/middleware"
|
"fonchain-fiee/pkg/middleware"
|
||||||
"fonchain-fiee/pkg/service"
|
"fonchain-fiee/pkg/service"
|
||||||
"fonchain-fiee/pkg/service/account"
|
"fonchain-fiee/pkg/service/account"
|
||||||
|
"fonchain-fiee/pkg/service/asChat"
|
||||||
"fonchain-fiee/pkg/service/auth"
|
"fonchain-fiee/pkg/service/auth"
|
||||||
"fonchain-fiee/pkg/service/file"
|
"fonchain-fiee/pkg/service/file"
|
||||||
"fonchain-fiee/pkg/service/governance"
|
"fonchain-fiee/pkg/service/governance"
|
||||||
@ -25,7 +26,10 @@ import (
|
|||||||
func NewRouter() *gin.Engine {
|
func NewRouter() *gin.Engine {
|
||||||
//使用默认gin路由
|
//使用默认gin路由
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
wsGroup := r.Group("api/fiee")
|
||||||
|
wsGroup.Use(
|
||||||
|
middleware.GinRecovery(true),
|
||||||
|
)
|
||||||
r.Use(gzip.Gzip(gzip.BestSpeed)) // 中间件占用绝大部分内存
|
r.Use(gzip.Gzip(gzip.BestSpeed)) // 中间件占用绝大部分内存
|
||||||
//加入日志中间件,跨域中间件
|
//加入日志中间件,跨域中间件
|
||||||
r.Use(middleware.NewLogger(), middleware.Cors(), middleware.GinRecovery(true))
|
r.Use(middleware.NewLogger(), middleware.Cors(), middleware.GinRecovery(true))
|
||||||
@ -111,6 +115,23 @@ func NewRouter() *gin.Engine {
|
|||||||
redirectRoute.POST("sdk/down/v2", auth.DownImgV2)
|
redirectRoute.POST("sdk/down/v2", auth.DownImgV2)
|
||||||
redirectRoute.POST("sdk/down/v3", auth.DownImgV3)
|
redirectRoute.POST("sdk/down/v3", auth.DownImgV3)
|
||||||
}
|
}
|
||||||
|
//========================================================================================
|
||||||
|
// 客户聊天
|
||||||
|
{
|
||||||
|
// websocket数据接收
|
||||||
|
wsGroup.GET("aschat/ws", asChat.ChatHandlerIns.Connection)
|
||||||
|
v1.POST("aschat/message/new", asChat.ChatHandlerIns.NewMessage)
|
||||||
|
v1.POST("aschat/media/upload", asChat.ChatHandlerIns.Upload)
|
||||||
|
v1.POST("aschat/message/list", asChat.ChatHandlerIns.MessageList)
|
||||||
|
v1.POST("aschat/user/stat", asChat.ChatHandlerIns.UserMessageStat)
|
||||||
|
v1.POST("aschat/voicetotext", asChat.ChatHandlerIns.VoiceToText)
|
||||||
|
//v1.POST("aschat/artistDetail", asChat.ChatHandlerIns.ArtistDetail)
|
||||||
|
v1.POST("aschat/autoReplyRuler/create", asChat.Handler.CreateChatAutoReplyRuler)
|
||||||
|
v1.POST("aschat/autoReplyRuler/delete", asChat.Handler.DeleteChatAutoReplyRuler)
|
||||||
|
v1.POST("aschat/autoReplyRuler/update", asChat.Handler.UpdateChatAutoReplyRuler)
|
||||||
|
v1.POST("aschat/autoReplyRuler/detail", asChat.Handler.GetChatAutoReplyRulerDetail)
|
||||||
|
v1.POST("aschat/autoReplyRuler/query", asChat.Handler.GetChatAutoReplyRulerList)
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// 素材库
|
// 素材库
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package serializer
|
package serializer
|
||||||
|
|
||||||
|
import "fonchain-fiee/pkg/e"
|
||||||
|
|
||||||
// Response 基础序列化器
|
// Response 基础序列化器
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Status int `json:"status"`
|
Status int `json:"status"`
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Code int `json:"code"`
|
Code e.ErrorCodeType `json:"code"`
|
||||||
Error error `json:"error"`
|
Error error `json:"error"`
|
||||||
Err string `json:"err"`
|
Err string `json:"err"`
|
||||||
Keys []string `json:"keys"`
|
Keys []string `json:"keys"`
|
||||||
|
241
pkg/service/asChat/cache.go
Normal file
241
pkg/service/asChat/cache.go
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
// Package asChat -----------------------------
|
||||||
|
// @file : cache.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2024/9/11 下午5:18
|
||||||
|
// -------------------------------------------
|
||||||
|
package asChat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"fonchain-fiee/api/accountFiee"
|
||||||
|
"fonchain-fiee/pkg/cache"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CacheChatRecordKey = "chatRecord"
|
||||||
|
const CacheSessionKey = "chatSession"
|
||||||
|
const CacheNewMsgStatKey = "newMsgStat"
|
||||||
|
|
||||||
|
var chatCacheLocker sync.RWMutex
|
||||||
|
|
||||||
|
type ChatCache struct {
|
||||||
|
newMessageStatExpireAfter time.Duration //消息统计的数据过期时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------存储用户的会话ID--------------------------------
|
||||||
|
func (cr ChatCache) GetUserSessionCacheKey(userId int64) string {
|
||||||
|
return fmt.Sprintf("%s:%d", CacheSessionKey, userId)
|
||||||
|
}
|
||||||
|
func (cr ChatCache) SaveUserSession(userId int64, sessionId string) {
|
||||||
|
chatCacheLocker.Lock()
|
||||||
|
defer chatCacheLocker.Unlock()
|
||||||
|
////var c = context.Background()
|
||||||
|
err := cache.RedisClient.Set(cr.GetUserSessionCacheKey(userId), sessionId, 0).Err()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("保存用户会话失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (cr ChatCache) GetUserSession(userId int64) (sessionId string) {
|
||||||
|
fmt.Println("GetUserSession-1")
|
||||||
|
chatCacheLocker.RLock()
|
||||||
|
defer chatCacheLocker.RUnlock()
|
||||||
|
//var c = context.Background()
|
||||||
|
sessionId, err := cache.RedisClient.Get(cr.GetUserSessionCacheKey(userId)).Result()
|
||||||
|
fmt.Println("GetUserSession-2")
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "redis: nil" {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
log.Fatal("获取用户会话失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("GetUserSession-3, sessionId:", sessionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------存储会话的聊天记录--------------------------------
|
||||||
|
func (cr ChatCache) GetChatRecordCacheKey(sessionId string) string {
|
||||||
|
return fmt.Sprintf("%s:%s", CacheChatRecordKey, sessionId)
|
||||||
|
}
|
||||||
|
func (cr ChatCache) AddChatRecord(sessionId string, data ...*accountFiee.ChatRecordData) (err error) {
|
||||||
|
////var c = context.Background()
|
||||||
|
messages := cr.GetChatRecord(sessionId)
|
||||||
|
fmt.Printf("AddChatRecord add data:%+v\n", data)
|
||||||
|
messages = append(messages, data...)
|
||||||
|
cacheBytes, _ := json.Marshal(messages)
|
||||||
|
fmt.Println("Marshal result", string(cacheBytes))
|
||||||
|
err = cache.RedisClient.Set(cr.GetChatRecordCacheKey(sessionId), cacheBytes, 2*time.Hour).Err()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr ChatCache) CoverChatRecord(sessionId string, data []*accountFiee.ChatRecordData) (err error) {
|
||||||
|
chatCacheLocker.Lock()
|
||||||
|
defer chatCacheLocker.Unlock()
|
||||||
|
//var c = context.Background()
|
||||||
|
cacheBytes, _ := json.Marshal(data)
|
||||||
|
err = cache.RedisClient.Set(cr.GetChatRecordCacheKey(sessionId), cacheBytes, 2*time.Hour).Err()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (cr ChatCache) GetChatRecord(sessionId string) (data []*accountFiee.ChatRecordData) {
|
||||||
|
chatCacheLocker.RLock()
|
||||||
|
defer chatCacheLocker.RUnlock()
|
||||||
|
data = make([]*accountFiee.ChatRecordData, 0)
|
||||||
|
//var c = context.Background()
|
||||||
|
messages, err := cache.RedisClient.Get(cr.GetChatRecordCacheKey(sessionId)).Bytes()
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "redis: nil" {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
//log.Fatal("获取聊天记录失败", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("cache data: %+v", string(messages))
|
||||||
|
if len(messages) > 0 {
|
||||||
|
_ = json.Unmarshal(messages, &data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------存储新消息统计--------------------------------
|
||||||
|
func (cr ChatCache) GetNewMsgStatCacheKey(ownerId int64) string {
|
||||||
|
return fmt.Sprintf("%s:%d", CacheNewMsgStatKey, ownerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息数量自增
|
||||||
|
func (cr ChatCache) IncreaseNewMessageTotal(ownerId int64, sessionId string) (err error) {
|
||||||
|
chatCacheLocker.Lock()
|
||||||
|
defer chatCacheLocker.Unlock()
|
||||||
|
ctx := context.Background()
|
||||||
|
data := cr.GetNewMessageStat(ctx, ownerId)
|
||||||
|
if len(data) > 0 {
|
||||||
|
foundIndex := -1
|
||||||
|
for i, v := range data {
|
||||||
|
if v.SessionId == sessionId {
|
||||||
|
foundIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundIndex > -1 {
|
||||||
|
data[foundIndex].Total += 1
|
||||||
|
}
|
||||||
|
//将foundIndex之后的所有元素右移动一位
|
||||||
|
if foundIndex > 0 {
|
||||||
|
elementToMove := data[foundIndex]
|
||||||
|
copy(data[1:], data[0:foundIndex])
|
||||||
|
data[0] = elementToMove
|
||||||
|
} else if foundIndex == -1 {
|
||||||
|
data = append([]UserMsgStatic{{SessionId: sessionId, Total: 1}}, data...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = []UserMsgStatic{{SessionId: sessionId, Total: 1}}
|
||||||
|
}
|
||||||
|
return cr.coverOwnerNewMessageStat(ctx, ownerId, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置新消息数量
|
||||||
|
func (cr ChatCache) ResetNewMessageTotal(ownerId int64, sessionId string, total ...int64) error {
|
||||||
|
chatCacheLocker.Lock()
|
||||||
|
defer chatCacheLocker.Unlock()
|
||||||
|
var tl int64
|
||||||
|
if len(total) > 0 {
|
||||||
|
tl = total[0]
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
data := cr.GetNewMessageStat(ctx, ownerId)
|
||||||
|
found := false
|
||||||
|
for i, v := range data {
|
||||||
|
if v.SessionId == sessionId {
|
||||||
|
found = true
|
||||||
|
data[i].Total = tl
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
data = append(data, UserMsgStatic{
|
||||||
|
SessionId: sessionId,
|
||||||
|
Total: tl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return cr.coverOwnerNewMessageStat(ctx, ownerId, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr ChatCache) RecountNewMessageTotal(ownerId int64) {
|
||||||
|
//var c = context.Background()
|
||||||
|
var keys []string
|
||||||
|
var err error
|
||||||
|
keys, err = cache.RedisClient.Keys(CacheChatRecordKey + "*").Result()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("获取聊天记录所有缓存KEY失败", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var countMap = make(map[string]int)
|
||||||
|
for _, key := range keys {
|
||||||
|
var messages []byte
|
||||||
|
var data []*accountFiee.ChatRecordData
|
||||||
|
messages, err = cache.RedisClient.Get(key).Bytes()
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "redis: nil" {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
log.Fatal("获取聊天记录失败", zap.Error(err))
|
||||||
|
data = make([]*accountFiee.ChatRecordData, 0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(messages) > 0 {
|
||||||
|
_ = json.Unmarshal(messages, &data)
|
||||||
|
}
|
||||||
|
var sessionId = strings.Split(key, ":")[1]
|
||||||
|
countMap[sessionId] = 0
|
||||||
|
for _, v := range data {
|
||||||
|
if v.WaiterRead == 2 { //统计未读消息数量
|
||||||
|
countMap[sessionId]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for sessionId, count := range countMap {
|
||||||
|
err = cr.ResetNewMessageTotal(ownerId, sessionId, int64(count))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("重置新消息数量统计",
|
||||||
|
zap.String("function", "RecountNewMessageTotal"),
|
||||||
|
zap.Int64("ownerId", ownerId),
|
||||||
|
zap.String("sessionId", sessionId),
|
||||||
|
zap.Int("count", count),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// erp获取最新的消息统计
|
||||||
|
func (cr ChatCache) GetNewMessageStat(ctx context.Context, ownerId int64) (result []UserMsgStatic) {
|
||||||
|
//chatCacheLocker.RLock()
|
||||||
|
//defer chatCacheLocker.RUnlock()
|
||||||
|
result = make([]UserMsgStatic, 0)
|
||||||
|
vals, err := cache.RedisClient.Get(cr.GetNewMsgStatCacheKey(ownerId)).Bytes()
|
||||||
|
if err != nil && errors.Is(err, redis.Nil) {
|
||||||
|
log.Fatal("从缓存获取新消息统计失败", zap.Error(err), zap.Int64("ownerId", ownerId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if vals != nil {
|
||||||
|
_ = json.Unmarshal(vals, &result)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 覆盖指定erp用户的新消息统计
|
||||||
|
func (cr ChatCache) coverOwnerNewMessageStat(ctx context.Context, ownerId int64, data []UserMsgStatic) (err error) {
|
||||||
|
value, _ := json.Marshal(data)
|
||||||
|
//err = cache.RedisClient.Set(ctx, cr.GetNewMsgStatCacheKey(ownerId), value, cr.newMessageStatExpireAfter).Err()
|
||||||
|
err = cache.RedisClient.Set(cr.GetNewMsgStatCacheKey(ownerId), value, 0).Err()
|
||||||
|
return
|
||||||
|
}
|
90
pkg/service/asChat/chatAutoReplyRulerHandler.go
Normal file
90
pkg/service/asChat/chatAutoReplyRulerHandler.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package asChat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fonchain-fiee/api/accountFiee"
|
||||||
|
"fonchain-fiee/pkg/service"
|
||||||
|
"fonchain-fiee/pkg/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Handler = &ChatAutoReplyRulerHandler{}
|
||||||
|
|
||||||
|
type ChatAutoReplyRulerHandler struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建自动回复规则
|
||||||
|
func (a *ChatAutoReplyRulerHandler) CreateChatAutoReplyRuler(c *gin.Context) {
|
||||||
|
var req accountFiee.ChatAutoReplyRulerData
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := service.AccountFieeProvider.CreateChatAutoReplyRuler(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除自动回复规则
|
||||||
|
func (a *ChatAutoReplyRulerHandler) DeleteChatAutoReplyRuler(c *gin.Context) {
|
||||||
|
var req accountFiee.DeleteChatAutoReplyRulerRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := service.AccountFieeProvider.DeleteChatAutoReplyRuler(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新自动回复规则
|
||||||
|
func (a *ChatAutoReplyRulerHandler) UpdateChatAutoReplyRuler(c *gin.Context) {
|
||||||
|
var req accountFiee.ChatAutoReplyRulerData
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := service.AccountFieeProvider.UpdateChatAutoReplyRuler(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用id查询自动回复规则
|
||||||
|
func (a *ChatAutoReplyRulerHandler) GetChatAutoReplyRulerDetail(c *gin.Context) {
|
||||||
|
var req accountFiee.GetChatAutoReplyRulerByIdRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := service.AccountFieeProvider.GetChatAutoReplyRulerDetail(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service.Success(c, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量查询自动回复规则
|
||||||
|
func (a *ChatAutoReplyRulerHandler) GetChatAutoReplyRulerList(c *gin.Context) {
|
||||||
|
var req GetChatAutoReplyRulerListRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var protoReq = accountFiee.GetChatAutoReplyRulerListRequest{Query: &accountFiee.ChatAutoReplyRulerData{}}
|
||||||
|
utils.RequestDataConvert(&req, &protoReq)
|
||||||
|
resp, err := service.AccountFieeProvider.GetChatAutoReplyRulerList(c, &protoReq)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service.Success(c, resp.List)
|
||||||
|
}
|
33
pkg/service/asChat/chatRoom.go
Normal file
33
pkg/service/asChat/chatRoom.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// package asChat -----------------------------
|
||||||
|
// @file : chatRoom.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2022/10/21 18:17:17
|
||||||
|
// -------------------------------------------
|
||||||
|
package asChat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fonchain-fiee/pkg/common/ws"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ChatRoom = ws.NewChatRoom()
|
||||||
|
)
|
||||||
|
|
||||||
|
type WsInfo struct {
|
||||||
|
Type ws.WsType `json:"type"` //消息类型
|
||||||
|
Content any `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func WsMessageRegisterCallback(clientId string, sessionId string) []byte {
|
||||||
|
var errMsg = WsInfo{
|
||||||
|
Type: ws.RegisterType,
|
||||||
|
Content: map[string]string{
|
||||||
|
//"clientId": clientId,
|
||||||
|
"sessionId": sessionId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(errMsg)
|
||||||
|
return byteMsg
|
||||||
|
}
|
136
pkg/service/asChat/dto.go
Normal file
136
pkg/service/asChat/dto.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Package asChat -----------------------------
|
||||||
|
// @file : dto.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2024/9/10 下午6:28
|
||||||
|
// -------------------------------------------
|
||||||
|
package asChat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fonchain-fiee/api/accountFiee"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
MsgType accountFiee.MsgType `json:"msgType"`
|
||||||
|
Text string `json:"text"` //文本内容
|
||||||
|
Media []MessageMedia `json:"media"`
|
||||||
|
LocalStamp int64 `json:"localStamp"`
|
||||||
|
}
|
||||||
|
type MessageMedia struct {
|
||||||
|
MediaId int64 `json:"mediaId"` //媒体文件id
|
||||||
|
MediaSize string `json:"mediaSize"` //媒体文件大小
|
||||||
|
Ext string `json:"ext"` //后缀格式
|
||||||
|
Url string `json:"url"` //文件地址
|
||||||
|
ConvText string `json:"convText"` //语音转文字内容,需要调用语音转文字接口后才会有值
|
||||||
|
Duration int64 `json:"duration"` //时长 单位:毫秒
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端发送消息请求,使用api发送消息
|
||||||
|
type NewMessageRequest struct {
|
||||||
|
Waiter bool `json:"waiter"` //是否是客服发送,客服没有userId
|
||||||
|
SessionId string `json:"sessionId"`
|
||||||
|
Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务端接收到消息后,使用websocket发送给userId关联的客户端,通知客户端有新消息,然后调用接口获取消息
|
||||||
|
type NewMessageNotice struct {
|
||||||
|
Name string `json:"name"` //名字
|
||||||
|
UserId int64 `json:"userId"` //用户id
|
||||||
|
SessionId string `json:"sessionId"`
|
||||||
|
MessageId int64 `json:"messageId"` //消息id
|
||||||
|
//NewMsgTotal int64 `json:"newMsgTotal"` //新消息数量
|
||||||
|
//Active bool `json:"active"` //是否在线
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取会话列表
|
||||||
|
type SessionType struct {
|
||||||
|
NewMessageNotice
|
||||||
|
RecentMessage []*Message `json:"recentMessage"` //最近消息
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageListType struct {
|
||||||
|
ID int64 `json:"ID"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Message Message `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MessageListType) BuildMessage(data *accountFiee.ChatRecordData) {
|
||||||
|
m.ID = data.ID
|
||||||
|
m.CreatedAt = data.CreatedAt
|
||||||
|
m.UserId = data.UserId
|
||||||
|
m.Name = data.Name
|
||||||
|
switch data.MsgType {
|
||||||
|
case accountFiee.MsgType_TextMsgType:
|
||||||
|
m.Message = Message{
|
||||||
|
MsgType: accountFiee.MsgType_TextMsgType,
|
||||||
|
Text: data.Content,
|
||||||
|
Media: []MessageMedia{},
|
||||||
|
LocalStamp: data.LocalStamp,
|
||||||
|
}
|
||||||
|
case accountFiee.MsgType_ImageMsgType, accountFiee.MsgType_AudioMsgType, accountFiee.MsgType_VideoMsgType:
|
||||||
|
m.Message.MsgType = data.MsgType
|
||||||
|
m.Message.Text = data.Content
|
||||||
|
m.Message.LocalStamp = data.LocalStamp
|
||||||
|
if data.Medias != nil {
|
||||||
|
for _, media := range data.Medias {
|
||||||
|
m.Message.Media = append(m.Message.Media, MessageMedia{
|
||||||
|
MediaId: media.ID,
|
||||||
|
MediaSize: media.Size,
|
||||||
|
Ext: media.Ext,
|
||||||
|
Url: media.Url,
|
||||||
|
ConvText: media.ConvText,
|
||||||
|
Duration: media.Duration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (m *MessageListType) ToJson() string {
|
||||||
|
jsonBytes, _ := json.Marshal(m)
|
||||||
|
return string(jsonBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserMsgStatic struct {
|
||||||
|
UserId int64 `json:"userId"` //用户id
|
||||||
|
Name string `json:"name"` //名称
|
||||||
|
ArtistUid string `json:"artistUid,omitempty"`
|
||||||
|
SessionId string `json:"sessionId"` //会话id
|
||||||
|
Total int64 `json:"total"` //新消息数量
|
||||||
|
//NewMessageTime string `json:"newMessageTime"` //最新消息的创建时间
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageListRequest struct {
|
||||||
|
SessionId string `json:"sessionId"` //不传则获取自己的会话消息里列表
|
||||||
|
CurrentId int64 `json:"currentId"` //组合查询条件1:基于某个消息id,向前或向后查找。两种组合条件不能同时使用
|
||||||
|
Direction int `json:"direction"` //组合查询条件1:方向 1=向前查找 2=向后查找
|
||||||
|
Recent bool `json:"recent"` //组合查询条件2:查找最新的若干条消息。两种组合条件不能同时使用
|
||||||
|
InHour time.Duration `json:"inHour"` //组合查询条件2:可选,查询指定小时内的数据
|
||||||
|
PageSize int64 `json:"pageSize"` //查找数量
|
||||||
|
}
|
||||||
|
|
||||||
|
type VoiceToTextRequest struct {
|
||||||
|
MediaId int64 `json:"mediaId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArtistInfoRequest struct {
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
}
|
||||||
|
type ArtistInfo struct {
|
||||||
|
Tnum string `json:"tnum"`
|
||||||
|
ArtistName string `json:"artistName"`
|
||||||
|
Age int64 `json:"age"`
|
||||||
|
Sex string `json:"sex"`
|
||||||
|
NativePlace string `json:"nativePlace"`
|
||||||
|
TelNum string `json:"telNum"`
|
||||||
|
RecentPhoto string `json:"recentPhoto"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetChatAutoReplyRulerListRequest struct {
|
||||||
|
Page int64 `json:"page"`
|
||||||
|
PageSize int64 `json:"pageSize"`
|
||||||
|
accountFiee.ChatAutoReplyRulerData
|
||||||
|
}
|
566
pkg/service/asChat/handler.go
Normal file
566
pkg/service/asChat/handler.go
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
// package asChat -----------------------------
|
||||||
|
// @file : handler.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2022/10/23 11:13:43
|
||||||
|
// -------------------------------------------
|
||||||
|
package asChat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"fonchain-fiee/api/accountFiee"
|
||||||
|
"fonchain-fiee/pkg/common/jwt"
|
||||||
|
"fonchain-fiee/pkg/common/ws"
|
||||||
|
"fonchain-fiee/pkg/e"
|
||||||
|
"fonchain-fiee/pkg/service"
|
||||||
|
"fonchain-fiee/pkg/service/upload"
|
||||||
|
"fonchain-fiee/pkg/utils"
|
||||||
|
"fonchain-fiee/pkg/utils/stime"
|
||||||
|
"github.com/fonchain/utils/voice"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ChatHandlerIns = ChatHandler{
|
||||||
|
cache: ChatCache{newMessageStatExpireAfter: 10 * time.Minute},
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChatHandler struct {
|
||||||
|
cache ChatCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr ChatHandler) Connection(c *gin.Context) {
|
||||||
|
conn, err := ws.UpGrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
conn.SetReadDeadline(time.Now().Add(time.Second * 10))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("无法升级为websocket连接", zap.Error(err))
|
||||||
|
c.String(500, "无法转为websocket连接")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, byteData, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
_ = conn.WriteMessage(websocket.TextMessage, ws.WsErrorConnection("null", err.Error(), "conn.ReadMessag1"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("22222222222222,AuthorizationVerify")
|
||||||
|
var ok bool
|
||||||
|
var userInfo *accountFiee.ChatUserData
|
||||||
|
userInfo, ok, err = ws.AuthorizationVerify(byteData)
|
||||||
|
if err != nil {
|
||||||
|
_ = conn.WriteMessage(websocket.TextMessage, ws.WsErrorConnection("null", err.Error(), "AuthorizationVerify2"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
_ = conn.WriteMessage(websocket.TextMessage, ws.WsErrorConnection("null", "登录状态失效", "AuthorizationVerify2.1"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("33333333333333,RecountNewMessageTotal")
|
||||||
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
go cr.cache.RecountNewMessageTotal(userInfo.ID)
|
||||||
|
|
||||||
|
fmt.Println("44444444444444,ws.NewClient")
|
||||||
|
//注册ws客户端,并发送clientId给ws客户端
|
||||||
|
var cli = ws.NewClient(userInfo.ID, "", conn, ChatRoom)
|
||||||
|
cli.Waiter = userInfo.Role == 2
|
||||||
|
fmt.Println("55555555555555,GetUserSession")
|
||||||
|
//查询是否有历史的sessionId
|
||||||
|
cli.SessionId = cr.cache.GetUserSession(userInfo.ID)
|
||||||
|
ChatRoom.Register(cli)
|
||||||
|
cr.cache.SaveUserSession(userInfo.ID, cli.SessionId)
|
||||||
|
fmt.Println("66666666666666666666666666")
|
||||||
|
go cli.WriteWait()
|
||||||
|
cli.Send <- WsMessageRegisterCallback(cli.ClientId, cli.SessionId)
|
||||||
|
fmt.Println("777777777777777777777777")
|
||||||
|
// 处理websocket连接的逻辑
|
||||||
|
ctx, _ := context.WithCancel(context.Background())
|
||||||
|
cli.Reading(ctx, HandleMessage)
|
||||||
|
fmt.Println("88888888888888888888888888")
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr ChatHandler) NewMessage(c *gin.Context) {
|
||||||
|
var request NewMessageRequest
|
||||||
|
if err := c.ShouldBindJSON(&request); err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if request.SessionId == "" {
|
||||||
|
service.Error(c, errors.New("sessionId不能为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if request.MsgType == 0 {
|
||||||
|
service.Error(c, errors.New("msgType不能为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("NewMessage 1111111111111111111111111111111")
|
||||||
|
//获取用户信息
|
||||||
|
chatUser, code := jwt.ParseToChatUser(c)
|
||||||
|
if code != 0 {
|
||||||
|
service.ErrWithCode(c, code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("NewMessage 22222222222222222222222222222222222")
|
||||||
|
//存储入库
|
||||||
|
if chatUser.NickName != "" {
|
||||||
|
chatUser.NickName = fmt.Sprintf("未知用户(%d)", chatUser.ID)
|
||||||
|
}
|
||||||
|
fmt.Println("NewMessage 3333333333333333333333333333333333")
|
||||||
|
var data = accountFiee.ChatRecordData{
|
||||||
|
SessionId: request.SessionId,
|
||||||
|
UserId: chatUser.ID,
|
||||||
|
Name: chatUser.NickName,
|
||||||
|
Avatar: "",
|
||||||
|
MsgType: request.MsgType,
|
||||||
|
Content: request.Message.Text,
|
||||||
|
LocalStamp: request.LocalStamp,
|
||||||
|
Medias: nil,
|
||||||
|
}
|
||||||
|
if len(request.Message.Media) > 0 {
|
||||||
|
for _, media := range request.Message.Media {
|
||||||
|
data.Medias = append(data.Medias, &accountFiee.ChatMediaData{
|
||||||
|
ID: media.MediaId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("NewMessage 4444444444444444444444444444444444")
|
||||||
|
resp, err := service.AccountFieeProvider.CreateChatRecord(c, &data)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, errors.New("创建失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("CreateChatRecord resp:%+v\n", resp)
|
||||||
|
//录入缓存
|
||||||
|
err = cr.cache.AddChatRecord(request.SessionId, resp.Data)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, errors.New("创建失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("NewMessage 5 消息数量+1")
|
||||||
|
//新消息数量统计+1
|
||||||
|
noticeUserId := ChatRoom.GetUserIdInSession(request.SessionId, chatUser.ID)
|
||||||
|
fmt.Println("NewMessage 5.1 消息数量配置结束")
|
||||||
|
fmt.Printf("noticeUserId %+v\n", noticeUserId)
|
||||||
|
for _, userId := range noticeUserId {
|
||||||
|
fmt.Println("userId")
|
||||||
|
cr.cache.IncreaseNewMessageTotal(userId, request.SessionId)
|
||||||
|
}
|
||||||
|
fmt.Println("NewMessage 6")
|
||||||
|
//发送websocket消息提醒通知
|
||||||
|
var notice = MessageListType{}
|
||||||
|
notice.BuildMessage(resp.Data)
|
||||||
|
fmt.Printf("ws消息提醒:%+v\n", notice)
|
||||||
|
_, err = ChatRoom.SendSessionMessage(chatUser.ID, request.SessionId, ws.NewChatMsgType, notice)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("发送新消息通知失败", zap.Error(err), zap.Any("notice", notice))
|
||||||
|
}
|
||||||
|
fmt.Println("NewMessage 7 -end")
|
||||||
|
//发送app推送(无横幅推送)
|
||||||
|
//go func() {
|
||||||
|
// omitMessage := ""
|
||||||
|
// switch request.MsgType {
|
||||||
|
// case accountFiee.MsgType_TextMsgType:
|
||||||
|
// runMsg := []rune(request.Text)
|
||||||
|
// if len(runMsg) > 15 {
|
||||||
|
// omitMessage = string(runMsg[:15]) + "..."
|
||||||
|
// } else {
|
||||||
|
// omitMessage = request.Text
|
||||||
|
// }
|
||||||
|
// case accountFiee.MsgType_ImageMsgType:
|
||||||
|
// omitMessage = "[图片]"
|
||||||
|
// case accountFiee.MsgType_AudioMsgType:
|
||||||
|
// omitMessage = "[音频]"
|
||||||
|
// case accountFiee.MsgType_VideoMsgType:
|
||||||
|
// omitMessage = "[视频]"
|
||||||
|
// default:
|
||||||
|
// omitMessage = "新消息请查收"
|
||||||
|
// }
|
||||||
|
// for _, userId := range noticeUserId {
|
||||||
|
// _ = asPusher.NewArtistinfoUniPush().NewChatMessageNotice(userId, omitMessage)
|
||||||
|
// }
|
||||||
|
//}()
|
||||||
|
service.Success(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr ChatHandler) MessageList(c *gin.Context) {
|
||||||
|
var request MessageListRequest
|
||||||
|
if err := c.ShouldBindJSON(&request); err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
domain := c.GetHeader("domain")
|
||||||
|
if (request.Direction == 0 && request.Recent == false) || (request.Direction > 0 && request.Recent == true) {
|
||||||
|
service.Error(c, errors.New("组合条件校验失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if request.SessionId == "" {
|
||||||
|
service.Error(c, errors.New("sessionId不能为空"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if request.PageSize < -1 {
|
||||||
|
service.Error(c, errors.New("pageSize校验错误"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var resp = make([]*MessageListType, 0)
|
||||||
|
if request.CurrentId == 0 && request.Direction == 1 {
|
||||||
|
service.Success(c, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chatUser, code := jwt.ParseToChatUser(c)
|
||||||
|
if code != 0 {
|
||||||
|
service.ErrWithCode(c, code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//if request.SessionId == "" {
|
||||||
|
// request.SessionId = cr.cache.GetUserSession(tokenResult.UserInfo.ID)
|
||||||
|
// if request.SessionId == "" {
|
||||||
|
// service.Success(c, resp)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//messages := cr.cache.GetChatRecord(request.SessionId)
|
||||||
|
messages := []*accountFiee.ChatRecordData{}
|
||||||
|
var returnDataIdList = make([]int64, 0)
|
||||||
|
defer func() {
|
||||||
|
//获取最新数据时,重置新消息数量统计
|
||||||
|
if request.Direction == 2 || request.Recent {
|
||||||
|
cr.cache.ResetNewMessageTotal(chatUser.ID, request.SessionId)
|
||||||
|
}
|
||||||
|
//设置消息已被客服阅读,当客服重新通过通过websocket连接时,这些消息将不被纳入新消息数量统计
|
||||||
|
if len(returnDataIdList) > 0 && domain == "fontree" {
|
||||||
|
for _, hasReadId := range returnDataIdList {
|
||||||
|
for i, message := range messages {
|
||||||
|
if message.ID == hasReadId {
|
||||||
|
messages[i].WaiterRead = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := cr.cache.CoverChatRecord(request.SessionId, messages)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("设置消息已读失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
for _, v := range messages {
|
||||||
|
_, err = service.AccountFieeProvider.SaveChatRecord(context.Background(), v)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("设置消息已读失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if len(messages) == 0 {
|
||||||
|
//从数据库获取
|
||||||
|
recordResp, err := service.AccountFieeProvider.GetChatRecordList(c, &accountFiee.GetChatRecordListRequest{
|
||||||
|
Query: &accountFiee.ChatRecordData{SessionId: request.SessionId},
|
||||||
|
Page: 1,
|
||||||
|
PageSize: -1,
|
||||||
|
//Where: fmt.Sprintf("id %s %d", utils.IfGec(request.Direction == 1, "<", ">"), request.CurrentId),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
messages = recordResp.List
|
||||||
|
err = cr.cache.CoverChatRecord(request.SessionId, messages)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("覆盖聊天记录失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if request.Recent {
|
||||||
|
if int64(len(messages)) >= request.PageSize {
|
||||||
|
messages = messages[len(messages)-int(request.PageSize):]
|
||||||
|
}
|
||||||
|
var now = time.Now()
|
||||||
|
for _, message := range messages {
|
||||||
|
if request.InHour > 0 {
|
||||||
|
messageCreatedAt, _ := stime.StringToTime(message.CreatedAt)
|
||||||
|
if now.Sub(*messageCreatedAt) >= request.InHour*time.Hour {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returnDataIdList = append(returnDataIdList, message.ID)
|
||||||
|
var msg = &MessageListType{}
|
||||||
|
msg.BuildMessage(message)
|
||||||
|
resp = append(resp, msg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sort.Slice(messages, func(i, j int) bool {
|
||||||
|
if request.Direction == 1 {
|
||||||
|
return messages[i].ID < messages[j].ID
|
||||||
|
} else {
|
||||||
|
return messages[i].ID > messages[j].ID
|
||||||
|
}
|
||||||
|
})
|
||||||
|
fmt.Printf("data is %+v\n", messages)
|
||||||
|
total := 0
|
||||||
|
for i, message := range messages {
|
||||||
|
switch request.Direction {
|
||||||
|
case 1: //向下查找,找比CurrentId大的数据
|
||||||
|
if message.ID <= request.CurrentId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case 2: //向上查找,找比CurrentId小的数据
|
||||||
|
if message.ID >= request.CurrentId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message := message
|
||||||
|
fmt.Println(i, message.ID)
|
||||||
|
if request.PageSize != -1 && int64(total+1) > request.PageSize {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total++
|
||||||
|
returnDataIdList = append(returnDataIdList, message.ID)
|
||||||
|
var msg = &MessageListType{}
|
||||||
|
msg.BuildMessage(message)
|
||||||
|
resp = append(resp, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//二次排序
|
||||||
|
sort.Slice(resp, func(i, j int) bool {
|
||||||
|
return resp[i].ID < resp[j].ID
|
||||||
|
})
|
||||||
|
//优化空列表
|
||||||
|
for i, v := range resp {
|
||||||
|
if v.Message.Media == nil {
|
||||||
|
resp[i].Message.Media = []MessageMedia{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
service.Success(c, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr ChatHandler) Upload(c *gin.Context) {
|
||||||
|
fmt.Println("111111111111")
|
||||||
|
//获取用户信息
|
||||||
|
chatUser, code := jwt.ParseToChatUser(c)
|
||||||
|
if code != 0 {
|
||||||
|
service.ErrWithCode(c, code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//获取文件对象
|
||||||
|
file, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("ERROR: upload file failed. ", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
duration := c.PostForm("duration")
|
||||||
|
fmt.Println(duration)
|
||||||
|
ext := c.PostForm("ext")
|
||||||
|
fileExt := strings.ToLower(path.Ext(file.Filename))
|
||||||
|
if ext != "" {
|
||||||
|
fileExt = ext
|
||||||
|
}
|
||||||
|
fileType := e.DetectFileTypeByExtension(fileExt)
|
||||||
|
if fileType == e.Audio {
|
||||||
|
if !slices.Contains([]string{".mp4", ".aac", ".mp3", ".opus", ".wav"}, fileExt) {
|
||||||
|
service.Error(c, errors.New("不支持的格式"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//计算md5
|
||||||
|
tmp, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, errors.New("上传失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileContent, err := io.ReadAll(tmp)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, errors.New("文件读取失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash := md5.New()
|
||||||
|
_, err = hash.Write(fileContent)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, errors.New("文件读取失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
md5Bytes := hash.Sum(nil) // 获取 MD5 字节切片
|
||||||
|
md5String := hex.EncodeToString(md5Bytes) // 转换为十六进制字符串表示
|
||||||
|
//检查文件是否存在
|
||||||
|
checkResp, err := service.AccountFieeProvider.GetChatMediaList(c, &accountFiee.GetChatMediaListRequest{Query: &accountFiee.ChatMediaData{Md5: md5String}, Page: 1, PageSize: 1})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("md5查询附件失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
if checkResp.Total > 0 {
|
||||||
|
service.Success(c, checkResp.List[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//文件不存在则上传文件
|
||||||
|
filename, _ := uuid.NewV4()
|
||||||
|
defer tmp.Close()
|
||||||
|
fileBuffer := bytes.NewBuffer(fileContent)
|
||||||
|
var bosUrl string
|
||||||
|
bosUrl, err = upload.UploadWithBuffer(fileBuffer, fmt.Sprintf("%d/%v%v", chatUser.ID, filename, fileExt))
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//存到数据库
|
||||||
|
var durationInt64, _ = strconv.ParseInt(duration, 10, 64)
|
||||||
|
var mediaData = accountFiee.ChatMediaData{
|
||||||
|
Url: bosUrl,
|
||||||
|
Md5: md5String,
|
||||||
|
Size: fmt.Sprintf("%d", file.Size),
|
||||||
|
Ext: fileExt,
|
||||||
|
Duration: durationInt64,
|
||||||
|
}
|
||||||
|
resp, err := service.AccountFieeProvider.CreateChatMedia(c, &mediaData)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service.Success(c, resp.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr ChatHandler) UserMessageStat(c *gin.Context) {
|
||||||
|
//获取用户信息
|
||||||
|
chatUser, code := jwt.ParseToChatUser(c)
|
||||||
|
if code != 0 {
|
||||||
|
service.ErrWithCode(c, code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result := cr.cache.GetNewMessageStat(c, chatUser.ID)
|
||||||
|
if len(result) == 0 {
|
||||||
|
service.Success(c, result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("cache stat:%+v\n", result)
|
||||||
|
//获取实名信息
|
||||||
|
var protoReq = accountFiee.GetChatUserListRequest2{
|
||||||
|
Page: 1,
|
||||||
|
PageSize: int64(len(result)),
|
||||||
|
}
|
||||||
|
for i, item := range result {
|
||||||
|
if item.UserId == 0 {
|
||||||
|
sessionId, _ := strconv.Atoi(item.SessionId)
|
||||||
|
item.UserId = int64(sessionId)
|
||||||
|
result[i].UserId = int64(sessionId)
|
||||||
|
}
|
||||||
|
protoReq.UserIdIn = append(protoReq.UserIdIn, item.UserId)
|
||||||
|
}
|
||||||
|
fmt.Printf("protoReq.UserIdIn:%+v\n", protoReq.UserIdIn)
|
||||||
|
listRes, err := service.AccountFieeProvider.GetChatUserList2(c, &protoReq)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("GetChatUserList:%+v\n", listRes)
|
||||||
|
for i, item := range result {
|
||||||
|
for _, user := range listRes.List {
|
||||||
|
if item.UserId == user.UserId {
|
||||||
|
user := user
|
||||||
|
result[i].Name = user.Name
|
||||||
|
//result[i].ArtistUid = user.ArtistUid
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result[i].Name == "" {
|
||||||
|
result[i].Name = beautifulZeroName(result[i].Name, result[i].UserId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reverse(result)
|
||||||
|
service.Success(c, result)
|
||||||
|
}
|
||||||
|
func reverse(slice []UserMsgStatic) {
|
||||||
|
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
slice[i], slice[j] = slice[j], slice[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (cr ChatHandler) VoiceToText(c *gin.Context) {
|
||||||
|
var req VoiceToTextRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
detail, err := service.AccountFieeProvider.GetChatMediaDetail(c, &accountFiee.GetChatMediaByIdRequest{Id: req.MediaId})
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if detail.ConvText != "" {
|
||||||
|
service.Success(c, map[string]string{"convText": detail.ConvText})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
voiceApi := voice.NewVoiceApi()
|
||||||
|
detail.ConvText, err = voiceApi.ToTextFromUrl(detail.Url)
|
||||||
|
if err != nil {
|
||||||
|
service.Error(c, errors.New("语音转文字失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
service.AccountFieeProvider.UpdateChatMedia(context.Background(), detail)
|
||||||
|
}()
|
||||||
|
service.Success(c, map[string]string{"convText": detail.ConvText})
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (cr ChatHandler) ArtistDetail(c *gin.Context) {
|
||||||
|
// var req ArtistInfoRequest
|
||||||
|
// if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
// service.Error(c, err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// if req.UserId == 0 {
|
||||||
|
// service.Success(c, ArtistInfo{})
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// detail, err := service.GrpcArtistInfoUserImpl.FindUsersUserView(c, &artistInfoUser.FindUsersRequest{UserId: req.UserId})
|
||||||
|
// if err != nil {
|
||||||
|
// service.Error(c, err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// var (
|
||||||
|
// tnum string
|
||||||
|
// artistName string
|
||||||
|
// age int64
|
||||||
|
// sex string
|
||||||
|
// nativePlace string
|
||||||
|
// telNum string
|
||||||
|
// recentPhoto string
|
||||||
|
// )
|
||||||
|
// if len(detail.Data) > 0 {
|
||||||
|
// tnum = detail.Data[0].Tnum
|
||||||
|
// artistName = beautifulZeroName(detail.Data[0].RealName, req.UserId)
|
||||||
|
// age = detail.Data[0].Age
|
||||||
|
// sex = detail.Data[0].Sex
|
||||||
|
// nativePlace = detail.Data[0].NativePlace
|
||||||
|
// telNum = detail.Data[0].TelNum
|
||||||
|
// recentPhoto = detail.Data[0].Photo
|
||||||
|
// }
|
||||||
|
// resp := ArtistInfo{
|
||||||
|
// Tnum: tnum,
|
||||||
|
// ArtistName: artistName,
|
||||||
|
// Age: age,
|
||||||
|
// Sex: sex,
|
||||||
|
// NativePlace: nativePlace,
|
||||||
|
// TelNum: telNum,
|
||||||
|
// RecentPhoto: recentPhoto,
|
||||||
|
// }
|
||||||
|
// service.Success(c, resp)
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 对没有名字的name进行优化
|
||||||
|
func beautifulZeroName(name string, userId int64) string {
|
||||||
|
return utils.IfGec(name == "", fmt.Sprintf("未实名用户:%d", userId), name)
|
||||||
|
}
|
26
pkg/service/asChat/readme.md
Normal file
26
pkg/service/asChat/readme.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# asChat 客服聊天
|
||||||
|
|
||||||
|
## 聊天室主要流程与功能描述
|
||||||
|
1. 用户通过画家包登录
|
||||||
|
2. 打开客服页面。 画家宝客户端自动进行websocket连接,后台会自动创建一个默认聊天室(聊天室携带一个SessionId)
|
||||||
|
3. 用户调用api接口,发送消息。 服务端接收到消息后,会通过websocket通知聊天室里面所有用户。
|
||||||
|
4. erp首次打开客服菜单时,会进行websocket连接,并调用一次api接口刷新消息列表。后续通过websocket接收消息推送,收到消息时,应主动调用一次消息列表刷新接口。
|
||||||
|
5. erp客服端发送消息时,加入到此聊天室。
|
||||||
|
6. 用户端调用api接口获取新消息列表。
|
||||||
|
|
||||||
|
## 客户端应具备的其它功能
|
||||||
|
1. weboscket断开自动重连
|
||||||
|
2. 当通过websocket接收到错误类型的消息,应具备对应的错误处理机制<p>
|
||||||
|
错误消息示例
|
||||||
|
```json
|
||||||
|
{"type":1,"content":"Connection error:登录状态失效","from":"0","to":"null"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 服务端应具备的功能
|
||||||
|
1. 通过redis缓存聊天消息
|
||||||
|
2. 通过redis缓存用户的sessionId避免ws断开后,找不到之前的sessionId
|
||||||
|
3. 客服端由于不是画家宝用户,没有userId。在websocket连接时,如果找不到userId,应该为其在画家宝创建一个账号。且经纪人不可见。
|
||||||
|
4. 由于没有创建聊天室的需求,所以每个用户使用一个聊天室即可。客服与之对话时,就自动加入用户端的聊天室
|
||||||
|
5. 新消息统计
|
||||||
|
- 当发送消息时,该聊天室中除了发信者以外,其它用户的新消息数都+1,录入缓存。
|
||||||
|
- 当新客服人员加入时,没有新消息统计的缓存。~~他的新消息数量应该从创建时间开始计算~~,所以都是0。
|
46
pkg/service/asChat/service.go
Normal file
46
pkg/service/asChat/service.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Package asChat -----------------------------
|
||||||
|
// @file : service.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2024/9/10 下午7:05
|
||||||
|
// -------------------------------------------
|
||||||
|
package asChat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fonchain-fiee/pkg/common/ws"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleMessage(sourceData []byte, cli *ws.Client) {
|
||||||
|
var msg map[string]any
|
||||||
|
err := json.Unmarshal(sourceData, &msg)
|
||||||
|
if err != nil {
|
||||||
|
cli.Send <- ws.WsErrorInvalidDataFormat(cli.ClientId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msg["type"] {
|
||||||
|
default:
|
||||||
|
cli.Send <- ws.WsErrorUnknownMessageType(cli.ClientId)
|
||||||
|
case ws.TestType:
|
||||||
|
var newMsg = ws.WsInfo{
|
||||||
|
Type: ws.TestType,
|
||||||
|
Content: msg["content"],
|
||||||
|
}
|
||||||
|
byteMsg, _ := json.Marshal(newMsg)
|
||||||
|
cli.Send <- byteMsg
|
||||||
|
//case ws.ChatType:
|
||||||
|
// var chatInfo ChatInfo
|
||||||
|
// _ = json.Unmarshal(sourceData, &chatInfo)
|
||||||
|
// //解析Content
|
||||||
|
// if clients, ok := cli.Room.clients[chatInfo.Content.TargetUserId]; ok {
|
||||||
|
// for _, targetObj := range clients {
|
||||||
|
// if targetObj != nil {
|
||||||
|
// targetObj.Send <- WsChatMessage(msg.From, chatInfo.Content.TargetClientId, chatInfo.Content.Msg)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// //对方不在线
|
||||||
|
// cli.Send <- WsErrorMessage(ChatType, msg.From, e.ErrTargetOutLine, nil)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
@ -189,3 +189,17 @@ func translateErrorMessage(c *gin.Context, message string) string {
|
|||||||
return common.EnMessages[message]
|
return common.EnMessages[message]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrWithCode(c *gin.Context, code e.ErrorCodeType, newMsg ...string) {
|
||||||
|
msg := e.GetCodeMsg(code)
|
||||||
|
if newMsg != nil {
|
||||||
|
msg = newMsg[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(e.Success, serializer.Response{
|
||||||
|
Code: code,
|
||||||
|
Status: 1,
|
||||||
|
Msg: msg,
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -329,3 +329,18 @@ func GetSnapshot(videoPath, snapshotPath string, frameNum int) (snapshotName str
|
|||||||
snapshotName = names[len(names)-1] + "." + PngType
|
snapshotName = names[len(names)-1] + "." + PngType
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UploadWithBuffer(fileBuffer *bytes.Buffer, cloudStoreSubPath string) (url string, err error) {
|
||||||
|
Client, err := objstorage.NewOSS(config.ConfigData.Oss.AccessKeyId, config.ConfigData.Oss.AccessKeySecret, config.ConfigData.Oss.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.New(fmt.Sprintf("云存储初始化失败:%s", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cloudStoreSubPath = getEnvDir(cloudStoreSubPath)
|
||||||
|
_, err = Client.PutObjectFromBytes(config.ConfigData.Oss.BucketName, cloudStoreSubPath, fileBuffer.Bytes())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func getEnvDir(cloudStoreSubPath string) (ep string) {
|
||||||
|
ep, _ = url.JoinPath("fiee", cloudStoreSubPath)
|
||||||
|
return ep
|
||||||
|
}
|
||||||
|
47
pkg/utils/if.go
Normal file
47
pkg/utils/if.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* @FileName: if.go
|
||||||
|
* @Author: JJXu
|
||||||
|
* @CreateTime: 2022/3/31 下午10:34
|
||||||
|
* @Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func If(condition bool, trueVal, falseVal interface{}) interface{} {
|
||||||
|
if condition {
|
||||||
|
return trueVal
|
||||||
|
}
|
||||||
|
return falseVal
|
||||||
|
}
|
||||||
|
func IfGec[T ~string | ~int | ~int32 | ~int64 | ~bool | ~float32 | ~float64](condition bool, trueVal, falseVal T) T {
|
||||||
|
if condition {
|
||||||
|
return trueVal
|
||||||
|
}
|
||||||
|
return falseVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValueInList 值是否在列表中
|
||||||
|
// value:查询的值
|
||||||
|
// list: 列表
|
||||||
|
// disableStrictCase: 禁用严格大小写检查。默认是严格大小写
|
||||||
|
func IsValueInList(value string, list []string, disableStrictCase ...bool) bool {
|
||||||
|
var disStrictCase bool
|
||||||
|
if disableStrictCase != nil {
|
||||||
|
disStrictCase = disableStrictCase[0]
|
||||||
|
}
|
||||||
|
for _, v := range list {
|
||||||
|
var listValue string
|
||||||
|
if disStrictCase {
|
||||||
|
listValue = strings.ToLower(v)
|
||||||
|
value = strings.ToLower(v)
|
||||||
|
} else {
|
||||||
|
listValue = v
|
||||||
|
}
|
||||||
|
if listValue == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
51
pkg/utils/requestDataToProto.go
Normal file
51
pkg/utils/requestDataToProto.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Package utils -----------------------------
|
||||||
|
// @file : requestDataToProto.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2023/8/28 17:57
|
||||||
|
// -------------------------------------------
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// http请求转proto请求
|
||||||
|
func RequestDataConvert(from interface{}, to interface{}) {
|
||||||
|
var proxyField = "Query"
|
||||||
|
fromValue := reflect.ValueOf(from)
|
||||||
|
toValue := reflect.ValueOf(to)
|
||||||
|
toType := reflect.TypeOf(to)
|
||||||
|
|
||||||
|
// 获取From结构体的字段信息
|
||||||
|
fromType := fromValue.Type().Elem()
|
||||||
|
for i := 0; i < fromType.NumField(); i++ {
|
||||||
|
// 获取字段名和字段值
|
||||||
|
fieldName := fromType.Field(i).Name
|
||||||
|
fieldValue := fromValue.Elem().FieldByName(fieldName)
|
||||||
|
if fieldName != proxyField {
|
||||||
|
_, exists := toType.Elem().FieldByName(fieldName)
|
||||||
|
if exists {
|
||||||
|
// 设置To结构体中相应字段的值
|
||||||
|
toValue.Elem().FieldByName(fieldName).Set(fieldValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryField, exists := toType.Elem().FieldByName(proxyField)
|
||||||
|
if exists {
|
||||||
|
var queryFieldTypeName string
|
||||||
|
// 指针类型额外处理,拿到真实的数据类型
|
||||||
|
if queryField.Type.Kind() == reflect.Ptr {
|
||||||
|
queryFieldTypeName = queryField.Type.Elem().String()
|
||||||
|
} else {
|
||||||
|
queryFieldTypeName = queryField.Type.Kind().String()
|
||||||
|
}
|
||||||
|
//处理拿到的结构体类型如 utils.xxxx的类型,去掉utils.这部分
|
||||||
|
if strings.Contains(queryFieldTypeName, ".") {
|
||||||
|
queryFieldTypeName = strings.Split(queryFieldTypeName, ".")[1]
|
||||||
|
}
|
||||||
|
fromQueryValue := fromValue.Elem().FieldByName(queryFieldTypeName)
|
||||||
|
toValue.Elem().FieldByName(proxyField).Set(fromQueryValue.Addr())
|
||||||
|
}
|
||||||
|
}
|
90
pkg/utils/stime/common.go
Normal file
90
pkg/utils/stime/common.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Package stime -----------------------------
|
||||||
|
// @file : common.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2022/10/21 00:19:04
|
||||||
|
// -------------------------------------------
|
||||||
|
package stime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Loc loc
|
||||||
|
|
||||||
|
type loc time.Location
|
||||||
|
|
||||||
|
func (l loc) Shanghai() *time.Location {
|
||||||
|
var shanghai, err = time.LoadLocation("Asia/Shanghai")
|
||||||
|
if err != nil {
|
||||||
|
shanghai = time.FixedZone("CST", 8*3600)
|
||||||
|
}
|
||||||
|
return shanghai
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
//常规时间格式(日期带横杠)
|
||||||
|
Format_Normal_YMDhms = "2006-01-02 15:04:05"
|
||||||
|
Format_Normal_YMDh = "2006-01-02 15:04"
|
||||||
|
Format_Normal_YMD = "2006-01-02"
|
||||||
|
Format_Normal_hms = "15:04:05"
|
||||||
|
Format_Normal_hm = "15:04"
|
||||||
|
Format_Normal_YM = "2006-01"
|
||||||
|
Format_Dot_YMD = "2006.01.02"
|
||||||
|
//带斜杠的时间格式
|
||||||
|
Format_Slash_YMDhms = "2006/01/02 15:04:05"
|
||||||
|
Format_Slash_YMD = "2006/01/02"
|
||||||
|
//无间隔符
|
||||||
|
Format_NoSpacer_YMDhms = "20060102150405"
|
||||||
|
Format_NoSpacer_YMD = "20060102"
|
||||||
|
Format_ChinaChar_YMD = "2006年01月02日"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MonthStrMap = map[string]string{
|
||||||
|
"January": "01",
|
||||||
|
"February": "02",
|
||||||
|
"March": "03",
|
||||||
|
"April": "04",
|
||||||
|
"May": "05",
|
||||||
|
"June": "06",
|
||||||
|
"July": "07",
|
||||||
|
"August": "08",
|
||||||
|
"September": "09",
|
||||||
|
"October": "10",
|
||||||
|
"November": "11",
|
||||||
|
"December": "12",
|
||||||
|
}
|
||||||
|
var MonthIntMap = map[string]int{
|
||||||
|
"January": 1,
|
||||||
|
"February": 2,
|
||||||
|
"March": 3,
|
||||||
|
"April": 4,
|
||||||
|
"May": 5,
|
||||||
|
"June": 6,
|
||||||
|
"July": 7,
|
||||||
|
"August": 8,
|
||||||
|
"September": 9,
|
||||||
|
"October": 10,
|
||||||
|
"November": 11,
|
||||||
|
"December": 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
var WeekIntMap = map[string]int{
|
||||||
|
"Monday": 1,
|
||||||
|
"Tuesday": 2,
|
||||||
|
"Wednesday": 3,
|
||||||
|
"Thursday": 4,
|
||||||
|
"Friday": 5,
|
||||||
|
"Saturday": 6,
|
||||||
|
"Sunday": 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
var WeekStrMap = map[string]string{
|
||||||
|
"Monday": "一",
|
||||||
|
"Tuesday": "二",
|
||||||
|
"Wednesday": "三",
|
||||||
|
"Thursday": "四",
|
||||||
|
"Friday": "五",
|
||||||
|
"Saturday": "六",
|
||||||
|
"Sunday": "日",
|
||||||
|
}
|
128
pkg/utils/stime/getTime.go
Normal file
128
pkg/utils/stime/getTime.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* @FileName: getTime.go
|
||||||
|
* @Author: JJXu
|
||||||
|
* @CreateTime: 2022/3/1 下午6:35
|
||||||
|
* @Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
package stime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StrNowDate() string {
|
||||||
|
return TimeToString(time.Now(), Format_Normal_YMD)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StrNowYearMonth() string {
|
||||||
|
return TimeToString(time.Now(), Format_Normal_YM)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ThisMorming 今天凌晨
|
||||||
|
func ThisMorming(format string) (strTime string) {
|
||||||
|
thisTime := time.Now()
|
||||||
|
year := thisTime.Year()
|
||||||
|
month := MonthStrMap[thisTime.Month().String()]
|
||||||
|
day := fmt.Sprintf("%02d", thisTime.Day())
|
||||||
|
strTime = fmt.Sprintf("%v-%v-%v 00:00:00", year, month, day)
|
||||||
|
if format != Format_Normal_YMDhms {
|
||||||
|
t1, _ := time.ParseInLocation(Format_Normal_YMDhms, strTime, Loc.Shanghai())
|
||||||
|
strTime = t1.Format(format)
|
||||||
|
}
|
||||||
|
return strTime
|
||||||
|
}
|
||||||
|
|
||||||
|
//ThisMorningUnix 获取当日凌晨的时间戳
|
||||||
|
func ThisMorningToUnix() int64 {
|
||||||
|
thist := time.Now()
|
||||||
|
zero_tm := time.Date(thist.Year(), thist.Month(), thist.Day(), 0, 0, 0, 0, thist.Location()).Unix()
|
||||||
|
return zero_tm
|
||||||
|
}
|
||||||
|
|
||||||
|
//TomorrowMorning 第二天凌晨
|
||||||
|
func TomorrowMorning(baseTime time.Time) *time.Time {
|
||||||
|
year := baseTime.Year()
|
||||||
|
month := MonthStrMap[baseTime.Month().String()]
|
||||||
|
day := fmt.Sprintf("%02d", baseTime.Day()+1)
|
||||||
|
strTime := fmt.Sprintf("%v-%v-%v 00:00:00", year, month, day)
|
||||||
|
res, _ := StringToTime(strTime)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
//ThisTimeUnix 获取当前时间的时间戳
|
||||||
|
func CurrentimeToUnix() int64 {
|
||||||
|
return time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Currentime 获取当前时间
|
||||||
|
func Currentime(format string) (strTime string) {
|
||||||
|
strTime = time.Now().Format(format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//HoursAgo 若干小时之前的时间
|
||||||
|
func HoursAgo(hours time.Duration, format string) (lastTimeStr string) {
|
||||||
|
lastStamp := time.Now().Unix() - int64((time.Hour * hours).Seconds())
|
||||||
|
lastTime := time.Unix(lastStamp, 0).In(Loc.Shanghai())
|
||||||
|
lastTimeStr = lastTime.Format(format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//TimeToString 时间转字符串
|
||||||
|
func TimeToString(t time.Time, format string) string {
|
||||||
|
return t.Format(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
//计算指定月份的天数
|
||||||
|
func YearMonthToDayNumber(year int, month int) int {
|
||||||
|
// 有31天的月份
|
||||||
|
day31 := map[int]bool{
|
||||||
|
1: true,
|
||||||
|
3: true,
|
||||||
|
5: true,
|
||||||
|
7: true,
|
||||||
|
8: true,
|
||||||
|
10: true,
|
||||||
|
12: true,
|
||||||
|
}
|
||||||
|
if day31[month] == true {
|
||||||
|
return 31
|
||||||
|
}
|
||||||
|
// 有30天的月份
|
||||||
|
day30 := map[int]bool{
|
||||||
|
4: true,
|
||||||
|
6: true,
|
||||||
|
9: true,
|
||||||
|
11: true,
|
||||||
|
}
|
||||||
|
if day30[month] == true {
|
||||||
|
return 30
|
||||||
|
}
|
||||||
|
// 计算是平年还是闰年
|
||||||
|
if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
|
||||||
|
// 得出2月的天数
|
||||||
|
return 29
|
||||||
|
}
|
||||||
|
// 得出2月的天数
|
||||||
|
return 28
|
||||||
|
}
|
||||||
|
|
||||||
|
// 求时间差(返回一个数字,该数字单位由传过来的unit决定。若unit为60,则单位是分钟。)
|
||||||
|
func GetDiffTime(start_time string, end_time string, unit int64) int64 {
|
||||||
|
// 转成时间戳
|
||||||
|
if len(start_time) == 10 {
|
||||||
|
start_time = fmt.Sprintf("%v 00:00:00", start_time)
|
||||||
|
}
|
||||||
|
if len(end_time) == 10 {
|
||||||
|
end_time = fmt.Sprintf("%v 00:00:00", end_time)
|
||||||
|
}
|
||||||
|
startUnix, _ := time.ParseInLocation("2006-01-02 15:04:05", start_time, Loc.Shanghai())
|
||||||
|
endUnix, _ := time.ParseInLocation("2006-01-02 15:04:05", end_time, Loc.Shanghai())
|
||||||
|
startTime := startUnix.Unix()
|
||||||
|
endTime := endUnix.Unix()
|
||||||
|
// 求相差天数
|
||||||
|
date := (endTime - startTime) / unit
|
||||||
|
return date
|
||||||
|
}
|
101
pkg/utils/stime/getTimeExt.go
Normal file
101
pkg/utils/stime/getTimeExt.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package stime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 根据指定时间获取后面的若干天工作日列表
|
||||||
|
// param baseOn 指定基准时间
|
||||||
|
// param daysNum 获取工作日的数量
|
||||||
|
func GetWorkDayList(baseOn *time.Time, daysNum int) []time.Time {
|
||||||
|
var timeList []time.Time
|
||||||
|
var basCount = 1
|
||||||
|
var workDay time.Time
|
||||||
|
for {
|
||||||
|
if len(timeList) == daysNum {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
workDay = baseOn.AddDate(0, 0, basCount)
|
||||||
|
switch workDay.Weekday() {
|
||||||
|
case time.Saturday:
|
||||||
|
basCount += 2
|
||||||
|
continue
|
||||||
|
case time.Sunday:
|
||||||
|
basCount++
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
timeList = append(timeList, workDay)
|
||||||
|
basCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timeList
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据指定时间获取后面的若干天工作日列表
|
||||||
|
// param baseOn 指定基准时间
|
||||||
|
// param daysNum 获取工作日的数量
|
||||||
|
func GetWorkDayStrList(baseOn *time.Time, daysNum int) []string {
|
||||||
|
var timeList []string
|
||||||
|
var basCount = 1
|
||||||
|
var workDay time.Time
|
||||||
|
for {
|
||||||
|
if len(timeList) == daysNum {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
workDay = baseOn.AddDate(0, 0, basCount)
|
||||||
|
switch workDay.Weekday() {
|
||||||
|
case time.Saturday:
|
||||||
|
basCount += 2
|
||||||
|
continue
|
||||||
|
case time.Sunday:
|
||||||
|
basCount++
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
timeList = append(timeList, TimeToString(workDay, Format_Normal_YMD))
|
||||||
|
basCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timeList
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取时间差文字描述
|
||||||
|
func GetTimeDifferenceDesc(now *time.Time, before *time.Time) string {
|
||||||
|
if before == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if now.After(*before) {
|
||||||
|
subTimestamp := now.Unix() - before.Unix()
|
||||||
|
day := subTimestamp / (3600 * 24)
|
||||||
|
hour := (subTimestamp - day*3600*24) / 3600
|
||||||
|
minute := (subTimestamp - day*3600*24 - hour*3600) / 60
|
||||||
|
second := subTimestamp - day*3600*24 - hour*3600 - minute*60
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case day > 0:
|
||||||
|
if hour > 0 {
|
||||||
|
return fmt.Sprintf("%d天%d小时", day, hour)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%d天", day)
|
||||||
|
}
|
||||||
|
case hour > 0:
|
||||||
|
if minute < 10 {
|
||||||
|
return fmt.Sprintf("%d小时", hour)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%d小时%d", hour, minute)
|
||||||
|
}
|
||||||
|
case hour == 0 && minute > 0:
|
||||||
|
return fmt.Sprintf("%d分钟", minute)
|
||||||
|
case hour == 0 && minute == 0:
|
||||||
|
return fmt.Sprintf("%d秒", second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeStampToBytes 时间戳转字节
|
||||||
|
func TimeStampToBytes(stamp int64) []byte {
|
||||||
|
timeStr := strconv.FormatInt(stamp, 2)
|
||||||
|
return []byte(timeStr)
|
||||||
|
}
|
12
pkg/utils/stime/getTimeExt_test.go
Normal file
12
pkg/utils/stime/getTimeExt_test.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package stime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetWorkDayStrList(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
result := GetWorkDayStrList(&now, 5)
|
||||||
|
t.Log(result)
|
||||||
|
}
|
64
pkg/utils/stime/timeTranslate.go
Normal file
64
pkg/utils/stime/timeTranslate.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* @Author: immortal
|
||||||
|
* @Date: 2022-03-11 20:55:38
|
||||||
|
* @LastEditors: immortal
|
||||||
|
* @LastEditTime: 2022-03-12 14:26:42
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: \monitor_env\utils\simpletime\timeTranslate.go
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @Author Puzzle
|
||||||
|
* @Date 2021/11/18 1:36 下午
|
||||||
|
**/
|
||||||
|
|
||||||
|
package stime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetTimestampMillisecond() int64 {
|
||||||
|
now := time.Now()
|
||||||
|
return now.UnixNano() / 1e6
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToTime(strTime string) (*time.Time, error) {
|
||||||
|
const TIME_LAYOUT = "2006-01-02 15:04:05" //此时间不可更改
|
||||||
|
timeobj, err := time.ParseInLocation(TIME_LAYOUT, strTime, Loc.Shanghai())
|
||||||
|
return &timeobj, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToTimeWithFormat(strTime string, timeFormat string) (*time.Time, error) {
|
||||||
|
timeobj, err := time.ParseInLocation(timeFormat, strTime, Loc.Shanghai())
|
||||||
|
return &timeobj, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除精确时间后面的小数点
|
||||||
|
func NowTimeToTime(layout string) *time.Time {
|
||||||
|
otime := time.Now().Format(layout) //"2006-01-02 15:04:05" and so on
|
||||||
|
tt, _ := StringToTime(otime)
|
||||||
|
return tt
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间之间的转换
|
||||||
|
func TimeStampToString(timestamp int64, format string) string {
|
||||||
|
t := time.Unix(timestamp, 0)
|
||||||
|
return t.Format(format)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAge(birthday time.Time) int {
|
||||||
|
if birthday.IsZero() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
year, month, day := now.Date()
|
||||||
|
if year == 0 || month == 0 || day == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
age := year - birthday.Year() - 1
|
||||||
|
//判断年龄
|
||||||
|
if birthday.Month() < month || birthday.Month() == month && birthday.Day() <= day {
|
||||||
|
age++
|
||||||
|
}
|
||||||
|
return age
|
||||||
|
}
|
26
pkg/utils/stime/timeTranslate_test.go
Normal file
26
pkg/utils/stime/timeTranslate_test.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* @FileName: time_test.go
|
||||||
|
* @Author: JJXu
|
||||||
|
* @CreateTime: 2022/2/25 下午2:37
|
||||||
|
* @Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
package stime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTime(t *testing.T) {
|
||||||
|
result := NowTimeToTime(Format_Normal_YMDhms)
|
||||||
|
fmt.Println(result)
|
||||||
|
}
|
||||||
|
func TestGetAge(t *testing.T) {
|
||||||
|
age := GetAge(time.Date(1991, 3, 6, 0, 0, 0, 0, Loc.Shanghai()))
|
||||||
|
fmt.Println(age)
|
||||||
|
if age != 31 {
|
||||||
|
t.Errorf("want 31 but get %v", age)
|
||||||
|
}
|
||||||
|
}
|
52
pkg/utils/stime/week.go
Normal file
52
pkg/utils/stime/week.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* @Author Puzzle
|
||||||
|
* @Date 2022/5/20 12:54 下午
|
||||||
|
**/
|
||||||
|
|
||||||
|
package stime
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func NowWeekDay() string {
|
||||||
|
var weekday = [7]string{"七", "一", "二", "三", "四", "五", "六"}
|
||||||
|
week := int(time.Now().Weekday())
|
||||||
|
return weekday[week]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取按年算的周数
|
||||||
|
func GetYearWeek(t *time.Time) int {
|
||||||
|
yearDay := t.YearDay()
|
||||||
|
yearFirstDay := t.AddDate(0, 0, -yearDay+1)
|
||||||
|
firstDayInWeek := int(yearFirstDay.Weekday())
|
||||||
|
|
||||||
|
//今年第一周有几天
|
||||||
|
firstWeekDays := 1
|
||||||
|
if firstDayInWeek != 0 {
|
||||||
|
firstWeekDays = 7 - firstDayInWeek + 1
|
||||||
|
}
|
||||||
|
var week int
|
||||||
|
if yearDay <= firstWeekDays {
|
||||||
|
week = 1
|
||||||
|
} else {
|
||||||
|
week = (yearDay-firstWeekDays)/7 + 2
|
||||||
|
}
|
||||||
|
return week
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWeekDate 获取基准时间范围最最近的某个星期时间
|
||||||
|
//
|
||||||
|
// param baseOn: 基准时间
|
||||||
|
// param weekNum: 中国星期数 1~7
|
||||||
|
// return *time.Time
|
||||||
|
func GetWeekDate(baseOn time.Time, weekNum int) *time.Time {
|
||||||
|
if baseOn.IsZero() || (weekNum <= 0 || weekNum > 7) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
baseDate := time.Date(baseOn.Year(), baseOn.Month(), baseOn.Day(), 0, 0, 0, 0, Loc.Shanghai())
|
||||||
|
var (
|
||||||
|
w = int(baseOn.Weekday())
|
||||||
|
weekDate time.Time
|
||||||
|
)
|
||||||
|
weekDate = baseDate.AddDate(0, 0, weekNum-w)
|
||||||
|
return &weekDate
|
||||||
|
}
|
20
pkg/utils/stime/week_test.go
Normal file
20
pkg/utils/stime/week_test.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Package simpletime -----------------------------
|
||||||
|
// @file : week_test.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2022/8/31 14:57
|
||||||
|
// -------------------------------------------
|
||||||
|
package stime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetYearWeek(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
t.Log(GetYearWeek(&now))
|
||||||
|
var w = int(now.Weekday())
|
||||||
|
t.Log(now.AddDate(0, 0, -w+1).Weekday())
|
||||||
|
t.Log(now.AddDate(0, 0, 7-w).Weekday())
|
||||||
|
}
|
19
pkg/utils/unqiue.go
Normal file
19
pkg/utils/unqiue.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Package utils -----------------------------
|
||||||
|
// @file : unqiue.go
|
||||||
|
// @author : JJXu
|
||||||
|
// @contact : wavingbear@163.com
|
||||||
|
// @time : 2024/9/12 下午5:03
|
||||||
|
// -------------------------------------------
|
||||||
|
package utils
|
||||||
|
|
||||||
|
func Unique[T int | int8 | int32 | int64 | string](slice []T) []T {
|
||||||
|
seen := make(map[T]bool) // 创建一个 map 来跟踪已经看到的元素
|
||||||
|
unique := make([]T, 0) // 创建一个新的切片来存储唯一的元素
|
||||||
|
for _, v := range slice {
|
||||||
|
if _, ok := seen[v]; !ok {
|
||||||
|
seen[v] = true // 标记元素为已见
|
||||||
|
unique = append(unique, v) // 将元素添加到唯一元素切片中
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unique
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user