diff --git a/README.md b/README.md index 31d081689..658a8cd32 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Open-IM-Server is open source instant messaging Server.Backend in Go. ![avatar](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/Open-IM.png) -[![LICENSE](https://img.shields.io/github/license/pingcap/tidb.svg)](https://github.com/pingcap/tidb/blob/master/LICENSE) +[![LICENSE](https://img.shields.io/github/license/pingcap/tidb.svg)](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/LICENSE) [![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/) ## Open-IM-Server: Open source Instant Messaging Server @@ -334,7 +334,7 @@ All images are available at https://hub.docker.com/r/lyt1123/open_im_server - **Authentication Clow Chart** -![avatar](https://github.com/Open-IM-IM/opim_admin/blob/main/docs/open-im-server.png) +![avatar](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/open-im-server.png) ## Architecture diff --git a/script/build_all_service.sh b/script/build_all_service.sh new file mode 100644 index 000000000..0bceb115e --- /dev/null +++ b/script/build_all_service.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +source ./style_info.cfg +source ./path_info.cfg +source ./function.sh + +#begin path +begin_path=$PWD + +for ((i = 0; i < ${#service_source_root[*]}; i++)); do + cd $begin_path + service_path=${service_source_root[$i]} + cd $service_path && echo -e "${SKY_BLUE_PREFIX}Current directory: $PWD $COLOR_SUFFIX" + make install && echo -e "${SKY_BLUE_PREFIX}build ${service_names[$i]} success,moving binary file to the bin directory${COLOR_SUFFIX}" && + echo -e "${SKY_BLUE_PREFIX}Successful moved ${service_names[$i]} to the bin directory${COLOR_SUFFIX}\n" +done diff --git a/script/start_all.sh b/script/start_all.sh new file mode 100644 index 000000000..15cbfb56e --- /dev/null +++ b/script/start_all.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +#fixme This script is the total startup script +#fixme The full name of the shell script that needs to be started is placed in the need_to_start_server_shell array + +#fixme Put the shell script name here +need_to_start_server_shell=( + auto_start_service.sh + msg_gateway_start.sh + push_start.sh + msg_transfer_start.sh +) + +for i in ${need_to_start_server_shell[*]}; do + chmod +x $i + ./$i +done +while [ true ]; do + sleep 60 +done diff --git a/script/start_rpc_service.sh b/script/start_rpc_service.sh new file mode 100644 index 000000000..ec249451c --- /dev/null +++ b/script/start_rpc_service.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +source ./style_info.cfg +source ./path_info.cfg +source ./function.sh + +#service filename +service_filename=( + #api + open_im_api + #rpc + open_im_user + open_im_friend + open_im_group + open_im_auth + ${msg_name} +) + +#service config port name +service_port_name=( + #api port name + openImApiPort + #rpc port name + openImUserPort + openImFriendPort + openImGroupPort + openImAuthPort + openImOfflineMessagePort + +) + +for ((i = 0; i < ${#service_filename[*]}; i++)); do + #Check whether the service exists + service_name="ps -aux |grep -w ${service_filename[$i]} |grep -v grep" + count="${service_name}| wc -l" + + if [ $(eval ${count}) -gt 0 ]; then + pid="${service_name}| awk '{print \$2}'" + echo -e "${SKY_BLUE_PREFIX}${service_filename[$i]} service has been started,pid:$(eval $pid)$COLOR_SUFFIX" + echo -e "${SKY_BLUE_PREFIX}Killing the service ${service_filename[$i]} pid:$(eval $pid)${COLOR_SUFFIX}" + #kill the service that existed + kill -9 $(eval $pid) + sleep 0.5 + fi + cd ../bin && echo -e "${SKY_BLUE_PREFIX}${service_filename[$i]} service is starting${COLOR_SUFFIX}" + #Get the rpc port in the configuration file + portList=$(cat $config_path | grep ${service_port_name[$i]} | awk -F '[:]' '{print $NF}') + list_to_string ${portList} + #Start related rpc services based on the number of ports + for j in ${ports_array}; do + echo -e "${SKY_BLUE_PREFIX}${service_filename[$i]} Service is starting,port number:$j $COLOR_SUFFIX" + #Start the service in the background + ./${service_filename[$i]} -port $j & + # nohup ./${service_filename[$i]} -port $j >/dev/null 2>&1 & + sleep 1 + pid="netstat -ntlp|grep $j |awk '{printf \$7}'|cut -d/ -f1" + echo -e "${RED_PREFIX}${service_filename[$i]} Service is started,port number:$j pid:$(eval $pid)$COLOR_SUFFIX" + done +done diff --git a/script/stop_all.sh b/script/stop_all.sh new file mode 100644 index 000000000..2e1e119c1 --- /dev/null +++ b/script/stop_all.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +#fixme This script is to stop the service + +source ./style_info.cfg +source ./path_info.cfg + + +for i in ${service_names[*]}; do + #Check whether the service exists + name="ps -aux |grep -w $i |grep -v grep" + count="${name}| wc -l" + if [ $(eval ${count}) -gt 0 ]; then + pid="${name}| awk '{print \$2}'" + echo -e "${SKY_BLUE_PREFIX}Killing service:$i pid:$(eval $pid)${COLOR_SUFFIX}" + #kill the service that existed + kill -9 $(eval $pid) + echo -e "${SKY_BLUE_PREFIX}service:$i was killed ${COLOR_SUFFIX}" + fi +done diff --git a/src/common/db/mysql_model/im_mysql_model/friend_model.go b/src/common/db/mysql_model/im_mysql_model/friend_model.go new file mode 100644 index 000000000..1df429714 --- /dev/null +++ b/src/common/db/mysql_model/im_mysql_model/friend_model.go @@ -0,0 +1,70 @@ +package im_mysql_model + +import ( + "Open_IM/src/common/db" + _ "github.com/jinzhu/gorm/dialects/mysql" + "time" +) + +func InsertToFriend(ownerId, friendId string, flag int32) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + toInsertFollow := Friend{ + OwnerId: ownerId, + FriendId: friendId, + FriendFlag: flag, + CreateTime: time.Now(), + } + err = dbConn.Table("friend").Create(toInsertFollow).Error + if err != nil { + return err + } + return nil +} + +func FindFriendRelationshipFromFriend(ownerId, friendId string) (*Friend, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + var friend Friend + err = dbConn.Table("friend").Where("owner_id=? and friend_id=?", ownerId, friendId).Find(&friend).Error + if err != nil { + return nil, err + } + return &friend, err +} + +func FindUserInfoFromFriend(ownerId string) ([]Friend, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + //dbConn.LogMode(true) + var friends []Friend + err = dbConn.Table("friend").Where("owner_id=?", ownerId).Find(&friends).Error + if err != nil { + return nil, err + } + return friends, nil +} + +func UpdateFriendComment(ownerId, friendId, comment string) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("update friend set comment=? where owner_id=? and friend_id=?", comment, ownerId, friendId).Error + return err +} + +func DeleteSingleFriendInfo(ownerId, friendId string) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Table("friend").Where("owner_id=? and friend_id=?", ownerId, friendId).Delete(Friend{}).Error + return err +} diff --git a/src/common/db/mysql_model/im_mysql_model/friend_request_model.go b/src/common/db/mysql_model/im_mysql_model/friend_request_model.go new file mode 100644 index 000000000..35a829488 --- /dev/null +++ b/src/common/db/mysql_model/im_mysql_model/friend_request_model.go @@ -0,0 +1,57 @@ +package im_mysql_model + +import ( + "Open_IM/src/common/db" + "time" +) + +func InsertIntoFriendReq(reqId, userId string, flag int32, reqMessage string) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("insert into friend_request(req_id,user_id,flag,req_message,create_time) values(?,?,?,?,?)", reqId, userId, flag, reqMessage, time.Now()).Error + if err != nil { + return err + } + return nil +} + +func FindFriendsApplyFromFriendReq(userId string) ([]FriendRequest, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + var usersInfo []FriendRequest + //dbConn.LogMode(true) + err = dbConn.Table("friend_request").Where("user_id=?", userId).Find(&usersInfo).Error + if err != nil { + return nil, err + } + return usersInfo, nil +} + +func FindFriendRelationshipFromFriendReq(reqId, userId string) (*FriendRequest, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + var friendRequest FriendRequest + err = dbConn.Table("friend_request").Where("req_id=? and user_id=?", reqId, userId).Find(&friendRequest).Error + if err != nil { + return nil, err + } + return &friendRequest, nil +} + +func UpdateFriendRelationshipToFriendReq(reqId, userId string, flag int32) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("update friend_request set flag=? where req_id=? and user_id=?", flag, reqId, userId).Error + if err != nil { + return err + } + return nil +} diff --git a/src/common/db/mysql_model/im_mysql_model/group_member_model.go b/src/common/db/mysql_model/im_mysql_model/group_member_model.go new file mode 100644 index 000000000..e92b1d3dc --- /dev/null +++ b/src/common/db/mysql_model/im_mysql_model/group_member_model.go @@ -0,0 +1,98 @@ +package im_mysql_model + +import "Open_IM/src/common/db" + +func InsertIntoGroupMember(groupId, userId string, isAdmin int64) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("insert into `group_member`(group_id,user_id,is_admin) values(?,?,?)", groupId, userId, isAdmin).Error + if err != nil { + return err + } + return nil +} + +func FindGroupMemberListByUserId(userId string) ([]GroupMember, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + var groupMemberList []GroupMember + err = dbConn.Raw("select * from `group_member` where user_id=?", userId).Find(&groupMemberList).Error + if err != nil { + return nil, err + } + return groupMemberList, nil +} + +func FindGroupMemberListByGroupId(groupId string) ([]GroupMember, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + var groupMemberList []GroupMember + err = dbConn.Raw("select * from `group_member` where group_id=?", groupId).Find(&groupMemberList).Error + if err != nil { + return nil, err + } + return groupMemberList, nil +} + +func FindGroupMemberInfoByGroupIdAndUserId(groupId, userId string) (*GroupMember, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + var groupMember GroupMember + err = dbConn.Raw("select * from `group_member` where group_id=? and user_id=? limit 1", groupId, userId).Scan(&groupMember).Error + if err != nil { + return nil, err + } + return &groupMember, nil +} + +func DeleteGroupMemberByGroupIdAndUserId(groupId, userId string) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("delete from `group_member` where group_id=? and user_id=?", groupId, userId).Error + if err != nil { + return err + } + return nil +} + +func UpdateOwnerGroupNickName(groupId, userId, groupNickName string) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("update `group_member` set nickname=? where group_id=? and user_id=?", groupNickName, groupId, userId).Error + if err != nil { + return err + } + return nil +} + +func SelectGroupList(groupID string) ([]string, error) { + var groupUserID string + var groupList []string + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return groupList, err + } + + rows, err := dbConn.Model(&GroupMember{}).Where("group_id = ?", groupID).Select("user_id").Rows() + if err != nil { + return groupList, err + } + defer rows.Close() + for rows.Next() { + rows.Scan(&groupUserID) + groupList = append(groupList, groupUserID) + } + return groupList, nil +} diff --git a/src/common/db/mysql_model/im_mysql_model/group_model.go b/src/common/db/mysql_model/im_mysql_model/group_model.go new file mode 100644 index 000000000..abe25b500 --- /dev/null +++ b/src/common/db/mysql_model/im_mysql_model/group_model.go @@ -0,0 +1,67 @@ +package im_mysql_model + +import "Open_IM/src/common/db" + +func InsertIntoGroup(groupId, name, groupHeadUrl string) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + //Default group name + if name == "" { + name = "groupChat" + } + err = dbConn.Exec("insert into `group`(group_id,name,head_url) values(?,?,?)", groupId, name, groupHeadUrl).Error + if err != nil { + return err + } + return nil +} + +func FindGroupInfoByGroupId(groupId string) (*Group, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + var groupInfo Group + err = dbConn.Raw("select * from `group` where group_id=?", groupId).Scan(&groupInfo).Error + if err != nil { + return nil, err + } + return &groupInfo, nil +} + +func UpdateGroupName(groupId, groupName string) (err error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("update `group` set name=? where group_id=?", groupName, groupId).Error + if err != nil { + return err + } + return nil +} + +func UpdateGroupBulletin(groupId, bulletinContent string) (err error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("update `group` set bulletin=? where group_id=?", bulletinContent, groupId).Error + if err != nil { + return err + } + return nil +} +func UpdateGroupHeadImage(groupId, headImageUrl string) (err error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("update `group` set head_url=? where group_id=?", headImageUrl, groupId).Error + if err != nil { + return err + } + return nil +} diff --git a/src/common/db/mysql_model/im_mysql_model/model_struct.go b/src/common/db/mysql_model/im_mysql_model/model_struct.go new file mode 100644 index 000000000..ac88573fc --- /dev/null +++ b/src/common/db/mysql_model/im_mysql_model/model_struct.go @@ -0,0 +1,48 @@ +package im_mysql_model + +import "time" + +type User struct { + UID string `gorm:"column:uid"` + Name string `gorm:"column:name"` + Icon string `gorm:"column:icon"` + Gender int32 `gorm:"column:gender"` + Mobile string `gorm:"column:mobile"` + Birth string `gorm:"column:birth"` + Email string `gorm:"column:email"` + Ex string `gorm:"column:ex"` + CreateTime time.Time `gorm:"column:create_time"` +} + +type Friend struct { + OwnerId string `gorm:"column:owner_id"` + FriendId string `gorm:"column:friend_id"` + Comment string `gorm:"column:comment"` + FriendFlag int32 `gorm:"column:friend_flag"` + CreateTime time.Time `gorm:"column:create_time"` +} +type FriendRequest struct { + ReqId string `gorm:"column:req_id"` + UserId string `gorm:"column:user_id"` + Flag int32 `gorm:"column:flag"` + ReqMessage string `gorm:"column:req_message"` + CreateTime time.Time `gorm:"column:create_time"` +} +type BlackList struct { + OwnerId string `gorm:"column:owner_id"` + BlockId string `gorm:"column:block_id"` + CreateTime time.Time `gorm:"column:create_time"` +} +type Group struct { + GroupId string `gorm:"column:group_id"` + Name string `gorm:"column:name"` + HeadURL string `gorm:"column:head_url"` + Bulletin string `gorm:"column:bulletin"` +} + +type GroupMember struct { + GroupId string `gorm:"column:group_id"` + UserId string `gorm:"column:user_id"` + NickName string `gorm:"column:nickname"` + IsAdmin int32 `gorm:"column:is_admin"` +} diff --git a/src/common/db/mysql_model/im_mysql_model/user_black_list_model.go b/src/common/db/mysql_model/im_mysql_model/user_black_list_model.go new file mode 100644 index 000000000..9d2e54f9d --- /dev/null +++ b/src/common/db/mysql_model/im_mysql_model/user_black_list_model.go @@ -0,0 +1,48 @@ +package im_mysql_model + +import ( + "Open_IM/src/common/db" + "time" +) + +func InsertInToUserBlackList(ownerID, blockID string) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + toInsertInfo := BlackList{OwnerId: ownerID, BlockId: blockID, CreateTime: time.Now()} + err = dbConn.Table("user_black_list").Create(toInsertInfo).Error + return err +} + +func FindRelationshipFromBlackList(ownerID, blockID string) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + var blackList BlackList + err = dbConn.Table("user_black_list").Where("owner_id=? and block_id=?", ownerID, blockID).Find(&blackList).Error + return err +} + +func RemoveBlackList(ownerID, blockID string) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + err = dbConn.Exec("delete from user_black_list where owner_id=? and block_id=?", ownerID, blockID).Error + return err +} + +func GetBlackListByUID(ownerID string) ([]BlackList, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + var blackListUsersInfo []BlackList + err = dbConn.Table("user_black_list").Where("owner_id=?", ownerID).Find(&blackListUsersInfo).Error + if err != nil { + return nil, err + } + return blackListUsersInfo, nil +} diff --git a/src/common/db/mysql_model/im_mysql_model/user_model.go b/src/common/db/mysql_model/im_mysql_model/user_model.go new file mode 100644 index 000000000..559665700 --- /dev/null +++ b/src/common/db/mysql_model/im_mysql_model/user_model.go @@ -0,0 +1,104 @@ +package im_mysql_model + +import ( + "Open_IM/src/common/db" + pbAuth "Open_IM/src/proto/auth" + _ "github.com/jinzhu/gorm/dialects/mysql" + "time" +) + +func UserRegister(pb *pbAuth.UserRegisterReq) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + addUser := User{ + UID: pb.UID, + Name: pb.Name, + Icon: pb.Icon, + Gender: pb.Gender, + Mobile: pb.Mobile, + Birth: pb.Birth, + Email: pb.Email, + Ex: pb.Ex, + CreateTime: time.Now(), + } + err = dbConn.Table("user").Create(&addUser).Error + if err != nil { + return err + } + return nil +} + +func FindUserByUID(uid string) (*User, error) { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return nil, err + } + var user User + err = dbConn.Table("user").Where("uid=?", uid).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func UpDateUserInfo(uid, name, headUrl, mobilePhoneNum, birth, email, extendInfo string, gender int32) error { + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return err + } + if name != "" { + if err = dbConn.Exec("update user set name=? where uid=?", name, uid).Error; err != nil { + return err + } + } + if headUrl != "" { + if err = dbConn.Exec("update user set icon=? where uid=?", headUrl, uid).Error; err != nil { + return err + } + } + if mobilePhoneNum != "" { + if err = dbConn.Exec("update user set mobile=? where uid=?", mobilePhoneNum, uid).Error; err != nil { + return err + } + } + if birth != "" { + if err = dbConn.Exec("update user set birth=? where uid=?", birth, uid).Error; err != nil { + return err + } + } + if email != "" { + if err = dbConn.Exec("update user set email=? where uid=?", email, uid).Error; err != nil { + return err + } + } + if extendInfo != "" { + if err = dbConn.Exec("update user set ex=? where uid=?", extendInfo, uid).Error; err != nil { + return err + } + } + if gender != 0 { + if err = dbConn.Exec("update user set gender=? where uid=?", gender, uid).Error; err != nil { + return err + } + } + return nil +} + +func SelectAllUID() ([]string, error) { + var uid []string + + dbConn, err := db.DB.MysqlDB.DefaultGormDB() + if err != nil { + return uid, err + } + rows, _ := dbConn.Raw("select uid from user").Rows() + defer rows.Close() + var strUID string + for rows.Next() { + rows.Scan(&strUID) + uid = append(uid, strUID) + } + return uid, nil +} diff --git a/src/common/multi_terminal_login/multi_terminal_login.go b/src/common/multi_terminal_login/multi_terminal_login.go new file mode 100644 index 000000000..02245cf7e --- /dev/null +++ b/src/common/multi_terminal_login/multi_terminal_login.go @@ -0,0 +1,72 @@ +package multi_terminal_login + +import ( + "Open_IM/src/common/config" + "Open_IM/src/common/constant" + "Open_IM/src/common/db" + pbChat "Open_IM/src/proto/chat" + "Open_IM/src/push/logic" + "Open_IM/src/utils" +) + +func MultiTerminalLoginChecker(uid, token string, platformID int32) error { + // 1.check userid and platform class 0 not exists and 1 exists + existsInterface, err := db.DB.ExistsUserIDAndPlatform(uid, utils.PlatformNameToClass(utils.PlatformIDToName(platformID))) + if err != nil { + return err + } + exists := existsInterface.(int64) + //get config multi login policy + if config.Config.MultiLoginPolicy.OnlyOneTerminalAccess { + //OnlyOneTerminalAccess policy need to check all terminal + if utils.PlatformNameToClass(utils.PlatformIDToName(platformID)) == "PC" { + existsInterface, err = db.DB.ExistsUserIDAndPlatform(uid, "Mobile") + if err != nil { + return err + } + } else { + existsInterface, err = db.DB.ExistsUserIDAndPlatform(uid, "PC") + if err != nil { + return err + } + } + exists = existsInterface.(int64) + if exists == 1 { + err := db.DB.SetUserIDAndPlatform(uid, utils.PlatformNameToClass(utils.PlatformIDToName(platformID)), token, config.Config.TokenPolicy.AccessExpire) + if err != nil { + return err + } + PushMessageToTheTerminal(uid, platformID) + return nil + } + } else if config.Config.MultiLoginPolicy.MobileAndPCTerminalAccessButOtherTerminalKickEachOther { + // common terminal need to kick eich other + if exists == 1 { + err := db.DB.SetUserIDAndPlatform(uid, utils.PlatformNameToClass(utils.PlatformIDToName(platformID)), token, config.Config.TokenPolicy.AccessExpire) + if err != nil { + return err + } + PushMessageToTheTerminal(uid, platformID) + return nil + } + } + err = db.DB.SetUserIDAndPlatform(uid, utils.PlatformNameToClass(utils.PlatformIDToName(platformID)), token, config.Config.TokenPolicy.AccessExpire) + if err != nil { + return err + } + PushMessageToTheTerminal(uid, platformID) + return nil +} + +func PushMessageToTheTerminal(uid string, platform int32) { + + logic.SendMsgByWS(&pbChat.WSToMsgSvrChatMsg{ + SendID: uid, + RecvID: uid, + Content: "Your account is already logged on other terminal,please confirm", + SendTime: utils.GetCurrentTimestampBySecond(), + MsgFrom: constant.SysMsgType, + ContentType: constant.KickOnlineTip, + PlatformID: platform, + }) +} diff --git a/src/rpc/auth/Makefile b/src/rpc/auth/Makefile new file mode 100644 index 000000000..12c10351d --- /dev/null +++ b/src/rpc/auth/Makefile @@ -0,0 +1,26 @@ +.PHONY: all build run gotool install clean help + +BINARY_NAME=open_im_auth +BIN_DIR=../../../bin/ +LAN_FILE=.go +GO_FILE:=${BINARY_NAME}${LAN_FILE} + +all: gotool build + +build: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BINARY_NAME} ${GO_FILE} + +run: + @go run ./ + +gotool: + go fmt ./ + go vet ./ + +install: + make build + mv ${BINARY_NAME} ${BIN_DIR} + +clean: + @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi + diff --git a/src/rpc/auth/auth/rpcAuth.go b/src/rpc/auth/auth/rpcAuth.go new file mode 100644 index 000000000..b93d7b8fc --- /dev/null +++ b/src/rpc/auth/auth/rpcAuth.go @@ -0,0 +1,61 @@ +package rpcAuth + +import ( + "Open_IM/src/common/config" + log2 "Open_IM/src/common/log" + pbAuth "Open_IM/src/proto/auth" + "Open_IM/src/utils" + "github.com/skiffer-git/grpc-etcdv3/getcdv3" + "google.golang.org/grpc" + "net" + "strconv" + "strings" +) + +type rpcAuth struct { + rpcPort int + rpcRegisterName string + etcdSchema string + etcdAddr []string +} + +func NewRpcAuthServer(port int) *rpcAuth { + return &rpcAuth{ + rpcPort: port, + rpcRegisterName: config.Config.RpcRegisterName.RpcGetTokenName, + etcdSchema: config.Config.Etcd.EtcdSchema, + etcdAddr: config.Config.Etcd.EtcdAddr, + } +} + +func (rpc *rpcAuth) Run() { + log2.Info("", "", "rpc get_token init...") + + address := utils.ServerIP + ":" + strconv.Itoa(rpc.rpcPort) + listener, err := net.Listen("tcp", address) + if err != nil { + log2.Error("", "", "listen network failed, err = %s, address = %s", err.Error(), address) + return + } + log2.Info("", "", "listen network success, address = %s", address) + + //grpc server + srv := grpc.NewServer() + defer srv.GracefulStop() + + //service registers with etcd + + pbAuth.RegisterAuthServer(srv, rpc) + err = getcdv3.RegisterEtcd(rpc.etcdSchema, strings.Join(rpc.etcdAddr, ","), utils.ServerIP, rpc.rpcPort, rpc.rpcRegisterName, 10) + if err != nil { + log2.Error("", "", "register rpc get_token to etcd failed, err = %s", err.Error()) + return + } + + err = srv.Serve(listener) + if err != nil { + log2.Info("", "", "rpc get_token fail, err = %s", err.Error()) + return + } + log2.Info("", "", "rpc get_token init success") +} diff --git a/src/rpc/auth/auth/user_register.go b/src/rpc/auth/auth/user_register.go new file mode 100644 index 000000000..aa2bc40f3 --- /dev/null +++ b/src/rpc/auth/auth/user_register.go @@ -0,0 +1,20 @@ +package rpcAuth + +import ( + "Open_IM/src/common/db/mysql_model/im_mysql_model" + "Open_IM/src/common/log" + pbAuth "Open_IM/src/proto/auth" + "context" +) + +func (rpc *rpcAuth) UserRegister(_ context.Context, pb *pbAuth.UserRegisterReq) (*pbAuth.UserRegisterResp, error) { + log.Info("", "", "rpc user_register start, [data: %s]", pb.String()) + + if err := im_mysql_model.UserRegister(pb); err != nil { + log.Error("", "", "rpc user_register error, [data: %s] [err: %s]", pb.String(), err.Error()) + return &pbAuth.UserRegisterResp{Success: false}, err + } + log.Info("", "", "rpc user_register success return") + + return &pbAuth.UserRegisterResp{Success: true}, nil +} diff --git a/src/rpc/auth/auth/user_token.go b/src/rpc/auth/auth/user_token.go new file mode 100644 index 000000000..59e91c33b --- /dev/null +++ b/src/rpc/auth/auth/user_token.go @@ -0,0 +1,29 @@ +package rpcAuth + +import ( + "Open_IM/src/common/db/mysql_model/im_mysql_model" + "Open_IM/src/common/log" + pbAuth "Open_IM/src/proto/auth" + "Open_IM/src/utils" + "context" +) + +func (rpc *rpcAuth) UserToken(_ context.Context, pb *pbAuth.UserTokenReq) (*pbAuth.UserTokenResp, error) { + log.Info("", "", "rpc user_token call start..., [pbTokenReq: %s]", pb.String()) + + _, err := im_mysql_model.FindUserByUID(pb.UID) + if err != nil { + log.Error("", "", "rpc user_token call..., im_mysql_model.AppServerFindFromUserByUserID fail [uid: %s] [err: %s]", pb.UID, err.Error()) + return &pbAuth.UserTokenResp{ErrCode: 500, ErrMsg: err.Error()}, err + } + log.Info("", "", "rpc user_token call..., im_mysql_model.AppServerFindFromUserByUserID") + + tokens, expTime, err := utils.CreateToken(pb.UID, "", pb.Platform) + if err != nil { + log.Error("", "", "rpc user_token call..., utils.CreateToken fail [uid: %s] [err: %s]", pb.UID, err.Error()) + return &pbAuth.UserTokenResp{ErrCode: 500, ErrMsg: err.Error()}, err + } + log.Info("", "", "rpc user_token success return, [uid: %s] [tokens: %s]", pb.UID, tokens) + + return &pbAuth.UserTokenResp{Token: tokens, ExpiredTime: expTime}, nil +} diff --git a/utils/cors_middleware.go b/utils/cors_middleware.go new file mode 100644 index 000000000..1d75c4ead --- /dev/null +++ b/utils/cors_middleware.go @@ -0,0 +1,23 @@ +package utils + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +func CorsHandler() gin.HandlerFunc { + return func(context *gin.Context) { + context.Writer.Header().Set("Access-Control-Allow-Origin", "*") + context.Header("Access-Control-Allow-Methods", "*") + context.Header("Access-Control-Allow-Headers", "*") + context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域关键设置 让浏览器可以解析 + context.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒 + context.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true + context.Header("content-type", "application/json") // 设置返回格式是json + //Release all option pre-requests + if context.Request.Method == http.MethodOptions { + context.JSON(http.StatusOK, "Options Request!") + } + context.Next() + } +} diff --git a/utils/file.go b/utils/file.go new file mode 100644 index 000000000..15ce153b0 --- /dev/null +++ b/utils/file.go @@ -0,0 +1,22 @@ +package utils + +import "os" + +// Determine whether the given path is a folder +func IsDir(path string) bool { + s, err := os.Stat(path) + if err != nil { + return false + } + return s.IsDir() +} + +// Determine whether the given path is a file +func IsFile(path string) bool { + return !IsDir(path) +} + +// Create a directory +func MkDir(path string) error { + return os.MkdirAll(path, os.ModePerm) +} diff --git a/utils/get_server_ip.go b/utils/get_server_ip.go new file mode 100644 index 000000000..21092ffa1 --- /dev/null +++ b/utils/get_server_ip.go @@ -0,0 +1,35 @@ +package utils + +import ( + "Open_IM/src/common/config" + "net" +) + +var ServerIP = "" + +func init() { + //fixme In the configuration file, ip takes precedence, if not, get the valid network card ip of the machine + if config.Config.ServerIP != "" { + ServerIP = config.Config.ServerIP + return + } + //fixme Get the ip of the local network card + netInterfaces, err := net.Interfaces() + if err != nil { + panic(err) + } + for i := 0; i < len(netInterfaces); i++ { + //Exclude useless network cards by judging the net.flag Up flag + if (netInterfaces[i].Flags & net.FlagUp) != 0 { + address, _ := netInterfaces[i].Addrs() + for _, addr := range address { + if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { + if ipNet.IP.To4() != nil { + ServerIP = ipNet.IP.String() + return + } + } + } + } + } +} diff --git a/utils/image.go b/utils/image.go new file mode 100644 index 000000000..393136e63 --- /dev/null +++ b/utils/image.go @@ -0,0 +1,56 @@ +package utils + +import ( + "errors" + "github.com/nfnt/resize" + "golang.org/x/image/bmp" + "image" + "image/gif" + "image/jpeg" + "image/png" + "io" + "os" +) + +func GenSmallImage(src, dst string) error { + fIn, _ := os.Open(src) + defer fIn.Close() + + fOut, _ := os.Create(dst) + defer fOut.Close() + + if err := scale(fIn, fOut, 0, 0, 0); err != nil { + return err + } + return nil +} + +func scale(in io.Reader, out io.Writer, width, height, quality int) error { + origin, fm, err := image.Decode(in) + if err != nil { + return err + } + if width == 0 || height == 0 { + width = origin.Bounds().Max.X / 2 + height = origin.Bounds().Max.Y / 2 + } + if quality == 0 { + quality = 25 + } + canvas := resize.Thumbnail(uint(width), uint(height), origin, resize.Lanczos3) + + switch fm { + case "jpeg": + return jpeg.Encode(out, canvas, &jpeg.Options{quality}) + case "png": + return png.Encode(out, canvas) + case "gif": + return gif.Encode(out, canvas, &gif.Options{}) + case "bmp": + return bmp.Encode(out, canvas) + default: + return errors.New("ERROR FORMAT") + } + + return nil +} diff --git a/utils/jwt_token.go b/utils/jwt_token.go new file mode 100644 index 000000000..f60cc201e --- /dev/null +++ b/utils/jwt_token.go @@ -0,0 +1,192 @@ +package utils + +import ( + "Open_IM/src/common/config" + "Open_IM/src/common/db" + "errors" + "time" +) + +var ( + TokenExpired = errors.New("token is timed out, please log in again") + TokenInvalid = errors.New("token has been invalidated") + TokenNotValidYet = errors.New("token not active yet") + TokenMalformed = errors.New("that's not even a token") + TokenUnknown = errors.New("couldn't handle this token") +) + +type Claims struct { + UID string + Platform string //login platform + jwt.StandardClaims +} + +func BuildClaims(uid, accountAddr, platform string, ttl int64) Claims { + now := time.Now().Unix() + //if ttl=-1 Permanent token + if ttl == -1 { + return Claims{ + UID: uid, + Platform: platform, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: -1, + IssuedAt: now, + NotBefore: now, + }} + } + return Claims{ + UID: uid, + Platform: platform, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: now + ttl, //Expiration time + IssuedAt: now, //Issuing time + NotBefore: now, //Begin Effective time + }} +} + +func CreateToken(userID, accountAddr string, platform int32) (string, int64, error) { + claims := BuildClaims(userID, accountAddr, PlatformIDToName(platform), config.Config.TokenPolicy.AccessExpire) + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString([]byte(config.Config.TokenPolicy.AccessSecret)) + + return tokenString, claims.ExpiresAt, err +} + +func secret() jwt.Keyfunc { + return func(token *jwt.Token) (interface{}, error) { + return []byte(config.Config.TokenPolicy.AccessSecret), nil + } +} + +func ParseToken(tokensString string) (claims *Claims, err error) { + token, err := jwt.ParseWithClaims(tokensString, &Claims{}, secret()) + if err != nil { + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { + return nil, TokenMalformed + } else if ve.Errors&jwt.ValidationErrorExpired != 0 { + return nil, TokenExpired + } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { + return nil, TokenNotValidYet + } else { + return nil, TokenUnknown + } + } + } + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + // 1.check userid and platform class 0 not exists and 1 exists + existsInterface, err := db.DB.ExistsUserIDAndPlatform(claims.UID, Platform2class[claims.Platform]) + if err != nil { + return nil, err + } + exists := existsInterface.(int64) + //get config multi login policy + if config.Config.MultiLoginPolicy.OnlyOneTerminalAccess { + //OnlyOneTerminalAccess policy need to check all terminal + //When only one end is allowed to log in, there is a situation that needs to be paid attention to. After PC login, + //mobile login should check two platform times. One of them is less than the redis storage time, which is the invalid token. + if Platform2class[claims.Platform] == "PC" { + existsInterface, err = db.DB.ExistsUserIDAndPlatform(claims.UID, "Mobile") + if err != nil { + return nil, err + } + exists = existsInterface.(int64) + if exists == 1 { + res, err := MakeTheTokenInvalid(*claims, "Mobile") + if err != nil { + return nil, err + } + if res { + return nil, TokenInvalid + } + } + } else { + existsInterface, err = db.DB.ExistsUserIDAndPlatform(claims.UID, "PC") + if err != nil { + return nil, err + } + exists = existsInterface.(int64) + if exists == 1 { + res, err := MakeTheTokenInvalid(*claims, "PC") + if err != nil { + return nil, err + } + if res { + return nil, TokenInvalid + } + } + } + + if exists == 1 { + res, err := MakeTheTokenInvalid(*claims, Platform2class[claims.Platform]) + if err != nil { + return nil, err + } + if res { + return nil, TokenInvalid + } + } + + } else if config.Config.MultiLoginPolicy.MobileAndPCTerminalAccessButOtherTerminalKickEachOther { + if exists == 1 { + res, err := MakeTheTokenInvalid(*claims, Platform2class[claims.Platform]) + if err != nil { + return nil, err + } + if res { + return nil, TokenInvalid + } + } + } + return claims, nil + } + return nil, TokenUnknown +} + +func MakeTheTokenInvalid(currentClaims Claims, platformClass string) (bool, error) { + storedRedisTokenInterface, err := db.DB.GetPlatformToken(currentClaims.UID, platformClass) + if err != nil { + return false, err + } + storedRedisPlatformClaims, err := ParseRedisInterfaceToken(storedRedisTokenInterface) + if err != nil { + return false, err + } + //if issue time less than redis token then make this token invalid + if currentClaims.IssuedAt < storedRedisPlatformClaims.IssuedAt { + return true, TokenInvalid + } + return false, nil +} +func ParseRedisInterfaceToken(redisToken interface{}) (*Claims, error) { + token, err := jwt.ParseWithClaims(string(redisToken.([]uint8)), &Claims{}, secret()) + if err != nil { + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { + return nil, TokenMalformed + } else if ve.Errors&jwt.ValidationErrorExpired != 0 { + return nil, TokenExpired + } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { + return nil, TokenNotValidYet + } else { + return nil, TokenInvalid + } + } + } + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + return nil, err +} + +//Validation token, false means failure, true means successful verification +func VerifyToken(token, uid string) bool { + claims, err := ParseToken(token) + if err != nil { + return false + } else if claims.UID != uid { + return false + } else { + return true + } +} diff --git a/utils/map.go b/utils/map.go new file mode 100644 index 000000000..bec647464 --- /dev/null +++ b/utils/map.go @@ -0,0 +1,119 @@ +package utils + +import ( + "encoding/json" + "sync" +) + +type Map struct { + sync.RWMutex + m map[interface{}]interface{} +} + +func (m *Map) init() { + if m.m == nil { + m.m = make(map[interface{}]interface{}) + } +} + +func (m *Map) UnsafeGet(key interface{}) interface{} { + if m.m == nil { + return nil + } else { + return m.m[key] + } +} + +func (m *Map) Get(key interface{}) interface{} { + m.RLock() + defer m.RUnlock() + return m.UnsafeGet(key) +} + +func (m *Map) UnsafeSet(key interface{}, value interface{}) { + m.init() + m.m[key] = value +} + +func (m *Map) Set(key interface{}, value interface{}) { + m.Lock() + defer m.Unlock() + m.UnsafeSet(key, value) +} + +func (m *Map) TestAndSet(key interface{}, value interface{}) interface{} { + m.Lock() + defer m.Unlock() + + m.init() + + if v, ok := m.m[key]; ok { + return v + } else { + m.m[key] = value + return nil + } +} + +func (m *Map) UnsafeDel(key interface{}) { + m.init() + delete(m.m, key) +} + +func (m *Map) Del(key interface{}) { + m.Lock() + defer m.Unlock() + m.UnsafeDel(key) +} + +func (m *Map) UnsafeLen() int { + if m.m == nil { + return 0 + } else { + return len(m.m) + } +} + +func (m *Map) Len() int { + m.RLock() + defer m.RUnlock() + return m.UnsafeLen() +} + +func (m *Map) UnsafeRange(f func(interface{}, interface{})) { + if m.m == nil { + return + } + for k, v := range m.m { + f(k, v) + } +} + +func (m *Map) RLockRange(f func(interface{}, interface{})) { + m.RLock() + defer m.RUnlock() + m.UnsafeRange(f) +} + +func (m *Map) LockRange(f func(interface{}, interface{})) { + m.Lock() + defer m.Unlock() + m.UnsafeRange(f) +} + +func MapToJsonString(param map[string]interface{}) string { + dataType, _ := json.Marshal(param) + dataString := string(dataType) + return dataString +} +func JsonStringToMap(str string) map[string]interface{} { + var tempMap map[string]interface{} + _ = json.Unmarshal([]byte(str), &tempMap) + return tempMap +} +func GetSwitchFromOptions(Options map[string]interface{}, key string) (result bool) { + if flag, ok := Options[key]; !ok || flag == 1 { + return true + } + return false +} diff --git a/utils/md5.go b/utils/md5.go new file mode 100644 index 000000000..8e3531668 --- /dev/null +++ b/utils/md5.go @@ -0,0 +1,13 @@ +package utils + +import ( + "crypto/md5" + "encoding/hex" +) + +func Md5(s string) string { + h := md5.New() + h.Write([]byte(s)) + cipher := h.Sum(nil) + return hex.EncodeToString(cipher) +} diff --git a/utils/platform_number_id_to_name.go b/utils/platform_number_id_to_name.go new file mode 100644 index 000000000..e9a3f7fed --- /dev/null +++ b/utils/platform_number_id_to_name.go @@ -0,0 +1,66 @@ +package utils + +// fixme 1<--->IOS 2<--->Android 3<--->Windows +//fixme 4<--->OSX 5<--->Web 6<--->MiniWeb 7<--->Linux + +const ( + //Platform ID + IOSPlatformID = 1 + AndroidPlatformID = 2 + WindowsPlatformID = 3 + OSXPlatformID = 4 + WebPlatformID = 5 + MiniWebPlatformID = 6 + LinuxPlatformID = 7 + + //Platform string match to Platform ID + IOSPlatformStr = "IOS" + AndroidPlatformStr = "Android" + WindowsPlatformStr = "Windows" + OSXPlatformStr = "OSX" + WebPlatformStr = "Web" + MiniWebPlatformStr = "MiniWeb" + LinuxPlatformStr = "Linux" + + //terminal types + TerminalPC = "PC" + TerminalMobile = "Mobile" +) + +var PlatformID2Name = map[int32]string{ + IOSPlatformID: IOSPlatformStr, + AndroidPlatformID: AndroidPlatformStr, + WindowsPlatformID: WindowsPlatformStr, + OSXPlatformID: OSXPlatformStr, + WebPlatformID: WebPlatformStr, + MiniWebPlatformID: MiniWebPlatformStr, + LinuxPlatformID: LinuxPlatformStr, +} +var PlatformName2ID = map[string]int32{ + IOSPlatformStr: IOSPlatformID, + AndroidPlatformStr: AndroidPlatformID, + WindowsPlatformStr: WindowsPlatformID, + OSXPlatformStr: OSXPlatformID, + WebPlatformStr: WebPlatformID, + MiniWebPlatformStr: MiniWebPlatformID, + LinuxPlatformStr: LinuxPlatformID, +} +var Platform2class = map[string]string{ + IOSPlatformStr: TerminalMobile, + AndroidPlatformStr: TerminalMobile, + MiniWebPlatformStr: TerminalMobile, + WindowsPlatformStr: TerminalPC, + OSXPlatformStr: TerminalPC, + WebPlatformStr: TerminalPC, + LinuxPlatformStr: TerminalPC, +} + +func PlatformIDToName(num int32) string { + return PlatformID2Name[num] +} +func PlatformNameToID(name string) int32 { + return PlatformName2ID[name] +} +func PlatformNameToClass(name string) string { + return Platform2class[name] +} diff --git a/utils/strings.go b/utils/strings.go new file mode 100644 index 000000000..e117f3cc9 --- /dev/null +++ b/utils/strings.go @@ -0,0 +1,41 @@ +/* +** description(""). +** copyright('tuoyun,www.tuoyun.net'). +** author("fg,Gordon@tuoyun.net"). +** time(2021/4/8 15:09). + */ +package utils + +import "strconv" + +func IntToString(i int) string { + return strconv.FormatInt(int64(i), 10) +} + +func StringToInt(i string) int { + j, _ := strconv.Atoi(i) + return j +} +func StringToInt64(i string) int64 { + j, _ := strconv.ParseInt(i, 10, 64) + return j +} + +//judge a string whether in the string list +func IsContain(target string, List []string) bool { + + for _, element := range List { + + if target == element { + return true + } + } + return false + +} +func InterfaceArrayToStringArray(data []interface{}) (i []string) { + for _, param := range data { + i = append(i, param.(string)) + } + return i +} diff --git a/utils/time_format.go b/utils/time_format.go new file mode 100644 index 000000000..91e6ddd3b --- /dev/null +++ b/utils/time_format.go @@ -0,0 +1,72 @@ +/* +** description(""). +** copyright('tuoyun,www.tuoyun.net'). +** author("fg,Gordon@tuoyun.net"). +** time(2021/2/22 11:52). + */ +package utils + +import ( + "strconv" + "time" +) + +const ( + TimeOffset = 8 * 3600 //8 hour offset + HalfOffset = 12 * 3600 //Half-day hourly offset +) + +//Get the current timestamp by Second +func GetCurrentTimestampBySecond() int64 { + return time.Now().Unix() +} + +//Convert timestamp to time.Time type +func UnixSecondToTime(second int64) time.Time { + return time.Unix(second, 0) +} + +//Get the current timestamp by Nano +func GetCurrentTimestampByNano() int64 { + return time.Now().UnixNano() +} + +//Get the current timestamp by Mill +func GetCurrentTimestampByMill() int64 { + return time.Now().UnixNano() / 1e6 +} + +//Get the timestamp at 0 o'clock of the day +func GetCurDayZeroTimestamp() int64 { + timeStr := time.Now().Format("2006-01-02") + t, _ := time.Parse("2006-01-02", timeStr) + return t.Unix() - TimeOffset +} + +//Get the timestamp at 12 o'clock on the day +func GetCurDayHalfTimestamp() int64 { + return GetCurDayZeroTimestamp() + HalfOffset + +} + +//Get the formatted time at 0 o'clock of the day, the format is "2006-01-02_00-00-00" +func GetCurDayZeroTimeFormat() string { + return time.Unix(GetCurDayZeroTimestamp(), 0).Format("2006-01-02_15-04-05") +} + +//Get the formatted time at 12 o'clock of the day, the format is "2006-01-02_12-00-00" +func GetCurDayHalfTimeFormat() string { + return time.Unix(GetCurDayZeroTimestamp()+HalfOffset, 0).Format("2006-01-02_15-04-05") +} +func GetTimeStampByFormat(datetime string) string { + timeLayout := "2006-01-02 15:04:05" + loc, _ := time.LoadLocation("Local") + tmp, _ := time.ParseInLocation(timeLayout, datetime, loc) + timestamp := tmp.Unix() + return strconv.FormatInt(timestamp, 10) +} + +func TimeStringFormatTimeUnix(timeFormat string, timeSrc string) int64 { + tm, _ := time.Parse(timeFormat, timeSrc) + return tm.Unix() +}