574 lines
16 KiB
Go
574 lines
16 KiB
Go
package clawdoll
|
||
|
||
import (
|
||
"container/list"
|
||
"crypto/md5"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io/ioutil"
|
||
"math/rand"
|
||
"mongo.games.com/game/common"
|
||
rule "mongo.games.com/game/gamerule/clawdoll"
|
||
"mongo.games.com/game/gamesrv/base"
|
||
"mongo.games.com/game/proto"
|
||
"mongo.games.com/game/protocol/clawdoll"
|
||
"mongo.games.com/game/protocol/machine"
|
||
"mongo.games.com/game/protocol/webapi"
|
||
"mongo.games.com/goserver/core/basic"
|
||
"mongo.games.com/goserver/core/logger"
|
||
"mongo.games.com/goserver/core/netlib"
|
||
"mongo.games.com/goserver/core/task"
|
||
"mongo.games.com/goserver/core/timer"
|
||
"mongo.games.com/goserver/srvlib"
|
||
"net/http"
|
||
"net/url"
|
||
"time"
|
||
)
|
||
|
||
type PlayerData struct {
|
||
SnId int32
|
||
Head int32 //头像框
|
||
VIP int32 //VIP帐号 等级
|
||
Name string //名字
|
||
Sex int32 //性别
|
||
IsRob bool
|
||
|
||
Coin int64
|
||
gainCoin int64 //本局赢的金币
|
||
taxCoin int64 //本局税收
|
||
isBilled bool //是否结算
|
||
CurIsWin int64 //当局输赢 负数:输 正数:赢
|
||
|
||
InviterId int32 //邀请人Id
|
||
BeUnderAgentCode string //隶属经销商(推广人)
|
||
IsPlayerFirst bool
|
||
|
||
Platform string //平台
|
||
Channel string //渠道信息
|
||
PackageID string //推广包标识 对应客户端的packagetag
|
||
flag int
|
||
}
|
||
|
||
type SceneEx struct {
|
||
*base.Scene // 场景
|
||
logic *rule.Logic //
|
||
players map[int32]*PlayerEx // 玩家信息
|
||
PlayerBackup map[int32]*PlayerData // 本局离场玩家数据备份
|
||
seats []*PlayerEx // 本局游戏中的玩家状态数据
|
||
|
||
waitPlayers *list.List
|
||
playingSnid int32 // 正在玩的玩家snid
|
||
|
||
RoundId int // 局数,第几局
|
||
robotNum int // 参与游戏的机器人数量
|
||
logid string
|
||
machineId int //娃娃机ID
|
||
machineConn *netlib.Session //娃娃机链接
|
||
machineStatus int32 //娃娃机链接状态 0:离线 1:在线
|
||
|
||
payCoinTimeHandle timer.TimerHandle //上分投币托管handle
|
||
grabTimerHandle timer.TimerHandle //下抓托管handle
|
||
}
|
||
|
||
// 游戏是否能开始
|
||
func (this *SceneEx) CanStart() bool {
|
||
//人数>=1自动开始
|
||
if len(this.players) >= 1 && (this.GetRealPlayerNum() >= 1 || this.IsPreCreateScene() || this.IsHasPlaying()) {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
// 从房间删除玩家
|
||
func (this *SceneEx) delPlayer(p *base.Player) {
|
||
if p, exist := this.players[p.SnId]; exist {
|
||
//this.seats[p.GetPos()] = nil
|
||
delete(this.players, p.SnId)
|
||
this.RemoveWaitPlayer(p.SnId)
|
||
}
|
||
}
|
||
|
||
// 广播玩家进入
|
||
func (this *SceneEx) BroadcastPlayerEnter(p *base.Player, reason int) {
|
||
pack := &clawdoll.SCCLAWDOLLPlayerEnter{}
|
||
|
||
pack.Data = &clawdoll.CLAWDOLLPlayerDigestInfo{}
|
||
|
||
pack.Data.SnId = p.GetSnId()
|
||
pack.Data.Head = p.Head
|
||
pack.Data.HeadUrl = p.HeadUrl
|
||
pack.Data.Name = p.Name
|
||
|
||
if playerEx, ok := p.ExtraData.(*PlayerEx); ok {
|
||
pack.Data.Stat = playerEx.clawDollState
|
||
}
|
||
|
||
proto.SetDefaults(pack)
|
||
|
||
this.Broadcast(int(clawdoll.CLAWDOLLPacketID_PACKET_SC_PlayerEnter), pack, p.GetSid())
|
||
}
|
||
|
||
// 广播玩家离开
|
||
func (this *SceneEx) BroadcastPlayerLeave(p *base.Player, reason int) {
|
||
scLeavePack := &clawdoll.SCCLAWDOLLPlayerLeave{
|
||
SnId: proto.Int32(p.SnId),
|
||
}
|
||
proto.SetDefaults(scLeavePack)
|
||
|
||
this.Broadcast(int(clawdoll.CLAWDOLLPacketID_PACKET_SC_PlayerLeave), scLeavePack, p.GetSid())
|
||
}
|
||
|
||
// 玩家进入事件
|
||
func (this *SceneEx) OnPlayerEnter(p *base.Player, reason int) {
|
||
this.BroadcastPlayerEnter(p, reason)
|
||
|
||
machineId := this.GetDBGameFree().GetId() % 6080000
|
||
machineInfo := this.GetMachineServerInfo(machineId, p.Platform)
|
||
if machineInfo == nil {
|
||
return
|
||
}
|
||
|
||
if this.GetPlayerNum() >= 1 {
|
||
logger.Logger.Trace("Clawdoll (*SceneEx) OnPlayerEnter, GetPlayerNum = ", this.GetPlayerNum())
|
||
// 发送http Get请求 恢复直播间流
|
||
//operateTask(this, 2, rule.Zego_ResumeRTCStream, p.Platform)
|
||
}
|
||
}
|
||
|
||
// 玩家离开事件
|
||
func (this *SceneEx) OnPlayerLeave(p *base.Player, reason int) {
|
||
this.delPlayer(p)
|
||
this.BroadcastPlayerLeave(p, reason)
|
||
|
||
machineId := this.GetDBGameFree().GetId() % 6080000
|
||
machineInfo := this.GetMachineServerInfo(machineId, p.Platform)
|
||
if machineInfo == nil {
|
||
return
|
||
}
|
||
|
||
if len(this.players) <= 0 {
|
||
logger.Logger.Trace("Clawdoll (*SceneEx) OnPlayerLeave, cur player num = ", len(this.players))
|
||
// 发送http Get请求 关闭直播间流
|
||
//operateTask(this, 2, rule.Zego_ForbidRTCStream, p.Platform)
|
||
}
|
||
|
||
}
|
||
func (this *SceneEx) SceneDestroy(force bool) {
|
||
//销毁房间
|
||
this.Scene.Destroy(force)
|
||
}
|
||
|
||
func (e *SceneEx) playerOpPack(snId int32, opcode int, opRetCode clawdoll.OpResultCode, params []int64) *clawdoll.SCCLAWDOLLOp {
|
||
pack := &clawdoll.SCCLAWDOLLOp{
|
||
SnId: proto.Int32(snId),
|
||
OpCode: proto.Int32(int32(opcode)),
|
||
Params: params,
|
||
OpRetCode: opRetCode,
|
||
}
|
||
|
||
proto.SetDefaults(pack)
|
||
return pack
|
||
}
|
||
|
||
// OnPlayerSCOp 发送玩家操作情况
|
||
func (e *SceneEx) OnPlayerSCOp(p *base.Player, opcode int, opRetCode clawdoll.OpResultCode, params []int64) {
|
||
pack := e.playerOpPack(p.SnId, opcode, opRetCode, params)
|
||
p.SendToClient(int(clawdoll.CLAWDOLLPacketID_PACKET_SC_PLAYEROP), pack)
|
||
logger.Logger.Tracef("OnPlayerSCOp %s", pack)
|
||
}
|
||
|
||
// 房间信息打包
|
||
func (this *SceneEx) ClawdollCreateRoomInfoPacket(s *base.Scene, p *base.Player) interface{} {
|
||
pack := &clawdoll.SCCLAWDOLLRoomInfo{
|
||
RoomId: proto.Int(s.GetSceneId()),
|
||
GameId: proto.Int(s.GetGameId()),
|
||
RoomMode: proto.Int(s.GetSceneMode()),
|
||
Params: common.CopySliceInt64ToInt32(s.Params),
|
||
State: proto.Int(s.GetSceneState().GetState()),
|
||
TimeOut: proto.Int(s.GetSceneState().GetTimeout(s)),
|
||
TotalPlayer: proto.Int(len(this.players)),
|
||
RoundId: proto.Int(this.RoundId),
|
||
ParamsEx: nil,
|
||
GameFreeId: 0,
|
||
BaseScore: proto.Int32(this.GetBaseScore()),
|
||
}
|
||
|
||
// 玩家信息
|
||
for _, playerEx := range this.players {
|
||
|
||
if p.SnId == playerEx.SnId {
|
||
pd := &clawdoll.CLAWDOLLPlayerData{
|
||
SnId: proto.Int32(playerEx.SnId),
|
||
Name: proto.String(playerEx.Name),
|
||
Head: proto.Int32(playerEx.Head),
|
||
Sex: proto.Int32(playerEx.Sex),
|
||
Coin: proto.Int64(playerEx.Coin),
|
||
Flag: proto.Int(playerEx.GetFlag()),
|
||
HeadOutLine: proto.Int32(playerEx.HeadOutLine),
|
||
VIP: proto.Int32(playerEx.VIP),
|
||
|
||
WinCoin: proto.Int64(playerEx.gainCoin),
|
||
ClawDollState: proto.Int32(playerEx.clawDollState),
|
||
}
|
||
|
||
pack.Players = append(pack.Players, pd)
|
||
}
|
||
}
|
||
|
||
proto.SetDefaults(pack)
|
||
if p != nil {
|
||
p.SyncFlag()
|
||
}
|
||
|
||
return pack
|
||
}
|
||
|
||
func NewClawdollSceneData(s *base.Scene) *SceneEx {
|
||
sceneEx := &SceneEx{
|
||
Scene: s,
|
||
logic: new(rule.Logic),
|
||
players: make(map[int32]*PlayerEx),
|
||
PlayerBackup: make(map[int32]*PlayerData),
|
||
waitPlayers: list.New(),
|
||
}
|
||
|
||
return sceneEx
|
||
}
|
||
|
||
func (this *SceneEx) init() bool {
|
||
this.Clear()
|
||
return true
|
||
}
|
||
|
||
// 检查上分投币是否合法
|
||
func (this *SceneEx) CheckPayCoinOp(p *PlayerEx) bool {
|
||
if p == nil {
|
||
return false
|
||
}
|
||
|
||
if p.SnId == this.playingSnid {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
// 检查移动是否合法
|
||
func (this *SceneEx) CheckMoveOp(p *PlayerEx) bool {
|
||
if p == nil {
|
||
return false
|
||
}
|
||
|
||
if p.SnId == this.playingSnid {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
// 下抓是否合法
|
||
func (this *SceneEx) CheckGrapOp(p *PlayerEx) bool {
|
||
if p == nil {
|
||
return false
|
||
}
|
||
|
||
if p.SnId == this.playingSnid {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (this *SceneEx) Clear() {
|
||
this.robotNum = 0
|
||
this.PlayerBackup = make(map[int32]*PlayerData)
|
||
this.RoundId = 0
|
||
|
||
for e := this.waitPlayers.Front(); e != nil; e = e.Next() {
|
||
if e != nil {
|
||
p := e.Value.(*PlayerEx)
|
||
p.Clear(0)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 是否有玩家正在玩
|
||
func (this *SceneEx) IsHasPlaying() bool {
|
||
if this.playingSnid == 0 {
|
||
return false
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// 等待下一个玩家
|
||
func (this *SceneEx) ReStartGame() {
|
||
logger.Logger.Trace("(*SceneEx) ReStartGame, playingSnid = ", this.playingSnid)
|
||
this.playingSnid = 0
|
||
}
|
||
|
||
func (this *SceneEx) BackupPlayer(p *PlayerEx, isBilled bool) {
|
||
this.PlayerBackup[p.SnId] = &PlayerData{
|
||
SnId: p.SnId,
|
||
gainCoin: p.gainCoin,
|
||
taxCoin: p.taxCoin,
|
||
isBilled: isBilled,
|
||
IsRob: p.IsRob,
|
||
Coin: p.Coin,
|
||
Head: p.Head,
|
||
flag: p.GetFlag(),
|
||
Platform: p.Platform,
|
||
Channel: p.Channel,
|
||
PackageID: p.PackageID,
|
||
CurIsWin: p.CurIsWin,
|
||
Name: p.Name,
|
||
Sex: p.Sex,
|
||
VIP: p.VIP,
|
||
InviterId: p.InviterId,
|
||
IsPlayerFirst: this.IsPlayerFirst(p.Player),
|
||
BeUnderAgentCode: p.BeUnderAgentCode,
|
||
}
|
||
}
|
||
|
||
func (this *SceneEx) GetPlayGrabType(player *PlayerEx) int32 {
|
||
if player == nil {
|
||
return rule.ClawWeak
|
||
}
|
||
|
||
if this.RoundId/2 == 0 && this.RoundId != 0 {
|
||
return rule.ClawStrong
|
||
}
|
||
|
||
return rule.ClawWeak
|
||
}
|
||
|
||
func (this *SceneEx) AddWaitPlayer(player *PlayerEx) {
|
||
if player == nil {
|
||
return
|
||
}
|
||
|
||
this.waitPlayers.PushBack(player)
|
||
}
|
||
|
||
func (this *SceneEx) RemoveWaitPlayer(SnId int32) bool {
|
||
l := this.waitPlayers
|
||
for e := l.Front(); e != nil; e = e.Next() {
|
||
if p := e.Value.(*PlayerEx); p.SnId == SnId {
|
||
this.waitPlayers.Remove(e)
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
func (this *SceneEx) GetPlayingEx() *PlayerEx {
|
||
if this.playingSnid == 0 {
|
||
return nil
|
||
}
|
||
|
||
return this.players[this.playingSnid]
|
||
}
|
||
|
||
func (this *SceneEx) SetPlayingState(state int32) {
|
||
if this.playingSnid == 0 {
|
||
return
|
||
}
|
||
|
||
playerEx := this.players[this.playingSnid]
|
||
if playerEx != nil {
|
||
oldState := playerEx.GetClawState()
|
||
if oldState != state {
|
||
if oldState == rule.ClawDollPlayerStateWait && (state >= rule.ClawDollPlayerStateStart && state <= rule.ClawDollPlayerWaitPayCoin) {
|
||
ClawdollBroadcastPlayingInfo(this.Scene)
|
||
}
|
||
|
||
if state == rule.ClawDollPlayerStateWait && (oldState >= rule.ClawDollPlayerStateStart && oldState <= rule.ClawDollPlayerWaitPayCoin) {
|
||
ClawdollBroadcastPlayingInfo(this.Scene)
|
||
}
|
||
}
|
||
|
||
playerEx.SetClawState(state)
|
||
}
|
||
}
|
||
|
||
// 时间到 系统开始下抓
|
||
func (this *SceneEx) TimeOutPlayGrab() bool {
|
||
|
||
playerEx := this.players[this.playingSnid]
|
||
if playerEx != nil {
|
||
grapType := this.GetPlayGrabType(playerEx)
|
||
this.OnPlayerSMGrabOp(this.playingSnid, int32(this.machineId), grapType)
|
||
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// OnPlayerSMGrabOp 下抓 //1-弱力抓 2 -强力抓 3-必出抓
|
||
func (this *SceneEx) OnPlayerSMGrabOp(SnId, Id, GrabType int32) {
|
||
|
||
pack := &machine.SMDollMachineGrab{
|
||
Snid: proto.Int32(SnId),
|
||
Id: proto.Int32(Id),
|
||
TypeId: proto.Int32(GrabType),
|
||
}
|
||
|
||
this.SendToMachine(int(machine.DollMachinePacketID_PACKET_SMDollMachineGrab), pack)
|
||
}
|
||
|
||
// OnPlayerSCOp 发送玩家操作情况 1-前 2-后 3-左 4-右 5-投币
|
||
func (this *SceneEx) OnPlayerSMPerateOp(SnId, Id, Perate int32) {
|
||
|
||
pack := &machine.SMDollMachineoPerate{
|
||
Snid: proto.Int32(SnId),
|
||
Id: proto.Int32(Id),
|
||
Perate: proto.Int32(Perate),
|
||
}
|
||
|
||
this.SendToMachine(int(machine.DollMachinePacketID_PACKET_SMDollMachinePerate), pack)
|
||
}
|
||
|
||
// 向娃娃机主机发送消息
|
||
func (this *SceneEx) SendToMachine(pid int, msg interface{}) {
|
||
this.machineConn = srvlib.ServerSessionMgrSington.GetSession(1, 10, 1001)
|
||
if this.machineConn != nil {
|
||
this.machineConn.Send(pid, msg)
|
||
} else {
|
||
logger.Logger.Error("MachineConn is nil !")
|
||
}
|
||
}
|
||
|
||
// 下抓托管handle
|
||
func (this *SceneEx) GrabTimerHandle() {
|
||
if this.grabTimerHandle != timer.TimerHandle(0) {
|
||
timer.StopTimer(this.grabTimerHandle)
|
||
this.grabTimerHandle = timer.TimerHandle(0)
|
||
}
|
||
|
||
this.grabTimerHandle, _ = timer.StartTimer(timer.TimerActionWrapper(func(h timer.TimerHandle, ud interface{}) bool {
|
||
|
||
logger.Logger.Tracef("ClawDoll SceneEx GrabTimerHandle: TimeOut: %v", rule.ClawDollSceneGrapTimeOut)
|
||
this.TimeOutPlayGrab()
|
||
return true
|
||
}), nil, rule.ClawDollSceneGrapTimeOut, 1)
|
||
}
|
||
|
||
// 上分投币托管handle
|
||
func (this *SceneEx) PayCoinTimeHandle() {
|
||
if this.payCoinTimeHandle != timer.TimerHandle(0) {
|
||
timer.StopTimer(this.payCoinTimeHandle)
|
||
this.payCoinTimeHandle = timer.TimerHandle(0)
|
||
}
|
||
|
||
this.payCoinTimeHandle, _ = timer.StartTimer(timer.TimerActionWrapper(func(h timer.TimerHandle, ud interface{}) bool {
|
||
logger.Logger.Tracef("ClawDoll SceneEx PayCoinTimeHandle: TimeOut: %v", rule.ClawDollScenePayCoinTimeOut)
|
||
return true
|
||
}), nil, rule.ClawDollScenePayCoinTimeOut, 1)
|
||
}
|
||
|
||
// actionType : 1 禁止推流 ForbidRTCStream
|
||
// actionType : 2 恢复推流 ResumeRTCStream
|
||
func operateTask(sceneEx *SceneEx, times int, actionType int, platform string) {
|
||
|
||
machineId := sceneEx.GetDBGameFree().GetId() % 6080000
|
||
machineInfo := sceneEx.GetMachineServerInfo(machineId, platform)
|
||
if machineInfo == nil {
|
||
logger.Logger.Errorf("ZegoRTCStreamAction machineId: %v, platform: %v", machineId, platform)
|
||
return
|
||
}
|
||
|
||
actionTypeStr := "NoneAction"
|
||
if actionType == rule.Zego_ForbidRTCStream {
|
||
actionTypeStr = "ForbidRTCStream"
|
||
} else {
|
||
actionTypeStr = "ResumeRTCStream"
|
||
}
|
||
|
||
logger.Logger.Tracef("ZegoRTCStreamAction: actionTypeStr: %v, machineId: %v, machineInfo: %v", actionTypeStr, machineId, machineInfo)
|
||
|
||
var resp rule.ZegoForbidRTCResp
|
||
task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
|
||
resp = ZegoRTCStreamAction(actionTypeStr, machineInfo)
|
||
return nil
|
||
}), task.CompleteNotifyWrapper(func(i interface{}, t task.Task) {
|
||
if resp.Error == 0 && resp.Code == 0 && resp.Message == "ok" {
|
||
|
||
} else {
|
||
logger.Logger.Errorf("ZegoForbidRTCResp Code: %v, Error: %v, Message: %v", resp.Code, resp.Error, resp.Message)
|
||
if times > 0 {
|
||
timer.StartTimer(timer.TimerActionWrapper(func(h timer.TimerHandle, ud interface{}) bool {
|
||
operateTask(sceneEx, times-1, actionType, platform)
|
||
return true
|
||
}), nil, time.Second*5, 1)
|
||
}
|
||
}
|
||
}), "ZegoRTCStream_Action").Start()
|
||
}
|
||
|
||
func ZegoRTCStreamAction(Action string, machineInfo *webapi.MachineInfo) rule.ZegoForbidRTCResp {
|
||
|
||
var zegoForbidRTCResp = rule.ZegoForbidRTCResp{
|
||
Error: 1,
|
||
}
|
||
|
||
timestamp := time.Now().Unix()
|
||
queryParams := url.Values{}
|
||
|
||
queryParams.Set("StreamId", "test")
|
||
queryParams.Set("Sequence", fmt.Sprintf("%d", timestamp))
|
||
|
||
// 生成16进制随机字符串(16位)
|
||
nonce := make([]byte, 8)
|
||
rand.Read(nonce)
|
||
hexNonce := hex.EncodeToString(nonce)
|
||
// 生成签名
|
||
signature := generateSignature(uint32(machineInfo.AppId), machineInfo.ServerSecret, hexNonce, timestamp)
|
||
authParams := url.Values{}
|
||
authParams.Set("AppId", fmt.Sprintf("%d", uint32(machineInfo.AppId)))
|
||
//公共参数中的随机数和生成签名的随机数要一致
|
||
authParams.Set("SignatureNonce", hexNonce)
|
||
authParams.Set("SignatureVersion", "2.0")
|
||
//公共参数中的时间戳和生成签名的时间戳要一致
|
||
authParams.Set("Timestamp", fmt.Sprintf("%d", timestamp))
|
||
authParams.Set("Signature", signature)
|
||
|
||
//authParams.Set("IsTest", "true")
|
||
// rtc-api.zego.im 表示使用的产品是云通讯产品,包括了实时音视频(Express Video)、实时音频(Express Audio)、低延迟直播(L3)
|
||
addr := fmt.Sprintf("https://rtc-api.zego.im/?Action=%s&%s&%s", Action, authParams.Encode(), queryParams.Encode())
|
||
|
||
logger.Logger.Tracef("ZegoRTCStreamAction Get addr: %+v", addr)
|
||
|
||
rsp, err := http.Get(addr)
|
||
if err != nil {
|
||
logger.Logger.Errorf("ZegoRTCStreamAction Get err: %v", err)
|
||
return zegoForbidRTCResp
|
||
}
|
||
defer rsp.Body.Close()
|
||
body, err := ioutil.ReadAll(rsp.Body)
|
||
if err != nil {
|
||
logger.Logger.Errorf("ZegoRTCStreamAction ioutil.ReadAll err: %v", err)
|
||
return zegoForbidRTCResp
|
||
}
|
||
logger.Logger.Tracef("ZegoRTCStreamAction Action: %+v, body: %+v", Action, string(body))
|
||
|
||
err = json.Unmarshal(body, &zegoForbidRTCResp)
|
||
if err != nil {
|
||
zegoForbidRTCResp.Error = 5
|
||
logger.Logger.Errorf("ZegoRTCStreamAction %v %v", zegoForbidRTCResp, err.Error())
|
||
return zegoForbidRTCResp
|
||
}
|
||
|
||
zegoForbidRTCResp.Error = 0
|
||
|
||
return zegoForbidRTCResp
|
||
}
|
||
|
||
// 生成签名
|
||
// Signature=md5(AppId + SignatureNonce + ServerSecret + Timestamp)
|
||
func generateSignature(appId uint32, serverSecret, signatureNonce string, timeStamp int64) string {
|
||
data := fmt.Sprintf("%d%s%s%d", appId, signatureNonce, serverSecret, timeStamp)
|
||
|
||
h := md5.New()
|
||
h.Write([]byte(data))
|
||
return hex.EncodeToString(h.Sum(nil))
|
||
}
|