first commit

This commit is contained in:
Yangtao
2025-11-18 17:48:20 +08:00
commit 6e56cab848
196 changed files with 65809 additions and 0 deletions

46
pkg/authmeta/rbac.go Normal file
View File

@ -0,0 +1,46 @@
package authmeta
// 定义通用
type TenantItem struct {
Name string
Type string
Code string
}
type ScopeItem struct {
Name string
Type string
Code string
}
type PrivilegeItem struct {
Name string
Type string
Code string
Children []PrivilegeItem
}
// (comment:(?<name>.+)){0}" json.*`$
type RoleItem struct {
Name string
Code string
UserType string
Desc string
PrivilegeResource []string
PrivilegeInterface []string
}
type AdminUser struct {
Username string
Channel string
ChannelCode string
Nickname string
Desc string
State int32
Password string
PassCipher string
PassSalt string
ExtID string
Roles []string
}

174
pkg/cache/RedisSingleton.go vendored Normal file
View File

@ -0,0 +1,174 @@
package cache
import (
"sync"
redis "github.com/redis/go-redis/v9"
)
// 公共redis单例
var commonInstance *singleton
var commonOnce sync.Once
func CommonClient() *redis.Client {
return GetCommonRedisInstance().RedisClient
}
// 获取公用redis连接
func GetCommonRedisInstance() *singleton {
commonOnce.Do(func() {
commonInstance = &singleton{}
commonInstance.RedisClient = createCommonmRedisClient()
})
return commonInstance
}
// 用户redis单例
var userInstance *singleton
var userOnce sync.Once
func UserClient() *redis.Client {
return GetUserRedisInstance().RedisClient
}
// 获取公用redis连接
func GetUserRedisInstance() *singleton {
userOnce.Do(func() {
userInstance = &singleton{}
userInstance.RedisClient = createUserRedisClient()
})
return userInstance
}
// 用户关系redis单例
var relationInstance *singleton
var relationOnce sync.Once
// 获取relation redis连接
func GetUserRelationRedisInstance() *singleton {
relationOnce.Do(func() {
relationInstance = &singleton{}
relationInstance.RedisClient = createUserRelationRedisClient()
})
return relationInstance
}
// 动态redis单例
var timelineInstance *singleton
var timelineOnce sync.Once
// 获取动态redis连接
func GetTimelineRedisInstance() *singleton {
timelineOnce.Do(func() {
timelineInstance = &singleton{}
timelineInstance.RedisClient = createTimelineRedisClient()
})
return timelineInstance
}
// 活动redis单例
var activityInstance *singleton
var activityOnce sync.Once
func ActivityClient() *redis.Client {
return GetActivityRedisInstance().RedisClient
}
// 获取活动redis连接
func GetActivityRedisInstance() *singleton {
activityOnce.Do(func() {
activityInstance = &singleton{}
activityInstance.RedisClient = createActivityRedisClient()
})
return activityInstance
}
// 公会redis单例
var guildInstance *singleton
var guildOnce sync.Once
func GuildClient() *redis.Client {
return GetGuildRedisInstance().RedisClient
}
// 获取公会redis连接
func GetGuildRedisInstance() *singleton {
guildOnce.Do(func() {
guildInstance = &singleton{}
guildInstance.RedisClient = createGuildRedisClient()
})
return guildInstance
}
// 聊天室redis单例
var chatroomInstance *singleton
var chatroomOnce sync.Once
func RoomClient() *redis.Client {
return GetChatroomRedisInstance().RedisClient
}
// 获取chatroom redis连接
func GetChatroomRedisInstance() *singleton {
chatroomOnce.Do(func() {
chatroomInstance = &singleton{}
chatroomInstance.RedisClient = createChatroomRedisClient()
})
return chatroomInstance
}
// SESSION redis单例
var sessionInstance *singleton
var sessionOnce sync.Once
// 获取Session redis连接
func GetSessionRedisInstance() *singleton {
sessionOnce.Do(func() {
sessionInstance = &singleton{}
sessionInstance.RedisClient = createSessionRedisClient()
})
return sessionInstance
}
// 技能订单redis单例
var skillInstance *singleton
var skillOnce sync.Once
// 获取用户关系redis连接
func GetSkillRedisInstance() *singleton {
skillOnce.Do(func() {
skillInstance = &singleton{}
skillInstance.RedisClient = createSkillRedisClient()
})
return skillInstance
}
// 活动redis单例
var imInstance *singleton
var imOnce sync.Once
// 获取用户关系redis连接
func GetIMRedisInstance() *singleton {
imOnce.Do(func() {
imInstance = &singleton{}
imInstance.RedisClient = createIMRedisClient()
})
return imInstance
}
// Cms系统 redis单例
var cmsInstance *singleton
var cmsOnce sync.Once
func CmsClient() *redis.Client {
return GetCmsRedisInstance().RedisClient
}
// 获取公用redis连接
func GetCmsRedisInstance() *singleton {
cmsOnce.Do(func() {
cmsInstance = &singleton{}
cmsInstance.RedisClient = createCmsRedisClient()
})
return cmsInstance
}

163
pkg/cache/redis_client.go vendored Normal file
View File

@ -0,0 +1,163 @@
package cache
import (
logs "servicebase/pkg/log"
"context"
redis "github.com/redis/go-redis/v9"
"github.com/spf13/viper"
)
type singleton struct {
RedisClient *redis.Client
}
func Ctx() context.Context {
return context.Background()
}
// 创建公共的redis
func createCommonmRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.default.addr"),
Password: viper.GetString("redis.default.pass"),
DB: 0,
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 common redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建user的redis
func createUserRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.default.addr"),
Password: viper.GetString("redis.default.pass"), // no password set
DB: 1,
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 user redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建relation的redis
func createUserRelationRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.default.addr"),
Password: viper.GetString("redis.default.pass"), // no password set
DB: 2,
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 relation redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建timeline的redis
func createTimelineRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.default.addr"),
Password: viper.GetString("redis.default.pass"), // no password set
DB: 3,
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 timeline redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建活动的redis
func createActivityRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.activity.addr"),
Password: viper.GetString("redis.activity.pass"), // no password set
DB: viper.GetInt("redis.activity.db"),
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 activity redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建俱乐部的redis
func createGuildRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.guild.addr"),
Password: viper.GetString("redis.guild.pass"), // no password set
DB: viper.GetInt("redis.guild.db"),
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 guild redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建chatroom的redis
func createChatroomRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.default.addr"),
Password: viper.GetString("redis.default.pass"), // no password set
DB: 6,
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 chatroom redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建session的redis
func createSessionRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.default.addr"),
Password: viper.GetString("redis.default.pass"), // no password set
DB: 7,
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 session redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建用户关系的redis
func createSkillRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.default.addr"),
Password: viper.GetString("redis.default.pass"), // no password set
DB: 8,
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 skill redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建IM的redis
func createIMRedisClient() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: viper.GetString("redis.default.addr"),
Password: viper.GetString("redis.default.pass"), // no password set
DB: 10,
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 im redis连接池返回一个连接" + "#REDIS#")
return client
}
// 创建CMS的redis
func createCmsRedisClient() *redis.Client {
redisHost := viper.GetString("redis.default.addr")
redisPwd := viper.GetString("redis.default.pass")
client := redis.NewClient(&redis.Options{
Addr: redisHost,
Password: redisPwd,
DB: 11,
PoolSize: 32,
MinIdleConns: 4,
})
logs.Info("#REDIS#" + "创建一个 cms redis连接池返回一个连接" + "#REDIS#")
return client
}

511
pkg/cache/redis_key.go vendored Normal file
View File

@ -0,0 +1,511 @@
package cache
import "time"
const (
// 验证码的RedisKEY + biz_type + mobile = verification code
AUTH_CODE_KEY_PREV = "AUTH_CODE_KEY_"
SMS_CODE_EXPIRATION_KEY = "SMS_CODE_EXPIRATION_"
)
const (
//微信登录用户的KEY + WX_SESSION_ID = WxUser json
WX_SESSION_USER_KEY_PREV = "WX_SESSION_USER_KEY_"
//微信unionID绑定的嗯嗯ID + WX_UNION_ID = USER_ID
WX_UNIONID_BINDED_ID_PREV = "WX_UNIONID_BINDED_ID_"
)
const (
//管理员Session + staff_id = staffmodel
SESSION_STAFF_PREV = "SESSION_STAFF_"
BANNER_KEY_PREV = "BANNER_LIST_KEY_PREV_"
LuckBoxPrize = "LUCK_BOX_PRIZE"
HotBoxPrize = "HOT_BOX_PRIZE"
HotBoxAngerPrize = "HOT_BOX_ANGER_PRIZE"
PetPrize = "PET_PRIZE"
// 下载页
COMMON_DOWNLOAD_DATA = "COMMON_DOWNLOAD_DATA"
WITHDRAW_WHITE_LIST = "WITHDRAW_SPEC_ACCOUNT_LIST_KEY"
JACKPOT_WHITE_LIST = "HIT_EGG_SINGLE_POOL_USER"
HomeDataConfig = "HOME_DATA_CONFIG"
AppSettingKey = "APP_SETTING_KEY"
PRODUCT_KEY_PREV = "PRODUCT_LIST_KEY"
ProductNCoin_KEY_PREV = "PRODUCT_N_COIN_LIST_KEY"
//礼物列表 = string
GIFT_WALL_VIEW_LIST_KEY = "TOTALLY_GIFT_LIST_KEY"
// 审核帐号列表
APPLE_APPROVE_ID_LIST_KEY = "APPLE_APPROVE_ID_LIST"
)
const (
// 验证码的RedisKEY +biz_type+mobile = verification code
VERICODE_KEY_PREV = "VERICODE_KEY_"
// 验证码的默认过期时间 5分钟
VERICODE_DEFAULT_EXPIRE_DURATION = time.Minute * 5
// 登录验证码的过期时间 5分钟
SIGN_IN_VERICODE_DURATION = time.Minute * 5
// 验证码发送时间 + biz_type + mobile = 时间
VERICODE_SEND_TIME_PREV = "VERICODE_SEND_TIME_"
// 授权令牌的redisKEY +access_token = user_id
// ACCESSTOKEN_KEY_PREV = "ACCESSTOKEN_"
//accesstoken 过期时间为30天
// ACCESSTOKEN_EXPIRE_DURATION = time.Hour * 24 * 180
// 用户信息的RedisKEY +user_id = user_model_json
USER_DETAIL_KEY_PREV = "USER_DETAIL_"
// 用户VIP信息的RedisKEY + user_id = user_vip_model_json
USER_VIP_MODEL_KEY_PREV = "USER_VIP_MODEL_"
// 用户粉丝列表 + user_id = zset 按关注时间
USER_FAN_LIST_KEY_PREV = "USER_FAN_LIST_KEY_"
// 用户关注列表 + user_id = zset 按关注时间
USER_FOLLOW_LIST_KEY_PREV = "USER_FOLLOW_LIST_KEY_"
// 用户黑名单列表 + user_id = set
USER_BLACK_LIST_KEY_PREV = "USER_BLACK_LIST_KEY_"
// 数据版本号缓存
DATA_VERSION_KEY = "DATA_VERSION_KEY"
// 兴趣列表的KEY
DATA_HOBBIES_KEY = "DATA_HOBBIES_KEY"
// 黑名单关键词的KEY
DATA_BLACK_KEYWORD_KEY = "DATA_BLACK_KEYWORD_KEY"
//AppSetting的KEY
APP_SETTING_KEY = "APP_SETTING_KEY"
// 首页内容配置
HOME_DATA_CONFIG = "HOME_DATA_CONFIG"
// 七牛图片上传的TOKEN EKY
QINIU_PHOTO_UPLOAD_KEY = "QINIU_PHOTO_UPLOAD_KEY"
// 运营后台七牛图片上传的TOKEN EKY
QINIU_PHOTO_UPLOAD_KEY_FOR_OPERATION = "QINIU_PHOTO_UPLOAD_KEY_FOR_OPERATION"
// 七牛audio上传的TOKEN EKY
QINIU_AUDIO_UPLOAD_KEY = "QINIU_AUDIO_UPLOAD_KEY"
// 七牛VIDEO上传的TOKEN EKY
QINIU_VIDEO_UPLOAD_KEY = "QINIU_VIDEO_UPLOAD_KEY"
// 用户照片墙 + user_id = string
USER_PHOTO_LIST_KEY_PREV = "USER_PHOTO_LIST_KEY_"
// 用户兴趣 + user_id = string
USER_HOBBIES_LIST_KEY_PREV = "USER_HOBBIES_LIST_KEY_"
// 用户Tag + user_id = string
USER_TAG_LIST_KEY_PREV = "USER_TAG_LIST_KEY_"
// 用户Tag + user_id = string
USER_CS_SET = "USER_CS_SET"
// 用户属性 + user_id = string
USER_PROP_LIST_KEY_PREV = "USER_PROP_LIST_KEY_"
// 用户别名 + user_id = hash
USER_ALIAS_NAME_KEY_PREV = "USER_ALIAS_NAME_KEY_"
// 用户最后活跃时间
USER_LAST_HEART_TIME = "USER_LAST_HEART_TIME"
// 商品列表 = string
PRODUCT_LIST_KEY = "PRODUCT_LIST_KEY"
PRODUCT_N_COIN_LIST_KEY = "PRODUCT_N_COIN_LIST_KEY"
// 正常的礼物列表 = string
GIFT_LIST_KEY = "GIFT_LIST_KEY"
GENERAL_RANK_REWARD_KEY = "GENERAL_RANK_CHARM_REWARD_KEY"
GENERAL_RANK_BALLER_KEY = "GENERAL_RANK_BALLER_REWARD_KEY"
// 全部礼物列表 包括过期的
TOTALLY_GIFT_LIST_KEY = "TOTALLY_GIFT_LIST_KEY"
// 礼物详情 + giftId = DBModel
GIFT_DETAIL_KEY_PREV = "GIFT_DETAIL_"
// 首页tab列表 = string
HOME_TAB_LIST_KEY = "HOME_TAB_LIST_KEY"
//banner列表 + position_code = string
BANNER_LIST_KEY_PREV = "app:data:banner"
// 银行列表
BANK_LIST_KEY = "BANK_LIST_KEY"
// 聊天室模板列表
CHATROOM_TEMPLATE_LIST_KEY = "CHATROOM_TEMPLATE_LIST_KEY"
// 用户收藏的聊天室KEY + user_id
USER_COLLECT_CHATROOM_LIST_KEY_PREV = "USER_COLLECT_CHATROOM_LIST_"
// 用户访问的聊天室KEY + user_id
USER_VISITED_CHATROOM_LIST_KEY_PREV = "USER_VISITED_CHATROOM_LIST_"
// 聊天室周榜key + chatroomid + 年 + 周 = 总魅力 某个聊天室当前周总魅力值
CHATROOM_WEEKLY_TOTAL_INCOME_KEY = "CHATROOM_WEEKLY_TOTAL_INCOME_"
// 聊天室周榜key + chatroomid + 年 + 周 = 总消费 某个聊天室当前周总贡献(流水)
CHATROOM_WEEKLY_TOTAL_CONSUME_KEY = "CHATROOM_WEEKLY_TOTAL_CONSUME_"
// 聊天室周榜key + 年 + 周 = ZSCORE 周聊天室收益排行榜
CHATROOM_WEEKLY_ROOM_REVENUE_TOP_LIST = "CHATROOM_WEEKLY_ROOM_REVENUE_TOP_LIST"
// 房间周贡献榜KEY + chatroomid + 年 + 周 = zset集合 某个聊天室的周贡献榜单
CHATROOM_WEEKLY_CONSUME_USER_LIST_KEY_PREV = "CHATROOM_WEEKLY_CONSUME_USER_LIST_KEY_"
// 房间月贡献榜KEY + chatroomid + 年月 = zset集合 某个聊天室的月贡献榜单
CHATROOM_MONTH_CONSUME_USER_LIST_KEY_PREV = "CHATROOM_MONTH_CONSUME_USER_LIST_KEY_"
// 房间总贡献榜KEY + chatroomid = zset集合 某个聊天室的贡献榜单
CHATROOM_ALL_CONSUME_USER_LIST_KEY_PREV = "CHATROOM_ALL_CONSUME_USER_LIST_KEY_"
// 房间日贡献榜KEY + chatroomid = zset集合 某个聊天室的贡献榜单
CHATROOM_DAY_CONSUME_USER_LIST_KEY_PREV = "CHATROOM_DAY_CONSUME_USER_LIST_KEY_"
// 房间周魅力KEY + chatroomid + 年 + 周 = zset集合 某个聊天室的周魅力榜单
CHATROOM_WEEKLY_CHARM_USER_LIST_KEY_PREV = "CHATROOM_WEEKLY_CHARM_USER_LIST_KEY_"
// 房间月魅力KEY + chatroomid + 年月 = zset集合 某个聊天室的月魅力榜单
CHATROOM_MONTH_CHARM_USER_LIST_KEY_PREV = "CHATROOM_MONTH_CHARM_USER_LIST_KEY_"
// 房间总魅力KEY + chatroomid + 年月 = zset集合 某个聊天室的总魅力榜单
CHATROOM_ALL_CHARM_USER_LIST_KEY_PREV = "CHATROOM_ALL_CHARM_USER_LIST_KEY_"
// 房间日魅力KEY + chatroomid + 日 = zset集合 某个聊天室的日魅力榜单
CHATROOM_DAY_CHARM_USER_LIST_KEY_PREV = "CHATROOM_DAY_CHARM_USER_LIST_KEY_"
// 聊天室超级管理员列表 SET
CHATROOM_SUPER_ADMIN_USER_LIST_KEY = "CHATROOM_SUPER_ADMIN_USER_LIST"
// 聊天室房管列表key+chatroomId SET 某个聊天室的管理员列表
CHATROOM_ADMIN_USER_LIST_PREV = "CHATROOM_ADMIN_USER_LIST_"
// 聊天室主持人列表key+chatroomId SET 某个聊天室的主持人列表
CHATROOM_HOST_USER_LIST_PREV = "CHATROOM_HOST_USER_LIST_"
// 聊天室黑名单列表key+chatroomId SET 某个聊天室的黑名单列表
CHATROOM_BLACK_USER_LIST_PREV = "CHATROOM_BLACK_USER_LIST_"
// 聊天室在线人数 + chatroomId 某个聊天室的在线人数
CHATROOM_ONLINE_USER_COUNT_PREV = "CHATROOM_ONLINE_USER_COUNT_"
// 聊天室在线人数 + chatroomId 某个跟聊天室的在线人数(包含子房间)
CHATROOM_ROOT_ONLINE_USER_SET__PREV = "CHATROOM_ROOT_ONLINE_USER_SET_"
// 开启的聊天室列表key 全服正在开厅的聊天室列表
CHATROOM_OPENING_LIST_KEY = "CHATROOM_OPENING_LIST_KEY"
// 推荐到首页的房间
SET_RECOMMEND_ROOM_IDS_KEY = "SET_RECOMMEND_ROOM"
CHATROOM_EMOJI_RESULT_PREV = "CHATROOM_EMOJI_RESULT_"
// 聊天室当前主持人 + chatroomId = user_id 某个聊天室当前的主持人ID
CHATROOM_CURRENT_HOST_USER_PREV = "CHATROOM_CURRENT_HOST_USER_"
// 聊天室DBModel缓存 某个聊天室的DB MODEL缓存
// CHATROOM_DB_MODEL_KEY_PREV = "CHATROOM_DB_MODEL_KEY_"
// 聊天室DBModel缓存 某个聊天室的DB MODEL缓存
CHATROOM_HOT_SET = "CHATROOM_HOT_SET_"
// 聊天室EXT DBModel缓存 某个聊天室的EXT DB MODEL缓存
CHATROOM_EXT_DB_MODEL_KEY_PREV = "CHATROOM_EXT_DB_MODEL_KEY_PREV"
// 聊天室全服周贡献榜KEY + 年 + 周 = zset 全服周贡献榜
CHATROOM_TOTAL_CONSUME_USER_LIST_PREV = "CHATROOM_TOTAL_CONSUME_USER_LIST_"
// 聊天室全服周魅力榜KEY + 年 + 周 = zset 全服周魅力榜
CHATROOM_TOTAL_CHARM_USER_LIST_PREV = "CHATROOM_TOTAL_CHARM_USER_LIST_"
// 牵手榜
CHATROOM_HAND_PREV = "CHATROOM_HAND_PREV_"
// 聊天室打赏连击次数key + chatroomId + userId + giftId + touserId = 连击次数
CHATROOM_USER_COMBO_HIT_COUNT_PREV = "CHATROOM_USER_COMBO_HIT_COUNT_"
// 连击已发送的全服消息数 + chatroomId + userId + giftId + touserId = 已发送的飞机数
CHATROOM_COMBOHIT_SENDED_FULL_MSG_COUNT_PREV = "CHATROOM_COMBOHIT_SENDED_FULL_MSG_COUNT_"
// 聊天室表情列表
CHATROOM_EMOJI_LIST_KEY = "CHATROOM_EMOJI_LIST_KEY"
// 聊天室座位缓存 key+chatroomId HASH 某个聊天室的座位列表
CHATROOM_SEAT_LIST_HASH_KEY_PREV = "CHATROOM_SEAT_LIST_HASH_KEY_"
// 管理员麦序
CHATROOM_SEAT_LIST_ADMIN_KEY_PREV = "CHATROOM_SEAT_LIST_ADMIN_"
CHATROOM_SEAT_HASH_ADMIN_KEY_PREV = "CHATROOM_SEAT_HASG_ADMIN_"
// 自由麦序
CHATROOM_SEAT_LIST_FREE_KEY_PREV = "CHATROOM_SEAT_LIST_FREE_"
CHATROOM_SEAT_HASH_FREE_KEY_PREV = "CHATROOM_SEAT_HASH_FREE_"
// 聊天室排队列表对象缓存 key+chatroomId Hset 某个聊天室的申请上麦列表
CHATROOM_WAITING_QUEUE_LIST_REVE = "CHATROOM_WAITING_QUEUE_LIST_"
// 聊天室排队列表 有序userId列表
CHATROOM_WAITING_QUEUE_SORTED_USERID_PREV = "CHATROOM_WAITING_QUEUE_SORTED_USERID_"
CHATROOM_COMMON_SEAT_COUNTDOWN_LOCK = "CHATROOM_COMMON_SEAT_COUNTDOWN_LOCK"
CHATROOM_COMMON_SEAT_COUNTDOWN_ZSET = "CHATROOM_COMMON_SEAT_COUNTDOWN"
// 聊天室的命令编号
CHATROOM_CMD_SEQUENCE_NUMBER_PREV = "CHATROOM_CMD_SEQUENCE_NUMBER_"
// VIP配置列表
VIP_CONFIG_MODEL_LIST_KEY = "VIP_CONFIG_MODEL_LIST"
// VIP配置列表
VIP_RIGHT_MODEL_LIST_KEY = "VIP_RIGHT_MODEL_LIST"
// 用户当前所在的房间ID + userid = chatroomid
// USER_CURRENT_IN_CHATROOM_ID_PREV = "USER_CURRENT_IN_CHATROOM_ID_"
// 用户在聊天室的心跳时间 + userid = datetime
USER_IN_CHATROOM_HEARTBEAT_TIME_PREV = "USER_IN_CHATROOM_HEARTBEAT_TIME_"
// 交友房间的阶段 开始时间 等数据 HASH + chatroomId = hash
FRIEND_CHATROOM_GAME_DATA_PREV = "FRIEND_CHATROOM_GAME_DATA_"
// 被踢出房间的用户KEY +chatroom_id + user_id = 1
KICKED_USER_FROM_CHATROOM_PREV = "KICKED_USER_FROM_CHATROOM_"
// 交友模板 房间当轮魅力值 + chatroomId = zset
CHATROOM_CURRENT_ROUND_CHARM_ZSET = "CHATROOM_CURRENT_ROUND_CHARM_ZSET"
// 聊天室关闭时间 10秒过期如果存在不能创建 +chatroomId = close_time
CHATROOM_CLOSE_TIME_PREV = "CHATROOM_CLOSE_TIME_PREV"
// 聊天室在线成员列表 + chatroomId = set
CHATROOM_ONLINE_MEMBER_LIST_PREV = "CHATROOM_ONLINE_MEMBER_LIST_PREV"
// 聊天室机器人列表 + chatroomId = set
CHATROOM_ROBOT_LIST_PREV = "CHATROOM_ROBOT_LIST_PREV"
// 聊天室守护配置 string
CHATROOM_GUARD_CONFIG_KEY = "CHATROOM_GUARD_CONFIG_KEY"
// 聊天室活动列表KEY
CHATROOM_ACTIVITY_LIST_KEY = "CHATROOM_ACTIVITY_LIST_KEY"
// 用户隐私设置的KEY +userId = string
USER_PRIVACY_SETTING_KEY = "USER_PRIVACY_SETTING_KEY_"
// 用户头像框的KEY +userId = string
USER_AVATAR_DECORATION_KEY = "USER_AVATAR_DECORATION_KEY_"
// 聊天室背景图 + chatroomID = string
CHATROOM_BACKGROUDN_IMG_URL_PREV = "CHATROOM_BACKGROUDN_IMG_URL_"
// 聊天室每分钟的流水 + chatroomID + 分钟 = int64
CHATROOM_MINUTE_REVENUE_PREV = "CHATROOM_MINUTE_REVENUE_"
// 个人房间在线人数排序SET + CHATROOMID = ZSET SCORE = 人数
PERSONAL_ROOM_SORTED_SET = "PERSONAL_ROOM_SORTED_SET"
// 用户在聊天室里的签结果 + chatroomId + useId = string
ROCK_SKEWER_RESULT_PREV = "ROCK_SKEWER_RESULT_"
// 礼物对应的头饰 + GIFTID = string
GIFT_INCLUDED_AVATAR_DECORATION_LIST_PREV = "GIFT_INCLUDED_AVATAR_DECORATION_LIST_"
// 商品详情 + goodsId = string
GOODS_DETAIL_DB_MODEL_PREV = "GOODS_DETAIL_DB_MODEL_"
// 用户当前使用的头饰或座驾 + GOODS_TYPE + "_" + USER_ID = USER_STORE MODEL STR
USER_CURRENT_USED_STORE_MODEL_PREV = "USER_CURRENT_USED_STORE_MODEL_"
// 订单是否正在支付 + order_id = 1
PAY_ORDER_IS_PAYING_PREV = "PAY_ORDER_IS_PAYING_"
//============= 技能订单相关 ==================
// 技能对象缓存的KEY + skill_id = string
SKILL_MODEL_KEY_PREV = "SKILL_MODEL_"
// 技能配置
SKILL_CONFIG_TREE_HASH_KEY = "SKILL_CONFIG_TREE_HASH"
// 技能配置
SKILL_CONFIG_KEY = "SKILL_CONFIG_"
// 用户审核中的技能 +user_id = SET
USER_APPLYING_SKILL_ID_LIST_PREV = "USER_APPLYING_SKILL_ID_LIST_"
// /////////////////////////
// 派单 1、添加到房间的派单列表 CHATROOM_CURRENT_DISPATCH_ORDER_LIST
// 2、追加接单的用户 DISPATCH_ORDER_TO_USER_LIST DISPATCH_ORDER_TO_USER_MAP
// 3、用户可接单 DISPATCH_ORDER_USER_GET_LIST
// 陪玩接单
// 1、移除用户可接单 DISPATCH_ORDER_USER_GET_LIST
// 2、添加用户已接单 DISPATCH_ORDER_USER_GET_TAKED_LIST
// 3、添加派单已接单用户 DISPATCH_ORDER_TO_USER_TAKED_LIST
// 派单结束
// 1、删除房间派单列表中的数据 CHATROOM_CURRENT_DISPATCH_ORDER_LIST
// 2、用户可解的单中删除该派单 DISPATCH_ORDER_USER_GET_LIST DISPATCH_ORDER_USER_GET_TAKED_LIST
// 3、删除派给的用户列表 DISPATCH_ORDER_TO_USER_LIST DISPATCH_ORDER_TO_USER_MAP
// /////////////////////////
// 聊天室当前派单ID + chatroomid = order_id
CHATROOM_CURRENT_DISPATCH_ORDER_ID = "CHATROOM_CURRENT_DISPATCH_ORDER_ID_"
// 聊天室当前派单列表 + chatroomid = order_id
CHATROOM_CURRENT_DISPATCH_ORDER_LIST = "CHATROOM_CURRENT_DISPATCH_ORDER_LIST_"
// 当前派单已派的用户LIST + order_id = userIds
DISPATCH_ORDER_TO_USER_LIST = "DISPATCH_ORDER_TO_USER_LIST_"
// 当前派单已派的用户SET + order_id = userIds (实际是Set)
DISPATCH_ORDER_TO_USER_MAP = "DISPATCH_ORDER_TO_USER_MAP_"
// 当前用户可接的派单 + user_id = orderIds
DISPATCH_ORDER_USER_GET_LIST = "DISPATCH_ORDER_USER_GET_LIST_"
// 当前派单接单的用户 + order_id = userIds
DISPATCH_ORDER_TO_USER_TAKED_LIST = "DISPATCH_ORDER_TO_USER_TAKED_LIST_"
// 当前用户已接的派单 + order_id = userIds
DISPATCH_ORDER_USER_GET_TAKED_LIST = "DISPATCH_ORDER_USER_GET_TAKED_LIST_"
// ============= 定时任务ID ==============
// 定时清理异常掉线的聊天室用户
TASK_ID_CLEAR_CHATROOM_DEADED_USER = "TASK_ID_CLEAR_CHATROOM_DEADED_USER"
TASK_ID_CLEAR_DISPATCH_CHATROOM_DEADED_USER = "TASK_ID_CLEAR_DISPATCH_CHATROOM_DEADED_USER"
// 自动解冻帐号
TASK_ID_AUTO_UNFROZEN_USER = "TASK_ID_AUTO_UNFROZEN_USER"
// 定时清理房间麦序
TASK_ID_CLEAR_CHATROOM_SEAT_USER = "TASK_ID_CLEAR_CHATROOM_SEAT_USER"
// 定时清理个人厅异常掉线的聊天室用户
TASK_ID_CLEAR_PERSONAL_CHATROOM_DEADED_USER = "TASK_ID_CLEAR_PERSONAL_CHATROOM_DEADED_USER"
// ============ 打赏特殊帐号 刷空单 ==================
// 老板号
DASHANG_SPEC_ACCOUNT_BOSS_LIST_KEY = "DASHANG_SPEC_ACCOUNT_BOSS_LIST"
// 服务员号
DASHANG_SPEC_ACCOUNT_WAITTER_LIST_KEY = "DASHANG_SPEC_ACCOUNT_WAITTER_LIST"
// 指定厅
DASHANG_SPEC_BOSS_WAITTER_ROOM_LIST_KEY = "DASHANG_SPEC_BOSS_WAITTER_ROOM_LIST_KEY"
// 置顶厅
SET_TOP_CHATROOM_ID_LIST_KEY = "SET_TOP_CHATROOM_ID_LIST_KEY"
// 提现特殊帐号 可日提的
WITHDRAW_SPEC_ACCOUNT_LIST_KEY = "WITHDRAW_SPEC_ACCOUNT_LIST_KEY"
// SVIP用户每次钻石打赏限额 + user_id = 已用
HIT_EGG_SVIP_USER_DAY_USED_MONEY = "HIT_EGG_SVIP_USER_DAY_USED_MONEY_"
// 用户动态数 + user_id = value
USER_TIMELINE_COUNT = "USER_TIMELINE_COUNT_"
// 用户位置的集合
LOCATION_USER = "LOCATION_USER"
// 用户最后获取广播的时间 + user_id = string
USER_LAST_GET_BROADCAST_TIME = "USER_LAST_GET_BROADCAST_TIME_"
// 动态详情model + timelineId = model json
TIMELINE_MODEL_PREV = "TIMELINE_MODEL_"
// 日注册用户数 + YYYY-MM-DD = string
DAY_SIGNUP_USER_COUNT_PREV = "DAY_SIGNUP_USER_COUNT_"
// 技能等级配置
SKILL_LEVEL_CONFIG = "SKILL_LEVEL_CONFIG"
// 动态Location的集合
TIMELINE_LOCATION_COLLECTION_KEY = "TIMELINE_LOCATION_COLLECTION"
// 动态的点赞用户列表 + TIMELINEID = LIST
TIMELINE_PRAISED_USER_LIST_PREV = "TIMELINE_PRAISED_USER_LIST_"
// 动态的点赞数 = string
TIMELINE_PRAISED_USER_COUNT_PREV = "TIMELINE_PRAISED_USER_COUNT_"
// 关注的动态 +userid = list
TIMELINE_FOLLOWED_KEY_PREV = "TIMELINE_FOLLOWED_KEY_"
// 关注的动态 +userid = list
TIMELINE_FOLLOWED_NEW_CNT_KEY_PREV = "TIMELINE_FOLLOWED_NEW_CNT_KEY_"
// 用户勋章缓存 + USER_ID = STR
USER_MEDAL_LIST = "USER_MEDAL_LIST_"
// SKU勋章缓存 + SKU_ID = STR
SKU_MEDAL_LIST = "SKU_MEDAL_LIST_"
// 勋章缓存 = STR
MEDAL_LIST_KEY = "MEDAL_LIST_KEY"
// 进入场景对象缓存 + sceneId = str
ENTER_ROOM_SCENE_MODEL_PREV = "ENTER_ROOM_SCENE_MODEL_"
// VIP SCENE 列表
VIP_SCENE_LIST = "VIP_SCENE_LIST"
// MainTabBar 列表 = string
MAIN_TAB_BAR_LIST_KEY = "MAIN_TAB_BAR_LIST"
// 青少年模式集合
CHILD_MODE_SET_KEY = "CHILD_MODE_SET"
// 爵位配置Hash
ALL_NOBILITY_CONFIG_LIST_KEY = "ALL_NOBILITY_CONFIG_LIST"
// 用户贵族信息的RedisKEY + user_id = user_nobility_model_json
USER_NOBILITY_MODEL_KEY_PREV = "USER_NOBILITY_MODEL_"
// 开箱排行榜KEY
USER_OPEN_BOX_RANK_PREV = "USER_OPEN_BOX_RANK_"
// 易盾TOKEN对应手机号
YIDUN_TOKEN_PREV = "YIDUN_TOKEN_"
//APPLE登录AUTH CODE的key
APPLE_AUTH_CODE_PREV = "APPLE_AUTH_CODE_"
// 用户支付宝充值的支付宝帐号hash + USERID = HASH
USER_ALIPAY_RECHARGE_PAY_ACCOUNT_HASH_PREV = "USER_ALIPAY_RECHARGE_PAY_ACCOUNT_HASH_"
USER_ALIPAY_RECHARGE_N_COIN_PAY_ACCOUNT_HASH_PREV = "USER_ALI_RECHARGE_N_COIN_ACCOUNT_HASH_"
// 谁给用户发了多少信息 + USER_ID = HASH
USER_MSG_MAP_FROM_PREV = "USER_MSG_MAP_FROM_"
// 用户特殊颜色缓存
USER_SPEC_COLOR_MODEL_PREV = "USER_SPEC_COLOR_MODEL_"
// 用户IAP充值 预检查通过的key
USER_IAP_RECHARGE_PASS_KEY = "USER_IAP_RECHARGE_PASS_"
// 用户最后获取访客记录的时间
USER_LAST_GET_VISITED_LIST_TIME_PREV = "USER_LAST_GET_VISITED_LIST_TIME_"
// 用户新访客set
USER_NEW_VISITOR_SET_PREV = "USER_NEW_VISITOR_SET_"
// 用户修改昵称缓存记录
USER_EDIT_NICKNAME_COUNT_PREV = "USER_EDIT_NICKNAME_COUNT_"
// 公会相关的
// 公会信息
GUILD_MODEL_KEY_PREV = "guild:model:"
// 周星礼物发放定时任务的礼物列表
WEEK_STAR_GRANT_GIFT_KEY = "WEEK_STAR_GRANT_GIFT_KEY"
)

68
pkg/cache/redis_lock.go vendored Normal file
View File

@ -0,0 +1,68 @@
package cache
import (
"servicebase/pkg/log"
"errors"
"time"
)
func Lock(key string) (e error) {
var success bool
TryMax := 6
redisClient := GetCommonRedisInstance().RedisClient
for i := 0; i < TryMax; i++ {
success, e = redisClient.SetNX(Ctx(), key, time.Now(), time.Millisecond*1000*3).Result()
if e != nil {
log.Info("Lock GetCommonRedisInstance error: " + e.Error())
}
if success {
return nil
}
time.Sleep(time.Millisecond * 50)
}
return errors.New("操作太频繁了~")
}
func Free(key string) {
client := GetCommonRedisInstance().RedisClient
_ = client.Del(Ctx(), key).Err()
}
const (
TryMax = int(30)
TryDuration = time.Millisecond * 13
LockTimeout = time.Millisecond * 700
)
func RedisLockGetTime(key string, duration time.Duration) (e error) {
redisClient := GetCommonRedisInstance().RedisClient
success, e := redisClient.SetNX(Ctx(), key, "1", duration).Result()
if !success {
e = errors.New("(LOCK_FAILED)系统繁忙,请稍后再试!")
}
return
}
func RedisLockGet(key string) (e error) {
redisClient := GetCommonRedisInstance().RedisClient
success := false
for i := 0; i < TryMax; i++ {
success, e = redisClient.SetNX(Ctx(), key, time.Now(), LockTimeout).Result()
if nil != e {
return
}
if success {
break
}
time.Sleep(TryDuration)
}
if !success {
e = errors.New("(LOCK_FAILED)系统繁忙,请稍后再试!")
}
return
}
func RedisLockDel(key string) {
redisClient := GetCommonRedisInstance().RedisClient
_ = redisClient.Del(Ctx(), key).Err()
}

68
pkg/cache/user/user.go vendored Normal file
View File

@ -0,0 +1,68 @@
package cache_user
import (
"servicebase/pkg/cache"
"servicebase/pkg/tools"
"fmt"
"strings"
"time"
"github.com/anxpp/beego/logs"
"github.com/anxpp/common-utils/str"
)
const key = "user:models"
const keyToken = "user:token"
// 用户当前所在的房间ID + userid = chatroomid
const ACCESSTOKEN_KEY_PREV = "ACCESSTOKEN_"
const ACCESSTOKEN_EXPIRE_DURATION = time.Hour * 24 * 180
// 通过token获取userId
func IdByToken(token string) (userId string) {
if len(token) == 0 {
return
}
var e error
if userId, e = cache.CommonClient().HGet(cache.Ctx(), keyToken, token).Result(); e != nil || len(userId) == 0 {
userId, e = cache.CommonClient().Get(cache.Ctx(), ACCESSTOKEN_KEY_PREV+token).Result()
if e != nil {
logs.Error("GetUserIdByAccessToken redisError=%v token=%v", e.Error(), token)
return
} else if len(userId) > 0 {
// 迁移token
ttl, err := cache.CommonClient().TTL(cache.Ctx(), ACCESSTOKEN_KEY_PREV+token).Result()
if err != nil {
return
}
value := fmt.Sprintf("%s#%s", userId, str.TimeToString(time.Now().Add(ttl)))
if err := cache.CommonClient().HSet(cache.Ctx(), keyToken, token, value).Err(); err == nil {
cache.CommonClient().Del(cache.Ctx(), ACCESSTOKEN_KEY_PREV+token)
}
}
} else {
arr := strings.Split(userId, "#")
if len(arr) != 2 {
_ = IdTokenDel(token)
return ""
}
over := tools.StrToDateTime(arr[1])
if over.Before(time.Now()) {
_ = IdTokenDel(token)
return ""
}
userId = arr[0]
}
return
}
func IdTokenSet(token, userId string) (e error) {
value := fmt.Sprintf("%s#%s", userId, str.TimeToString(time.Now().Add(ACCESSTOKEN_EXPIRE_DURATION)))
return cache.CommonClient().HSet(cache.Ctx(), keyToken, token, value).Err()
}
func IdTokenDel(token string) (e error) {
_, e = cache.CommonClient().HDel(cache.Ctx(), keyToken, token).Result()
cache.CommonClient().Del(cache.Ctx(), ACCESSTOKEN_KEY_PREV+token)
return
}

63
pkg/cerrors/const.go Normal file
View File

@ -0,0 +1,63 @@
package cerrors
import "errors"
const (
Success = 200
BadRequest = 400
Unauthorized = 401
Forbidden = 403
NotFound = 404
NotAcceptable = 406
Gone = 410
RangeNotSatisfiable = 416
ParamError = 501
InternalServerError = 500
ServiceUnavailable = 503
DataCreateError = 511
DataUpdateError = 512
DataUpdateNoneError = 515
DataReadError = 513
DataDeleteError = 514
// ContentSmsTemplateExistErr 81xx 内容维护错误码
ContentSmsTemplateExistErr = 8100
)
var (
CodeMsg = map[int]string{
Success: "成功",
BadRequest: "请求出错",
Unauthorized: "登录信息过期",
Forbidden: "无权限访问",
NotFound: "数据不存在",
NotAcceptable: "服务器忙",
Gone: "数据已失效",
RangeNotSatisfiable: "请求参数范围不支持",
ParamError: "参数错误",
InternalServerError: "服务器错误",
ServiceUnavailable: "服务暂不可用",
DataCreateError: "数据创建失败",
DataUpdateError: "数据更新失败",
DataUpdateNoneError: "数据更新无效",
DataReadError: "数据查询失败",
DataDeleteError: "数据删除失败",
ContentSmsTemplateExistErr: "短信模板已存在",
}
)
func Msg(code int) string {
return CodeMsg[code]
}
func GetError(code int) error {
return errors.New(Msg(code))
}

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{{.Title}}</title>
</head>
<body>
<p>Hello!</p>
<p>This is a password reset email.</p>
<a href="{{.Url}}">{{.Url}}</a>
</body>
</html>

View File

@ -0,0 +1,225 @@
package client_game_wz
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"servicebase/pkg/utils"
"github.com/anxpp/common-utils/logg"
"github.com/anxpp/common-utils/str"
)
const WZYD_TOKEN = "SZN62jr4"
const WZYD_UID = "1702857155"
func RoleInfo(targetUid string) (targetRole map[string]interface{}, e error) {
if len(targetUid) == 0 {
e = errors.New("参数不足")
return
}
apiUrl := "https://kohcamp.qq.com/game/koh/profile"
token := WZYD_TOKEN // 登录王者营地的token
uid := WZYD_UID // 营地ID
headerMap := make(map[string]string, 0)
headerMap["Content-Type"] = "application/json"
headerMap["userId"] = uid
headerMap["cCurrentGameId"] = "20001"
headerMap["Accept-Language"] = "zh-Hans-CN;q=1, en-CN;q=0.9"
headerMap["token"] = token
paraMap := make(map[string]string, 4)
paraMap["CampPreloadHTTPHandledIdentifier"] = "2"
paraMap["targetUserId"] = targetUid
resStr, err2 := utils.HttpDo(http.MethodPost, apiUrl, headerMap, str.ToJsonString(paraMap))
if err2 != nil {
logg.Info("调用王者营地数据查询失败", err2.Error())
e = fmt.Errorf("调用王者营地数据查询失败: %v", err2.Error())
return
}
logg.Info(resStr)
resMap := make(map[string]interface{}, 0)
json.Unmarshal([]byte(resStr), &resMap)
if resMap["returnCode"].(float64) != 0 {
e = fmt.Errorf("调用王者营地数据查询失败2: %v", resMap["returnMsg"])
return
}
// 获取成功
data := resMap["data"].(map[string]interface{})
// 默认角色ID
targetRoleId := data["targetRoleId"].(string)
//角色列表
roleList := data["roleList"].([]interface{})
// 匹配默认角色
for _, item := range roleList {
m := item.(map[string]interface{})
if m["roleId"].(string) == targetRoleId {
targetRole = m
}
}
if targetRole == nil {
e = errors.New("未找到角色信息")
return
}
// 其他数据
head := data["head"].(map[string]interface{})
mods := head["mods"].([]interface{})
//targetRole["mods"] = mods
for _, item := range mods {
mod := item.(map[string]interface{})
modId := mod["modId"].(float64)
if modId == 401 {
// 总场次
targetRole["totalPlayCount"] = mod["content"].(string)
} else if modId == 408 {
// MVP
targetRole["mvpCount"] = mod["content"].(string)
} else if modId == 409 {
// 胜率
targetRole["winRate"] = mod["content"].(string)
} else if modId == 201 {
// 英雄数
targetRole["heroCount"] = mod["content"].(string)
} else if modId == 202 {
// 皮肤数
targetRole["skinCount"] = mod["content"].(string)
} else if modId == 702 {
// 巅峰赛
targetRole["topScore"] = mod["content"].(string)
} else if modId == 304 {
// 战力
targetRole["battleScore"] = mod["content"].(string)
}
}
return
}
func RoleSkinList(roleId string) (result map[string][]interface{}, e error) {
apiUrl := "https://kohcamp.qq.com/game/itempage/skinlist"
token := WZYD_TOKEN // 登录王者营地的token
uid := WZYD_UID // 营地ID
if len(roleId) == 0 {
e = errors.New("参数不足")
return
}
headerMap := make(map[string]string, 0)
headerMap["Content-Type"] = "application/json"
headerMap["userId"] = uid
headerMap["cCurrentGameId"] = "20001"
headerMap["Accept-Language"] = "zh-Hans-CN;q=1, en-CN;q=0.9"
headerMap["token"] = token
paraMap := make(map[string]string, 4)
paraMap["roleId"] = roleId
resStr, err2 := utils.HttpDo(http.MethodPost, apiUrl, headerMap, str.ToJsonString(paraMap))
if err2 != nil {
logg.Info("调用王者营地数据查询失败", err2.Error())
e = fmt.Errorf("调用王者营地数据查询失败:%v", err2)
return
}
resMap := make(map[string]interface{}, 0)
json.Unmarshal([]byte(resStr), &resMap)
if _, ok := resMap["returnCode"]; ok {
e = fmt.Errorf("调用王者营地数据查询失败2: %v", resMap["returnMsg"])
return
}
//全部皮肤
allSkin := resMap["allSkinConf"].([]interface{})
// 用户的皮肤
userSkinList := resMap["skinData"].([]interface{})
resultList := []interface{}{}
rongyaoList := []interface{}{} // 荣耀典藏
xiandingList := []interface{}{} // 限定
shishiList := []interface{}{} // 史诗级
chuanshuoList := []interface{}{} // 传说
wushuangList := []interface{}{} //无双
for _, item := range userSkinList {
skinMap := item.(map[string]interface{})
userSkinId := skinMap["skinId"].(float64)
mySkin := getSkinModel(allSkin, userSkinId)
mySkin["acquireTime"] = skinMap["acquireTime"].(string)
// 去掉隐藏的皮肤
if mySkin["isHidden"].(float64) == 1 {
continue
}
// 去掉原皮
if int64(userSkinId)%100 == 0 {
continue
}
// 等级处理
classTypeName := mySkin["classTypeName"].([]interface{})
nameList := []string{}
for _, name := range classTypeName {
nameList = append(nameList, name.(string))
}
str := fmt.Sprintf("%s(%s)", mySkin["skinName"].(string), mySkin["heroName"].(string))
if utils.IsInStringList("荣耀典藏", nameList) {
rongyaoList = append(rongyaoList, str)
}
if utils.IsInStringList("限定", nameList) {
xiandingList = append(xiandingList, str)
}
if utils.IsInStringList("史诗品质", nameList) {
shishiList = append(shishiList, str)
}
if utils.IsInStringList("传说品质", nameList) {
chuanshuoList = append(chuanshuoList, str)
}
if utils.IsInStringList("无双", nameList) {
wushuangList = append(wushuangList, str)
}
resultList = append(resultList, mySkin)
}
logg.Info("皮肤数量", len(resultList))
result = make(map[string][]interface{}, 0)
// result["totalCount"] = len(resultList)
result["allSkin"] = resultList
result["荣耀典藏"] = rongyaoList
result["限定"] = xiandingList
result["史诗品质"] = shishiList
result["传说品质"] = chuanshuoList
result["无双"] = wushuangList
return
}
func getSkinModel(allSkin []interface{}, userSkinId float64) (result map[string]interface{}) {
for _, item := range allSkin {
skin := item.(map[string]interface{})
skinId := skin["skinId"].(float64)
if skinId == userSkinId {
result = skin
break
}
}
return
}

89
pkg/client/http.go Normal file
View File

@ -0,0 +1,89 @@
package client
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"
)
func NewClient() *http.Client {
tr := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: true,
}
return &http.Client{
Transport: tr,
Timeout: 5 * time.Second,
}
}
func Get(url string, headers map[string]string, params map[string]string) (res []byte, e error) {
var (
client = NewClient()
resp *http.Response
)
req, _ := http.NewRequest(http.MethodGet, url, nil)
for k, v := range headers {
req.Header.Add(k, v)
}
q := req.URL.Query()
for k, v := range params {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
if resp, e = client.Do(req); e != nil {
return
}
defer func(body io.ReadCloser) {
_ = body.Close()
}(resp.Body)
if res, e = io.ReadAll(resp.Body); e != nil {
return
}
if !success(resp.StatusCode) {
e = errors.New(fmt.Sprintf("status code error: %s", resp.Status))
}
return
}
func Post(url string, headers map[string]string, body map[string]interface{}) (res []byte, e error) {
var (
client = NewClient()
resp *http.Response
)
j, e := json.Marshal(body)
if e != nil {
return
}
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(j))
for k, v := range headers {
req.Header.Add(k, v)
}
//q := req.URL.Query()
//for k, v := range params {
// q.Add(k, v)
//}
//req.URL.RawQuery = q.Encode()
if resp, e = client.Do(req); e != nil {
return
}
defer func(body io.ReadCloser) {
_ = body.Close()
}(resp.Body)
if res, e = io.ReadAll(resp.Body); e != nil {
return
}
if !success(resp.StatusCode) {
e = errors.New(fmt.Sprintf("status code error: %s", resp.Status))
}
return
}
// func success(code int) bool {
// return code >= 200 && code < 400
// }

View File

@ -0,0 +1,45 @@
package http_api
import (
"servicebase/pkg/log"
"servicebase/pkg/res"
"servicebase/pkg/utils"
"errors"
"github.com/go-resty/resty/v2"
)
type InnerLoginReq struct {
Mobile string `json:"mobile"`
Signature string `json:"signature"`
}
type InnerLoginRes struct {
Code string
Result res.AccessTokenDTO
Msg string
}
func InnerLogin(url, secret, mobile string) (token string, e error) {
var req = map[string]string{
"mobile": mobile,
}
sign := utils.CreateApiSign(req, secret)
req["Signature"] = sign
client := resty.New()
_ = client
var res InnerLoginRes
resp, err := client.R().
SetHeader("Content-Type", "application/json").
SetBody(req).
SetResult(&res). // or SetResult(AuthSuccess{}).
Post(url)
if err != nil {
return "", err
}
log.InfoF("%++v %v", res, string(resp.Body()))
if res.Code == "8000" {
return res.Result.AccessToken, nil
}
return "", errors.New("登录失败:" + res.Msg)
}

112
pkg/client/mongo.go Normal file
View File

@ -0,0 +1,112 @@
package client
import (
"context"
"fmt"
"servicebase/pkg/common/req"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type MongoInitStarter struct {
}
var Client *mongo.Client
var DB *mongo.Database
func (s *MongoInitStarter) Init() error {
var (
ctx = context.TODO()
host = viper.GetString("db.mongo.host")
port = viper.GetString("db.mongo.port")
username = viper.GetString("db.mongo.username")
password = viper.GetString("db.mongo.password")
dbname = viper.GetString("db.mongo.dbname")
e error
)
// log.InfoF(ctx, "host=%s port=%s db=%s", host, port, dbname)
if Client, e = mongo.Connect(ctx, options.Client().
SetAuth(options.Credential{Username: username, Password: password}).
ApplyURI(fmt.Sprintf("mongodb://%s:%s", host, port))); e != nil {
return e
}
DB = Client.Database(dbname)
return nil
}
func CheckTable(c context.Context, table string) error {
if r, e := DB.ListCollectionNames(c, bson.M{"name": "table"}); e != nil {
return e
} else if len(r) == 0 {
return DB.CreateCollection(c, table)
}
return nil
}
func InsertOne(c context.Context, table string, record interface{}) (*mongo.InsertOneResult, error) {
if e := CheckTable(c, table); e != nil {
return nil, e
}
return DB.Collection(table).InsertOne(c, record)
}
func SaveOne(c context.Context, table string, filter, record interface{}) (*mongo.UpdateResult, error) {
if e := CheckTable(c, table); e != nil {
return nil, e
}
return DB.Collection(table).ReplaceOne(c, filter, record, options.Replace().SetUpsert(true))
}
func DeleteOne(c context.Context, table string, record interface{}) (*mongo.DeleteResult, error) {
if e := CheckTable(c, table); e != nil {
return nil, e
}
return DB.Collection(table).DeleteOne(c, record)
}
func UpdateByID(c context.Context, table string, id interface{}, record interface{}) (*mongo.UpdateResult, error) {
if e := CheckTable(c, table); e != nil {
return nil, e
}
return DB.Collection(table).UpdateByID(c, id, bson.D{{Key: "$set", Value: record}})
}
func Page(c context.Context, table string, page req.Page, filter bson.D, results interface{}) (int64, error) {
if e := CheckTable(c, table); e != nil {
return 0, e
}
cursor, e := DB.Collection(table).Find(
c,
filter,
options.Find().SetSkip(int64(page.Offset())),
options.Find().SetLimit(int64(page.Limit())),
)
if e != nil {
// log.ErrorF(c, "db Page error: %v", e)
return 0, e
}
defer func() {
if e := cursor.Close(c); e != nil {
// log.Error(c, e.Error())
}
}()
if e != nil {
return 0, e
}
cnt, e := DB.Collection(table).CountDocuments(c, filter)
if e != nil {
return 0, e
}
return cnt, cursor.All(c, results)
}
func MToD(m bson.M) (d bson.D) {
for key, value := range m {
d = append(d, bson.E{Key: key, Value: value})
}
return
}

View File

@ -0,0 +1,132 @@
package Netease
import (
"servicebase/pkg/common/HyTools"
"encoding/json"
"net/url"
"time"
Netease "servicebase/pkg/common/netease"
"github.com/anxpp/beego"
"github.com/anxpp/beego/logs"
)
const (
// ChatRoomManagerUserId 聊天室消息管理员ID
ChatRoomManagerUserId = "34cc7185d0b60eacf38f616b8aad8c51"
SystemNoticeUserId = "x94b992dbb4e4dfca9c971200d797a02"
P2pMessagesTypeSystemNotice = "104"
TimeDefaultFormat = "Y-m-d H:i:s"
)
type ImService struct {
}
// ImMsgBodyAttach IM消息 body对象
type ImMsgBodyAttach struct {
TypeCode string `json:"TypeCode"`
BizData interface{} `json:"BizData"`
Msg string `json:"msg,omitempty"`
}
// ChatRoomMsgAttach 聊天室自定义消息
type ChatRoomMsgAttach struct {
TypeCode string `json:"TypeCode"`
BizData interface{} `json:"BizData"`
}
// SendCustomMsgToChatroomByChatroomManager 向聊天室发消息
func (s *ImService) SendCustomMsgToChatroomByChatroomManager(messageRoomId string, attachData ChatRoomMsgAttach) error {
msgParameter := make(map[string]string)
msgParameter["roomid"] = messageRoomId
msgParameter["msgId"] = HyTools.GetUUID()
msgParameter["fromAccid"] = ChatRoomManagerUserId
msgParameter["msgType"] = "100" //自定义消息
msgParameter["resendFlag"] = "0"
bodyBytes, _ := json.Marshal(attachData)
msgParameter["ext"] = ""
msgParameter["attach"] = string(bodyBytes)
imClient := Netease.NewImClient()
return imClient.SendChatroomMsg(msgParameter)
}
// SendCustomerMsgFromAToB 发IM点对点自定义消息 系统通知 用户关系变化
func (s *ImService) SendCustomerMsgFromAToB(fromAccid, toAccid, pushContent, payloadJson string, bodyModel ImMsgBodyAttach, optionMap map[string]string) error {
msgParameter := make(map[string]string)
msgParameter["from"] = fromAccid
msgParameter["ope"] = "0" //0=p2p 1=群消息
msgParameter["to"] = toAccid
msgParameter["type"] = "100" //0 表示文本消息, 1 表示图片, 2 表示语音, 3 表示视频, 4 表示地理位置信息, 6 表示文件, 100 自定义消息类型
bodyBytes, _ := json.Marshal(bodyModel)
msgParameter["body"] = string(bodyBytes) //最大长度5000字符为一个JSON串
msgParameter["antispam"] = "false" //对于对接了易盾反垃圾功能的应用,本消息是否需要指定经由易盾检测的内容 只对消息类型为100 自定义消息类型 的消息生效。
optionBytes, _ := json.Marshal(optionMap)
msgParameter["option"] = string(optionBytes) //发消息时特殊指定的行为选项,JSON格式可用于指定消息的漫游存云端历史发送方多端同步推送消息抄送等特殊行为
//需要推送
if optionMap["push"] == "true" {
if len(pushContent) > 0 {
msgParameter["pushcontent"] = pushContent // ios推送内容不超过150字符option选项中允许推送push=true此字段可以指定推送内容
}
if len(payloadJson) > 0 {
msgParameter["payload"] = payloadJson // ios 推送对应的payload,必须是JSON,不能超过2k字符
}
}
imClient := Netease.NewImClient()
return imClient.SendMsg(msgParameter)
}
// SendSystemNoticeToUser 给用户发系统消息
// _ = messageService.SendSystemNoticeToUser(userId, "厅补贴到账通知", msgContent, "", "")
func (s *ImService) SendSystemNoticeToUser(toAccid, msgTitle, msgText, schemeUrl, imageUrl string) {
option := GetDefaultMsgOption()
fromUserId := SystemNoticeUserId
logs.Info("SendSystemNoticeToUser from " + fromUserId + "to " + toAccid)
pushContent := ""
payloadJson := ""
bizData := make(map[string]string, 0)
bizData["Title"] = url.QueryEscape(msgTitle)
bizData["Content"] = url.QueryEscape(msgText)
bizData["Scheme"] = schemeUrl
bizData["ImageUrl"] = imageUrl
bizData["CreateTime"] = beego.Date(time.Now(), TimeDefaultFormat)
//body的业务数据
var bodyAttach ImMsgBodyAttach
bodyAttach.TypeCode = P2pMessagesTypeSystemNotice
bodyAttach.BizData = bizData
bodyAttach.Msg = msgTitle
err := s.SendCustomerMsgFromAToB(fromUserId, toAccid, pushContent, payloadJson, bodyAttach, option)
if nil != err {
logs.Error("通知发送失败!!!!")
logs.Info(err.Error())
}
}
// GetDefaultMsgOption 发送消息默认的配置
func GetDefaultMsgOption() map[string]string {
/*
1. roam: 该消息是否需要漫游默认true需要app开通漫游消息功能
2. history: 该消息是否存云端历史默认true
3. sendersync: 该消息是否需要发送方多端同步默认true
4. push: 该消息是否需要APNS推送或安卓系统通知栏推送默认true
5. route: 该消息是否需要抄送第三方默认true (需要app开通消息抄送功能);
6. badge:该消息是否需要计入到未读计数中默认true;
7. needPushNick: 推送文案是否需要带上昵称不设置该参数时默认true;
8. persistent: 是否需要存离线消息不设置该参数时默认true。
*/
result := make(map[string]string)
result["roam"] = "true"
result["history"] = "true"
result["sendersync"] = "false"
result["push"] = "true"
result["route"] = "true"
result["badge"] = "true"
result["needPushNick"] = "false"
result["persistent"] = "true"
return result
}

View File

@ -0,0 +1,104 @@
package Netease
const (
// ChatroomCustomMessageTypePresentActivityMsg 聊天室打赏活动物品消息
ChatroomCustomMessageTypePresentActivityMsg = "380"
// ChatroomCustomMessageTypeFullScreenNoticeMsg 聊天室 全服通知动效
ChatroomCustomMessageTypeFullScreenNoticeMsg = "381"
)
const (
//聊天室字体颜色 - 黄色
CHATROOM_SYSTEM_MSG_COLOR_YELLOW = "#FFD700"
//聊天室字体颜色 - 绿色
CHATROOM_SYSTEM_MSG_COLOR_GREEN = "#8ADE4D"
//聊天室字体颜色 - 系统栏
CHATROOM_SYSTEM_MSG_COLOR_SYSTEM_BLUE = "#43EAE1"
//聊天室字体颜色 - 紫色
CHATROOM_SYSTEM_MSG_COLOR_PURPLE = "#E031FE"
// 默认等级的背景颜色
SKILL_LEVEL_DEFAULT_BG_COLOR = "#FFD700"
//聊天室字体颜色 - 蓝色
CHATROOM_SYSTEM_MSG_COLOR_BLUE = "#25C6FD"
//聊天室字体颜色 - 青色
CHATROOM_SYSTEM_MSG_COLOR_QING = "#00FFCC"
//聊天室字体颜色 - 白色
CHATROOM_SYSTEM_MSG_COLOR_WHITE = "#FFFFFF"
)
type MessageService struct {
}
type TextAndColor struct {
Text string
Color string
}
// ChatroomFullScreenNoticeMsgBizData 聊天室活动全服飘屏通知
type ChatroomFullScreenNoticeMsgBizData struct {
ChatroomId string
ContentIcon string
ContentText string
ContentTextColor string //内容字体颜色
ContentBgColor string //背景颜色
AnimationApngUrl string //加入播放礼物动效队列
BgImgUrl string //背景图
TextList []TextAndColor //文字颜色列表
StayTime string //停留时长(秒)
}
// ChatroomPresentActivityMsgBizData 聊天室打赏活动消息data数据结构
type ChatroomPresentActivityMsgBizData struct {
ChatroomId string
FromUserId string
FromNickName string
FromAvatar string
FromNickNameColor string //打赏人昵称颜色
ActionText string //动作文本 :赠送一个盲盒给
ActionTextColor string //动作文本颜色
ToNickName string
ToNickNameColor string //被打赏人昵称颜色
PresentCount string //物品个数
PresentGoodsName string
PresentGoodsIcon string
PresentGoodsNameColor string
ResultGoodsIcon string
ComboHitCount string //连击次数
AnimationApngUrl string //加入播放礼物动效队列 有的话显示播放动画
AnimationFormat string //动画格式 SVGA APNG
FillMode string //填充模式 1=左右 2=上下
IsViewFlyView string //是否显示飘屏view 有的话显示连击信息
//显示到信息流
TextList []TextAndColor //信息流文字颜色列表
}
// SendChatroomSystemMsgV2 发送聊天室系统消息 V2
func (s *MessageService) SendChatroomSystemMsgV2(messageRoomId string, msgData ChatroomPresentActivityMsgBizData) (err error) {
// 发打赏消息
var service ImService
attachModel := ChatRoomMsgAttach{}
//消息类型
typeCode := ChatroomCustomMessageTypePresentActivityMsg
attachModel.TypeCode = typeCode
attachModel.BizData = msgData
err = service.SendCustomMsgToChatroomByChatroomManager(messageRoomId, attachModel)
return
}
// SendChatroomPresentActivityFullServiceMsg 发送聊天室活动全服
func (s *MessageService) SendChatroomPresentActivityFullServiceMsg(messageRoomId string, msgData ChatroomFullScreenNoticeMsgBizData) (err error) {
// 发打赏消息
var service ImService
attachModel := ChatRoomMsgAttach{}
//消息类型
typeCode := ChatroomCustomMessageTypeFullScreenNoticeMsg
attachModel.TypeCode = typeCode
attachModel.BizData = msgData
err = service.SendCustomMsgToChatroomByChatroomManager(messageRoomId, attachModel)
return
}

View File

@ -0,0 +1,43 @@
package Netease
import (
"servicebase/pkg/log"
"encoding/json"
"github.com/pkg/errors"
"github.com/spf13/viper"
captcha "github.com/yidun/yidun-golang-sdk/yidun/service/captcha"
)
var (
CaptchaId = "28a052c000324d2e992e9e184291c92d"
)
// CaptchaSecondVerify 验证码二次校验请求
func CaptchaSecondVerify(captchaId, validate string) (bool, error) {
request := captcha.NewCaptchaVerifyRequest()
var user string = ""
request.SetCaptchaId(captchaId).SetValidate(validate).SetUser(user)
secretId := viper.GetString("netease.captcha.secretId")
secretKey := viper.GetString("netease.captcha.secretKey")
captchaClient := captcha.NewCaptchaVerifyClientWithAccessKey(secretId, secretKey)
response, err := captchaClient.Verify(request)
if err != nil {
log.ErrorF("CaptchaSecondVerify err: %+v", err)
return false, err
}
if response == nil {
log.ErrorF("CaptchaSecondVerify response is nil")
return false, errors.Errorf("CaptchaSecondVerify response is nil")
}
respBody, err := json.Marshal(response)
if err != nil {
return false, err
}
log.InfoF("CaptchaSecondVerify response is : %s", string(respBody))
if response.Result == nil {
log.ErrorF("CaptchaSecondVerify response.Result is nil")
return false, errors.Errorf("CaptchaSecondVerify response.Result is nil")
}
return *response.Result, nil
}

View File

@ -0,0 +1,196 @@
package Netease
import (
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"servicebase/pkg/log"
"sort"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/tjfoc/gmsm/sm3"
)
const (
apiURL = "https://verify.dun.163.com/v1/face/liveness/h5/auth"
recheckTokenUrl = "https://verify.dun.163.com/v1/face/liveness/h5/recheck"
version = "v1"
)
type ApplyInfoReq struct {
Name string
CardNo string
RedirectUrl string
CallBackUrl string
DataId string
CallbackValidate string
EncryptType string
}
type BaseRsp[T any] struct {
Code int `json:"code"`
Msg string `json:"msg"`
Result T `json:"result"`
}
type H5ApplyLivePersonRsp struct {
AuthUrl string `json:"authUrl"`
AuthToken string `json:"authToken"`
}
type H5ReCheckLivePersonTokenRsp struct {
TaskId string `json:"taskId"`
PicType int64 `json:"picType"`
Avatar string `json:"avatar"`
Status int64 `json:"status"`
ReasonType int `json:"reasonType"`
IsPayed int64 `json:"isPayed"`
SimilarityScore float64 `json:"similarityScore"`
FaceMatched int64 `json:"faceMatched"`
FaceAttributeInfo interface{} `json:"faceAttributeInfo"`
ExtInfo ExtInfoEntity `json:"extInfo"`
}
type ExtInfoEntity struct {
SuspectedNonageFlag bool `json:"suspectedNonageFlag"`
}
// H5ApplyLivePerson 请求易盾生成h5人身验证h5 url 和 authToken
func H5ApplyLivePerson(ctx context.Context, req *ApplyInfoReq) (*H5ApplyLivePersonRsp, error) {
params := url.Values{
"name": []string{req.Name},
"cardNo": []string{req.CardNo},
"redirectUrl": []string{req.RedirectUrl},
}
rsp, err := apply(params)
if err != nil {
return nil, err
}
return rsp, nil
}
// H5ReCheckLivePersonToken 根据authToken二次验证并获取相关信息如正面拍照的活体图片
func H5ReCheckLivePersonToken(ctx context.Context, token string) (*H5ReCheckLivePersonTokenRsp, error) {
params := url.Values{
"authToken": []string{token},
}
rsp, err := checkToken(params)
if err != nil {
return nil, err
}
return rsp, nil
}
// 生成签名信息
func genSignature(params url.Values) string {
var paramStr string
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
paramStr += key + params[key][0]
}
paramStr += viper.GetString("netease.captcha.secretKey")
if params["signatureMethod"] != nil && params["signatureMethod"][0] == "SM3" {
sm3Reader := sm3.New()
sm3Reader.Write([]byte(paramStr))
return hex.EncodeToString(sm3Reader.Sum(nil))
}
md5Reader := md5.New()
md5Reader.Write([]byte(paramStr))
return hex.EncodeToString(md5Reader.Sum(nil))
}
// 请求易盾接口
func apply(params url.Values) (*H5ApplyLivePersonRsp, error) {
params["secretId"] = []string{viper.GetString("netease.captcha.secretId")}
params["businessId"] = []string{viper.GetString("netease.captcha.businessId")}
params["version"] = []string{version}
params["timestamp"] = []string{strconv.FormatInt(time.Now().UnixNano()/1000000, 10)}
params["nonce"] = []string{strconv.FormatInt(rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(10000000000), 10)}
params["signature"] = []string{genSignature(params)}
resp, err := http.Post(apiURL, "application/x-www-form-urlencoded", strings.NewReader(params.Encode()))
if err != nil {
return nil, errors.WithStack(err)
}
if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("请求网易易盾获取h5人身验证失败状态码为%d", resp.StatusCode)
}
defer resp.Body.Close()
contents, _ := io.ReadAll(resp.Body)
log.InfoF("请求网易易盾获取h5人身验证响应: %s", string(contents))
var rsp BaseRsp[*H5ApplyLivePersonRsp]
err = json.Unmarshal(contents, &rsp)
if err != nil {
return nil, errors.WithStack(err)
}
if rsp.Code != 200 {
return nil, errors.Errorf("响应体状态码不是200(%d),信息: %s", rsp.Code, rsp.Msg)
}
return rsp.Result, nil
}
func checkToken(params url.Values) (*H5ReCheckLivePersonTokenRsp, error) {
params["secretId"] = []string{viper.GetString("netease.captcha.secretId")}
params["businessId"] = []string{viper.GetString("netease.captcha.businessId")}
params["version"] = []string{version}
params["timestamp"] = []string{strconv.FormatInt(time.Now().UnixNano()/1000000, 10)}
params["nonce"] = []string{strconv.FormatInt(rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(10000000000), 10)}
params["signature"] = []string{genSignature(params)}
resp, err := http.Post(recheckTokenUrl, "application/x-www-form-urlencoded", strings.NewReader(params.Encode()))
if err != nil {
return nil, errors.WithStack(err)
}
if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("请求网易易盾获取h5人身验证失败状态码为%d", resp.StatusCode)
}
defer resp.Body.Close()
contents, _ := io.ReadAll(resp.Body)
log.InfoF("请求网易云盾获取人身核验二次验证token响应: %s", string(contents))
var rsp BaseRsp[*H5ReCheckLivePersonTokenRsp]
err = json.Unmarshal(contents, &rsp)
if err != nil {
return nil, errors.WithStack(err)
}
if rsp.Code != 200 {
return nil, errors.Errorf("响应体状态码不是200(%d),信息: %s", rsp.Code, rsp.Msg)
}
return rsp.Result, nil
}
var livePersonAndIdCheckFailReason = map[int]string{
2: "活体通过,姓名身份证号一致,人脸比对非同一人",
3: "活体通过,姓名身份证号不一致",
4: "活体不通过",
5: "活体检测超时或出现异常",
6: "活体通过,查无此身份证",
7: "活体通过,库中无此身份证照片",
8: "活体通过,人脸照过大",
9: "活体通过,权威数据源出现异常",
10: "疑似攻击,建议拦截",
11: "检测对象为未成年人",
}
func GetLivePersonAndIdCheckFailReason(code int) string {
r, ok := livePersonAndIdCheckFailReason[code]
if !ok {
return fmt.Sprintf("其他原因ReasonType==%d", code)
}
return r
}

View File

@ -0,0 +1,78 @@
package Netease
import (
"servicebase/pkg/common/HyTools"
"servicebase/pkg/log"
"servicebase/pkg/partner/qiniu"
"context"
"github.com/spf13/viper"
"io"
"net/http"
"testing"
)
func TestApplyH5LivePerson(t *testing.T) {
log.Init()
rst, err := H5ApplyLivePerson(context.Background(), &ApplyInfoReq{
Name: "xxx", // 姓名
CardNo: "xxxxxxx", // 身份证号码x需要变成大写X
RedirectUrl: "https://www.baidu.com",
CallBackUrl: "",
DataId: "",
CallbackValidate: "",
EncryptType: "",
})
if err != nil {
t.Error(err)
}
println(rst.AuthToken)
println(rst.AuthUrl)
}
func TestH5ReCheckLivePersonToken(t *testing.T) {
log.Init()
_, err := H5ReCheckLivePersonToken(context.Background(), "6ZgHU-x9XsRVF8aS7PIFppBho28QEUAR")
if err != nil {
t.Error(err)
}
}
func TestB(t *testing.T) {
log.Init()
// key: CwGN8XGmbEZr7qqJl-y-QodcUYREz8ph_glVKCqp
// secret: KbZKt8WwQcZ6II0tygMLsO3KWpm50aMz737VaMV0
viper.Set("qiniu.key", "CwGN8XGmbEZr7qqJl-y-QodcUYREz8ph_glVKCqp")
viper.Set("qiniu.secret", "KbZKt8WwQcZ6II0tygMLsO3KWpm50aMz737VaMV0")
viper.Set("qiniu.bucket.photo", "ddphoto")
key := func() string {
resp, err := http.Get("xxx")
if err != nil {
log.ErrorF("获取人脸核身正面照图片错误:%+v", err)
return ""
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.ErrorF("获取人脸核身正面照图片错误 http code为%d", resp.StatusCode)
return ""
}
imageData, err := io.ReadAll(resp.Body)
if err != nil {
log.ErrorF("")
return ""
}
key := "upload/" + HyTools.GetUUID() + ".jpg"
//上传文件
newKey := qiniu.UploadFile(context.Background(), viper.GetString("qiniu.bucket.photo"), imageData, key)
if len(newKey) == 0 {
log.ErrorF("获取人脸核身正面照图片上传到七牛oss失败")
return ""
}
return key
}()
println(key)
}

View File

@ -0,0 +1,84 @@
package client
import (
"fmt"
"servicebase/pkg/tools"
rtctokenbuilder "github.com/AgoraIO/Tools/DynamicKey/AgoraDynamicKey/go/src/rtctokenbuilder2"
"github.com/spf13/viper"
)
func GenerateShenWangToken(userNo, RoomId string) (token string, e error) {
// Need to set environment variable AGORA_APP_ID
appId := viper.GetString("agora.tokenGen.appId")
// Need to set environment variable AGORA_APP_CERTIFICATE
appCertificate := viper.GetString("agora.tokenGen.appCertificate")
channelName := RoomId
uidStr := userNo
uid := uint32(tools.StrToInt32(userNo))
expire := 3600 * 24
tokenExpirationInSeconds := uint32(expire)
privilegeExpirationInSeconds := uint32(expire)
joinChannelPrivilegeExpireInSeconds := uint32(expire)
pubAudioPrivilegeExpireInSeconds := uint32(expire)
pubVideoPrivilegeExpireInSeconds := uint32(expire)
pubDataStreamPrivilegeExpireInSeconds := uint32(expire)
fmt.Println("App Id:", appId)
fmt.Println("App Certificate:", appCertificate)
if appId == "" || appCertificate == "" {
fmt.Println("Need to set environment variable AGORA_APP_ID and AGORA_APP_CERTIFICATE")
return
}
result, err := rtctokenbuilder.BuildTokenWithUid(appId, appCertificate, channelName, uid, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
if err != nil {
fmt.Println(err)
return "", err
} else {
fmt.Printf("Token with int uid: %s\n", result)
}
result, err = rtctokenbuilder.BuildTokenWithUserAccount(appId, appCertificate, channelName, uidStr, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
if err != nil {
fmt.Println(err)
return "", err
} else {
fmt.Printf("Token with user account: %s\n", result)
}
result, err = rtctokenbuilder.BuildTokenWithUidAndPrivilege(appId, appCertificate, channelName, uid,
tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds, pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds)
if err != nil {
fmt.Println(err)
return "", err
} else {
fmt.Printf("Token with int uid and privilege: %s\n", result)
}
result, err = rtctokenbuilder.BuildTokenWithUserAccountAndPrivilege(appId, appCertificate, channelName, uidStr,
tokenExpirationInSeconds, joinChannelPrivilegeExpireInSeconds, pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds)
if err != nil {
fmt.Println(err)
return "", err
} else {
fmt.Printf("Token with user account and privilege: %s\n", result)
}
result, err = rtctokenbuilder.BuildTokenWithRtm(appId, appCertificate, channelName, uidStr, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds)
if err != nil {
fmt.Println(err)
return "", err
} else {
fmt.Printf("Token with RTM: %s\n", result)
}
result, err = rtctokenbuilder.BuildTokenWithRtm2(appId, appCertificate, channelName, uidStr, rtctokenbuilder.RolePublisher, tokenExpirationInSeconds, privilegeExpirationInSeconds, pubAudioPrivilegeExpireInSeconds, pubVideoPrivilegeExpireInSeconds, pubDataStreamPrivilegeExpireInSeconds, uidStr, tokenExpirationInSeconds)
if err != nil {
fmt.Println(err)
return "", err
} else {
fmt.Printf("Token with RTM: %s\n", result)
}
return result, err
}

View File

@ -0,0 +1,11 @@
package client
import (
"fmt"
"testing"
)
func TestGenerateShenWangToken(t *testing.T) {
gotToken, err := GenerateShenWangToken("65945666", "9956050")
fmt.Printf("%+v %+v\n", gotToken, err)
}

311
pkg/client/sms_client.go Normal file
View File

@ -0,0 +1,311 @@
package client
import (
"errors"
"fmt"
"servicebase/pkg/helper"
"servicebase/pkg/log"
"sync"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v3/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
"github.com/spf13/viper"
)
// CreateAliSmsClient
/**
* 使用AK&SK初始化账号Client
* @param accessKeyId
* @param accessKeySecret
* @return Client
* @throws Exception
*/
func CreateAliSmsClient(accessKeyId *string, accessKeySecret *string) (_result *dysmsapi20170525.Client, _err error) {
config := &openapi.Config{
// 必填,您的 AccessKey ID
AccessKeyId: accessKeyId,
// 必填,您的 AccessKey Secret
AccessKeySecret: accessKeySecret,
}
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
config.Endpoint = tea.String("dysmsapi.aliyuncs.com")
_result = &dysmsapi20170525.Client{}
_result, _err = dysmsapi20170525.NewClient(config)
return _result, _err
}
var (
smsSignList = []string{"淮南东东网络科技"}
tempCodeList = []string{"SMS_320216048"}
smsSignCodeLock sync.Mutex
smsSignCodeIndex int64
)
func getSignAndCode() (sign, code string) {
smsSignCodeLock.Lock()
defer smsSignCodeLock.Unlock()
i := smsSignCodeIndex % int64(len(smsSignList))
sign = smsSignList[int(i)]
code = tempCodeList[int(i)]
smsSignCodeIndex++
return
}
func SendVerifyCode01(mobile, content string, fn func(_result *dysmsapi20170525.SendSmsResponse, success bool)) (_result *dysmsapi20170525.SendSmsResponse, e error) {
if !helper.IsMobile(mobile) {
return nil, errors.New("手机号有误!")
}
sign, code := getSignAndCode()
log.InfoF("get sms sign and code: %s %s", sign, code)
_result, e = SendMobileMsg01(mobile, sign, code, content)
if fn != nil {
fn(_result, e == nil)
}
return
}
func SendMobileMsg01(mobile, signName, tempCode, content string) (_result *dysmsapi20170525.SendSmsResponse, _err error) {
var (
ID = viper.GetString("aliyun.sms.accessKeyId")
Secret = viper.GetString("aliyun.sms.accessKeySecret")
)
// 请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用,仅供参考,建议使用更安全的 STS 方式更多鉴权访问方式请参见https://help.aliyun.com/document_detail/378661.html
client, _err := CreateAliSmsClient(tea.String(ID), tea.String(Secret))
if _err != nil {
return nil, _err
}
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
PhoneNumbers: tea.String(mobile),
SignName: tea.String(signName),
TemplateCode: tea.String(tempCode),
TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", content)),
}
res, tryErr := func() (_result *dysmsapi20170525.SendSmsResponse, _e error) {
defer func() {
if r := tea.Recover(recover()); r != nil {
_e = r
}
}()
// 复制代码运行请自行打印 API 的返回值
_result, _err = client.SendSmsWithOptions(sendSmsRequest, &util.RuntimeOptions{})
if _err != nil {
return nil, _err
}
return
}()
if tryErr != nil {
var e = &tea.SDKError{}
if _t, ok := tryErr.(*tea.SDKError); ok {
e = _t
} else {
e.Message = tea.String(tryErr.Error())
}
// 如有需要,请打印 error
_, _err = util.AssertAsString(e.Message)
if _err != nil {
return nil, _err
}
}
return res, _err
}
func SendVerifyCodeUsaAndCanada(mobile, content string, fn func(_result *dysmsapi20170525.SendSmsResponse, success bool)) (_result *dysmsapi20170525.SendSmsResponse, e error) {
// if !helper.IsMobile(mobile) {
// return nil, errors.New("手机号有误!")
// }
sign, code := getSignAndCode()
log.InfoF("SendVerifyCodeUsaAndCanada get sms sign and code: %s %s", sign, code)
_result, e = SendMobileMsgUsaAndCanada(mobile, sign, code, content)
if fn != nil {
fn(_result, e == nil)
}
return
}
func SendVerifyCodeGlobalOther(mobile, content string, fn func(_result *dysmsapi20170525.SendSmsResponse, success bool)) (_result *dysmsapi20170525.SendSmsResponse, e error) {
// if !helper.IsMobile(mobile) {
// return nil, errors.New("手机号有误!")
// }
sign, code := getSignAndCode()
log.InfoF("SendVerifyCodeUsaAndCanada get sms sign and code: %s %s", sign, code)
_result, e = SendMobileMsgGlobalOther(mobile, sign, code, content)
if fn != nil {
fn(_result, e == nil)
}
return
}
// SendMobileMsgUsaAndCanada 美国/加拿大地区短信发送
func SendMobileMsgUsaAndCanada(mobile, signName, tempCode, content string) (_result *dysmsapi20170525.SendSmsResponse, _err error) {
var (
ID = viper.GetString("aliyun.sms.accessKeyId")
Secret = viper.GetString("aliyun.sms.accessKeySecret")
)
// 请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用,仅供参考,建议使用更安全的 STS 方式更多鉴权访问方式请参见https://help.aliyun.com/document_detail/378661.html
client, _err := CreateAliSmsClient(tea.String(ID), tea.String(Secret))
if _err != nil {
return nil, _err
}
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
PhoneNumbers: tea.String(mobile),
SignName: tea.String(signName),
TemplateCode: tea.String(tempCode),
TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", content)),
}
res, tryErr := func() (_result *dysmsapi20170525.SendSmsResponse, _e error) {
defer func() {
if r := tea.Recover(recover()); r != nil {
_e = r
}
}()
// 复制代码运行请自行打印 API 的返回值
_result, _err = client.SendSmsWithOptions(sendSmsRequest, &util.RuntimeOptions{})
if _err != nil {
return nil, _err
}
return
}()
if tryErr != nil {
var e = &tea.SDKError{}
if _t, ok := tryErr.(*tea.SDKError); ok {
e = _t
} else {
e.Message = tea.String(tryErr.Error())
}
// 如有需要,请打印 error
_, _err = util.AssertAsString(e.Message)
if _err != nil {
return nil, _err
}
}
return res, _err
}
// SendMobileMsgGlobalOther 全球其他国家
func SendMobileMsgGlobalOther(mobile, signName, tempCode, content string) (_result *dysmsapi20170525.SendSmsResponse, _err error) {
var (
// ID AccessKey ID
// LTAI5tRfkJnr6Y6NU2T6jWoB
//
// AccessKey Secret
// BcaMWgXkwdxdJXFnCwRQbEl3Q3Xtxl
// TODO: 替换为您的AccessKey ID和AccessKey Secret
ID = viper.GetString("aliyun.sms.accessKeyId")
Secret = viper.GetString("aliyun.sms.accessKeySecret")
)
// 请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用,仅供参考,建议使用更安全的 STS 方式更多鉴权访问方式请参见https://help.aliyun.com/document_detail/378661.html
client, _err := CreateAliSmsClient(tea.String(ID), tea.String(Secret))
if _err != nil {
return nil, _err
}
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
PhoneNumbers: tea.String(mobile),
SignName: tea.String(signName),
TemplateCode: tea.String(tempCode),
TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", content)),
}
res, tryErr := func() (_result *dysmsapi20170525.SendSmsResponse, _e error) {
defer func() {
if r := tea.Recover(recover()); r != nil {
_e = r
}
}()
// 复制代码运行请自行打印 API 的返回值
_result, _err = client.SendSmsWithOptions(sendSmsRequest, &util.RuntimeOptions{})
if _err != nil {
return nil, _err
}
return
}()
if tryErr != nil {
var e = &tea.SDKError{}
if _t, ok := tryErr.(*tea.SDKError); ok {
e = _t
} else {
e.Message = tea.String(tryErr.Error())
}
// 如有需要,请打印 error
_, _err = util.AssertAsString(e.Message)
if _err != nil {
return nil, _err
}
}
return res, _err
}
func SendVerifyHKMacauAndTW(mobile, content string, fn func(_result *dysmsapi20170525.SendSmsResponse, success bool)) (_result *dysmsapi20170525.SendSmsResponse, e error) {
if !helper.IsMobile(mobile) {
return nil, errors.New("手机号有误!")
}
sign, code := getSignAndCode()
log.InfoF("get sms sign and code: %s %s", sign, code)
_result, e = SendMobileMsgHKMacauAndTW(mobile, sign, code, content)
if fn != nil {
fn(_result, e == nil)
}
return
}
// SendMobileMsgHKMacauAndTW 港澳台
func SendMobileMsgHKMacauAndTW(mobile, signName, tempCode, content string) (_result *dysmsapi20170525.SendSmsResponse, _err error) {
var (
ID = viper.GetString("aliyun.sms.accessKeyId")
Secret = viper.GetString("aliyun.sms.accessKeySecret")
)
// 请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用,仅供参考,建议使用更安全的 STS 方式更多鉴权访问方式请参见https://help.aliyun.com/document_detail/378661.html
client, _err := CreateAliSmsClient(tea.String(ID), tea.String(Secret))
if _err != nil {
return nil, _err
}
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
PhoneNumbers: tea.String(mobile),
SignName: tea.String(signName),
TemplateCode: tea.String(tempCode),
TemplateParam: tea.String(fmt.Sprintf("{\"code\":\"%s\"}", content)),
}
res, tryErr := func() (_result *dysmsapi20170525.SendSmsResponse, _e error) {
defer func() {
if r := tea.Recover(recover()); r != nil {
_e = r
}
}()
// 复制代码运行请自行打印 API 的返回值
_result, _err = client.SendSmsWithOptions(sendSmsRequest, &util.RuntimeOptions{})
if _err != nil {
return nil, _err
}
return
}()
if tryErr != nil {
var e = &tea.SDKError{}
if _t, ok := tryErr.(*tea.SDKError); ok {
e = _t
} else {
e.Message = tea.String(tryErr.Error())
}
// 如有需要,请打印 error
_, _err = util.AssertAsString(e.Message)
if _err != nil {
return nil, _err
}
}
return res, _err
}

260
pkg/client/tencent.go Normal file
View File

@ -0,0 +1,260 @@
package client
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/spf13/viper"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111" // 引入sms
)
func _SmsFullContent(template string, param []interface{}) string {
var smsTemplateContentMap = map[string]string{
viper.GetString("tencent.smsTemplateCode"): "验证码为:{1},您正在登录,若非本人操作,请勿泄露。",
}
return fmt.Sprintf(smsTemplateContentMap[template], param...)
}
func _SmsSendVerifyCode(mobile, code string) (*sms.SendSmsResponseParams, error) {
return _SmsSendByTemplate(viper.GetString("tencent.smsTemplateCode"), mobile, []string{code})
}
func _SmsSendByTemplate(templateID, mobile string, param []string) (*sms.SendSmsResponseParams, error) {
if !strings.HasPrefix(mobile, "+86") && !strings.HasPrefix(mobile, "86") {
mobile = "+86" + mobile
}
credential := common.NewCredential(viper.GetString("tencent.secretId"), viper.GetString("tencent.secretKey"))
cpf := profile.NewClientProfile()
cpf.HttpProfile.ReqMethod = "POST"
cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
cpf.SignMethod = "HmacSHA1"
client, _ := sms.NewClient(credential, "ap-guangzhou", cpf)
request := sms.NewSendSmsRequest()
request.SmsSdkAppId = common.StringPtr(viper.GetString("tencent.sdkAppId"))
request.SignName = common.StringPtr(viper.GetString("tencent.signName"))
request.TemplateId = common.StringPtr(templateID)
request.TemplateParamSet = common.StringPtrs(param)
request.PhoneNumberSet = common.StringPtrs([]string{mobile})
request.SessionContext = common.StringPtr("")
request.ExtendCode = common.StringPtr("")
request.SenderId = common.StringPtr("")
response, err := client.SendSms(request)
if _, ok := err.(*errors.TencentCloudSDKError); ok {
fmt.Printf("An API error has returned: %s", err)
return nil, err
}
if err != nil {
return nil, err
}
return response.Response, nil
// {"SendStatusSet":[{"SerialNo":"4012:319315169316952992330592922","PhoneNumber":"+8615215229221","Fee":1,"SessionContext":"","Code":"Ok","Message":"send success","IsoCode":"CN"}],"RequestId":"82798726-4312-4ea8-926b-b23894a39cf1"}
}
func _SmsQueryStatus(mobile string) (*sms.PullSmsSendStatusByPhoneNumberResponseParams, error) {
credential := common.NewCredential(viper.GetString("tencent.secretId"), viper.GetString("tencent.secretKey"))
cpf := profile.NewClientProfile()
cpf.HttpProfile.ReqMethod = "POST"
cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
cpf.SignMethod = "HmacSHA1"
client, _ := sms.NewClient(credential, "ap-guangzhou", cpf)
request := sms.NewPullSmsSendStatusByPhoneNumberRequest()
request.SmsSdkAppId = common.StringPtr(viper.GetString("tencent.sdkAppId"))
request.Limit = common.Uint64Ptr(10)
request.PhoneNumber = common.StringPtr(mobile)
request.BeginTime = common.Uint64Ptr(uint64(time.Now().Unix()) - (60 * 60))
request.Offset = common.Uint64Ptr(0)
response, err := client.PullSmsSendStatusByPhoneNumber(request)
if _, ok := err.(*errors.TencentCloudSDKError); ok {
fmt.Printf("An API error has returned: %s", err)
return nil, err
}
if err != nil {
return nil, err
}
return response.Response, nil
// {"PullSmsSendStatusSet":[{"UserReceiveTime":1695299239,"CountryCode":"86","SubscriberNumber":"15215229221","PhoneNumber":"+8615215229221","SerialNo":"4012:319315169316952992330592922","ReportStatus":"SUCCESS","Description":"DELIVRD","SessionContext":""}],"RequestId":"40476317-2cf4-4c48-bf30-2b33ac14a8b7"}
}
func _SendSms() {
/* 必要步骤:
* 实例化一个认证对象入参需要传入腾讯云账户密钥对secretIdsecretKey。
* 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
* 您也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
* 以免泄露密钥对危及您的财产安全。
* SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */
credential := common.NewCredential(
// os.Getenv("TENCENTCLOUD_SECRET_ID"),
// os.Getenv("TENCENTCLOUD_SECRET_KEY"),
viper.GetString("tencent.secretId"),
viper.GetString("tencent.secretKey"),
)
/* 非必要步骤:
* 实例化一个客户端配置对象,可以指定超时时间等配置 */
cpf := profile.NewClientProfile()
/* SDK默认使用POST方法。
* 如果您一定要使用GET方法可以在这里设置。GET方法无法处理一些较大的请求 */
cpf.HttpProfile.ReqMethod = "POST"
/* SDK有默认的超时时间非必要请不要进行调整
* 如有需要请在代码中查阅以获取最新的默认值 */
// cpf.HttpProfile.ReqTimeout = 5
/* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */
cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
/* SDK默认用TC3-HMAC-SHA256进行签名非必要请不要修改这个字段 */
cpf.SignMethod = "HmacSHA1"
/* 实例化要请求产品(以sms为例)的client对象
* 第二个参数是地域信息可以直接填写字符串ap-guangzhou支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */
client, _ := sms.NewClient(credential, "ap-guangzhou", cpf)
/* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
* 您可以直接查询SDK源码确定接口有哪些属性可以设置
* 属性可能是基本类型,也可能引用了另一个数据结构
* 推荐使用IDE进行开发可以方便的跳转查阅各个接口和数据结构的文档说明 */
request := sms.NewSendSmsRequest()
/* 基本类型的设置:
* SDK采用的是指针风格指定参数即使对于基本类型您也需要用指针来对参数赋值。
* SDK提供对基本类型的指针引用封装函数
* 帮助链接:
* 短信控制台: https://console.cloud.tencent.com/smsv2
* 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */
/* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId示例如1400006666 */
// 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看
request.SmsSdkAppId = common.StringPtr(viper.GetString("tencent.sdkAppId"))
/* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */
// 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看
request.SignName = common.StringPtr("好出行Trip小程序")
/* 模板 ID: 必须填写已审核通过的模板 ID */
// 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看
request.TemplateId = common.StringPtr("1916515")
/* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空*/
request.TemplateParamSet = common.StringPtrs([]string{"6666"})
/* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
* 示例如:+8613711112222 其中前面有一个+号 86为国家码13711112222为手机号最多不要超过200个手机号*/
request.PhoneNumberSet = common.StringPtrs([]string{"+8615215229221"})
/* 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息server 会原样返回 */
request.SessionContext = common.StringPtr("")
/* 短信码号扩展号(无需要可忽略): 默认未开通,如需开通请联系 [腾讯云短信小助手] */
request.ExtendCode = common.StringPtr("")
/* 国内短信无需填写该项;国际/港澳台短信已申请独立 SenderId 需要填写该字段,默认使用公共 SenderId无需填写该字段。注月度使用量达到指定量级可申请独立 SenderId 使用,详情请联系 [腾讯云短信小助手](https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81)。 */
request.SenderId = common.StringPtr("")
// 通过client对象调用想要访问的接口需要传入请求对象
response, err := client.SendSms(request)
// 处理异常
if _, ok := err.(*errors.TencentCloudSDKError); ok {
fmt.Printf("An API error has returned: %s", err)
return
}
// 非SDK异常直接失败。实际代码中可以加入其他的处理。
if err != nil {
panic(err)
}
b, _ := json.Marshal(response.Response)
// 打印返回的json字符串
fmt.Printf("%s", b)
// {"SendStatusSet":[{"SerialNo":"4012:319315169316952992330592922","PhoneNumber":"+8615215229221","Fee":1,"SessionContext":"","Code":"Ok","Message":"send success","IsoCode":"CN"}],"RequestId":"82798726-4312-4ea8-926b-b23894a39cf1"}
/* 当出现以下错误码时,快速解决方案参考
* [FailedOperation.SignatureIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.signatureincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F)
* [FailedOperation.TemplateIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.templateincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F)
* [UnauthorizedOperation.SmsSdkAppIdVerifyFail](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunauthorizedoperation.smssdkappidverifyfail-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F)
* [UnsupportedOperation.ContainDomesticAndInternationalPhoneNumber](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunsupportedoperation.containdomesticandinternationalphonenumber-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F)
* 更多错误,可咨询[腾讯云助手](https://tccc.qcloud.com/web/im/index.html#/chat?webAppId=8fa15978f85cb41f7e2ea36920cb3ae1&title=Sms)
*/
}
func _PullStatus() {
/* 必要步骤:
* 实例化一个认证对象入参需要传入腾讯云账户密钥对secretIdsecretKey。
* 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。
* 您也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人,
* 以免泄露密钥对危及您的财产安全。
* SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */
credential := common.NewCredential(
// os.Getenv("TENCENTCLOUD_SECRET_ID"),
// os.Getenv("TENCENTCLOUD_SECRET_KEY"),
viper.GetString("tencent.secretId"),
viper.GetString("tencent.secretKey"),
)
/* 非必要步骤:
* 实例化一个客户端配置对象,可以指定超时时间等配置 */
cpf := profile.NewClientProfile()
/* SDK默认使用POST方法。
* 如果您一定要使用GET方法可以在这里设置。GET方法无法处理一些较大的请求 */
cpf.HttpProfile.ReqMethod = "POST"
/* SDK有默认的超时时间非必要请不要进行调整
* 如有需要请在代码中查阅以获取最新的默认值 */
// cpf.HttpProfile.ReqTimeout = 5
/* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */
cpf.HttpProfile.Endpoint = "sms.tencentcloudapi.com"
/* SDK默认用TC3-HMAC-SHA256进行签名
* 非必要请不要修改这个字段 */
cpf.SignMethod = "HmacSHA1"
/* 实例化要请求产品(以sms为例)的client对象
* 第二个参数是地域信息可以直接填写字符串ap-guangzhou支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */
client, _ := sms.NewClient(credential, "ap-guangzhou", cpf)
/* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
* 您可以直接查询SDK源码确定接口有哪些属性可以设置
* 属性可能是基本类型,也可能引用了另一个数据结构
* 推荐使用IDE进行开发可以方便的跳转查阅各个接口和数据结构的文档说明 */
request := sms.NewPullSmsSendStatusByPhoneNumberRequest()
/* 基本类型的设置:
* SDK采用的是指针风格指定参数即使对于基本类型您也需要用指针来对参数赋值。
* SDK提供对基本类型的指针引用封装函数
* 帮助链接:
* 短信控制台: https://console.cloud.tencent.com/smsv2
* 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */
/* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId示例如1400006666 */
request.SmsSdkAppId = common.StringPtr(viper.GetString("tencent.sdkAppId"))
/* 拉取最大条数最多100条 */
request.Limit = common.Uint64Ptr(10)
PhoneNumber := "+8615215229221"
request.PhoneNumber = &PhoneNumber
beginTime := uint64(time.Now().Unix()) - (60 * 60)
request.BeginTime = &beginTime
Offset := uint64(0)
request.Offset = &Offset
// 通过client对象调用想要访问的接口需要传入请求对象
response, err := client.PullSmsSendStatusByPhoneNumber(request)
// 处理异常
if _, ok := err.(*errors.TencentCloudSDKError); ok {
fmt.Printf("An API error has returned: %s", err)
return
}
// 非SDK异常直接失败。实际代码中可以加入其他的处理。
if err != nil {
panic(err)
}
b, _ := json.Marshal(response.Response)
// 打印返回的json字符串
fmt.Printf("%s", b)
// {"PullSmsSendStatusSet":[{"UserReceiveTime":1695299239,"CountryCode":"86","SubscriberNumber":"15215229221","PhoneNumber":"+8615215229221","SerialNo":"4012:319315169316952992330592922","ReportStatus":"SUCCESS","Description":"DELIVRD","SessionContext":""}],"RequestId":"40476317-2cf4-4c48-bf30-2b33ac14a8b7"}
}

View File

@ -0,0 +1,74 @@
package client
import (
"testing"
sms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms/v20210111"
)
func TestSendSms(t *testing.T) {
tests := []struct {
name string
}{
{
name: "test01",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_SendSms()
})
}
}
func TestPullStatus(t *testing.T) {
tests := []struct {
name string
}{
{
name: "test01",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_PullStatus()
})
}
}
func TestSmsSendByTemplate(t *testing.T) {
type args struct {
templateID string
mobile string
param []string
}
tests := []struct {
name string
args args
want *sms.SendSmsResponseParams
wantErr bool
}{
{
name: "01",
args: args{
templateID: "2319145",
mobile: "",
param: []string{"6666"},
},
want: &sms.SendSmsResponseParams{},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := _SmsSendByTemplate(tt.args.templateID, tt.args.mobile, tt.args.param)
if (err != nil) != tt.wantErr {
t.Errorf("SmsSendByTemplate() error = %v, wantErr %v", err, tt.wantErr)
return
}
// if !reflect.DeepEqual(got, tt.want) {
// t.Errorf("SmsSendByTemplate() = %v, want %v", got, tt.want)
// }
})
}
}

215
pkg/client/wechat_client.go Normal file
View File

@ -0,0 +1,215 @@
package client
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
const (
UserAppID = ""
UserSecret = ""
DriverAppID = ""
DriverSecret = ""
Code2SessionAppURL = "https://api.weixin.qq.com/sns/oauth2/access_token"
UserInfoAppURL = "https://api.weixin.qq.com/sns/userinfo"
Code2SessionJsURL = "https://api.weixin.qq.com/sns/jscode2session"
SendMessageURL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN"
GetAccessTokenURL = "https://api.weixin.qq.com/cgi-bin/token"
)
func WeChatNewAuthJsReqMap(code string) map[string]string {
return map[string]string{
"appid": UserAppID,
"secret": UserSecret,
"js_code": code,
"grant_type": "authorization_code"}
}
func WeChatNewAuthAppReqMap(code string) map[string]string {
return map[string]string{
"appid": UserAppID,
"secret": UserSecret,
"code": code,
"grant_type": "authorization_code"}
}
func WeChatGetAccessTokenReq(_type int) map[string]string {
switch _type {
case 1: // 用户
return map[string]string{
"grant_type": "client_credential",
"appid": UserAppID,
"secret": UserSecret,
}
case 2: // 其他
return map[string]string{
"grant_type": "client_credential",
"appid": DriverAppID,
"secret": DriverSecret,
}
}
return nil
}
// 小程序换取token的返回
type WeChatLoginJsRes struct {
SessionKey string `json:"session_key"`
UnionID string `json:"unionid"`
OpenID string `json:"openid"`
ErrCode int32 `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
// APP换取token的返回
type WeChatLoginAppRes struct {
AccessToken string `json:"access_token"` // 接口调用凭证
ExpiresIn int32 `json:"expires_in"` // access_token 接口调用凭证超时时间,单位(秒)
RefreshToken string `json:"refresh_token"` // 用户刷新 access_token
UnionID string `json:"unionid"` // 用户统一标识。针对一个微信开放平台账号下的应用,同一用户的 unionid 是唯一的
OpenID string `json:"openid"` // 授权用户唯一标识
Scope string `json:"scope"` // 用户授权的作用域,使用逗号(,)分隔
ErrCode int32 `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
// APP获取用户信息
type WeChatUserInfoAppRes struct {
UnionID string `json:"unionid"` // 用户统一标识。针对一个微信开放平台账号下的应用,同一用户的 unionid 是唯一的
OpenID string `json:"openid"` // 授权用户唯一标识
Nickname string `json:"nickname"` // 普通用户昵称
Sex int32 `json:"sex"` // 普通用户性别1 为男性2 为女性
Province string `json:"province"` // 普通用户个人资料填写的省份
City string `json:"city"` // 普通用户个人资料填写的城市
Country string `json:"country"` // 国家,如中国为 CN
Avatar string `json:"headimgurl"` // 用户头像,最后一个数值代表正方形头像大小(有 0、46、64、96、132 数值可选0 代表 640*640 正方形头像),用户没有头像时该项为空
ErrCode int32 `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
// 换取token所用的请求
func WeChatNewAuthReqMapUser(code string) map[string]string {
return map[string]string{
"appid": UserAppID,
"secret": UserSecret,
"js_code": code,
"grant_type": "authorization_code"}
}
// 小程序换取token的请求
func WeChatCode2JsSession(headers map[string]string, params map[string]string) (wxLoginBody WeChatLoginJsRes, e error) {
var (
client = NewClient()
resp *http.Response
res []byte
)
req, _ := http.NewRequest(http.MethodGet, Code2SessionJsURL, nil)
for k, v := range headers {
req.Header.Add(k, v)
}
q := req.URL.Query()
for k, v := range params {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
if resp, e = client.Do(req); e != nil {
return
}
defer func(body io.ReadCloser) {
_ = body.Close()
}(resp.Body)
if res, e = io.ReadAll(resp.Body); e != nil {
return
}
if !success(resp.StatusCode) {
e = fmt.Errorf("status code error: %s", resp.Status)
}
if e = json.Unmarshal(res, &wxLoginBody); e != nil {
return
}
if wxLoginBody.ErrCode != 0 {
e = fmt.Errorf("获取微信登录凭证错误,错误码: %d 错误信息:%s", wxLoginBody.ErrCode, wxLoginBody.ErrMsg)
return
}
return
}
// APP换取token的请求
func WeChatCode2AppToken(headers map[string]string, params map[string]string) (wxLoginBody WeChatLoginAppRes, e error) {
var (
client = NewClient()
resp *http.Response
res []byte
)
req, _ := http.NewRequest(http.MethodGet, Code2SessionAppURL, nil)
for k, v := range headers {
req.Header.Add(k, v)
}
q := req.URL.Query()
for k, v := range params {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
if resp, e = client.Do(req); e != nil {
return
}
defer func(body io.ReadCloser) {
_ = body.Close()
}(resp.Body)
if res, e = io.ReadAll(resp.Body); e != nil {
return
}
if !success(resp.StatusCode) {
e = fmt.Errorf("status code error: %s", resp.Status)
}
if e = json.Unmarshal(res, &wxLoginBody); e != nil {
return
}
if wxLoginBody.ErrCode != 0 {
e = fmt.Errorf("获取微信登录凭证错误,错误码: %d 错误信息:%s", wxLoginBody.ErrCode, wxLoginBody.ErrMsg)
return
}
return
}
// APP获取用户信息
func WeChatUserInfoApp(token, openId string) (wxUserInfoBody WeChatUserInfoAppRes, e error) {
var (
client = NewClient()
resp *http.Response
res []byte
)
req, _ := http.NewRequest(http.MethodGet, UserInfoAppURL, nil)
q := req.URL.Query()
q.Add("access_token", token)
q.Add("openid", openId)
req.URL.RawQuery = q.Encode()
if resp, e = client.Do(req); e != nil {
return
}
defer func(body io.ReadCloser) {
_ = body.Close()
}(resp.Body)
if res, e = io.ReadAll(resp.Body); e != nil {
return
}
if !success(resp.StatusCode) {
e = fmt.Errorf("status code error: %s", resp.Status)
}
if e = json.Unmarshal(res, &wxUserInfoBody); e != nil {
return
}
if wxUserInfoBody.ErrCode != 0 {
e = fmt.Errorf("获取微信登录凭证错误,错误码: %d 错误信息:%s", wxUserInfoBody.ErrCode, wxUserInfoBody.ErrMsg)
return
}
return
}
func success(code int) bool {
return code >= 200 && code < 400
}

View File

@ -0,0 +1,76 @@
package client
import (
"encoding/json"
"fmt"
"strings"
"time"
)
type WeGetTokenRes struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
func WeGetToken() (token string, e error) {
gotRes, err := Get(GetAccessTokenURL, nil, WeChatGetAccessTokenReq(1))
if err != nil {
return "", err
}
var body WeGetTokenRes
if e = json.Unmarshal(gotRes, &body); e != nil {
return
}
token = body.AccessToken
return
}
type WeSendMessageReq map[string]WeSendMessageItemReq
type WeSendMessageItemReq struct {
Value string `json:"value"`
}
func WeSendMessage(openID, templateID string, message WeSendMessageReq, token string) (e error) {
if len(token) == 0 {
if token, e = WeGetToken(); e != nil {
return
}
}
gotRes, err := Post(strings.ReplaceAll(SendMessageURL, "ACCESS_TOKEN", token), nil, map[string]interface{}{
"template_id": templateID,
"touser": openID,
"data": message,
"miniprogram_state": "formal", // developer为开发版trial为体验版formal为正式版默认为正式版
"lang": "zh_CN", // 进入小程序查看”的语言类型支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为 zh_CN
})
if err != nil {
return err
}
fmt.Printf("%s\n", string(gotRes))
return
}
func WeSendNoticeTakeOrder(openID, company, driver, vehicel string, date time.Time) error {
const tid = "Ff5tDsIlBy-t51Usxh_oBvge9WbMbBFVKI020hIpWcA"
return WeSendMessage(openID, tid, messageNoticeTakeOrder(company, driver, vehicel, date), "")
}
// messageBookReminder 预约提醒消息
func messageNoticeTakeOrder(company, driver, vehicel string, date time.Time) WeSendMessageReq {
return WeSendMessageReq{
"thing1": WeSendMessageItemReq{Value: company}, // 公司名称
"thing2": WeSendMessageItemReq{Value: driver}, // 接单司机
"car_number3": WeSendMessageItemReq{Value: vehicel}, // 车牌号
"time4": WeSendMessageItemReq{Value: StrFromTime(date)}, // 接单时间
}
}
const (
TimeFormat = "2006-01-02 15:04:05"
DateFormat = "2006-01-02"
)
func StrFromTime(t time.Time) string {
return t.Format(TimeFormat)
}

5
pkg/comm/p_vars.go Normal file
View File

@ -0,0 +1,5 @@
package comm
const (
ApiDomain = "https://www.jx3api.com"
)

View File

@ -0,0 +1,118 @@
package main
import (
"flag"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/slb"
)
// LTAI5tCB8SmJcwfO
// dX4kgF4VcblsbCVdKvtCGCmvNbLkZK
// ./AliyunCloudClient -t b -o update -key LTAI5tCB8SmJcwfO -sec dX4kgF4VcblsbCVdKvtCGCmvNbLkZK -reg cn-hangzhou -lb "[{"ServerId":"ServerId","Weight":"0"}]
func main() {
var target string
flag.StringVar(&target, "t", "b", "target: b=backendServer")
var operation string
flag.StringVar(&operation, "o", "update", "operation: add、update、remove")
var accessKeyId string
flag.StringVar(&accessKeyId, "key", "accessKeyId", "accessKeyId")
var accessSecret string
flag.StringVar(&accessSecret, "sec", "accessSecret", "accessSecret")
var regionId string
flag.StringVar(&regionId, "reg", "regionId", "regionId: cn-hangzhou")
var loadBalancerId string
flag.StringVar(&loadBalancerId, "lb", "loadBalancerId", "loadBalancerId")
var params string
flag.StringVar(&params, "params", "", `SetBackendServers: [{"ServerId":"ServerId","Weight":"0"}]
AddBackendServers: [{"ServerId":"ServerId","Weight":"100","Type":"eni","ServerIp":"192.168.11.1"}]
RemoveBackendServers: [{"ServerId":"ServerId","Weight":"100"}]
`)
flag.Parse()
flag.Usage()
fmt.Printf("target=%s,operation=%s,accessKeyId=%s,accessSecret=%s,regionId=%s,loadBalancerId=%s,params=%s", target, operation, accessKeyId, accessSecret, regionId, loadBalancerId, params)
if target == "b" {
switch operation {
case "update":
SetBackendServers(regionId, accessKeyId, accessSecret, loadBalancerId, params)
break
case "add":
AddBackendServers(regionId, accessKeyId, accessSecret, loadBalancerId, params)
break
case "remove":
RemoveBackendServers(regionId, accessKeyId, accessSecret, loadBalancerId, params)
break
default:
fmt.Println("operation: " + operation + " not support")
}
} else {
fmt.Println("target: " + target + " not support")
}
}
func SetBackendServers(regionId, accessKeyId, accessSecret, loadBalancerId, params string) {
// client, err := slb.NewClientWithAccessKey(regionId, accessKeyId, accessSecret)
// if nil != err {
// fmt.Println("get client error:")
// fmt.Println(err.Error())
// }
// request := slb.CreateSetBackendServersRequest()
// request.Scheme = "https"
// request.LoadBalancerId = loadBalancerId
// request.BackendServers = params
// if nil == client {
// fmt.Println("error: client is nil")
// return
// }
// response, err := client.SetBackendServers(request)
// if err != nil {
// fmt.Print(err.Error())
// }
// fmt.Printf("response is %#v\n", response)
}
func AddBackendServers(regionId, accessKeyId, accessSecret, loadBalancerId, params string) {
client, err := slb.NewClientWithAccessKey(regionId, accessKeyId, accessSecret)
request := slb.CreateAddBackendServersRequest()
request.Scheme = "https"
request.LoadBalancerId = loadBalancerId
request.BackendServers = params
if nil == client {
fmt.Println("error: client is nil")
return
}
response, err := client.AddBackendServers(request)
if err != nil {
fmt.Print(err.Error())
}
fmt.Printf("response is %#v\n", response)
}
func RemoveBackendServers(regionId, accessKeyId, accessSecret, loadBalancerId, params string) {
client, err := slb.NewClientWithAccessKey(regionId, accessKeyId, accessSecret)
request := slb.CreateRemoveBackendServersRequest()
request.Scheme = "https"
request.LoadBalancerId = loadBalancerId
request.BackendServers = params
if nil == client {
fmt.Println("error: client is nil")
return
}
response, err := client.RemoveBackendServers(request)
if err != nil {
fmt.Print(err.Error())
}
fmt.Printf("response is %#v\n", response)
}

View File

@ -0,0 +1,70 @@
package main
import (
"encoding/json"
"fmt"
"strconv"
"github.com/tealeg/xlsx"
)
func main02() {
data := ReadSheet("./CmdTools/星钻VIP体系.xlsx", 0)
fmt.Println(json.Marshal(data))
for index, item := range data {
if index > 0 {
level, _ := strconv.Atoi(item[0])
exp, _ := strconv.Atoi(item[1])
fmt.Printf("insert into t_vip_config (id,vip_level,min_exp,vip_name,vip_icon,create_time) values (%d,%d,%d,'v%d','https://xz-static.10909.com/XingZuanCommon/Img/Vip/%d@2x.png',now());\n", level, level, exp, level, level)
}
}
}
func ReadSheet(file string, sheet int) [][]string {
var result [][]string
xlFile, err := xlsx.OpenFile(file)
if err != nil {
fmt.Println(err.Error())
return result
}
for index, row := range xlFile.Sheets[sheet].Rows {
if index > 60 {
break
}
var data []string
for index, cell := range row.Cells {
if index > 1 {
break
}
text := cell.String()
println(text)
data = append(data, text)
}
result = append(result, data)
}
return result
}
func ReadAll(file string) {
// 打开文件
xlFile, err := xlsx.OpenFile(file)
if err != nil {
fmt.Println(err.Error())
return
}
// 遍历sheet页读取
for _, sheet := range xlFile.Sheets {
fmt.Println("sheet name: ", sheet.Name)
//遍历行读取
for _, row := range sheet.Rows {
// 遍历每行的列读取
for _, cell := range row.Cells {
text := cell.String()
fmt.Printf("%20s", text)
}
fmt.Print("\n")
}
}
fmt.Println("\n\nimport success")
}

View File

@ -0,0 +1,94 @@
package main
import (
"flag"
"fmt"
clb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
)
// ./TencentCloudClient -t b -o add -key AKIDSoxxjI5tukPr9ECsQmwthKhroaCZlldI -sec b1FG2b3JMrESVLxjJdhYDgx12qPiplXB -reg ap-shanghai -params "{\"LoadBalancerId\":\"lb-jip7505r\",\"Targets\":[{\"InstanceId\":\"ins-n2i7x483\",\"Weight\":20}]}"
// ./TencentCloudClient -t b -o remove -key AKIDSoxxjI5tukPr9ECsQmwthKhroaCZlldI -sec b1FG2b3JMrESVLxjJdhYDgx12qPiplXB -reg ap-shanghai -params "{\"LoadBalancerId\":\"lb-jip7505r\",\"InstanceIds\":[\"ins-n2i7x483\"]}"
func main03() {
var target string
flag.StringVar(&target, "t", "b", "target: b=backendServer")
var operation string
flag.StringVar(&operation, "o", "add", "operation: add、remove")
var accessKeyId string
flag.StringVar(&accessKeyId, "key", "secretId", "accessKeyId")
var accessSecret string
flag.StringVar(&accessSecret, "sec", "secretKey", "accessSecret")
var regionId string
flag.StringVar(&regionId, "reg", "regionId", "regionId: cn-hangzhou")
var params string
flag.StringVar(&params, "params", "params", `
AddBackendServers: [{"ServerId":"ServerId","Weight":"100","Type":"eni","ServerIp":"192.168.11.1"}]
RemoveBackendServers: [{"ServerId":"ServerId","Weight":"100"}]
`)
flag.Parse()
flag.Usage()
fmt.Printf("target=%s,operation=%s,accessKeyId=%s,accessSecret=%s,regionId=%s,params=%s\n", target, operation, accessKeyId, accessSecret, regionId, params)
if target == "b" {
switch operation {
case "add":
ClassicBind(regionId, accessKeyId, accessSecret, params)
break
case "remove":
ClassicRemove(regionId, accessKeyId, accessSecret, params)
break
default:
fmt.Println("operation: " + operation + " not support")
}
} else {
fmt.Println("target: " + target + " not support")
}
}
func ClassicBind(regionId, accessKeyId, accessSecret, params string) {
credential := common.NewCredential(accessKeyId, accessSecret)
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "clb.tencentcloudapi.com"
client, _ := clb.NewClient(credential, regionId, cpf)
request := clb.NewRegisterTargetsWithClassicalLBRequest()
err := request.FromJsonString(params)
if err != nil {
panic(err)
}
response, err := client.RegisterTargetsWithClassicalLB(request)
if _, ok := err.(*errors.TencentCloudSDKError); ok {
fmt.Printf("An API error has returned: %s", err)
return
}
if err != nil {
panic(err)
}
fmt.Printf("%s", response.ToJsonString())
}
func ClassicRemove(regionId, accessKeyId, accessSecret, params string) {
credential := common.NewCredential(accessKeyId, accessSecret)
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "clb.tencentcloudapi.com"
client, _ := clb.NewClient(credential, regionId, cpf)
request := clb.NewDeregisterTargetsFromClassicalLBRequest()
err := request.FromJsonString(params)
if err != nil {
panic(err)
}
response, err := client.DeregisterTargetsFromClassicalLB(request)
if _, ok := err.(*errors.TencentCloudSDKError); ok {
fmt.Printf("An API error has returned: %s", err)
return
}
if err != nil {
panic(err)
}
fmt.Printf("%s", response.ToJsonString())
}

View File

@ -0,0 +1,57 @@
# CMD 工具
## 阿里云SLB后端服务器client工具
### Usage
Example: SlbClient.exe -t lb -o update -reg cn-hangzhou -key LTAI5tCB8SmJcwfO -sec dX4kgF4VcblsbCVdKvtCGCmvNbLkZK -lb lb-bp1nz8xl5q8kiqfv0iumf -params "[{\"ServerId\":\"i-bp1hcv8qx0677hv9pr0s\",\"Weight\":\"100\"}]"
```
Usage of SlbClient.exe:
-key string
accessKeyId (default "accessKeyId")
-lb string
loadBalancerId (default "loadBalancerId")
-o string
operation: add、update、remove (default "update")
-params string
SetBackendServers: [{"ServerId":"ServerId","Weight":"0"}]
AddBackendServers: [{"ServerId":"ServerId","Weight":"100","Type":"eni","ServerIp":"192.168.11.1"}]
RemoveBackendServers: [{"ServerId":"ServerId","Weight":"100"}]
(default "params")
-reg string
regionId: cn-hangzhou (default "regionId")
-sec string
accessSecret (default "accessSecret")
-t string
target: lb (default "lb")
```
## 腾讯云CLB后端服务器client工具
### Usage
Example1 ./TencentCloudClient -t b -o add -key AKIDSoxxjI5tukPr9ECsQmwthKhroaCZlldI -sec b1FG2b3JMrESVLxjJdhYDgx12qPiplXB -reg ap-shanghai -params "{\"LoadBalancerId\":\"lb-jip7505r\",\"Targets\":[{\"InstanceId\":\"ins-n2i7x483\",\"Weight\":20}]}"
Example2 ./TencentCloudClient -t b -o remove -key AKIDSoxxjI5tukPr9ECsQmwthKhroaCZlldI -sec b1FG2b3JMrESVLxjJdhYDgx12qPiplXB -reg ap-shanghai -params "{\"LoadBalancerId\":\"lb-jip7505r\",\"InstanceIds\":[\"ins-n2i7x483\"]}"
```
Usage of ./TencentCloudClient:
-key string
accessKeyId (default "secretId")
-o string
operation: add、remove (default "add")
-params string
AddBackendServers: [{"ServerId":"ServerId","Weight":"100","Type":"eni","ServerIp":"192.168.11.1"}]
RemoveBackendServers: [{"ServerId":"ServerId","Weight":"100"}]
(default "params")
-reg string
regionId: cn-hangzhou (default "regionId")
-sec string
accessSecret (default "secretKey")
-t string
target: b=backendServer (default "b")
```

146
pkg/common/ConstVar.go Normal file
View File

@ -0,0 +1,146 @@
package common
import (
"encoding/json"
"github.com/anxpp/beego/logs"
)
const (
//公用
//API版本号
// API_V_NUM = "1"
//API密钥
// API_SECRECT = "x63363eacf804b4394a120aea240fd9a"
//图片域名
PHOTO_URL_PREV = "https://photo-app.ddegame.cn/"
MEDIA_URL_PREV = "https://media-qiniu-app.ddegame.cn/"
//====== 三方key =======
//NETEASE_IM_APPKEY = "a0671ecde01ef03928ccfd460c62d203"
//NETEASE_IM_SECRET = "eb88916031ca"
//用户认证申请状态
USER_CERTIFY_STATUS_INIT = "1" //已提交
USER_CERTIFY_STATUS_SUCCESS = "2" //成功
USER_CERTIFY_STATUS_INIT_FAIL = "3" //初始化失败
USER_CERTIFY_STATUS_AUTH_FAIL = "4" //认证失败
USER_CERTIFY_STATUS_REFUSE = "5" //拒绝
// 模块和页面ID
//用户管理
MODULE_ID_USER_MANAGE = "1"
//基础数据
MODULE_ID_BASE_DATA = "4"
//财务管理
MODULE_ID_FIN_MANAGE = "7"
//数据报表
MODULE_ID_DATA_REPORT = "14"
//聊天室管理
MODULE_ID_ROOM_MANAGE = "20"
//付款管理
MODULE_ID_PAY_MANAGE = "100"
//抽奖管理
MODULE_ID_LUCK_MANAGE = "200"
//技能管理
MODULE_ID_SKILL_MANAGE = "300"
//技能订单管理
MODULE_ID_SKILL_ORDER_MANAGE = "400"
//技能订单列表
PAGE_ID_FIN_SKILL_ORDER_LIST = "401"
//用户列表
PAGE_ID_USER_LIST = "2"
//用户认证列表
PAGE_ID_USER_AUTH_LIST = "3"
//礼物列表
PAGE_ID_GIFT_LIST = "5"
//首页分类列表
PAGE_ID_HOME_TAB_LIST = "6"
//充值列表
PAGE_ID_FIN_RECHARGE_LIST = "8"
//提现列表
PAGE_ID_FIN_WITHDRAW_LIST = "9"
//兑换列表
PAGE_ID_FIN_EXCHANGE_LIST = "10"
//表情列表
PAGE_ID_FIN_EMOJI_ORDER_LIST = "11"
//守护订单列表
PAGE_ID_FIN_GUARD_ORDER_LIST = "12"
//打赏列表
PAGE_ID_FIN_PRESENT_GIFT_LIST = "13"
//日报表
PAGE_ID_DAY_REPORT = "15"
//厅日流水报表
PAGE_ID_DAY_ROOM_SALE_REPORT = "142"
//实时厅流水报表
PAGE_ID_TODAY_ROOM_SALE_RANK = "143"
//厅费用排行
PAGE_ID_TODAY_ROOM_MARKET_FEE_RANK = "144"
//聊天室列表
PAGE_ID_CHATROOM_LIST = "21"
//房主列表
PAGE_ID_CHATROOM_OWNER_LIST = "22"
//打赏列表
PAGE_ID_FIN_OPERATION_DIAMOND_LIST = "24"
//
PAGE_ID_FILTER_USER_NO = "25"
//付款单列表
PAGE_ID_PAY_ORDER_LIST = "101"
//客户列表
PAGE_ID_PAY_CUSTOMER_LIST = "102"
//砸蛋列表
PAGE_ID_HIT_EGG_LIST = "201"
//方案列表
PAGE_ID_SOLUTION_LIST_LIST = "202"
//权益列表
PAGE_ID_QUANYI_LIST_LIST = "203"
//用户砸蛋汇总
PAGE_ID_USER_HITEGG_SUMMARY = "204"
//技能申请列表
PAGE_ID_SKILL_APPLY_LIST_LIST = "301"
//技能列表
PAGE_ID_SKILL_LIST = "302"
//卖家列表
PAGE_ID_SELLER_LIST = "303"
//SKU列表
PAGE_ID_SKILL_SKU_LIST = "304"
//技能修改申请列表
PAGE_ID_SKILL_EDIT_LIST = "305"
//技能价格列表
PAGE_ID_SKILL_PRICE_LIST = "306"
//技能订单列表
PAGE_ID_SKILL_ORDER_LIST = "401"
// 代理订单类型 后台充值
AGENT_DIAMOND_ORDER_TYPE_ADMIN_RECHARGE = "1"
// 代理订单类型 上级转账
AGENT_DIAMOND_ORDER_TYPE_UPER_TRANSFER = "2"
// 文本反垃圾类型 - 昵称检测
RISK_TYPE_TEXT_NICKNAME = "nickname_detection"
RISK_TYPE_TEXT_COMMENT = "comment_detection" //评论检测
)
func LogJson(v interface{}) {
b, _ := json.Marshal(v)
logs.Info(string(b))
}
const photoDomainUrl = "https://ddphoto.youkeya.cn"

View File

@ -0,0 +1,96 @@
package HyTools
import (
"io/ioutil"
"net/http"
"strings"
)
// 调用POST请求
func HttpPost(url string, body string) (remoteResponse string, err error) {
bodyReader := strings.NewReader(body)
//application/x-www-form-urlencoded
//application/json
response, err1 := http.Post(url, "application/x-www-form-urlencoded", bodyReader)
if err1 != nil {
err = err1
return
}
defer response.Body.Close()
resBody, err2 := ioutil.ReadAll(response.Body)
if err2 != nil {
err = err2
return
}
remoteResponse = string(resBody)
return
}
// 复杂http请求
func HttpDo(httpMethod string, url string, headerMap map[string]string, rawBody string) (remoteResponse string, err error) {
client := &http.Client{}
req, err0 := http.NewRequest(httpMethod, url, strings.NewReader(rawBody))
if err0 != nil {
err = err0
return
}
if len(headerMap) > 0 {
for k, v := range headerMap {
req.Header.Set(k, v)
}
}
resp, err1 := client.Do(req)
if err1 != nil {
err = err1
return
}
defer resp.Body.Close()
body, err2 := ioutil.ReadAll(resp.Body)
if err2 != nil {
err = err2
return
}
remoteResponse = string(body)
return
}
func HttpPostJson(url string, body string) (remoteResponse string, err error) {
bodyReader := strings.NewReader(body)
//application/x-www-form-urlencoded
//application/json
response, err1 := http.Post(url, "application/json", bodyReader)
if err1 != nil {
err = err1
return
}
defer response.Body.Close()
resBody, err2 := ioutil.ReadAll(response.Body)
if err2 != nil {
err = err2
return
}
remoteResponse = string(resBody)
return
}

View File

@ -0,0 +1,40 @@
package HyTools
import (
"math/rand"
"time"
)
func RandInt64(min, max int64) int64 {
if min >= max || min == 0 || max == 0 {
return max
}
return rand.Int63n(max-min) + min
}
// 获取随机数字字符串
func GetRandNumber(l int) string {
str := "0123456789"
bytes := []byte(str)
result := []byte{}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < l; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return string(result)
}
// 生成随机字符串
func GetRandomString(l int) string {
str := "0123456789abcdefghijklmnopqrstuvwxyz"
bytes := []byte(str)
result := []byte{}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < l; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return string(result)
}

View File

@ -0,0 +1,146 @@
package HyTools
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
)
// RSA加密
func RsaEncrypt(origData string, publicKey string) (string, error) {
block, _ := pem.Decode([]byte(publicKey)) //将密钥解析成公钥实例
if block == nil {
return "", errors.New("public key error")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) //解析pem.Decode返回的Block指针实例
if err != nil {
return "", err
}
pub := pubInterface.(*rsa.PublicKey)
partLen := pub.N.BitLen()/8 - 11
chunks := ByteSplit([]byte(origData), partLen)
buffer := bytes.NewBufferString("")
for _, chunk := range chunks {
bytes, err := rsa.EncryptPKCS1v15(rand.Reader, pub, chunk)
if err != nil {
return "", err
}
buffer.Write(bytes)
}
return base64.StdEncoding.EncodeToString(buffer.Bytes()), nil
}
// RSA解密
func RsaDecrypt(ciphertext string, privateKey string) (string, error) {
block, _ := pem.Decode([]byte(privateKey))
if block == nil {
return "", errors.New("private key error!")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", err
}
partLen := priv.N.BitLen() / 8
raw, err := base64.StdEncoding.DecodeString(ciphertext)
chunks := ByteSplit([]byte(raw), partLen)
buffer := bytes.NewBufferString("")
for _, chunk := range chunks {
decrypted, err := rsa.DecryptPKCS1v15(rand.Reader, priv, chunk)
if err != nil {
return "", err
}
buffer.Write(decrypted)
}
return buffer.String(), err
}
// RSA SHA1加签
func RsaSHA1Sign(data string, privateKey string) (string, error) {
block, _ := pem.Decode([]byte(privateKey))
if block == nil {
return "", errors.New("Sign private key decode error")
}
prk8, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", err
}
h := sha1.New()
h.Write([]byte(data))
hashed := h.Sum(nil)
sign, err := rsa.SignPKCS1v15(rand.Reader, prk8, crypto.SHA1, hashed)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(sign), err
}
// RSA SHA1验签
func RsaSHA1Verify(data string, sign string, publicKey string) error {
h := sha1.New()
h.Write([]byte(data))
hashed := h.Sum(nil)
decodedSign, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
return err
}
block, _ := pem.Decode([]byte(publicKey))
if block == nil {
return errors.New("Sign public key decode error")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) //解析pem.Decode返回的Block指针实例
if err != nil {
return err
}
pub := pubInterface.(*rsa.PublicKey)
return rsa.VerifyPKCS1v15(pub, crypto.SHA1, hashed, decodedSign)
}

View File

@ -0,0 +1,30 @@
package HyTools
import (
"bytes"
"encoding/json"
"fmt"
)
type StringBuilder struct {
buf bytes.Buffer
}
func NewStringBuilder() *StringBuilder {
return &StringBuilder{buf: bytes.Buffer{}}
}
func (this *StringBuilder) Append(obj interface{}) *StringBuilder {
this.buf.WriteString(fmt.Sprintf("%v", obj))
return this
}
func (this *StringBuilder) ToString() string {
return this.buf.String()
}
// 字符串转int32
func JsonStr(v interface{}) string {
bys, _ := json.Marshal(v)
return string(bys)
}

View File

@ -0,0 +1,243 @@
package HyTools
import (
"bytes"
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"fmt"
"math"
"strconv"
"strings"
"time"
)
// MD5加密
func StringToMD5(waitMD5string string) string {
h := md5.New()
h.Write([]byte(waitMD5string))
cipherStr := h.Sum(nil)
result := hex.EncodeToString(cipherStr)
return result
}
// SHA1加密
func StringToSHA1(waitMD5string string) string {
h := sha1.New()
h.Write([]byte(waitMD5string))
cipherStr := h.Sum(nil)
result := hex.EncodeToString(cipherStr)
return result
}
// 字符串转int64
func StringToInt64(waitString string) int64 {
stringInt64, err := strconv.ParseInt(waitString, 10, 64)
if err != nil {
return 0
}
return stringInt64
}
// 字符串转int32
func StringToInt(waitString string) int {
stringInt, err := strconv.Atoi(waitString)
if err != nil {
return 0
}
return stringInt
}
// 字符串转float64
func StringToFloat64(waitString string) float64 {
stringInt64, err := strconv.ParseFloat(waitString, 64)
if err != nil {
return 0
}
return stringInt64
}
// strconv.FormatFloat(float64, 'E', -1, 64)
// float64转字符串
func Float64ToString(waitFloat64 float64) string {
stringInt64 := strconv.FormatFloat(waitFloat64, 'f', -1, 64)
return stringInt64
}
// 截取字符串
func Substr(str string, start int, length int) string {
rs := []rune(str)
rl := len(rs)
end := 0
if start < 0 {
start = rl - 1 + start
}
end = start + length
if start > end {
start, end = end, start
}
if start < 0 {
start = 0
}
if start > rl {
start = rl
}
if end < 0 {
end = 0
}
if end > rl {
end = rl
}
return string(rs[start:end])
}
// map转html
func MapToXML(mapData map[string]string) string {
if len(mapData) == 0 {
return ""
}
sb := NewStringBuilder()
sb.Append("<xml>")
for key, val := range mapData {
sb.Append("<" + key + ">")
sb.Append(val)
sb.Append("</" + key + ">")
}
sb.Append("</xml>")
return sb.ToString()
}
// 获取当前年周
func GetCurrentYearWeek() string {
year, week := time.Now().ISOWeek()
return strconv.Itoa(year) + strconv.Itoa(week)
}
// 字符串转time类型
func StringToTime(dateString string) time.Time {
//获取本地location
toBeCharge := dateString //待转化为时间戳的字符串 注意 这里的小时和分钟还要秒必须写 因为是跟着模板走的 修改模板的话也可以不写
timeLayout := "2006-01-02 15:04:05" //转化所需模板
loc, _ := time.LoadLocation("Local") //重要:获取时区
theTime, _ := time.ParseInLocation(timeLayout, toBeCharge, loc) //使用模板在对应时区转化为time.time类型
//sr := theTime.Unix() //转化为时间戳 类型是int64
//时间戳转日期
//dataTimeStr := time.Unix(sr, 0).Format(timeLayout) //设置时间戳 使用模板格式化为日期字符串
return theTime
}
// 判断数组是否包含某个元素
func CheckStringIsInArray(arrayList []string, element string) bool {
if len(arrayList) == 0 {
return false
}
isInArray := false
for _, data := range arrayList {
if data == element {
isInArray = true
break
}
}
return isInArray
}
// slice to 字符串
func StringListToString(stringList []string, split string) string {
if len(split) == 0 {
split = ","
}
return strings.Replace(strings.Trim(fmt.Sprint(stringList), "[]"), " ", split, -1)
}
// map 转 url参数
func MapToUrlParams(mapData map[string]string) string {
mapLen := len(mapData)
if mapLen == 0 {
return ""
}
sb := NewStringBuilder()
i := 0
for k, v := range mapData {
sb.Append(k + "=" + v)
if i < mapLen-1 {
sb.Append("&")
}
i++
}
return sb.ToString()
}
// []byte 合并
func BytesCombine(pBytes ...[]byte) []byte {
return bytes.Join(pBytes, []byte(""))
}
// 获取当前UTC时间 秒数
func GetCurrentUtcTimeSecond() int64 {
return time.Now().UTC().Unix()
}
// byte数组分组
func ByteSplit(buf []byte, lim int) [][]byte {
var chunk []byte
chunks := make([][]byte, 0, len(buf)/lim+1)
for len(buf) >= lim {
chunk, buf = buf[:lim], buf[lim:]
chunks = append(chunks, chunk)
}
bufLen := len(buf)
if bufLen > 0 {
chunks = append(chunks, buf[:bufLen])
}
return chunks
}
func Float64ToIntRound(f float64) int {
return int(math.Round(f))
}

111
pkg/common/HyTools/Uuid.go Normal file
View File

@ -0,0 +1,111 @@
package HyTools
import (
crand "crypto/rand"
"encoding/hex"
"errors"
"fmt"
mrand "math/rand"
"regexp"
"strings"
"time"
"github.com/google/uuid"
)
// seeded indicates if math/rand has been seeded
var seeded bool = false
// uuidRegex matches the UUID string
var uuidRegex *regexp.Regexp = regexp.MustCompile(`^\{?([a-fA-F0-9]{8})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{12})\}?$`)
// UUID type.
type UUID [16]byte
// Hex returns a hex string representation of the UUID in xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format.
func (this UUID) Hex() string {
x := [16]byte(this)
return fmt.Sprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
x[0], x[1], x[2], x[3], x[4],
x[5], x[6],
x[7], x[8],
x[9], x[10], x[11], x[12], x[13], x[14], x[15])
}
// Rand generates a new version 4 UUID.
func Rand() UUID {
var x [16]byte
randBytes(x[:])
x[6] = (x[6] & 0x0F) | 0x40
x[8] = (x[8] & 0x3F) | 0x80
return x
}
// FromStr returns a UUID based on a string.
// The string could be in the following format:
//
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
//
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
//
// If the string is not in one of these formats, it'll return an error.
func FromStr(s string) (id UUID, err error) {
if s == "" {
err = errors.New("Empty string")
return
}
parts := uuidRegex.FindStringSubmatch(s)
if parts == nil {
err = errors.New("Invalid string format")
return
}
var array [16]byte
slice, _ := hex.DecodeString(strings.Join(parts[1:], ""))
copy(array[:], slice)
id = array
return
}
// MustFromStr behaves similarly to FromStr except that it'll panic instead of
// returning an error.
func MustFromStr(s string) UUID {
id, err := FromStr(s)
if err != nil {
panic(err)
}
return id
}
// randBytes uses crypto random to get random numbers. If fails then it uses math random.
func randBytes(x []byte) {
length := len(x)
n, err := crand.Read(x)
if n != length || err != nil {
if !seeded {
mrand.Seed(time.Now().UnixNano())
}
for length > 0 {
length--
x[length] = byte(mrand.Int31n(256))
}
}
}
func GetUUID() string {
u7, _ := uuid.NewV7()
return strings.ReplaceAll(u7.String(), "-", "")
// var uuid UUID = Rand()
// return uuid.Hex()
}

1352
pkg/common/app_const.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,94 @@
package document
type User struct {
Id string
Mobile string
RegionCode string
NickName string
Salt string
Password string
Status string
Avatar string
Gender string
Birthday string
WxUnionId string
QqOpenId string
MarketChannel string
CreateTime string
IsActive string
ActiveTime string
SignupSource string
SignupDevicePlatform string
SignupDeviceUdid string
IdNo string
IsAuth string
TrueName string
Sign string
ViewFlag string
VideoUrl string
School string
UserNo string
BankName string
BankUserName string
BankBranchName string
BankCardNo string
AuthFailReason string
AlipayAccount string
WellNoIcon string
}
//字符串: text keyword
//整数 : byte, short, integer, long
//浮点数: float, double
//布尔型: boolean
//日期: date
var UserIndex = map[string]interface{}{
"settings": map[string]interface{}{"number_of_shards": 5, "number_of_replicas": 1},
"mappings": map[string]interface{}{
"properties": map[string]interface{}{
"ActiveTime": map[string]string{"type": "date", "format": "yyyy-MM-dd HH:mm:ssZ||yyyy-MM-dd HH:mm:ss.SSSZ||yyyy-MM-ddZ||epoch_millis||epoch_second"},
"AlipayAccount": map[string]string{"type": "keyword"},
"AuthFailReason": map[string]string{"type": "text", "analyzer": "ik_max_word"},
"Avatar": map[string]string{"type": "text", "analyzer": "ik_max_word"},
"BankBranchName": map[string]string{"type": "keyword"},
"BankCardNo": map[string]string{"type": "keyword"},
"BankName": map[string]string{"type": "text"},
"BankUserName": map[string]string{"type": "text"},
"Birthday": map[string]string{"type": "date", "format": "yyyy-MM-dd HH:mm:ssZ||yyyy-MM-dd HH:mm:ss.SSSZ||yyyy-MM-ddZ||epoch_millis||epoch_second"},
"CreateTime": map[string]string{"type": "date", "format": "yyyy-MM-dd HH:mm:ssZ||yyyy-MM-dd HH:mm:ss.SSSZ||yyyy-MM-ddZ||epoch_millis||epoch_second"},
"Gender": map[string]string{"type": "keyword"},
"Id": map[string]string{"type": "keyword"},
"IdNo": map[string]string{"type": "keyword"},
"IsActive": map[string]string{"type": "keyword"},
"IsAuth": map[string]string{"type": "keyword"},
"MarketChannel": map[string]string{"type": "keyword"},
"Mobile": map[string]string{"type": "keyword"},
"NickName": map[string]string{"type": "text", "analyzer": "ik_max_word"},
"QqOpenId": map[string]string{"type": "keyword"},
"RegionCode": map[string]string{"type": "keyword"},
"Salt": map[string]string{"type": "keyword"},
"School": map[string]string{"type": "text", "analyzer": "ik_max_word"},
"Sign": map[string]string{"type": "text", "analyzer": "ik_max_word"},
"SignupDevicePlatform": map[string]string{"type": "text", "analyzer": "ik_max_word"},
"SignupDeviceUdid": map[string]string{"type": "keyword"},
"SignupSource": map[string]string{"type": "text", "analyzer": "ik_max_word"},
"Status": map[string]string{"type": "keyword"},
"TrueName": map[string]string{"type": "text", "analyzer": "ik_max_word"},
"UserNo": map[string]string{"type": "keyword"},
"VideoUrl": map[string]string{"type": "text", "analyzer": "ik_max_word"},
"ViewFlag": map[string]string{"type": "keyword"},
"WellNoIcon": map[string]string{"type": "keyword"},
"WxUnionId": map[string]string{"type": "keyword"},
},
},
}
var MessageIndex = map[string]interface{}{
"settings": map[string]interface{}{"number_of_shards": 5, "number_of_replicas": 1},
"mappings": map[string]interface{}{
"properties": map[string]interface{}{
"id": map[string]string{"type": "keyword"},
"curTime": map[string]string{"type": "date", "format": "yyyy-MM-dd HH:mm:ssZ||yyyy-MM-dd HH:mm:ss.SSSZ||yyyy-MM-ddZ||epoch_millis||epoch_second"},
},
},
}

261
pkg/common/es/es_client.go Normal file
View File

@ -0,0 +1,261 @@
package ess
import (
"servicebase/pkg/common/es/document"
"servicebase/pkg/common/messages"
"context"
"encoding/json"
"reflect"
"time"
"github.com/anxpp/beego/logs"
"github.com/olivere/elastic/v7"
"github.com/spf13/viper"
)
var client *elastic.Client
type EsClient struct {
}
func Init() {
index := messages.TagIndex(string(messages.EventTagUser))
client = _connect()
exists, e := client.IndexExists(index).Do(context.Background())
if e != nil {
logs.Error("IndexExists ", index, " error: ", e.Error())
return
}
if !exists {
logs.Info("es user not exist")
createIndex, e := client.CreateIndex(index).BodyJson(document.UserIndex).Do(context.Background())
if e != nil {
logs.Error("IndexCreate ", index, " error: ", e.Error())
return
}
if !createIndex.Acknowledged {
}
}
indexMessage := messages.TagIndex(string(messages.EventTagMessage))
existsMessage, e := client.IndexExists(indexMessage).Do(context.Background())
if e != nil {
logs.Error("IndexExists indexMessage", index, " error: ", e.Error())
return
}
if !existsMessage {
logs.Info("es indexMessage not exist")
createIndex, e := client.CreateIndex(indexMessage).BodyJson(document.MessageIndex).Do(context.Background())
if e != nil {
logs.Error("IndexCreate ", indexMessage, " error: ", e.Error())
return
}
if !createIndex.Acknowledged {
}
}
logs.Info("init elasticsearch finish")
}
func _connect() (c *elastic.Client) {
c, e := elastic.NewSimpleClient(
elastic.SetHealthcheck(true),
elastic.SetHealthcheckInterval(10*time.Second),
elastic.SetURL(viper.GetString("es.default.addr")),
// elastic.SetBasicAuth(beego.AppConfig.String("es_username"), beego.AppConfig.String("es_password")),
)
if e != nil {
logs.Error("NewClient_error: ", e.Error())
}
return c
}
// 创建记录
func (*EsClient) Create(index, id string, model interface{}) (body string, success bool, msg string) {
client := _connect()
_, e := client.Index().Index(index).Id(id).BodyJson(model).Do(context.Background())
if e != nil {
success = false
msg = e.Error()
b, _ := json.Marshal(model)
logs.Error("Document Create Error: ", e.Error(), " document: ", string(b), index, id)
return
}
success = true
return
}
// 判断存在
func (*EsClient) Exists(index, id string) (exists, success bool, msg string, e error) {
client := _connect()
exists, e = client.Exists().Index(index).Type("_doc").Id(id).Do(context.Background())
if e != nil {
success = false
msg = e.Error()
logs.Error("Id Exists Error: ", e.Error(), " id: ", id)
return
}
success = true
return
}
// 更新记录
func (*EsClient) Update(index, id string, model interface{}) (body string, success bool, msg string) {
client := _connect()
_, e := client.Update().Index(index).Id(id).Doc(model).Do(context.Background())
if e != nil {
success = false
msg = e.Error()
return
}
success = true
return
}
// 搜索
func (*EsClient) Search(index string, key string, fields ...string) (body []interface{}, success bool, msg string) {
client := _connect()
logs.Info("ES Search index =", index, " key =", key, " fields =", fields)
var list []*elastic.WildcardQuery
for _, field := range fields {
list = append(list, elastic.NewWildcardQuery(field, key))
}
s := client.Search().Index(index)
for _, query := range list {
s = s.Query(query)
}
res, e := s.From(0).Size(20).Pretty(true).Do(context.Background())
if e != nil {
success = false
msg = e.Error()
return
}
logs.Info(res.Status)
logs.Info(res.Hits.TotalHits)
var item document.User
for _, item := range res.Each(reflect.TypeOf(item)) {
if t, ok := item.(document.User); ok {
body = append(body, t)
}
}
//if res.Hits.TotalHits.Value > 0 {
// for _, hit := range res.Hits.Hits {
// var t document.User
// _ := json.Unmarshal(hit.Source, &t)
// body = append(body, t)
// }
//}
return
}
type ESFilter struct {
Queries []ESQuery
BoolMustInShouldQueries [][]ESQuery
Sort ESSort
}
type ESQuery struct {
Key string
Value string
Type string
}
type ESSort struct {
Field string
Ascending bool
}
// 搜索消息
func (*EsClient) SearchMulti(index string, filter ESFilter, page, size int) (result interface{}, success bool, msg string) {
client := _connect()
var list []elastic.Query
for _, item := range filter.Queries {
switch item.Type {
case "match":
list = append(list, elastic.NewMatchQuery(item.Key, item.Value))
case "multi_match":
list = append(list, elastic.NewMultiMatchQuery(item.Value).Type("best_fields").Lenient(true))
case "range_gte":
list = append(list, elastic.NewRangeQuery(item.Key).Gte(item.Value))
case "range_lte":
list = append(list, elastic.NewRangeQuery(item.Key).Lte(item.Value))
}
}
if len(filter.BoolMustInShouldQueries) > 0 {
BQ := elastic.NewBoolQuery()
var bqList []elastic.Query
for _, bq := range filter.BoolMustInShouldQueries {
var listBq []elastic.Query
boolQuery := elastic.NewBoolQuery()
for _, item := range bq {
switch item.Type {
case "match":
listBq = append(listBq, elastic.NewMatchQuery(item.Key, item.Value))
case "multi_match":
listBq = append(listBq, elastic.NewMultiMatchQuery(item.Value).Type("best_fields").Lenient(true))
case "range_gte":
listBq = append(listBq, elastic.NewRangeQuery(item.Key).Gte(item.Value))
case "range_lte":
listBq = append(listBq, elastic.NewRangeQuery(item.Key).Lte(item.Value))
}
}
boolQuery.Must(listBq...)
bqList = append(bqList, boolQuery)
}
BQ.Should(bqList...)
list = append(list, BQ)
}
query := elastic.NewBoolQuery().Filter(list...)
//logs.Info(len(list))
//a1, _ := query.Source()
//a2, _ := json.MarshalIndent(a1, "", " ")
//logs.Info(string(a2))
s := client.Search().Index(index).Query(query)
if len(filter.Sort.Field) > 0 {
s = s.Sort(filter.Sort.Field, filter.Sort.Ascending)
}
res, e := s.From(page * size).Size(size).Pretty(false).Do(context.Background())
if e != nil {
return
}
var body []interface{}
for _, item := range res.Hits.Hits {
b, _ := item.Source.MarshalJSON()
m := make(map[string]interface{})
_ = json.Unmarshal(b, &m)
body = append(body, m)
}
result = map[string]interface{}{
"Status": res.Status,
"Total": res.Hits.TotalHits,
"List": body,
}
return
}
// 搜索
func (*EsClient) QueryString(page, size int, index string, key string, fields ...string) (list []document.User, count int64, success bool, msg string) {
client := _connect()
logs.Info("ES QueryString index =", index, " key =", key, " fields =", fields)
query := elastic.NewQueryStringQuery(key)
for _, field := range fields {
query.Field(field)
}
query.AnalyzeWildcard(false)
s := client.Search().Index(index)
s = s.Query(query)
s = s.Sort("_score", false)
res, e := s.From(page * size).Size(size).Pretty(true).Do(context.Background())
if e != nil {
success = false
msg = e.Error()
return
}
count = res.TotalHits()
var user document.User
for _, item := range res.Each(reflect.TypeOf(user)) {
if t, ok := item.(document.User); ok {
list = append(list, t)
}
}
success = true
return
}

View File

@ -0,0 +1,81 @@
package ess
import (
"servicebase/pkg/common/messages"
"context"
"testing"
"time"
"github.com/anxpp/beego/logs"
"github.com/olivere/elastic/v7"
"github.com/spf13/viper"
)
func TestClient(t *testing.T) {
Init()
}
func TestClientCreateUser(t *testing.T) {
//for _, item := range models.LogUserRegisterAll() {
// cli := EsClient{}
// _, success, msg := cli.Create("test_user_register", item.Id, item)
// if !success {
// logs.Error(msg)
// }
//}
}
func TestClientSearch(t *testing.T) {
}
func _testConnect() (c *elastic.Client) {
c, e := elastic.NewSimpleClient(
elastic.SetHealthcheck(true),
elastic.SetHealthcheckInterval(10*time.Second),
// elastic.SetURL("http://47.97.157.234:9200"),
elastic.SetURL(viper.GetString("es.default.addr")),
// elastic.SetBasicAuth(beego.AppConfig.String("es_username"), beego.AppConfig.String("es_password")),
)
if e != nil {
logs.Error("NewClient_error: ", e.Error())
}
return c
}
func TestClientNew(t *testing.T) {
var client EsClient
client.Create("es_index_message", "003", map[string]interface{}{
"id": "003",
"curTime": 1440570500855,
"f1": 1234,
"f2": "22222",
"f3": "22222",
"f4": "22222",
"f5": "22222",
"f6": "2020-08-09 23:37:00",
})
}
func TestSearchMessage(t *testing.T) {
index := messages.TagIndex(string(messages.EventTagMessage))
client := _testConnect()
var list []elastic.Query
list = append(list, elastic.NewWildcardQuery("eventType", "1"))
list = append(list, elastic.NewMatchQuery("msgType", "TEXT"))
s := client.Search().Index(index)
for _, query := range list {
s = s.Query(query)
}
res, e := s.From(0).Size(20).Pretty(true).Do(context.Background())
if e != nil {
return
}
logs.Info(res.Status)
logs.Info(res.Hits.TotalHits)
logs.Info(len(res.Hits.Hits))
for i, item := range res.Hits.Hits {
b, _ := item.Source.MarshalJSON()
logs.Info(i, string(b))
}
time.Sleep(time.Second * 3)
}

View File

@ -0,0 +1,25 @@
package dto
type ChatRoomModelDTO struct {
Id string
OwnerUserId string
RoomNo string
RoomName string
MessageRoomId string
TabId string //分类ID
TemplateId string
CreateTime string
Status string
OpenTime string
RoomPwd string
MaxMemberCount string
IsHot string
RobotCount string
RoomTagIcon string
GuestCanUseEmoji string
SendMessageTimeGap string //发送消息间隔
UpSeatType string //1=排麦 2=自由麦
AudioQualityLevel string //音质等级 1=正常 2=高音质 3=超高音质
ViewSkewer string //是否显示福签 1=显示 0=不显示
IsViewHitegg string // 是否显示砸蛋
}

View File

@ -0,0 +1,5 @@
package dto
type RequestDTO struct {
Token string
}

View File

@ -0,0 +1,17 @@
package dto
type UserDTO struct {
UserId string
UserNo string
NickName string
Avatar string
Gender string
BirthDay string
VipLevel string
VipExp string
IsAuth string
Status string
TrueName string
Mobile string
AvatarDecoration string //头饰
}

View File

@ -0,0 +1,7 @@
package dto
type UserInviteSummaryDTO struct {
UserId string
InviteDiamond string
InviteUserCount string
}

View File

@ -0,0 +1,51 @@
package response
import "servicebase/pkg/common/http/dto"
type CommonResponse struct {
Code string
Result interface{}
Msg string
}
// 用户信息
type UserInfoResponse struct {
Code string
Result dto.UserDTO
Msg string
}
//获取用户邀请汇总数据返回
type UserInviteSummaryResponse struct {
Code string
Result dto.UserInviteSummaryDTO
Msg string
}
func Success(data interface{}) (res CommonResponse) {
res.Code = "6000"
res.Result = data
return
}
func Page(data interface{}, count int64) (res CommonResponse) {
res.Code = "6000"
res.Result = map[string]interface{}{
"list": data,
"count": count,
}
return
}
func Failed(msg string) (res CommonResponse) {
res.Code = "8020"
res.Msg = msg
return
}
func Gen(code, msg string, result interface{}) (res CommonResponse) {
res.Code = code
res.Msg = msg
res.Result = result
return
}

View File

@ -0,0 +1,12 @@
package messages
// 活跃消息
type Active struct {
MessageId string
UserId string //用户ID
DeviceId string //设备ID
ActiveTime string //活跃的平台
ActivePlatform string //活跃的平台
ActiveDeviceModel string //活跃的设备型号
ActiveIp string //活跃的IP
}

View File

@ -0,0 +1,26 @@
package messages
// 错误消息(补偿)
type Error struct {
MessageId string
Code string // 错误代码
Content string // 错误内容(json)
Tag ErrorTag // 消息标签auto=自动处理 manual=手动处理 auto_to_manual=自动转手动
}
type ErrorTag string
const (
Auto ErrorTag = "AUTO"
Manual ErrorTag = "MANUAL"
AutoToManual ErrorTag = "AUTO_TO_MANUAL"
)
type ErrorState string
const (
ErrorCreate ErrorState = "CREATED"
ErrorSuccess ErrorState = "SUCCESS"
ErrorFailed ErrorState = "FAILED"
ErrorReSuccess ErrorState = "RE_SUCCESS"
)

View File

@ -0,0 +1,49 @@
package messages
import (
"encoding/json"
"github.com/spf13/viper"
)
// 事件
type Event struct {
MessageId string
Tag EventTag // 消息标签EventTagUser=用户
Flag EventFlag // 消息标签EventFlagCreate=创建 EventFlagUpdate=更新
EventId string // 事件ID
EventContent interface{} // 事件内容
}
type EventTag string
const (
EventTagUser EventTag = "user"
EventTagRoomInto EventTag = "into_room"
EventTagMessage EventTag = "message"
)
func TagIndex(key string) string {
switch key {
case string(EventTagUser):
return viper.GetString("es.default.indexUser")
case string(EventTagMessage):
return viper.GetString("es.default.indexMessage")
default:
return ""
}
}
type EventFlag string
const (
EventFlagSave EventFlag = "save"
EventFlagCreate EventFlag = "create"
EventFlagUpdate EventFlag = "update"
EventFlagDelete EventFlag = "delete"
)
func (message *Event) ToJson() string {
b, _ := json.Marshal(message)
return string(b)
}

View File

@ -0,0 +1,12 @@
package messages
// 注册消息
type Register struct {
MessageId string
UserId string //用户ID
DeviceId string //设备ID
RegisterTime string //注册时间
ChannelCode string //渠道代码
Ip string //ip
Platform string //平台
}

View File

@ -0,0 +1,98 @@
package messages
// 交易消息
type Transaction struct {
MessageId string
FromUserId string //发起用户ID
FromCurrency TransactionCurrencyEnum //扣款类型
FromAmount string //扣款数量
FromPlatform string //扣款所在平台
ToUserId string //接收用户
ToCurrency TransactionCurrencyEnum //接收类型
ToAmount string //接收数量
TransactionRate string //交易抽成
TransactionState TransactionStateEnum //交易状态
TransactionType TransactionTypeEnum //充值、打赏、购买表情、购买守护、兑换、砸蛋、提现、订单等
TransactionId string //充值ID、打赏ID、购买表情ID、购买守护ID、兑换ID、砸蛋ID、提现ID、订单ID等
TransactionCreateTime string //交易创建时间
TransactionCompleteTime string //交易完成时间
TransactionSubjectId string //充值为产品ID、打赏为房间ID、购买表情为表情ID、购买守护为房间ID、兑换为配置ID、砸蛋为配置ID、提现为配置ID、订单为品类ID
TransactionCode TransactionCodeEnum //充值为充值渠道支付宝H5、支付宝App、微信、运营赠送等、打赏为打赏类型(单个、批量)、购买表情为空、购买守护为守护类型、兑换为空、砸蛋为中奖等级、提现为银行名称、订单为空
TransactionAmount string //交易金额(单位为扣款类型)
TransactionAmountCoupon string //交易优惠金额(单位为扣款类型)
TransactionAmountCouponId string //交易优惠凭证ID
TransactionAmountPay string //交易实际支付金额(单位为扣款类型)
GuildId string //俱乐部ID
}
// 交易更新消息
type TransactionUpdate struct {
MessageId string
TransactionState TransactionStateEnum //交易状态
TransactionId string //充值ID、打赏ID、购买表情ID、购买守护ID、兑换ID、砸蛋ID、提现ID、订单ID等
TransactionCompleteTime string //交易完成时间
}
type TransactionCodeEnum string
const (
RewardSimple TransactionCodeEnum = "SIMPLE" //单个打赏
RewardMulti TransactionCodeEnum = "MULTI" //批量打赏
RewardBagSimple TransactionCodeEnum = "BAG_SIMPLE" //背包礼物单个打赏
RewardBagMulti TransactionCodeEnum = "BAG_MULTI" //背包礼物批量打赏
GuardLevel01 TransactionCodeEnum = "1" //青铜守护
GuardLevel02 TransactionCodeEnum = "2" //白银守护
GuardLevel03 TransactionCodeEnum = "3" //黄金守护
Hunting1 TransactionCodeEnum = "1" //单砸
Hunting10 TransactionCodeEnum = "10" //十砸
Hunting100 TransactionCodeEnum = "100" //百砸
RechargeAppAlipay TransactionCodeEnum = "AppAlipay" //app支付宝
RechargeAppWeChat TransactionCodeEnum = "AppWeChat" //app微信
RechargeAppIap TransactionCodeEnum = "AppIap" //appIap
RechargeH5Alipay TransactionCodeEnum = "H5Alipay" //H5支付宝
RechargeH5WeChatClub TransactionCodeEnum = "H5WeChatClub" //H5微信公众号
RechargeOperation TransactionCodeEnum = "Operation" //Operation
)
type TransactionStateEnum string
const (
CREATED TransactionStateEnum = "CREATED" //已创建
SUCCESS TransactionStateEnum = "SUCCESS" //成功
FAILED TransactionStateEnum = "FAILED" //失败
)
type TransactionCurrencyEnum string
const (
DIAMOND TransactionCurrencyEnum = "DIAMOND" //钻石
CRYSTAL TransactionCurrencyEnum = "CRYSTAL" //晶石
MONEY TransactionCurrencyEnum = "MONEY" //钱
FRAGMENT TransactionCurrencyEnum = "FRAGMENT" //碎片
)
type TransactionTypeEnum string
const (
RECHARGE TransactionTypeEnum = "RECHARGE" //充值
RechargeNCoin TransactionTypeEnum = "RECHARGE_N_COIN" //充值N币
REWARD TransactionTypeEnum = "REWARD" //打赏
REWARDNCoin TransactionTypeEnum = "REWARD_N_COIN" //打赏
GUARD TransactionTypeEnum = "GUARD" //购买守护
EMOJI TransactionTypeEnum = "EMOJI" //购买表情
OperationDiamond TransactionTypeEnum = "OPERATION_DIAMOND" //手工加钻石
OperationCrystal TransactionTypeEnum = "OPERATION_CRYSTAL" //手工加晶石
OperationCrystalReduce TransactionTypeEnum = "OPERATION_CRYSTAL_REDUCE" //手工扣除晶石
EXCHANGE TransactionTypeEnum = "EXCHANGE" //兑换
HUNTING TransactionTypeEnum = "HUNTING" //星空寻宝
WithdrawNCoin TransactionTypeEnum = "WITHDRAW_N_COIN" //提现 N币
ORDER TransactionTypeEnum = "ORDER" //订单
ORDERNCoin TransactionTypeEnum = "ORDER_N_COIN" //订单
ORDER_H5 TransactionTypeEnum = "ORDER_H5" //订单
ORDER_H5_NCoin TransactionTypeEnum = "ORDER_H5_N_COIN" //订单
LUCK_GIFT TransactionTypeEnum = "LUCK_GIFT" //幸运礼物
LuckGiftNCoin TransactionTypeEnum = "LUCK_GIFT_N_COIN" //幸运礼物
)

View File

@ -0,0 +1,767 @@
package Netease
import (
"context"
"encoding/json"
"fmt"
"servicebase/pkg/common"
"servicebase/pkg/common/HyTools"
yunxin "servicebase/pkg/common/netease/dto"
"servicebase/pkg/htools"
"servicebase/pkg/log"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cast"
"math/rand"
"strconv"
"time"
"github.com/anxpp/beego/logs"
"github.com/spf13/viper"
)
type ImClient struct {
AppKey string
AppSecret string
}
type CreateImRes struct {
Code int
Desc string
}
type BroadcastNotificationReq struct {
FromAccountId string `json:"from_account_id"`
Content string `json:"content"`
TargetOs []string `json:"target_os"`
}
func NewImClient() *ImClient {
fmt.Printf("NewImClient: %s\n", viper.GetString("netease.im.key"))
key := viper.GetString("netease.im.key")
secret := viper.GetString("netease.im.secret")
return &ImClient{AppKey: key, AppSecret: secret}
}
// 生成云信header
func (client *ImClient) generateHeader() map[string]string {
nonce := randNumber()
t := time.Now()
currentTime := strconv.FormatInt(t.UTC().Unix(), 10)
result := make(map[string]string)
result["AppKey"] = client.AppKey
result["Nonce"] = nonce
result["CurTime"] = currentTime
result["CheckSum"] = client.generateCheckSum(nonce, currentTime)
result["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8"
return result
}
func (client *ImClient) generateJsonHeader() map[string]string {
nonce := randNumber()
t := time.Now()
currentTime := strconv.FormatInt(t.UTC().Unix(), 10)
result := make(map[string]string)
result["AppKey"] = client.AppKey
result["Nonce"] = nonce
result["CurTime"] = currentTime
result["CheckSum"] = client.generateCheckSum(nonce, currentTime)
result["Content-Type"] = "application/json"
return result
}
// 生成云信签名
func (client *ImClient) generateCheckSum(nonce, currentTime string) string {
waitSignStr := client.AppSecret + nonce + currentTime
result := HyTools.StringToSHA1(waitSignStr)
return result
}
// 创建云信账户
// imService.CreateImUser(common.SYNC_DATA_USER_ID,"数据同步",common.PHOTO_DOMAIN_URL+"assets/sys/guanfanggonggao.png")
func (client *ImClient) CreateImUser(userId, nickName, avatar string) error {
url := "https://api.netease.im/nimserver/user/create.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := HyTools.NewStringBuilder()
sb.Append("accid=" + userId)
sb.Append("&")
sb.Append("token=123456")
sb.Append("&")
sb.Append("name=" + nickName)
sb.Append("&")
sb.Append("icon=" + avatar)
res, err := HyTools.HttpDo(httpMethod, url, header, sb.ToString())
if err != nil {
// {"desc":"already register","code":414}
logs.Info("YunXin_SignUp_Fail_AccID_%s:"+err.Error(), userId)
return err
} else {
// {"code":200,"info":{"token":"123456","accid":"b8af4bebe2064435974ba2340d852055","name":"b8af4bebe2064435974ba2340d852055"}}
var resDTO CreateImRes
json.Unmarshal([]byte(res), &resDTO)
if resDTO.Code != 200 {
logs.Info("YunXin_SignUp_Fail_AccID_%s:"+resDTO.Desc, userId)
} else {
logs.Info("YunXin_SignUp_Success_AccID_%s:"+res, userId)
}
}
var body map[string]interface{}
if errJson := json.Unmarshal([]byte(res), &body); errJson != nil {
return errJson
}
if int(body["code"].(float64)) != 200 {
return errors.New(body["desc"].(string))
}
return err
}
// 查询用户信息
func (client *ImClient) GetImUserInfo(userId string) error {
url := "https://api.netease.im/nimserver/user/getUinfos.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := HyTools.NewStringBuilder()
accids := []string{userId}
byteData, _ := json.Marshal(accids)
sb.Append("accids=" + string(byteData))
res, err := HyTools.HttpDo(httpMethod, url, header, sb.ToString())
// #查询云信用户信息结果:{"desc":"b8af4bebe2064435974ba2340d852055not register","code":414}#
// {"code":200,"uinfos":[{"icon":"https://xzphoto.meetalk.tech/upload/6bba4098-6ea8-4758-8193-19abed540c0b.0","accid":"2251bae8a0514e6892f0374f5dd260d4","name":"弦乐","gender":0}]}
logs.Info("#" + "查询云信用户信息结果:" + res + "#")
var body map[string]interface{}
if errJson := json.Unmarshal([]byte(res), &body); errJson != nil {
return errJson
}
if int(body["code"].(float64)) != 200 {
return errors.New(body["desc"].(string))
}
return err
}
// 更新云信信息
func (client *ImClient) UpdateImUserInfo(accid, nickName, avatar string) error {
url := "https://api.netease.im/nimserver/user/updateUinfo.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
sb.Append("accid=" + accid)
sb.Append("&")
sb.Append("name=" + nickName)
sb.Append("&")
sb.Append("icon=" + avatar)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "云信更新用户信息结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 更新云信用户扩展信息
func (client *ImClient) UpdateImUserExt(accid, ext string) error {
url := "https://api.netease.im/nimserver/user/updateUinfo.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
sb.Append("accid=" + accid)
sb.Append("&")
sb.Append("ex=" + ext)
logs.Info("update_im_user userID[%s] ext:%s", accid, ext)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING+"云信更新用户ext%s信息结果:"+res+common.LOG_QUOTE_STRING, ext)
return err
}
// 封禁云信帐号
func (client *ImClient) BlockImUser(accid string) error {
url := "https://api.netease.im/nimserver/user/block.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
sb.Append("accid=" + accid)
sb.Append("&")
sb.Append("needkick=true")
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "封禁云信帐号结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 解禁云信帐号
func (client *ImClient) UnBlockImUser(accid string) error {
url := "https://api.netease.im/nimserver/user/unblock.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
sb.Append("accid=" + accid)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "解禁云信帐号结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 创建云信聊天室
func (client *ImClient) CreateChatroom(ownerUserId string, roomName string) (chatroomModel NeteaseChatroomDTO) {
logs.Info("CreateChatroom ownerUserId=%s roomName=%s", ownerUserId, roomName)
url := "https://api.netease.im/nimserver/chatroom/create.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
sb.Append("creator=" + ownerUserId)
sb.Append("&")
sb.Append("name=" + roomName)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
if err != nil {
logs.Info(common.LOG_QUOTE_STRING + "云信创建聊天室请求失败:" + err.Error() + common.LOG_QUOTE_STRING)
return
}
// {"valid":true,"ext":"","creator":"1b94b5e7af8a45ed9fda27d28165d4c9","name":"32452345","muted":false,"roomid":11142139973,"queuelevel":0}
logs.Info(common.LOG_QUOTE_STRING + "云信创建聊天室结果:" + res + common.LOG_QUOTE_STRING)
var createRes CreateChatroomResponse
err1 := json.Unmarshal([]byte(res), &createRes)
if err1 != nil {
logs.Info(common.LOG_QUOTE_STRING + "创建聊天室失败,解析结果错误:" + res + common.LOG_QUOTE_STRING)
panic(err1)
}
if createRes.Code != 200 {
logs.Info(common.LOG_QUOTE_STRING + "创建Im聊天室失败:code= " + strconv.Itoa(createRes.Code) + common.LOG_QUOTE_STRING)
panic(errors.New("创建云信Im聊天室失败"))
}
chatroomModel = createRes.Chatroom
return
}
// IM发送消息
func (client *ImClient) SendMsg(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/msg/sendMsg.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "云信IM发消息请求:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "云信IM发消息结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 添加机器人
func (client *ImClient) AddRobot(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/chatroom/addRobot.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "添加机器人:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "添加机器人:" + res + common.LOG_QUOTE_STRING)
return err
}
// 移除机器人
func (client *ImClient) RemoveRobot(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/chatroom/removeRobot.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "移除机器人:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "移除机器人:" + res + common.LOG_QUOTE_STRING)
return err
}
// 发送聊天室消息
func (client *ImClient) SendChatroomMsg(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/chatroom/sendMsg.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "聊天室发消息请求:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "聊天室发消息结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 获取在线成员信息
func (client *ImClient) GetChatroomMembers(parameters map[string]string) (imResponse ImResponse, err error) {
url := "https://api.netease.im/nimserver/chatroom/queryMembers.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
body := generateBodyData(parameters)
logs.Info(common.LOG_QUOTE_STRING + "获取在线成员请求:" + body + common.LOG_QUOTE_STRING)
res, err1 := htools.HttpDo(httpMethod, url, header, body)
if err1 != nil {
logs.Error(common.LOG_QUOTE_STRING + "获取在线成员失败:" + err1.Error() + common.LOG_QUOTE_STRING)
err = err1
return
}
logs.Info(common.LOG_QUOTE_STRING + "获取在线成员结果:" + res + common.LOG_QUOTE_STRING)
var response ImResponse
if err2 := json.Unmarshal([]byte(res), &response); err2 != nil {
logs.Error(common.LOG_QUOTE_STRING + "获取在线成员解析失败:" + err2.Error() + common.LOG_QUOTE_STRING)
err = err2
return
}
imResponse = response
return
}
// 分页获取聊天室在线用户列表
func (client *ImClient) GetChatroomOnlineUserListByPage(parameters map[string]string) (imResponse ImResponse, err error) {
url := "https://api.netease.im/nimserver/chatroom/membersByPage.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
body := generateBodyData(parameters)
logs.Info(common.LOG_QUOTE_STRING + "分页获取聊天室在线列表:" + body + common.LOG_QUOTE_STRING)
res, err1 := htools.HttpDo(httpMethod, url, header, body)
if err1 != nil {
logs.Error(common.LOG_QUOTE_STRING + "分页获取聊天室在线列表失败:" + err1.Error() + common.LOG_QUOTE_STRING)
err = err1
return
}
logs.Info(common.LOG_QUOTE_STRING + "分页获取聊天室在线列表结果:" + res + common.LOG_QUOTE_STRING)
var response ImResponse
if err2 := json.Unmarshal([]byte(res), &response); err2 != nil {
logs.Error(common.LOG_QUOTE_STRING + "分页获取聊天室在线列表失败:" + err2.Error() + common.LOG_QUOTE_STRING)
err = err2
return
}
imResponse = response
return
}
// 更新聊天室房间信息
func (client *ImClient) UpdateRoomInfo(request yunxin.UpdateRoomInfoRequest) (err error) {
url := "https://api.netease.im/nimserver/chatroom/update.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
sb.Append("roomid=" + request.Roomid)
sb.Append("&")
sb.Append("name=")
sb.Append("&")
sb.Append("announcement=")
sb.Append("&")
sb.Append("ext=")
sb.Append("&")
sb.Append("notifyExt=" + request.NotifyExt)
logs.Info(common.LOG_QUOTE_STRING + "更新聊天室信息请求:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, httpErr := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "更新聊天室信息结果:" + res + common.LOG_QUOTE_STRING)
if httpErr != nil {
logs.Info(common.LOG_QUOTE_STRING + "更新聊天室信息失败:" + httpErr.Error() + common.LOG_QUOTE_STRING)
err = httpErr
return
}
resMap := make(map[string]interface{})
jsonErr := json.Unmarshal([]byte(res), &resMap)
if jsonErr != nil {
logs.Info(common.LOG_QUOTE_STRING + "解析更新房间信息结果失败:" + jsonErr.Error() + common.LOG_QUOTE_STRING)
err = jsonErr
return
}
code := resMap["code"].(float64)
codeStr := htools.Float64ToString(code)
if codeStr != "200" {
logs.Info(common.LOG_QUOTE_STRING + "更新房间信息结果失败: code=" + codeStr + common.LOG_QUOTE_STRING)
err = errors.New("更新失败 code=" + codeStr)
return
}
return
}
// 聊天室临时禁言
func (client *ImClient) ChatroomMuteUser(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/chatroom/temporaryMute.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "聊天室临时禁言请求:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "聊天室临时禁言结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 切换聊天室状态
func (client *ImClient) ChangeChatroomStat(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/chatroom/toggleCloseStat.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
requestId := HyTools.GetUUID()
logs.Info(common.LOG_QUOTE_STRING + "聊天室切换聊天室开关状态请求:requestId " + requestId + " body " + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
if len(res) == 0 {
logs.Info(common.LOG_QUOTE_STRING + "聊天室切换聊天室开关状态结果:失败 res为空 requestId " + requestId + common.LOG_QUOTE_STRING)
return errors.New("操作失败")
}
if err != nil {
logs.Info(common.LOG_QUOTE_STRING + "聊天室切换聊天室开关状态结果:失败 返回error requestId " + requestId + "error " + err.Error() + common.LOG_QUOTE_STRING)
return err
}
//{"desc":"parameter roomid should be long","code":414}
var resData struct {
Code int `json:"code"`
Desc string `json:"desc"`
}
_ = json.Unmarshal([]byte(res), &resData)
if resData.Code != 200 {
if resData.Code != 417 { // 重复操作 直接忽略
logs.Info(fmt.Sprintf(common.LOG_QUOTE_STRING+"聊天室切换聊天室开关状态结果:失败 code[%d] requestId "+requestId+" desc[%s] "+common.LOG_QUOTE_STRING, resData.Code, resData.Desc))
return errors.New("操作失败")
}
}
logs.Info(common.LOG_QUOTE_STRING + "聊天室切换聊天室开关状态结果: requestId " + requestId + " response " + res + common.LOG_QUOTE_STRING)
return err
}
// 设置聊天室成员角色
func (client *ImClient) SetChatroomMemberRole(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/chatroom/setMemberRole.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "设置聊天室成员角色请求:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "设置聊天室成员角色结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 发送广播消息
func (client *ImClient) SendBroadCastMsg(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/msg/broadcastMsg.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "发送广播请求:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "发送广播结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 发送全员广播 V2
func (client *ImClient) SendBroadCastMsgV2(req BroadcastNotificationReq) error {
url := "https://open.yunxinapi.com/im/v2/broadcast_notification"
httpMethod := common.HTTP_METHOD_POST
header := client.generateJsonHeader()
bodyStr := HyTools.JsonStr(req)
logs.Info(common.LOG_QUOTE_STRING + "发送广播请求V2:" + bodyStr + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, bodyStr)
logs.Info(common.LOG_QUOTE_STRING + "发送广播结果V2:" + res + common.LOG_QUOTE_STRING)
return err
}
// 踢出用户登录 V2 https://{endpoint}/im/v2/accounts/{account_id}/actions/kick
func (client *ImClient) KickOutUserV2(userId string) error {
url := "https://open.yunxinapi.com/im/v2/accounts/" + userId + "/actions/kick"
httpMethod := common.HTTP_METHOD_POST
header := client.generateJsonHeader()
req := make(map[string]any, 3)
req["type"] = 1
req["device_id_list"] = []string{}
req["kick_notify_extension"] = ""
bodyStr := HyTools.JsonStr(req)
logs.Info(common.LOG_QUOTE_STRING + "发送踢下线请求V2:" + bodyStr + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, bodyStr)
logs.Info(common.LOG_QUOTE_STRING + "发送踢下线结果V2:" + res + common.LOG_QUOTE_STRING)
return err
}
// 设置/取消 拉黑 静音
func (client *ImClient) SetSpecialRelation(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/user/setSpecialRelation.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "设置/取消 拉黑请求:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "设置/取消 拉黑结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 发送自定义系统通知
func (client *ImClient) BatchSendMsg(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/msg/sendBatchMsg.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "云信IM批量发消息请求:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "云信IM批量发消息结果:" + res + common.LOG_QUOTE_STRING)
return err
}
// 批量发送消息
func (client *ImClient) SendAttachMsg(parameters map[string]string) error {
url := "https://api.netease.im/nimserver/msg/sendAttachMsg.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
logs.Info(common.LOG_QUOTE_STRING + "云信IM发系统通知请求:" + sb.ToString() + common.LOG_QUOTE_STRING)
res, err := htools.HttpDo(httpMethod, url, header, sb.ToString())
logs.Info(common.LOG_QUOTE_STRING + "云信IM发系统通知结果:" + res + common.LOG_QUOTE_STRING)
if parameters["to"] == "b260b07ff3d1411f926e2d767cb9d6f3" {
log.Info("完成给老板发接单消息")
}
return err
}
// CreateChatGroup 创建群组
func (client *ImClient) CreateChatGroup(ctx context.Context, ownerUserId string, groupName string, inviteMem []string) (string, error) {
url := "https://open.yunxinapi.com/im/v2.1/teams"
httpMethod := common.HTTP_METHOD_POST
header := client.generateJsonHeader()
reqBody := &CreateChatGroupReq{
OwnerAccountID: ownerUserId,
TeamType: 1,
Name: groupName,
MembersLimit: 3000,
InviteAccountIDs: inviteMem,
InviteMsg: "管理",
Configuration: &TeamConfiguration{
JoinMode: 1,
},
AntispamConfiguration: nil,
}
bodyBytes, err := json.Marshal(reqBody)
if err != nil {
return "", errors.WithStack(err)
}
res, err := HyTools.HttpDo(httpMethod, url, header, string(bodyBytes))
if err != nil {
// {"desc":"already register","code":414}
logs.Info("YunXin_CreateChatGroup_Fail_AccID_%s:"+err.Error(), ownerUserId)
return "", errors.WithStack(err)
}
log.InfoF("create group chat resp: %s", res)
var body BaseRes[CreateChatGroupRsp]
err = json.Unmarshal([]byte(res), &body)
if err != nil {
return "", errors.WithStack(err)
}
if body.Code != 200 {
return "", errors.Errorf("YunXin_CreateChatGroup code(%d) not 200,msg: %s", body.Code, body.Msg)
}
if len(body.Data.FailedList) > 0 {
var errInfo []string
for _, member := range body.Data.FailedList {
errInfo = append(errInfo, fmt.Sprintf("account: %s ,code: %d ,msg: %s", member.AccountID, member.ErrorCode, member.ErrorMsg))
}
return "", errors.Errorf("YunXin_CreateChatGroup err: %s", strings.Join(errInfo, ";"))
}
return cast.ToString(body.Data.TeamInfo.TeamID), nil
}
// ================ 私有方法 ========================
func generateBodyData(parameters map[string]string) string {
sb := htools.NewStringBuilder()
parametersCount := len(parameters)
i := 0
for k, v := range parameters {
sb.Append(k + "=" + v)
if i < parametersCount-1 {
sb.Append("&")
}
i++
}
return sb.ToString()
}
func randNumber() string {
str := "0123456789"
bytes := []byte(str)
var result []byte
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 4; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return string(result)
}

View File

@ -0,0 +1,124 @@
package Netease
import (
"context"
"testing"
)
// 测试获取IM用户信息
func TestImClient_GetImUserInfo(t *testing.T) {
type fields struct {
AppKey string
AppSecret string
}
type args struct {
userId string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "test",
fields: fields{
AppKey: "543b1a440b940b170fccdf494839efc01",
AppSecret: "41134093938f1",
},
args: args{
// 2251bae8a0514e6892f0374f5dd260d4
// b8af4bebe2064435974ba2340d852055
userId: "fcda23bc00e641f5ae17492503b566ed",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &ImClient{
AppKey: tt.fields.AppKey,
AppSecret: tt.fields.AppSecret,
}
if err := client.GetImUserInfo(tt.args.userId); (err != nil) != tt.wantErr {
t.Errorf("GetImUserInfo() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// 测试注册IM用户
func TestImClient_CreateImUser(t *testing.T) {
type fields struct {
AppKey string
AppSecret string
}
type args struct {
userId string
nickname string
avatar string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "test",
fields: fields{
AppKey: "543b1a440b940b170fccdf494839efc01",
AppSecret: "41134093938f1",
},
args: args{
// 2251bae8a0514e6892f0374f5dd260d4
// b8af4bebe2064435974ba2340d852055
userId: "fcda23bc00e641f5ae17492503b566ed",
nickname: "杏",
avatar: "https://photo-app.ddegame.cn/upload/19986a27-3bae-49f7-9643-cd18cda87557.jpg",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &ImClient{
AppKey: tt.fields.AppKey,
AppSecret: tt.fields.AppSecret,
}
if err := client.CreateImUser(tt.args.userId, tt.args.nickname, tt.args.avatar); (err != nil) != tt.wantErr {
t.Errorf("GetImUserInfo() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// 测试注册IM用户
func TestImClient_CreateImUserOne(t *testing.T) {
client := &ImClient{
AppKey: "543b1a440b940b170fccdf494839efc01",
AppSecret: "41134093938f1",
}
client.CreateImUser("fcda23bc00e641f5ae17492503b566ed", "杏", "https://photo-app.ddegame.cn/upload/19986a27-3bae-49f7-9643-cd18cda87557.jpg")
}
func TestImClient_CreateChatGroup(t *testing.T) {
client := &ImClient{
AppKey: "543b1a440b940b170fccdf494839efc01",
AppSecret: "41134093938f1",
}
groupId, err := client.CreateChatGroup(context.Background(), "e1fbefbbd77a402e8c2101a0ebe15f5d", "测试群", []string{"e1fbefbbd77a402e8c2101a0ebe15f5d"})
if err != nil {
t.Error(err)
}
println(groupId)
}
func init() {
// viper.SetConfigName("dev.yaml")
// viper.AddConfigPath("../../../configs/admin/")
// viper.SetConfigType("yaml")
// _ = viper.ReadInConfig()
// log.Init()
// datasource.InitMySQlMaster()
}

View File

@ -0,0 +1,33 @@
package Netease
type ImResponse struct {
Code int `json:"code"`
Desc interface{} `json:"desc"`
}
// 获取聊天室在线列表返回对象
type GetChatroomMemberResponse struct {
Data []ChatroomMemberDTO `json:"data"`
}
// 聊天室会员
type ChatroomMemberDTO struct {
RoomId int `json:"roomid"`
AccId string `json:"accid"`
OnlineStat bool `json:"onlineStat"`
}
// 创建聊天室返回结果
type CreateChatroomResponse struct {
Code int `json:"code"`
Chatroom NeteaseChatroomDTO `json:"chatroom"`
}
// 聊天室会员
type NeteaseChatroomDTO struct {
RoomId int `json:"roomid"`
Valid bool `json:"valid"`
RoomName string `json:"name"`
Ext string `json:"ext"`
Creator string `json:"creator"`
}

View File

@ -0,0 +1,10 @@
package yunxin
// 聊天室在线成员对象
type ImChatroomOnlineMember struct {
MessageRoomId int `json:"roomid"`
UserId string `json:"accid"`
EnterTime int64 `json:"enterTime"`
Type string `json:"type"`
OnlineStat bool `json:"onlineStat"`
}

View File

@ -0,0 +1,9 @@
package yunxin
// IM用户扩展信息 会话列表用户数据从IM信息获取
type ImUserExtData struct {
VipLevel string // Vip等级
AvatarDecoration string // 头饰
GroupType string
VipConfig string // json字符串dto.UserVipConfigDTO
}

View File

@ -0,0 +1,7 @@
package yunxin
// 更新聊天室信息请求对象
type UpdateRoomInfoRequest struct {
Roomid string `json:"roomid"` // 房间ID
NotifyExt string `json:"notifyExt" // 扩展信息`
}

38
pkg/common/netease/req.go Normal file
View File

@ -0,0 +1,38 @@
package Netease
// CreateChatGroupReq 创建群组请求
// https://doc.yunxin.163.com/messaging2/server-apis/DIwODUwMTE?platform=server
type CreateChatGroupReq struct {
OwnerAccountID string `json:"owner_account_id"` // M 群主(创建者)的 IM 账号 ID。
TeamType int `json:"team_type"` // M 1高级群。2超大群。
Name string `json:"name"` // M 长度上限 64 位字符。
Icon string `json:"icon,omitempty"` // O 群组头像的 URL 地址,例如 "https://netease/xxx.png",长度上限 1024 位字符。
Announcement string `json:"announcement,omitempty"` // O 群组公告,长度上限 1024 位字符。
Intro string `json:"intro,omitempty"` // O群组简介长度上限 512 位字符。
MembersLimit int `json:"members_limit"` // O 群组成员数上限(包含群主),默认为 200。可设置范围的为[2 ~ 200]。
ServerExtension string `json:"server_extension,omitempty"` // O 自定义群组扩展字段,第三方可以跟据此属性自定义扩展自己的群属性,建议封装成 JSONObject 格式,{key:value}。长度上限 1024 位字符。 对应 SDK 的 serverExtension 字段。
CustomerExtension string `json:"customer_extension,omitempty"` // O 客户端自定义扩展字段,仅服务器 API 可以设置SDK 仅负责透传,不解析内容。 对应 SDK 的 customerExtension 字段。
Extension string `json:"extension,omitempty"` // M 创建群组时邀请入群的成员列表。
InviteAccountIDs []string `json:"invite_account_ids,omitempty"` // M 邀请入群的附言,长度上限 150 位字符。
InviteMsg string `json:"invite_msg,omitempty"`
Configuration *TeamConfiguration `json:"configuration,omitempty"`
AntispamConfiguration *AntispamConfig `json:"antispam_configuration,omitempty"`
}
type TeamConfiguration struct {
JoinMode int `json:"join_mode"` // 通过 SDK 侧操作申请入群的验证方式。 0默认无需验证直接入群。 1需要群主或管理员验证通过才能入群。 2不允许任何人申请入群。
AgreeMode int `json:"agree_mode"` // 邀请入群时是否需要被邀请人的同意。 0默认需要被邀请人同意才能入群。 1不需要被邀请人同意直接入群。
InviteMode int `json:"invite_mode"` // 邀请权限,即谁可以邀请他人入群。 0默认群主和管理员。 1所有人。
UpdateTeamInfoMode int `json:"update_team_info_mode"`
UpdateExtensionMode int `json:"update_extension_mode"`
}
type AntispamConfig struct {
Enabled bool `json:"enabled"`
BusinessIDMap *BusinessIDMap `json:"business_id_map,omitempty"`
}
type BusinessIDMap struct {
Type int `json:"type"`
AntispamBusinessID string `json:"antispam_business_id"`
}

35
pkg/common/netease/rsp.go Normal file
View File

@ -0,0 +1,35 @@
package Netease
type CreateChatGroupRsp struct {
FailedList []*FailedMember `json:"failed_list,omitempty"`
TeamInfo *TeamInfo `json:"team_info"`
}
type FailedMember struct {
AccountID string `json:"account_id"`
ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
}
type TeamInfo struct {
TeamID int64 `json:"team_id"`
OwnerAccountID string `json:"owner_account_id"`
Name string `json:"name"`
Icon string `json:"icon,omitempty"`
Announcement string `json:"announcement,omitempty"`
Intro string `json:"intro,omitempty"`
MembersLimit int `json:"members_limit"`
MemberCount int `json:"member_count"`
ServerExtension string `json:"server_extension,omitempty"`
CustomerExtension string `json:"customer_extension,omitempty"`
CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
TeamType int `json:"team_type"`
Configuration *TeamConfiguration `json:"configuration,omitempty"`
}
type BaseRes[T any] struct {
Code int
Msg string
Data T
}

View File

@ -0,0 +1,45 @@
package network
import (
"encoding/json"
)
const (
SuccessCode = "6000" // 成功
NeedLogin = "6018" // 登陆过期
ErrorCode = "6020" // 错误
)
type Response struct {
Code string
Result interface{}
Msg string
}
type EmptyResponse struct {
}
func Fail(msg string) Response {
response := Response{Code: ErrorCode, Msg: msg}
return response
}
func Invalid() string {
response := Response{Code: NeedLogin, Msg: "用户身份已过期"}
str, _ := json.Marshal(response)
return string(str)
}
func Success(data interface{}) Response {
response := Response{Code: SuccessCode, Result: data}
return response
}
func Page(data interface{}, count int64) Response {
response := Response{Code: SuccessCode, Result: map[string]interface{}{
"List": data,
"Count": count,
}}
return response
}

148
pkg/common/pinyin/pinyin.go Normal file
View File

@ -0,0 +1,148 @@
package pinyin
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"unicode/utf8"
)
type vowel int32
var (
ys = []vowel{'ā', 'ē', 'ī', 'ō', 'ū', 'ǖ', 'Ā', 'Ē', 'Ī', 'Ō', 'Ū', 'Ǖ'} // 单韵母 一声
es = []vowel{'á', 'é', 'í', 'ó', 'ú', 'ǘ', 'Á', 'É', 'Í', 'Ó', 'Ú', 'Ǘ'} // 单韵母 二声
ss = []vowel{'ǎ', 'ě', 'ǐ', 'ǒ', 'ǔ', 'ǚ', 'Ǎ', 'Ě', 'Ǐ', 'Ǒ', 'Ǔ', 'Ǚ'} // 单韵母 三声
fs = []vowel{'à', 'è', 'ì', 'ò', 'ù', 'ǜ', 'À', 'È', 'Ì', 'Ò', 'Ù', 'Ǜ'} // 单韵母 四声
ws = []vowel{'a', 'e', 'i', 'o', 'u', 'v', 'A', 'E', 'I', 'O', 'U', 'V'} // 单韵母 无声调
)
var (
pinyinTemp map[vowel]interface{}
toneTemp map[vowel]vowel
)
const (
Tone string = "带声调的拼音" // 带声调的拼音 例如Cào
InitialsInCapitals string = "首字母大写不带声调" // 首字母大写不带声调 例如Cao
None string = "" //如果匹配不到汉字,就靠大家维护下 【匹配失败手动添加代码到pinyin.txt】
)
func LoadingPYFile(filename string) {
f, err := os.Open(filename)
if err != nil {
fmt.Println(err)
}
if err != nil {
panic(err)
}
pinyinTemp = make(map[vowel]interface{}, 0)
toneTemp = make(map[vowel]vowel)
for i, t := range ys {
toneTemp[t] = ws[i]
}
for i, t := range es {
toneTemp[t] = ws[i]
}
for i, t := range ss {
toneTemp[t] = ws[i]
}
for i, t := range fs {
toneTemp[t] = ws[i]
}
defer func() {
_ = f.Close()
}()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
//单行分割取出拼音
str := strings.Split(scanner.Text(), "=>")
if len(str) < 2 {
continue
}
i, err := strconv.ParseInt(str[0], 16, 32)
if err != nil {
continue
}
pinyinTemp[vowel(i)] = str[1]
//fmt.Println(pinyinTemp[vowel(i)])
}
}
func PY(source string) (string, error) {
return PYSplit(source, "", "")
}
func PYSplit(source string, split string, types string) (string, error) {
hz := []vowel(source)
words := make([]string, 0)
for _, s := range hz {
word, err := _vowel(s, types)
if err != nil {
return None, err
}
if len(word) > 0 {
words = append(words, word)
}
}
return strings.Join(words, split), nil
}
func _vowel(source vowel, types string) (string, error) {
switch types {
case Tone:
return getTone(source), nil
case InitialsInCapitals:
return getInitialsInCapitals(source), nil
default:
return getDefault(source), nil
}
}
func getTone(source vowel) string {
if pinyinTemp[source] != nil {
return pinyinTemp[source].(string)
}
return string(source)
}
func getInitialsInCapitals(source vowel) string {
def := getDefault(source)
var result string
if def == "" {
return def
}
str := []vowel(def)
if str[0] > 32 {
str[0] = str[0] - 32
}
for _, v := range str {
result += string(v)
}
return result
}
func getDefault(source vowel) string {
tone := getTone(source)
var result string
if tone == "" {
return None
}
resultLen := make([]vowel, utf8.RuneCountInString(tone))
count := 0
for _, t := range tone {
changes, ok := toneTemp[vowel(t)]
if ok {
resultLen[count] = changes
} else {
resultLen[count] = vowel(t)
}
count++
}
for _, v := range resultLen {
result += string(v)
}
return result
}

41208
pkg/common/pinyin/pinyin.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
package pinyin
import "testing"
func TestTo_Py(t *testing.T) {
LoadingPYFile("./pinyin.txt")
s, _ := PY("3425另264325一4个44平444台")
println(s)
}

30
pkg/common/req/req.go Normal file
View File

@ -0,0 +1,30 @@
package req
const (
defaultPage = 1
defaultSize = 10
)
type Page struct {
Page int64 `json:"page" form:"page" uri:"page"`
Size int64 `json:"size" form:"size" uri:"size"`
}
func (p *Page) check() {
if p.Page < 1 {
p.Page = defaultPage
}
if p.Size <= 0 {
p.Size = defaultSize
}
}
func (p *Page) Offset() int {
p.check()
return int(p.Page*p.Size - p.Size)
}
func (p *Page) Limit() int {
p.check()
return int(p.Size)
}

View File

@ -0,0 +1,154 @@
package util
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
const (
PhotoDomainUrl = "https://photo-app.ddegame.cn/"
TimeLayout = "2006-01-02 15:04:05"
DateLayout = "2006-01-02"
DateLayoutShort = "20060102"
)
func FullPhotoUrl(path string) string {
if len(path) == 0 {
return ""
}
if strings.Index(path, "http") == 0 {
return path
}
return PhotoDomainUrl + path
}
func Md5ForList(s ...string) string {
sort.Strings(s)
h := md5.New()
h.Write([]byte(strings.Join(s, "")))
return hex.EncodeToString(h.Sum(nil))
}
func StringToInt(source string) int {
target, _ := strconv.Atoi(source)
return target
}
func StringToFloat(source string) float64 {
target, _ := strconv.ParseFloat(source, 64)
return target
}
func StringIntPlus(s1, s2 string) string {
return strconv.Itoa(StringToInt(s1) + StringToInt(s2))
}
func StringIntMultiply(s1, s2 string) string {
return strconv.Itoa(StringToInt(s1) * StringToInt(s2))
}
func StringIntSub(s1, s2 string) string {
return strconv.Itoa(StringToInt(s1) - StringToInt(s2))
}
func StringFloatSub(s1, s2 string) string {
return strconv.FormatFloat(StringToFloat(s1)-StringToFloat(s2), 'f', -1, 64)
}
func ListToSet(list []string) (set []string) {
m := make(map[string]bool, 0)
for i := range list {
m[list[i]] = true
}
for k := range m {
set = append(set, k)
}
return
}
func ToJson(source interface{}) []byte {
b, _ := json.Marshal(source)
return b
}
func ToJsonString(source interface{}) string {
return string(ToJson(source))
}
func NowTimeToString() string {
return time.Now().Format(TimeLayout)
}
func NowTimeIncreaseString(t time.Duration) string {
return time.Now().Add(t).Format(TimeLayout)
}
func NowDateToString() string {
return time.Now().Format(DateLayout)
}
func NowDateToStringShort() string {
return time.Now().Format(DateLayoutShort)
}
func DateToString(source time.Time) string {
return source.Format(TimeLayout)
}
func Md5(s string) string {
h := md5.New()
h.Write([]byte(s))
return hex.EncodeToString(h.Sum(nil))
}
func ProcessTimeStr(s string) (string, error) {
// 定义两种时间格式
layout1 := "2006-01-02 15:04:05" // 无时区的格式
layout2 := "2006-01-02 15:04:05 -0700 MST" // 带时区的格式(包含偏移和时区名称)
// 尝试解析为无时区格式
_, err1 := time.Parse(layout1, s)
if err1 == nil {
return s, nil // 直接返回原字符串
}
// 尝试解析为带时区格式
t2, err2 := time.Parse(layout2, s)
if err2 == nil {
// 格式化为无时区格式
return t2.Format(layout1), nil
}
// 两种格式都不匹配
return "", fmt.Errorf("invalid time format: %s", s)
}
// 解析url里的参数为map
func ParseUrlParameter(fullUrl string) (result map[string]string) {
result = make(map[string]string)
// 解析URL
parsedURL, err := url.Parse(fullUrl)
if err != nil {
fmt.Printf("解析URL失败: %v\n", err)
return
}
// 解析查询参数
queryParams := parsedURL.Query()
// 输出所有参数
for key, values := range queryParams {
result[key] = values[0]
}
return
}

View File

@ -0,0 +1,17 @@
package util
import (
"servicebase/pkg/common"
"fmt"
"testing"
)
func Test_processTimeStr(t *testing.T) {
tmp, e := ProcessTimeStr("2025-05-29 09:08:38 +0800 CST")
fmt.Printf("%v %v\n", tmp, e)
}
func TestParseUrlParameter(t *testing.T) {
// assets/img/op_1754967614.png?c=#000000&s=160
ParseUrlParameter(common.FullPhotoUrl("assets/img/op_1754967614.png?c=%23000000&s=160"))
}

18
pkg/constant/content.go Normal file
View File

@ -0,0 +1,18 @@
package constant
const (
JxApiToken = "8056be17be26d7e8f4"
ContentSmsTemplateTypeLogin = "LOGIN"
ContentSmsTemplateTypeRegDriver = "REG-DRIVER"
ContentSmsTemplateTypeRegPassenger = "REG-PASSENGER"
ContentSmsRecordSendSuccess = 2
ContentSmsRecordSendFailed = 1
)
var ContentSmsTemplateType map[string]string
func init() {
ContentSmsTemplateType = map[string]string{}
}

View File

@ -0,0 +1,170 @@
package exam_order
type OrderStatus string
const (
// 订单原始状态 已下单待支付
ORDER_ORIGIN_STATUS_CREATED_WAIT_PAY OrderStatus = "1"
// 订单原始状态 已支付待接单
ORDER_ORIGIN_STATUS_PAID_WAIT_SELLER_AGREE OrderStatus = "2"
// 订单原始状态 已接单
ORDER_ORIGIN_STATUS_SELLER_AGREED OrderStatus = "3"
// 订单原始状态 已完成
ORDER_ORIGIN_STATUS_FINISHED OrderStatus = "4"
// 订单原始状态 退款中
ORDER_ORIGIN_STATUS_REFUNDING OrderStatus = "5"
// 订单原始状态 售后中(申诉中)
ORDER_ORIGIN_STATUS_IN_APPEAL OrderStatus = "6"
// 订单原始状态 申诉失败 订单完成
ORDER_ORIGIN_STATUS_APPEAL_FAILED OrderStatus = "8"
// 订单原始状态 售后完成(申诉完成)
ORDER_ORIGIN_STATUS_APPEAL_DONE OrderStatus = "9"
// 订单原始状态 支付前买家取消
ORDER_ORIGIN_STATUS_UNPAY_BUYER_CANCELED OrderStatus = "20"
// 订单原始状态 10分钟未支付自动过期
ORDER_ORIGIN_STATUS_UNPAY_AUTO_EXPISED OrderStatus = "21"
// 订单原始状态 支付后买家取消
ORDER_ORIGIN_STATUS_PAID_BUYER_CANCELED OrderStatus = "22"
// 订单原始状态 支付后卖家未接单 自动取消
ORDER_ORIGIN_STATUS_PAID_SELLER_NOT_AGREE_AUTO_CANCELED OrderStatus = "23"
// 订单原始状态 卖家拒绝接单
ORDER_ORIGIN_STATUS_SELLER_REJECTED_ORDER OrderStatus = "24"
// 订单原始状态 卖家同意退款
ORDER_ORIGIN_STATUS_SELLER_AGREE_REFUND OrderStatus = "25"
// 订单原始状态 (售后成功)申诉成功,并免单退款
ORDER_ORIGIN_STATUS_APPEAL_SUCCESS_CANCELED OrderStatus = "26"
// 订单原始状态 买家申请退款卖家超时未处理自动取消
ORDER_ORIGIN_STATUS_REFUNDING_EXPIRED_AUTO_CANCELED OrderStatus = "27"
)
func (o OrderStatus) String() string {
return string(o)
}
type PayStatus string
const (
// 订单支付状态 未支付
ORDER_PAY_STATUS_UNPAY PayStatus = "0"
// 订单支付状态 已支付
ORDER_PAY_STATUS_PAID PayStatus = "1"
// 订单支付状态 已退款
ORDER_PAY_STATUS_REFUND PayStatus = "2"
)
func (o PayStatus) String() string {
return string(o)
}
type ExaminerOrderStatus string
const (
ExaminerIdle ExaminerOrderStatus = "idle" // 空闲
ExaminerAccept ExaminerOrderStatus = "accept" // 空闲
)
func (o ExaminerOrderStatus) String() string {
return string(o)
}
type ExamSkillStatus string
const (
ExamSkillNormal ExamSkillStatus = "normal"
ExamSkillDown ExamSkillStatus = "down"
)
func (o ExamSkillStatus) String() string {
return string(o)
}
type OrderAction string
const (
//PayOrder = 支付订单 CancelOrder = 取消订单 AgreeOrder= 同意接单 RefuseOrder=拒绝接单 FinishOrder = 确认完成 RequestRefund=请求退款 AgreeRefund=同意退款 RefuseRefund=拒绝退款
// 订单操作 创建订单
ORDER_ACTION_CREATE_ORDER OrderAction = "CreateOrder"
// 订单操作 支付订单
ORDER_ACTION_PAY_ORDER OrderAction = "PayOrder"
// 订单操作 取消订单
ORDER_ACTION_CANCEL_ORDER OrderAction = "CancelOrder"
// 订单操作 卖家同意接单
ORDER_ACTION_AGREE_ORDER OrderAction = "AgreeOrder"
// 订单操作 卖家完成一局
ORDER_ACTION_SUB_COMPLETE_ORDER OrderAction = "SubCompleteOrder"
// 订单操作 买家续单
ORDER_ACTION_RENEW_ORDER OrderAction = "RenewOrder"
// 订单操作 卖家开始陪玩
ORDER_ACTION_START_ORDER OrderAction = "StartOrder"
// 订单操作 卖家拒绝接单
ORDER_ACTION_REJECT_ORDER OrderAction = "RejectOrder"
// 订单操作 买家完成订单
ORDER_ACTION_FINISH_ORDER OrderAction = "FinishOrder"
// 订单操作 提前结束
ORDER_ACTION_EARLY_END_ORDER OrderAction = "EarlyEndOrder"
// 订单操作 按局订单 陪玩结束
ORDER_ACTION_SELLER_END_ORDER OrderAction = "SellerEndOrderByCount"
// 订单操作 买家请求退款
ORDER_ACTION_REQUEST_REFUND OrderAction = "RequestRefund"
// 订单操作 卖家同意退款
ORDER_ACTION_AGREE_REFUND OrderAction = "AgreeRefund"
// 订单操作 卖家拒绝退款
ORDER_ACTION_REJECT_REFUND OrderAction = "RejectRefund"
// 订单操作 评价订单
ORDER_ACTION_RATE_ORDER OrderAction = "RateOrder"
// 订单操作 再次下单
ORDER_ACTION_ORDER_AGAIN OrderAction = "ReCreateOrder"
// 订单操作 申请售后
ORDER_ACTION_ORDER_SUPPORT OrderAction = "ApplySupportOrder"
// 订单操作 删除订单
ORDER_ACTION_ORDER_DEL OrderAction = "DeleteOrder"
// 订单操作 订单免单
ORDER_ACTION_ORDER_FREE OrderAction = "FreeOrder"
// 订单操作 联系售后
ORDER_ACTION_CONTACT_SUPPORT OrderAction = "ContactSupport"
// 订单操作 删除评价订单
ORDER_ACTION_DELETE_ORDER_RATE OrderAction = "DeleteOrderRate"
// 订单操作 删除订单
ORDER_ACTION_DELETE_ORDER OrderAction = "DeleteOrder"
// 订单操作 申诉
ORDER_ACTION_APPEAL_SUCCESS OrderAction = "AppealSuccess"
// 订单操作 申诉失败
ORDER_ACTION_APPEAL_FAIL OrderAction = "AppealFail"
//订单操作 增加单量
ORDER_ACTION_ADD_ORDER_COUNT OrderAction = "AddOrderCount"
//订单操作 减少单量
ORDER_ACTION_REDUCE_ORDER_COUNT OrderAction = "ReduceOrderCount"
//订单操作 进入房间
ORDER_ACTION_ENTER_ROOM OrderAction = "EnterRoom"
// 订单操作 提交审核
ORDER_ACTION_SUBMIT_REVIEW OrderAction = "SubmitReview"
)
func (o OrderAction) String() string {
return string(o)
}
type OrderReviewStatus string
const (
OrderReviewInit OrderReviewStatus = "init"
OrderReviewAccept OrderReviewStatus = "accept"
OrderReviewDeny OrderReviewStatus = "deny"
)
func (o OrderReviewStatus) String() string {
return string(o)
}
type OrderSupportStatus string
const (
OrderSupportInit OrderSupportStatus = "init"
OrderSupportDone OrderSupportStatus = "done"
)
func (o OrderSupportStatus) String() string {
return string(o)
}

24
pkg/constant/p_vars.go Normal file
View File

@ -0,0 +1,24 @@
package constant
const (
PhotoDomainUrl = "https://photo-app.ddegame.cn/"
API_SECRET = "c9ca017f078a4cd1ba5ccb9d02d4869a"
HTTP_METHOD_POST = "POST"
LOG_QUOTE_STRING = "#"
// 验证码类型
SMS_CODE_TYPE_SIGN_IN = "signIn" //登录验证码
// 未登录 需要跳登录页
ErrorCodeNeedLogin int = 7031
ErrorCodeSignError int = 7032
// APP登录方式
AppLoginTypePwd = "1" //密码登录
AppLoginTypeSmsCode = "2" //验证码登录
CustomerProductId = "CUSTOMER" // 自定义充值的产品ID
WeekStarGiftGrantToReward = "TO_REWARD" // 给周星名气榜用户发放的礼物列表key
WeekStarGiftGrantToReceive = "TO_RECEIVE" // 给周星人气榜用户发放的礼物列表key
)

15
pkg/constant/sys_vars.go Normal file
View File

@ -0,0 +1,15 @@
package constant
// 资源类型
type ResourceTypeEnum int
const (
Image ResourceTypeEnum = iota // = 0
Video // = 1
Audio
)
func (actionType ResourceTypeEnum) String() string {
names := []string{"图片", "视频", "音频"}
return names[actionType]
}

7
pkg/constant/tenant.go Normal file
View File

@ -0,0 +1,7 @@
package constant
const (
HeaderUserId = "USER-ID"
TenantId = "TENANT-ID"
ScopeId = "SCOPE-ID"
)

View File

@ -0,0 +1,21 @@
package constant
const (
WithdrawStatusReview = "1"
WithdrawStatusApproved = "2"
WithdrawStatusReject = "3"
WithdrawStatusPayFailed = "4"
WithdrawStatusPaySuccess = "5"
)
var WithdrawStatusCon map[string]string
func init() {
WithdrawStatusCon = map[string]string{
WithdrawStatusReview: "审核中",
WithdrawStatusApproved: "审核通过",
WithdrawStatusReject: "审核失败",
WithdrawStatusPayFailed: "打款失败",
WithdrawStatusPaySuccess: "打款成功",
}
}

View File

@ -0,0 +1,185 @@
package fields
import (
"bytes"
"context"
"database/sql/driver"
"encoding/binary"
"errors"
"fmt"
"math"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
const (
timeFormat = "2006-01-02 15:04:05"
dateFormat = "2006-01-02"
)
type STPoint struct {
Lng float64
Lat float64
}
type Time time.Time
func (t *Time) UnmarshalJSON(data []byte) (err error) {
now, err := time.ParseInLocation(`"`+timeFormat+`"`, string(data), time.Local)
*t = Time(now)
return
}
func (t *Time) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(timeFormat)+2)
b = append(b, '"')
b = time.Time(*t).AppendFormat(b, timeFormat)
b = append(b, '"')
return b, nil
}
func (t *Time) String() string {
return time.Time(*t).Format(timeFormat)
}
func (t *Time) Value() (driver.Value, error) {
return time.Time(*t).Format(timeFormat), nil
}
func (t *Time) Time() time.Time {
return time.Time(*t)
}
func NowTime() Time {
return Time(time.Now())
}
type Date time.Time
func (t *Date) UnmarshalJSON(data []byte) (err error) {
now, err := time.ParseInLocation(`"`+dateFormat+`"`, string(data), time.Local)
*t = Date(now)
return
}
func (t *Date) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(dateFormat)+2)
b = append(b, '"')
b = time.Time(*t).AppendFormat(b, dateFormat)
b = append(b, '"')
return b, nil
}
func (t *Date) String() string {
return time.Time(*t).Format(dateFormat)
}
func (t *Date) FromStr(s string) error {
now, err := time.ParseInLocation(dateFormat, string(s), time.Local)
*t = Date(now)
return err
}
func (t *Date) Time() time.Time {
return time.Time(*t)
}
type Month time.Time
const (
monthFormat = "2006-01"
)
func (t *Month) UnmarshalJSON(data []byte) (err error) {
if len(data) <= 2 {
return nil
}
now, err := time.ParseInLocation(`"`+monthFormat+`"`, string(data), time.Local)
*t = Month(now)
return
}
func (t *Month) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(monthFormat)+2)
b = append(b, '"')
b = time.Time(*t).AppendFormat(b, monthFormat)
b = append(b, '"')
return b, nil
}
func (t *Month) String() string {
return time.Time(*t).Format(monthFormat)
}
type Point struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
func (p Point) Value() (driver.Value, error) {
return fmt.Sprintf("POINT(%f %f)", p.Lng, p.Lat), nil
}
func (p *Point) Scan(src interface{}) error {
switch src.(type) {
case []byte:
var b = src.([]byte)
if len(b) != 25 {
return errors.New(fmt.Sprintf("Expected []bytes with length 25, got %d", len(b)))
}
var longitude float64
var latitude float64
buf := bytes.NewReader(b[9:17])
err := binary.Read(buf, binary.LittleEndian, &longitude)
if err != nil {
return err
}
buf = bytes.NewReader(b[17:25])
err = binary.Read(buf, binary.LittleEndian, &latitude)
if err != nil {
return err
}
p.Lng = longitude
p.Lat = latitude
default:
return errors.New(fmt.Sprintf("Expected []byte for Location type, got %T", src))
}
return nil
}
func (loc Point) GormDataType() string {
return "point"
}
func (loc Point) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%f %f)", loc.Lng, loc.Lat)},
}
}
func (p Point) DistanceTo(other Point) float64 {
const earthRadius = 6371000 // 地球半径,单位为米
lat1 := p.Lat * math.Pi / 180
lng1 := p.Lng * math.Pi / 180
lat2 := other.Lat * math.Pi / 180
lng2 := other.Lng * math.Pi / 180
dlat := lat2 - lat1
dlng := lng2 - lng1
a := math.Sin(dlat/2)*math.Sin(dlat/2) +
math.Cos(lat1)*math.Cos(lat2)*math.Sin(dlng/2)*math.Sin(dlng/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
distance := earthRadius * c
// point1 := Point{Lat: 40.7128, Lng: -74.0060}
// point2 := Point{Lat: 34.0522, Lng: -118.2437}
// distance := point1.DistanceTo(point2)
// fmt.Printf("Distance between points: %.2f km\n", distance)
// fmt.Printf("Distance between points: %d meters\n", distance)
return distance
}

128
pkg/datasource/mysql.go Normal file
View File

@ -0,0 +1,128 @@
package datasource
import (
"servicebase/pkg/log"
"servicebase/pkg/repo"
"servicebase/pkg/utils"
"strings"
"time"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"gorm.io/plugin/dbresolver"
)
type BaseModel struct {
ID int `gorm:"primaryKey" json:"id"`
CreateTime utils.Time `gorm:"type:datetime;index;not null" json:"createTime"`
}
func (model *BaseModel) BeforeCreate(tx *gorm.DB) (err error) {
model.CreateTime.Time = time.Now()
return
}
var DB *gorm.DB
type MySQLStarter struct {
}
func (s *MySQLStarter) Init() error {
InitMySQl()
return nil
}
func InitMySQl() {
dsn := viper.GetString("db.connectString")
log.InfoF("init db config with: %s", dsn)
var e error
if DB, e = gorm.Open(mysql.New(mysql.Config{
DriverName: "",
ServerVersion: "",
DSN: dsn,
Conn: nil,
SkipInitializeWithVersion: false,
DefaultStringSize: 255,
DefaultDatetimePrecision: nil,
DisableDatetimePrecision: true,
DontSupportRenameIndex: false,
DontSupportRenameColumn: true,
DontSupportForShareClause: false,
// DontSupportNullAsDefaultValue: false,
}), &gorm.Config{
SkipDefaultTransaction: false,
NamingStrategy: schema.NamingStrategy{
TablePrefix: "", // table name prefix, table for `User` would be `t_users`
SingularTable: true, // use singular table name, table for `User` would be `user` with this option enabled
NoLowerCase: false, // skip the snake_casing of names
NameReplacer: strings.NewReplacer("PID", "pid"), // use name replacer to change struct/field name before convert it to db name
},
FullSaveAssociations: false,
Logger: &log.GLog{}, //
NowFunc: func() time.Time {
return time.Now().Local()
},
DryRun: false,
PrepareStmt: false, // 执行任何 SQL 时都会创建一个 prepared statement 并将其缓存,以提高后续的效率
DisableAutomaticPing: false, // 在完成初始化后GORM 会自动 ping 数据库以检查数据库的可用性
DisableForeignKeyConstraintWhenMigrating: true, // 在 AutoMigrate 或 CreateTable 时GORM 会自动创建外键约束,若要禁用该特性,可将其设置为 true
DisableNestedTransaction: false, // 嵌套事务
AllowGlobalUpdate: false, // true = 启用全局 update/delete
QueryFields: false,
CreateBatchSize: 0,
ClauseBuilders: nil,
ConnPool: nil,
Dialector: nil,
Plugins: nil,
}); e != nil {
panic("failed to connect database")
}
_ = DB.Use(dbresolver.Register(dbresolver.Config{
Replicas: []gorm.Dialector{mysql.Open(dsn)},
Policy: dbresolver.RandomPolicy{},
}).Register(dbresolver.Config{
Replicas: []gorm.Dialector{mysql.Open(dsn)},
} /* &model.User{} */))
sqlDB, _ := DB.DB()
// SetMaxIdleConns 用于设置连接池中空闲连接的最大数量。
sqlDB.SetMaxIdleConns(20)
// SetMaxOpenConns 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour * 24)
// log.InfoF("gorm metrics at: %d", viper.GetUint32("db.metrics.port"))
// _ = DB.Use(prometheus.New(prometheus.Config{
// DBName: "db_data-front", // 使用 `DBName` 作为指标 label
// RefreshInterval: 10, // 指标刷新频率(默认为 15 秒)
// PushAddr: "", // 如果配置了 `PushAddr`,则推送指标
// StartServer: false, // 启用一个 http 服务来暴露指标
// //HTTPServerPort: viper.GetUint32("db.metrics.port"), // 配置 http 服务监听端口,默认端口为 8080 (如果您配置了多个,只有第一个 `HTTPServerPort` 会被使用)
// MetricsCollector: []prometheus.MetricsCollector{
// &prometheus.MySQL{
// // 指标名前缀,默认为 `gorm_status_`
// // 例如: Threads_running 的指标名就是 `gorm_status_Threads_running`
// Prefix: "gorm_status_",
// // 拉取频率,默认使用 Prometheus 的 RefreshInterval
// Interval: 30,
// // 从 SHOW STATUS 选择变量变量,如果不设置,则使用全部的状态变量
// VariableNames: []string{"Threads_running"},
// },
// }, // 用户自定义指标
// }))
//_ = DB.Use(tracing.NewPlugin(tracing.WithoutMetrics()))
autoMigrate()
repo.SetDefault(DB)
}
func autoMigrate() {
//_ = DB.AutoMigrate(&model.User{})
//_ = DB.AutoMigrate(&model.LeaseType{})
}

144
pkg/helper/AliAudio.go Normal file
View File

@ -0,0 +1,144 @@
package helper
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/spf13/viper"
)
func TransAudioToText(fileUrl string) (result string, err error) {
// 地域ID固定值。
const REGION_ID string = "cn-shanghai"
const ENDPOINT_NAME string = "cn-shanghai"
const PRODUCT string = "nls-filetrans"
const DOMAIN string = "filetrans.cn-shanghai.aliyuncs.com"
const API_VERSION string = "2018-08-17"
const POST_REQUEST_ACTION string = "SubmitTask"
const GET_REQUEST_ACTION string = "GetTaskResult"
// 请求参数
const KEY_APP_KEY string = "appkey" //此处appkey无需替换。
const KEY_FILE_LINK string = "file_link"
const KEY_VERSION string = "version"
const KEY_ENABLE_WORDS string = "enable_words"
// 响应参数
const KEY_TASK string = "Task"
const KEY_TASK_ID string = "TaskId"
const KEY_STATUS_TEXT string = "StatusText"
const KEY_RESULT string = "Result"
// 状态值
const STATUS_SUCCESS string = "SUCCESS"
const STATUS_RUNNING string = "RUNNING"
const STATUS_QUEUEING string = "QUEUEING"
var accessKeyId string = viper.GetString("aliyun.audio.accessKeyId")
var accessKeySecret string = viper.GetString("aliyun.audio.accessKeySecret")
var appKey string = viper.GetString("aliyun.audio.appKey")
var fileLink string = fileUrl
c := sdk.NewConfig()
credential := credentials.NewAccessKeyCredential(accessKeyId, accessKeySecret)
client, err := sdk.NewClientWithOptions(REGION_ID, c, credential)
if err != nil {
panic(err)
}
postRequest := requests.NewCommonRequest()
postRequest.Domain = DOMAIN
postRequest.Version = API_VERSION
postRequest.Product = PRODUCT
postRequest.ApiName = POST_REQUEST_ACTION
postRequest.Method = "POST"
mapTask := make(map[string]string)
mapTask[KEY_APP_KEY] = appKey
mapTask[KEY_FILE_LINK] = fileLink
// 新接入请使用4.0版本已接入默认2.0)如需维持现状,请注释掉该参数设置。
mapTask[KEY_VERSION] = "4.0"
// 设置是否输出词信息默认为false。开启时需要设置version为4.0。
mapTask[KEY_ENABLE_WORDS] = "false"
mapTask["enable_sample_rate_adaptive"] = "true" //大于
task, err := json.Marshal(mapTask)
if err != nil {
panic(err)
}
postRequest.FormParams[KEY_TASK] = string(task)
postResponse, err := client.ProcessCommonRequest(postRequest)
if err != nil {
panic(err)
}
postResponseContent := postResponse.GetHttpContentString()
fmt.Println(postResponseContent)
if postResponse.GetHttpStatus() != 200 {
fmt.Println("录音文件识别请求失败Http错误码: ", postResponse.GetHttpStatus())
return
}
var postMapResult map[string]interface{}
err = json.Unmarshal([]byte(postResponseContent), &postMapResult)
if err != nil {
panic(err)
}
var taskId string = ""
var statusText string = ""
statusText = postMapResult[KEY_STATUS_TEXT].(string)
if statusText == STATUS_SUCCESS {
fmt.Println("录音文件识别请求成功响应!")
taskId = postMapResult[KEY_TASK_ID].(string)
} else {
fmt.Println("录音文件识别请求失败!")
err = errors.New("录音文件识别请求失败")
return
}
getRequest := requests.NewCommonRequest()
getRequest.Domain = DOMAIN
getRequest.Version = API_VERSION
getRequest.Product = PRODUCT
getRequest.ApiName = GET_REQUEST_ACTION
getRequest.Method = "GET"
getRequest.QueryParams[KEY_TASK_ID] = taskId
statusText = ""
for {
getResponse, err := client.ProcessCommonRequest(getRequest)
if err != nil {
panic(err)
}
getResponseContent := getResponse.GetHttpContentString()
fmt.Println("识别查询结果:", getResponseContent)
if getResponse.GetHttpStatus() != 200 {
fmt.Println("识别结果查询请求失败Http错误码", getResponse.GetHttpStatus())
break
}
var getMapResult map[string]interface{}
err = json.Unmarshal([]byte(getResponseContent), &getMapResult)
if err != nil {
panic(err)
}
statusText = getMapResult[KEY_STATUS_TEXT].(string)
if statusText == STATUS_RUNNING || statusText == STATUS_QUEUEING {
time.Sleep(10 * time.Second)
} else if statusText == STATUS_SUCCESS {
var resResult = getMapResult[KEY_RESULT].(map[string]interface{})
fmt.Println("result", resResult["Sentences"].([]interface{}))
ss := resResult["Sentences"].([]interface{})
for _, seg := range ss {
item := seg.(map[string]interface{})
result += item["Text"].(string)
}
break
} else {
break
}
}
if statusText == STATUS_SUCCESS {
fmt.Println("录音文件识别成功!", result)
} else {
fmt.Println("录音文件识别失败!")
}
return
}

View File

@ -0,0 +1,11 @@
package helper
import (
"fmt"
"testing"
)
func TestTransAudioToText(t *testing.T) {
gotResult, err := TransAudioToText("https://check.dongdongdianjing.com/videos/20250606/d69960523fe2756076f1f8d7c37fa742.mp4")
fmt.Sprintf("%+v %+v", gotResult, err)
}

156
pkg/helper/Helper.go Normal file
View File

@ -0,0 +1,156 @@
package helper
import (
"crypto/md5"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"sort"
"strconv"
"strings"
"time"
"github.com/spf13/viper"
)
// 生成header
func GenerateApiHeader(jsonBody string) map[string]string {
sign := GenerateSign(jsonBody)
headerMap := getCommonHeaderMap()
headerMap["Signature"] = sign
return headerMap
}
// 根据body生成签名
func GenerateSign(jsonBody string) string {
var mapRequest map[string]string
err := json.Unmarshal([]byte(jsonBody), &mapRequest)
if err != nil {
return ""
}
commonHeaderMap := getCommonHeaderMap()
for k, v := range commonHeaderMap {
mapRequest[k] = v
}
sign := createSign(mapRequest)
return sign
}
// 获取公共的header字段
func getCommonHeaderMap() map[string]string {
headerMap := make(map[string]string, 0)
headerMap["ClientVersion"] = "1.0.1"
headerMap["DeviceId"] = "MWEB"
headerMap["Platform"] = "H5"
headerMap["MarketChannel"] = ""
headerMap["DeviceModel"] = "MWEB"
headerMap["ApiVersionNum"] = viper.GetString("api-num")
headerMap["TimeStamp"] = strconv.Itoa(time.Now().Second())
headerMap["BundleId"] = "meetalkH5"
headerMap["OsVersion"] = "10.01"
headerMap["INNER-TOKEN"] = "b028c52286c95de48a1c773d7ed02d04"
return headerMap
}
func createSign(params map[string]string) string {
keys := make([]string, 20)
for key, _ := range params {
if key == "Signature" {
continue
}
keys = append(keys, key)
}
//按key升序
sort.Strings(keys)
//把KEY值合并为字符串
waitSignString := ""
for _, value := range keys {
waitSignString += base64.StdEncoding.EncodeToString([]byte(params[value]))
}
//加上密钥
appSecret := "x63363eacf804b4394a120aea240fd9a"
waitSignString += appSecret
//sha1加密
t := sha1.New()
io.WriteString(t, waitSignString)
sign := t.Sum(nil)
return hex.EncodeToString(sign)
}
// 复杂http请求
func HttpDo(httpMethod string, url string, headerMap map[string]string, rawBody string) (remoteResponse string, err error) {
client := &http.Client{}
req, err0 := http.NewRequest(httpMethod, url, strings.NewReader(rawBody))
if err0 != nil {
err = err0
return
}
if len(headerMap) > 0 {
for k, v := range headerMap {
req.Header.Set(k, v)
}
}
resp, err1 := client.Do(req)
if err1 != nil {
err = err1
return
}
defer resp.Body.Close()
body, err2 := ioutil.ReadAll(resp.Body)
if err2 != nil {
err = err2
return
}
remoteResponse = string(body)
return
}
func StringToMD5(waitMD5string string) string {
h := md5.New()
h.Write([]byte(waitMD5string))
cipherStr := h.Sum(nil)
result := hex.EncodeToString(cipherStr)
return result
}
// SHA1加密
func StringToSHA1(waitMD5string string) string {
h := sha1.New()
h.Write([]byte(waitMD5string))
cipherStr := h.Sum(nil)
result := hex.EncodeToString(cipherStr)
return result
}

68
pkg/helper/NetHelper.go Normal file
View File

@ -0,0 +1,68 @@
package helper
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"time"
"github.com/spf13/viper"
)
func Url(url string) string {
return viper.GetString("service.host.api") + url
}
// 发送GET请求
// url:请求地址
// response:请求返回的内容
func Get(url string) (response string) {
client := http.Client{Timeout: 30 * time.Second}
resp, e := client.Get(url)
if e != nil {
panic(e)
}
defer resp.Body.Close()
var buffer [512]byte
result := bytes.NewBuffer(nil)
for {
n, err := resp.Body.Read(buffer[0:])
result.Write(buffer[0:n])
if err != nil && err == io.EOF {
break
} else if err != nil {
panic(err)
}
}
response = result.String()
return
}
// 发送POST请求
// url:请求地址data:POST请求提交的数据,contentType:请求体格式application/json
// response:请求返回的内容
func Post(url string, data interface{}, header map[string]string) (response string) {
jsonStr, _ := json.Marshal(data)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
if err != nil {
panic(err)
}
req.Header.Add("content-type", "application/json")
for key := range header {
req.Header.Add(key, header[key])
}
defer req.Body.Close()
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
result, _ := ioutil.ReadAll(resp.Body)
response = string(result)
return
}

371
pkg/helper/NumberHelper.go Normal file
View File

@ -0,0 +1,371 @@
package helper
import (
"encoding/json"
"github.com/anxpp/beego/logs"
"strconv"
"strings"
)
type NumberInfo struct {
Level int
Rule map[string]bool
}
// 计算号码的登记
func Calc(src int64) NumberInfo {
level := 0
rule := make(map[string]bool)
number := strconv.FormatInt(src, 10)
length := len(number)
// 尾号AAA
checkEndAAA := length >= 3 && number[length-1] == number[length-2] && number[length-2] == number[length-3]
if checkEndAAA {
level++
}
rule["EndAAA"] = checkEndAAA
// 尾号ABAB
checkABAB := length >= 4 && number[length-4] == number[length-2] && number[length-3] == number[length-1]
if checkABAB {
level++
}
rule["EndABAB"] = checkABAB
// 尾号AABB
checkAABB := length >= 4 && number[length-4] == number[length-3] && number[length-2] == number[length-1]
if checkAABB {
level++
}
rule["EndAABB"] = checkAABB
// 520
check520 := strings.HasSuffix(number, "520")
if check520 {
level++
}
rule["520"] = check520
// 1314
check1314 := strings.HasSuffix(number, "1314")
if check1314 {
level++
}
rule["1314"] = check1314
// 5201314
check5201314 := strings.HasSuffix(number, "5201314")
if check5201314 {
level++
}
rule["5201314"] = check5201314
// AAA
checkAAA := _checkRepeat(number, 3)
if checkAAA {
level++
}
rule["AAA"] = checkAAA
// AAAA
checkAAAA := _checkRepeat(number, 4)
if checkAAAA {
level++
}
rule["AAAA"] = checkAAAA
// AAAAA
checkAAAAA := _checkRepeat(number, 5)
if checkAAAAA {
level++
level++
}
rule["AAAAA"] = checkAAAAA
// AAAAAA
checkAAAAAA := _checkRepeat(number, 6)
if checkAAAAAA {
level++
}
rule["AAAAAA"] = checkAAAAAA
// AAAAAAA
checkAAAAAAA := _checkRepeat(number, 7)
if checkAAAAAAA {
level++
}
rule["AAAAAAA"] = checkAAAAAAA
// AAAAAAA
checkAAAAAAAA := _checkRepeat(number, 8)
if checkAAAAAAAA {
level++
}
rule["AAAAAAAA"] = checkAAAAAAAA
// ABCABC
checkABCABC := _checkABCABC(number)
if checkABCABC {
level++
}
rule["ABCABC"] = checkABCABC
// AABBCC
checkAABBCC := _checkAABBCC(number)
if checkAABBCC {
level++
}
rule["AABBCC"] = checkAABBCC
// ABABAB
checkABABAB := _checkABABAB(number)
if checkABABAB {
level++
}
rule["ABABAB"] = checkABABAB
// AAABBB
checkAAABBB := _checkAAABBB(number)
if checkAAABBB {
level++
}
rule["AAABBB"] = checkAAABBB
// ABBABB
checkABBABB := _checkABBABB(number)
if checkABBABB {
level++
}
rule["ABBABB"] = checkABBABB
// ABBCBB
checkABBCBB := _checkABBCBB(number)
if checkABBCBB {
level++
}
rule["ABBCBB"] = checkABBCBB
// BBCBBA
checkBBCBBA := _checkBBCBBA(number)
if checkBBCBBA {
level++
}
rule["BBCBBA"] = checkBBCBBA
// AABAAB
checkAABAAB := _checkAABAAB(number)
if checkAABAAB {
level++
}
rule["AABAAB"] = checkAABAAB
// 102030
check102030 := _check102030(number)
if check102030 {
level++
}
rule["102030"] = check102030
// 010203
check010203 := _check010203(number)
if check010203 {
level++
}
rule["010203"] = check010203
// AABBB
checkAABBB := _checkAABBB(number)
if checkAABBB {
level++
}
rule["AABBB"] = checkAABBB
// 123
check123 := _checkIncrease(number, 3)
if check123 {
level++
}
rule["123"] = check123
// 123
check1234 := _checkIncrease(number, 4)
if check1234 {
level++
}
rule["1234"] = check1234
// 123
check12345 := _checkIncrease(number, 5)
if check12345 {
level++
}
rule["12345"] = check12345
// 123
check123456 := _checkIncrease(number, 6)
if check123456 {
level++
}
rule["123456"] = check123456
// 123
check1234567 := _checkIncrease(number, 7)
if check1234567 {
level++
}
rule["1234567"] = check1234567
info := NumberInfo{level, rule}
bytes, _ := json.Marshal(info)
if src%100000 == 0 {
logs.Info(number + ": " + string(bytes))
}
return info
}
func _checkRepeat(number string, length int) bool {
count := 1
old := ' '
for _, ch := range number {
if ch == old {
count++
if count == length {
return true
}
continue
}
old = ch
count = 1
}
return false
}
func _checkIncrease(number string, length int) bool {
count := 0
old := ' '
for _, ch := range number {
if count <= 0 {
count++
if count == length {
return true
}
old = ch
continue
}
if ch == old+1 {
count++
if count == length {
return true
}
old = ch
continue
}
old = ch
count = 0
}
return false
}
func _checkABCABC(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-3] == number[i] && number[i-2] == number[i+1] && number[i-1] == number[i+2] {
return true
}
}
}
return false
}
func _checkABBABB(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-3] == number[i] && number[i-2] == number[i+1] && number[i-1] == number[i+2] && number[i-2] == number[i-1] {
return true
}
}
}
return false
}
func _checkABBCBB(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-2] == number[i-1] && number[i-1] == number[i+1] && number[i+1] == number[i+2] {
return true
}
}
}
return false
}
func _checkBBCBBA(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-3] == number[i-2] && number[i-2] == number[i] && number[i] == number[i+1] {
return true
}
}
}
return false
}
func _checkAABAAB(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-3] == number[i] && number[i-3] == number[i-2] && number[i-2] == number[i+1] && number[i-1] == number[i+2] {
return true
}
}
}
return false
}
func _check102030(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-2] == number[i] && number[i] == number[i+2] && number[i-3]-number[i-1] == number[i-1]-number[i+1] {
return true
}
}
}
return false
}
func _check010203(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-2]-number[i] == number[i]-number[i+2] && number[i-3] == number[i-1] && number[i-1] == number[i+1] {
return true
}
}
}
return false
}
func _checkAABBCC(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-3] == number[i-2] && number[i-1] == number[i] && number[i+1] == number[i+2] {
return true
}
}
}
return false
}
func _checkABABAB(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-3] == number[i-1] && number[i-1] == number[i+1] && number[i-2] == number[i] && number[i] == number[i+2] {
return true
}
}
}
return false
}
func _checkAAABBB(number string) bool {
length := len(number)
if length >= 6 {
for i := 3; i < length-2; i++ {
if number[i-3] == number[i-2] && number[i-2] == number[i-1] && number[i] == number[i+1] && number[i+1] == number[i+2] {
return true
}
}
}
return false
}
func _checkAABBB(number string) bool {
length := len(number)
if length >= 5 {
for i := 3; i < length-2; i++ {
if number[i-3] == number[i-2] && number[i-1] == number[i] && number[i] == number[i+1] {
return true
}
}
}
return false
}

85
pkg/helper/QiNiuHelper.go Normal file
View File

@ -0,0 +1,85 @@
package helper
import (
"encoding/base64"
"fmt"
"strings"
"github.com/anxpp/beego/logs"
"github.com/qiniu/go-sdk/v7/auth"
"github.com/qiniu/go-sdk/v7/storage"
"github.com/spf13/viper"
)
//{"id":"z0.0A2234420A3600F3365DD1FA282B440D","pipeline":"1381904061.XZVideoToImagesQueue","code":0,
// "desc":"The fop was completed successfully","reqid":"Ry0AAMB45er_HtgV","inputBucket":"media","inputKey":"shot/xingzuan_appstore_video.mp4_000001.jpg",
// "items":[{"cmd":"animate/duration/5/merge/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDAyLmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDAzLmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDA0LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDA1LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDA2LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDA3LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDA4LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDA5LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDEwLmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDExLmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDEyLmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDEzLmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDE0LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDE1LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDE2LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDE3LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDE4LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDE5LmpwZw==/key/c2hvdC94aW5nenVhbl9hcHBzdG9yZV92aWRlby5tcDRfMDAwMDIwLmpwZw==/effect/0",
// "code":0,"desc":"The fop was completed successfully","hash":"lmW7N4-x40al165ufZj2SJGbut6L","key":"-KqubldLD_opMcwnpcOJ2FOml7M=/Ftq7oAVtIsQ-hOdz1amflU4f0mxO","returnOld":0}]}
type QiNiuVideoToImagesParam struct {
Id string `json:"id"`
Pipeline string `json:"pipeline"`
Code int `json:"code"`
Desc string `json:"desc"`
Reqid string `json:"reqid"`
InputBucket string `json:"inputBucket"`
InputKey string `json:"inputKey"`
Items []QiNiuVideoToImagesParamItem `json:"items"`
}
type QiNiuVideoToImagesParamItem struct {
Cmd string `json:"cmd"`
Code int `json:"code"`
Desc string `json:"desc"`
Keys []string `json:"keys"`
Key string `json:"key"`
Hash string `json:"hash"`
ReturnOld int `json:"returnOld"`
}
// 获取视频抽帧的token
// 第2秒开始每秒10张抽20张
func QiNiuVideoToImages(key string) (persistentId string) {
mac := auth.New(viper.GetString("qiniu.key"), viper.GetString("qiniu.secret"))
cfg := storage.Config{UseHTTPS: true}
operationManager := storage.NewOperationManager(mac, &cfg)
saveBucket := "dddjmedia"
// 处理指令集合
// vsample/<Format>/ss/<StartTime>/t/<Duration>/s/<Resolution>/rotate/<Degree>/interval/<Interval>/pattern/<Pattern>
// /ss/<StartTime> 是 指定截取视频的开始时刻,单位:秒,精度为 100ms。例如 /ss/1.1
// /t/<Duration> 是 采样总时长,单位:秒,精度为 100ms。例如 t/1.1
// /s/<Resolution> 缩略图分辨率单位像素px格式<Width>x<Height>宽度取值范围为1-1920高度取值范围为1-1080。 默认为原始视频分辨率。
// /rotate/<Degree> 指定顺时针旋转的度数可取值为90、180、270、auto。 默认为不旋转。
// /interval/<Interval> 指定采样间隔,单元:秒。 默认为5秒,精度为 100ms例如/interval/0.1
// /pattern/<Pattern> 是 指定各张截图的资源名格式,支持如下魔法变量: $(count) :六个占位符的数字串,不足位的填充前导零即%06d如 000001
fopVSample := fmt.Sprintf("vsample/png/ss/2/t/2/interval/0.1/pattern/%s", base64.URLEncoding.EncodeToString([]byte("shot/"+key+"_$(count).jpg")))
fopBatch := []string{fopVSample}
fops := strings.Join(fopBatch, ";")
persistentId, err := operationManager.Pfop(saveBucket, key, fops, "FYVideoToImagesQueue", viper.GetString("open.notify.qiniuVideoShot"), true)
if err != nil {
logs.Error(err.Error(), " ", persistentId, "", key)
}
return persistentId
}
func QiNiuImagesToGif(keys []string) (persistentId string) {
mac := auth.New(viper.GetString("qiniu.key"), viper.GetString("qiniu.secret"))
cfg := storage.Config{UseHTTPS: true}
operationManager := storage.NewOperationManager(mac, &cfg)
saveBucket := "dddjmedia"
an := "animate/duration/5/merge"
for i := 1; i < len(keys); i++ {
an += "/key/" + base64.URLEncoding.EncodeToString([]byte(keys[i]))
}
an += "/effect/0"
logs.Info("result of img merge", an)
fopBatch := []string{an}
fops := strings.Join(fopBatch, ";")
persistentId, err := operationManager.Pfop(saveBucket, keys[0], fops, "FYVideoToImagesQueue", viper.GetString("open.notify.qiniuImgMergeShot"), true)
if err != nil {
fmt.Println(err)
logs.Error(err.Error())
}
return persistentId
}

13
pkg/helper/WxHelper.go Normal file
View File

@ -0,0 +1,13 @@
package helper
import "strings"
//是否使用微信打开
func IsWeiXinBrowser(httpUserAgent string) bool {
wxAgentKey := "MicroMessenger"
if strings.Contains(httpUserAgent, wxAgentKey) {
return true
}
return false
}

View File

@ -0,0 +1,23 @@
package helper
import (
"crypto/md5"
"encoding/hex"
"math/rand"
"strconv"
"time"
)
// 获取随机盐值
func PasswordSalt() string {
randInt := rand.New(rand.NewSource(time.Now().UnixNano()))
return strconv.Itoa(randInt.Int() % 10000)
}
// 计算加密后的密码
func PasswordWithMd5(password, salt string) string {
h := md5.New()
h.Write([]byte(password + salt))
cipherStr := h.Sum(nil)
return hex.EncodeToString(cipherStr)
}

144
pkg/helper/string_util.go Normal file
View File

@ -0,0 +1,144 @@
package helper
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
const (
PhotoDomainUrl = "https://photo-app.ddegame.cn/"
TimeLayout = "2006-01-02 15:04:05"
DateLayout = "2006-01-02"
DateLayoutShort = "20060102"
)
func FullPhotoUrl(path string) string {
if len(path) == 0 {
return ""
}
if strings.Index(path, "http") == 0 {
return path
}
return PhotoDomainUrl + path
}
func Md5ForList(s ...string) string {
sort.Strings(s)
h := md5.New()
h.Write([]byte(strings.Join(s, "")))
return hex.EncodeToString(h.Sum(nil))
}
func StringToInt(source string) int {
target, _ := strconv.Atoi(source)
return target
}
func StringToInt64(source string) int64 {
target, _ := strconv.Atoi(source)
return int64(target)
}
func StringToFloat(source string) float64 {
target, _ := strconv.ParseFloat(source, 64)
return target
}
func StringIntPlus(s1, s2 string) string {
return strconv.Itoa(StringToInt(s1) + StringToInt(s2))
}
func StringIntMultiply(s1, s2 string) string {
return strconv.Itoa(StringToInt(s1) * StringToInt(s2))
}
func StringIntSub(s1, s2 string) string {
return strconv.Itoa(StringToInt(s1) - StringToInt(s2))
}
func StringFloatSub(s1, s2 string) string {
return strconv.FormatFloat(StringToFloat(s1)-StringToFloat(s2), 'f', -1, 64)
}
func ListToSet(list []string) (set []string) {
m := make(map[string]bool, 0)
for i := range list {
m[list[i]] = true
}
for k := range m {
set = append(set, k)
}
return
}
func ToJson(source interface{}) []byte {
b, _ := json.Marshal(source)
return b
}
func NowTimeToString() string {
return time.Now().Format(TimeLayout)
}
func NowTimeIncreaseString(t time.Duration) string {
now := time.Now()
now.Add(t)
return now.Format(TimeLayout)
}
func NowDateToString() string {
return time.Now().Format(DateLayout)
}
func NowDateToStringShort() string {
return time.Now().Format(DateLayoutShort)
}
func Md5(s string) string {
h := md5.New()
h.Write([]byte(s))
return hex.EncodeToString(h.Sum(nil))
}
func ZeroFillByStr(str string, resultLen int, reverse bool) string {
if len(str) > resultLen || resultLen <= 0 {
return str
}
if reverse {
return fmt.Sprintf("%0*s", resultLen, str) //不足前置补零
}
result := str
for i := 0; i < resultLen-len(str); i++ {
result += "0"
}
return result
}
func IsMobile(no string) bool {
return regexp.MustCompile("^1[3456789]\\d{9}$").MatchString(no)
}
func IsEmail(email string) bool {
pattern := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$`
return regexp.MustCompile(pattern).MatchString(email)
}
func LegalUsername(source string) bool {
return regexp.MustCompile("^\\D[\\w-]{5,17}$").MatchString(source)
}
func LegalShortUsername(source string) bool {
return regexp.MustCompile("^[a-zA-Z][\\w-]{2,17}$").MatchString(source)
}
func LegalPassword(source string) bool {
return regexp.MustCompile("^[a-zA-Z0-9,.!@#$%^&*()_-]{6,18}$").MatchString(source)
}

71
pkg/htools/HttpClient.go Normal file
View File

@ -0,0 +1,71 @@
package htools
import (
"io/ioutil"
"net/http"
"strings"
)
// 调用POST请求
func HttpPost(url string, body string) (remoteResponse string, err error) {
bodyReader := strings.NewReader(body)
//application/x-www-form-urlencoded
//application/json
response, err1 := http.Post(url, "application/x-www-form-urlencoded", bodyReader)
if err1 != nil {
err = err1
return
}
defer response.Body.Close()
resBody, err2 := ioutil.ReadAll(response.Body)
if err2 != nil {
err = err2
return
}
remoteResponse = string(resBody)
return
}
// 复杂http请求
func HttpDo(httpMethod string, url string, headerMap map[string]string, rawBody string) (remoteResponse string, err error) {
client := &http.Client{}
req, err0 := http.NewRequest(httpMethod, url, strings.NewReader(rawBody))
if err0 != nil {
err = err0
return
}
if len(headerMap) > 0 {
for k, v := range headerMap {
req.Header.Set(k, v)
}
}
resp, err1 := client.Do(req)
if err1 != nil {
err = err1
return
}
defer resp.Body.Close()
body, err2 := ioutil.ReadAll(resp.Body)
if err2 != nil {
err = err2
return
}
remoteResponse = string(body)
return
}

47
pkg/htools/Random.go Normal file
View File

@ -0,0 +1,47 @@
package htools
import (
"math/rand"
"time"
)
func GetVericode() string {
result := RandNumber()
return result
}
func RandInt64(min, max int64) int64 {
if min >= max || min == 0 || max == 0 {
return max
}
return rand.Int63n(max-min) + min
}
func RandNumber() string {
str := "0123456789"
bytes := []byte(str)
result := []byte{}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 4; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return string(result)
}
// 生成随机字符串
func GetRandomString(l int) string {
str := "0123456789abcdefghijklmnopqrstuvwxyz"
bytes := []byte(str)
result := []byte{}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < l; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return string(result)
}

146
pkg/htools/RsaUtils.go Normal file
View File

@ -0,0 +1,146 @@
package htools
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
)
// RSA加密
func RsaEncrypt(origData string, publicKey string) (string, error) {
block, _ := pem.Decode([]byte(publicKey)) // 将密钥解析成公钥实例
if block == nil {
return "", errors.New("public key error")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) // 解析pem.Decode返回的Block指针实例
if err != nil {
return "", err
}
pub := pubInterface.(*rsa.PublicKey)
partLen := pub.N.BitLen()/8 - 11
chunks := ByteSplit([]byte(origData), partLen)
buffer := bytes.NewBufferString("")
for _, chunk := range chunks {
bytes, err := rsa.EncryptPKCS1v15(rand.Reader, pub, chunk)
if err != nil {
return "", err
}
buffer.Write(bytes)
}
return base64.StdEncoding.EncodeToString(buffer.Bytes()), nil
}
// RSA解密
func RsaDecrypt(ciphertext string, privateKey string) (string, error) {
block, _ := pem.Decode([]byte(privateKey))
if block == nil {
return "", errors.New("private key error!")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", err
}
partLen := priv.N.BitLen() / 8
raw, err := base64.StdEncoding.DecodeString(ciphertext)
chunks := ByteSplit([]byte(raw), partLen)
buffer := bytes.NewBufferString("")
for _, chunk := range chunks {
decrypted, err := rsa.DecryptPKCS1v15(rand.Reader, priv, chunk)
if err != nil {
return "", err
}
buffer.Write(decrypted)
}
return buffer.String(), err
}
// RSA SHA1加签
func RsaSHA1Sign(data string, privateKey string) (string, error) {
block, _ := pem.Decode([]byte(privateKey))
if block == nil {
return "", errors.New("Sign private key decode error")
}
prk8, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", err
}
h := sha1.New()
h.Write([]byte(data))
hashed := h.Sum(nil)
sign, err := rsa.SignPKCS1v15(rand.Reader, prk8, crypto.SHA1, hashed)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(sign), err
}
// RSA SHA1验签
func RsaSHA1Verify(data string, sign string, publicKey string) error {
h := sha1.New()
h.Write([]byte(data))
hashed := h.Sum(nil)
decodedSign, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
return err
}
block, _ := pem.Decode([]byte(publicKey))
if block == nil {
return errors.New("Sign public key decode error")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) // 解析pem.Decode返回的Block指针实例
if err != nil {
return err
}
pub := pubInterface.(*rsa.PublicKey)
return rsa.VerifyPKCS1v15(pub, crypto.SHA1, hashed, decodedSign)
}

View File

@ -0,0 +1,23 @@
package htools
import (
"bytes"
"fmt"
)
type StringBuilder struct {
buf bytes.Buffer
}
func NewStringBuilder() *StringBuilder {
return &StringBuilder{buf: bytes.Buffer{}}
}
func (this *StringBuilder) Append(obj interface{}) *StringBuilder {
this.buf.WriteString(fmt.Sprintf("%v", obj))
return this
}
func (this *StringBuilder) ToString() string {
return this.buf.String()
}

335
pkg/htools/Tools.go Normal file
View File

@ -0,0 +1,335 @@
package htools
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/rand"
"reflect"
"sort"
"strconv"
"strings"
"time"
)
// MD5加密
func StringToMD5(waitMD5string string) string {
h := md5.New()
h.Write([]byte(waitMD5string))
cipherStr := h.Sum(nil)
result := hex.EncodeToString(cipherStr)
return result
}
// SHA1加密
func StringToSHA1(waitMD5string string) string {
h := sha1.New()
h.Write([]byte(waitMD5string))
cipherStr := h.Sum(nil)
result := hex.EncodeToString(cipherStr)
return result
}
// HMAC SHA1
func StringToHmacSha1(waitShaString, keyStr string) string {
key := []byte(keyStr)
mac := hmac.New(sha1.New, key)
mac.Write([]byte(waitShaString))
result := hex.EncodeToString(mac.Sum(nil))
return result
}
// 字符串转int64
func StringToInt64(waitString string) int64 {
stringInt64, err := strconv.ParseInt(waitString, 10, 64)
if err != nil {
return 0
}
return stringInt64
}
// 字符串转int32
func StringToInt(waitString string) int {
if waitString == "" {
return 0
}
stringInt, err := strconv.Atoi(waitString)
if err != nil {
return 0
}
return stringInt
}
func StringNumPlus(v1, v2 string) string {
return strconv.Itoa(StringToInt(v1) + StringToInt(v2))
}
// 字符串转float64
func StringToFloat64(waitString string) float64 {
stringInt64, err := strconv.ParseFloat(waitString, 64)
if err != nil {
return 0
}
return stringInt64
}
// float64保留2位
func Float64Decimal(waitFloatValue float64) float64 {
value, err := strconv.ParseFloat(fmt.Sprintf("%.2f", waitFloatValue), 64)
if err != nil {
return 0
}
return value
}
// strconv.FormatFloat(float64, 'E', -1, 64)
// float64转字符串
func Float64ToString(waitFloat64 float64) string {
stringInt64 := strconv.FormatFloat(waitFloat64, 'f', -1, 64)
return stringInt64
}
// 截取字符串
func Substr(str string, start int, length int) string {
rs := []rune(str)
rl := len(rs)
end := 0
if start < 0 {
start = rl - 1 + start
}
end = start + length
if start > end {
start, end = end, start
}
if start < 0 {
start = 0
}
if start > rl {
start = rl
}
if end < 0 {
end = 0
}
if end > rl {
end = rl
}
return string(rs[start:end])
}
// map转html
func MapToXML(mapData map[string]string) string {
if len(mapData) == 0 {
return ""
}
sb := NewStringBuilder()
sb.Append("<xml>")
for key, val := range mapData {
sb.Append("<" + key + ">")
sb.Append(val)
sb.Append("</" + key + ">")
}
sb.Append("</xml>")
return sb.ToString()
}
// 获取当前年周
func GetCurrentYearWeek() string {
year, week := time.Now().ISOWeek()
return strconv.Itoa(year) + strconv.Itoa(week)
}
// 字符串转time类型
func StringToTime(dateString string) time.Time {
// 获取本地location
toBeCharge := dateString // 待转化为时间戳的字符串 注意 这里的小时和分钟还要秒必须写 因为是跟着模板走的 修改模板的话也可以不写
timeLayout := "2006-01-02 15:04:05" // 转化所需模板
loc, _ := time.LoadLocation("Local") // 重要:获取时区
theTime, _ := time.ParseInLocation(timeLayout, toBeCharge, loc) // 使用模板在对应时区转化为time.time类型
//sr := theTime.Unix() // 转化为时间戳 类型是int64
// 时间戳转日期
//dataTimeStr := time.Unix(sr, 0).Format(timeLayout) // 设置时间戳 使用模板格式化为日期字符串
return theTime
}
// 判断数组是否包含某个元素
func CheckStringIsInArray(arrayList []string, element string) bool {
if len(arrayList) == 0 {
return false
}
isInArray := false
for _, data := range arrayList {
if data == element {
isInArray = true
break
}
}
return isInArray
}
// slice to 字符串
func StringListToString(stringList []string, split string) string {
if len(split) == 0 {
split = ","
}
return strings.Replace(strings.Trim(fmt.Sprint(stringList), "[]"), " ", split, -1)
}
// map 转 url参数
func MapToUrlParams(mapData map[string]string) string {
mapLen := len(mapData)
if mapLen == 0 {
return ""
}
sb := NewStringBuilder()
i := 0
for k, v := range mapData {
sb.Append(k + "=" + v)
if i < mapLen-1 {
sb.Append("&")
}
i++
}
return sb.ToString()
}
// []byte 合并
func BytesCombine(pBytes ...[]byte) []byte {
return bytes.Join(pBytes, []byte(""))
}
// 获取当前UTC时间 秒数
func GetCurrentUtcTimeSecond() int64 {
return time.Now().UTC().Unix()
}
// byte数组分组
func ByteSplit(buf []byte, lim int) [][]byte {
var chunk []byte
chunks := make([][]byte, 0, len(buf)/lim+1)
for len(buf) >= lim {
chunk, buf = buf[:lim], buf[lim:]
chunks = append(chunks, chunk)
}
bufLen := len(buf)
if bufLen > 0 {
chunks = append(chunks, buf[:bufLen])
}
return chunks
}
// 强类型数组转为interface类型的数组
func ToSlice(arr interface{}) []interface{} {
v := reflect.ValueOf(arr)
if v.Kind() != reflect.Slice {
panic("toslice arr not slice")
}
l := v.Len()
ret := make([]interface{}, l)
for i := 0; i < l; i++ {
ret[i] = v.Index(i).Interface()
}
return ret
}
// 从字符串数组随机获取一个数组
func GetRandArrayFromArray(sourceArr []interface{}, resultCount int) (resultArr []interface{}) {
// 结果
resultArr = make([]interface{}, 0)
if len(sourceArr) < resultCount {
return
}
r1 := rand.New(rand.NewSource(time.Now().UnixNano()))
for _, k := range r1.Perm(len(sourceArr)) {
if len(resultArr) >= resultCount {
break
}
val := sourceArr[k]
resultArr = append(resultArr, val)
}
return
}
func RelationKey(userIds ...string) (key string) {
sort.Strings(userIds)
return strings.Join(userIds, "")
}
func ApplyKey(userIds ...string) (key string) {
return strings.Join(userIds, "")
}
func IPToUint32(ip string) (uint32, error) {
ips := strings.Split(ip, ".")
if len(ips) != 4 {
return 0, errors.New("ip error")
}
num := uint64(0)
for i, item := range ips {
n, _ := strconv.ParseUint(item, 10, 32)
num += n << (8 * uint(3-i))
}
return uint32(num), nil
}
func JsonStr(v interface{}) string {
b, _ := json.Marshal(v)
return string(b)
}

102
pkg/htools/UUID.go Normal file
View File

@ -0,0 +1,102 @@
package htools
import (
crand "crypto/rand"
"encoding/hex"
"errors"
"fmt"
mrand "math/rand"
"regexp"
"strings"
)
// seeded indicates if math/rand has been seeded
var seeded bool = false
// uuidRegex matches the UUID string
var uuidRegex *regexp.Regexp = regexp.MustCompile(`^\{?([a-fA-F0-9]{8})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{12})\}?$`)
// UUID type.
type UUID [16]byte
// Hex returns a hex string representation of the UUID in xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format.
func (this UUID) Hex() string {
x := [16]byte(this)
return fmt.Sprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
x[0], x[1], x[2], x[3], x[4],
x[5], x[6],
x[7], x[8],
x[9], x[10], x[11], x[12], x[13], x[14], x[15])
}
// Rand generates a new version 4 UUID.
func Rand() UUID {
var x [16]byte
randBytes(x[:])
x[6] = (x[6] & 0x0F) | 0x40
x[8] = (x[8] & 0x3F) | 0x80
return x
}
// FromStr returns a UUID based on a string.
// The string could be in the following format:
//
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
//
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
//
// If the string is not in one of these formats, it'll return an error.
func FromStr(s string) (id UUID, err error) {
if s == "" {
err = errors.New("Empty string")
return
}
parts := uuidRegex.FindStringSubmatch(s)
if parts == nil {
err = errors.New("Invalid string format")
return
}
var array [16]byte
slice, _ := hex.DecodeString(strings.Join(parts[1:], ""))
copy(array[:], slice)
id = array
return
}
// MustFromStr behaves similarly to FromStr except that it'll panic instead of
// returning an error.
func MustFromStr(s string) UUID {
id, err := FromStr(s)
if err != nil {
panic(err)
}
return id
}
// randBytes uses crypto random to get random numbers. If fails then it uses math random.
func randBytes(x []byte) {
length := len(x)
n, err := crand.Read(x)
if n != length || err != nil {
for length > 0 {
length--
x[length] = byte(mrand.Int31n(256))
}
}
}
func GetUUID() string {
var uuid UUID = Rand()
return uuid.Hex()
}

195
pkg/log/zap.go Normal file
View File

@ -0,0 +1,195 @@
package log
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/natefinch/lumberjack"
"github.com/spf13/viper"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gorm.io/gorm/logger"
)
var Log *zap.Logger
type ZapStarter struct {
}
func (s *ZapStarter) Init() error {
Init()
return nil
}
func Init() {
// 编码器
encoderConfig := zap.NewProductionEncoderConfig() // NewJSONEncoder()输出json格式NewConsoleEncoder()输出普通文本格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 指定时间格式
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 按级别显示不同颜色,不需要的话取值 CapitalLevelEncoder 就可以了
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder // 是否显示完整文件路径
encoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000")
env := viper.GetString("env")
//encoder := zapcore.NewJSONEncoder(encoderConfig)
encoder := zapcore.NewConsoleEncoder(encoderConfig) // todo
if env != "dev" {
fmt.Printf("zap log with %s env\n", env)
encoder = zapcore.NewConsoleEncoder(encoderConfig)
}
normalPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { // info 级别
return lev < zap.ErrorLevel && lev >= zap.InfoLevel
})
errorPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { // info 级别
return lev > zap.InfoLevel
})
normalWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: viper.GetString("log.normal.file"), // 日志文件存放目录
MaxSize: viper.GetInt("log.normal.size"), // 文件大小限制,单位MB
MaxBackups: viper.GetInt("log.normal.backups"), // 最大保留日志文件数量
MaxAge: viper.GetInt("log.normal.age"), // 日志文件保留天数
Compress: viper.GetBool("log.normal.compress"), // 是否压缩处理
LocalTime: true,
})
errorWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
Filename: viper.GetString("log.error.file"),
MaxSize: viper.GetInt("log.error.size"),
MaxBackups: viper.GetInt("log.error.backups"),
MaxAge: viper.GetInt("log.error.age"),
Compress: viper.GetBool("log.error.compress"),
LocalTime: true,
})
Log = zap.New(zapcore.NewTee(
[]zapcore.Core{
zapcore.NewCore(
encoder,
zapcore.NewMultiWriteSyncer(normalWriteSyncer, zapcore.AddSync(os.Stdout)),
normalPriority,
),
zapcore.NewCore(
encoder,
zapcore.NewMultiWriteSyncer(errorWriteSyncer, zapcore.AddSync(os.Stdout)),
errorPriority,
),
}...,
), zap.AddCaller(), zap.AddCallerSkip(1))
}
func traceID(ctx context.Context, fields ...zap.Field) []zap.Field {
sp := trace.SpanContextFromContext(ctx)
traceID := zap.String("trace_id", sp.TraceID().String())
fields = append(fields, traceID)
return fields
}
func InfoWithCtx(ctx context.Context, msg string, fields ...zap.Field) {
Info(msg, traceID(ctx, fields...)...)
}
func Info(msg string, fields ...zap.Field) {
if strings.Contains(msg, "SHOW STATUS") {
return
}
if strings.Contains(msg, "GET _health") {
return
}
if strings.Contains(msg, "GET url: /health") {
return
}
Log.Info(msg, fields...)
}
func WarnWithCtx(ctx context.Context, msg string, fields ...zap.Field) {
Warn(msg, traceID(ctx, fields...)...)
}
func Warn(msg string, fields ...zap.Field) {
Log.Warn(msg, fields...)
}
func ErrorWithCtx(ctx context.Context, msg string, fields ...zap.Field) {
Log.Error(msg, traceID(ctx, fields...)...)
}
func Error(msg string, fields ...zap.Field) {
Log.Error(msg, fields...)
}
func InfoFWithCtx(ctx context.Context, msg string, args ...interface{}) {
Log.Info(fmt.Sprintf(msg, args...), traceID(ctx)...)
}
func InfoF(msg string, args ...interface{}) {
msg = fmt.Sprintf(msg, args...)
Info(msg)
}
func WarnFWithCtx(ctx context.Context, msg string, args ...interface{}) {
Log.Warn(fmt.Sprintf(msg, args...), traceID(ctx)...)
}
func WarnF(msg string, args ...interface{}) {
Log.Warn(fmt.Sprintf(msg, args...))
}
func ErrorFWithCtx(ctx context.Context, msg string, args ...interface{}) {
Log.Warn(fmt.Sprintf(msg, args...), traceID(ctx)...)
}
func ErrorF(msg string, args ...interface{}) {
Log.Error(fmt.Sprintf(msg, args...))
}
type GLog struct {
}
func (g GLog) LogMode(_ logger.LogLevel) logger.Interface {
return &g
}
func (g GLog) Info(_ context.Context, msg string, args ...interface{}) {
Info(fmt.Sprintf(msg, args...))
}
func (g GLog) Warn(_ context.Context, msg string, args ...interface{}) {
Warn(fmt.Sprintf(msg, args...))
}
func (g GLog) Error(_ context.Context, msg string, args ...interface{}) {
Error(fmt.Sprintf(msg, args...))
}
func (g GLog) Trace(_ context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
sql, ra := fc()
if sql == "SHOW STATUS" {
return
}
Info("gorm callback trace", zap.String("sql", sql), zap.Int64("rows", ra), zap.Error(err))
}
func (g GLog) Printf(msg string, args ...interface{}) {
Info(fmt.Sprintf(msg, args...))
}
// CLog cron logger
type CLog struct {
}
func (c CLog) Info(msg string, keysAndValues ...interface{}) {
InfoF(msg, keysAndValues...)
}
// Error logs an error condition.
func (c CLog) Error(err error, msg string, keysAndValues ...interface{}) {
ErrorF(msg, keysAndValues...)
}
func (c CLog) Printf(msg string, args ...interface{}) {
InfoF(msg, args...)
}

View File

@ -0,0 +1,30 @@
package middleware
import (
"github.com/anxpp/common-utils/logg"
"github.com/anxpp/common-utils/net"
"github.com/gin-gonic/gin"
"net/http"
"net/url"
)
func Authorize() gin.HandlerFunc {
return func(c *gin.Context) {
inputToken := c.Request.Header.Get("X-Token")
if len(inputToken) == 0 {
c.JSON(http.StatusOK, net.Custom(500, "缺少授权token"))
c.Abort()
}
c.Next()
}
}
func LogRequest() gin.HandlerFunc {
return func(c *gin.Context) {
decodedStr, _ := url.QueryUnescape(c.Request.RequestURI)
logg.Info("request:", c.Request.RemoteAddr, decodedStr)
c.Next()
}
}

25
pkg/middleware/cors.go Normal file
View File

@ -0,0 +1,25 @@
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 处理跨域请求,支持options访问
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名
c.Header("Access-Control-Allow-Headers", "Content-Type,X-TOKEN") //你想放行的header也可以在后面自行添加
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") //我自己只使用 get post 所以只放行它
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
// 放行所有OPTIONS方法
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
// 处理请求
c.Next()
}
}

View File

@ -0,0 +1,35 @@
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
"runtime/debug"
)
func Recover(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
debug.PrintStack()
c.JSON(http.StatusOK, gin.H{
"code": 500,
"message": "系统异常",
"detailError": errorToString(r),
"data": nil,
})
c.Abort()
}
}()
c.Next()
}
func errorToString(r interface{}) string {
switch v := r.(type) {
case error:
return v.Error()
default:
return r.(string)
}
}

View File

@ -0,0 +1,38 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameAdminPrivilege = "admin_privilege"
// AdminPrivilege 租户-权限表
type AdminPrivilege struct {
ID string `gorm:"column:id;type:char(32);not null;uniqueIndex:id,priority:1;comment:业务ID" json:"id"` // 业务ID
TenantID string `gorm:"column:tenant_id;type:char(32);not null;default:system;comment:企业ID" json:"tenant_id"` // 企业ID
ScopeID string `gorm:"column:scope_id;type:char(32);not null;comment:范围" json:"scope_id"` // 范围
ParentID string `gorm:"column:parent_id;type:char(32);not null;comment:父ID" json:"parent_id"` // 父ID
Code string `gorm:"column:code;type:varchar(64);not null;comment:代码" json:"code"` // 代码
Name string `gorm:"column:name;type:varchar(64);not null;index:idx_name,priority:1;comment:权限名称" json:"name"` // 权限名称
Level int32 `gorm:"column:level;type:int;not null;comment:权限级别" json:"level"` // 权限级别
SourceType string `gorm:"column:source_type;type:varchar(64);not null;comment:类型resource、interface等" json:"source_type"` // 类型resource、interface等
TargetType string `gorm:"column:target_type;type:varchar(255);not null" json:"target_type"`
Target string `gorm:"column:target;type:varchar(255);not null;comment:权限值 resource:菜单路径; api=url-pre" json:"target"` // 权限值 resource:菜单路径; api=url-pre
Desc string `gorm:"column:desc;type:varchar(64);not null;comment:描述" json:"desc"` // 描述
DeleteAt *gorm.DeletedAt `gorm:"column:delete_at;type:datetime;index:idx_delete_at,priority:1;comment:del标志" json:"delete_at"` // del标志
CreateAt time.Time `gorm:"column:create_at;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"create_at"` // 创建时间
CreateBy string `gorm:"column:create_by;type:varchar(255);not null;comment:创建人" json:"create_by"` // 创建人
UpdateAt time.Time `gorm:"column:update_at;type:datetime;not null;index:idx_delete_at,priority:2;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_at"` // 更新时间
UpdateBy string `gorm:"column:update_by;type:varchar(255);not null;comment:更新人" json:"update_by"` // 更新人
}
// TableName AdminPrivilege's table name
func (*AdminPrivilege) TableName() string {
return TableNameAdminPrivilege
}

View File

@ -0,0 +1,33 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameAdminRole = "admin_role"
// AdminRole 租户-角色表
type AdminRole struct {
ID string `gorm:"column:id;type:char(32);not null;uniqueIndex:id,priority:1;comment:业务ID" json:"id"` // 业务ID
TenantID string `gorm:"column:tenant_id;type:char(32);not null;default:system;comment:企业ID" json:"tenant_id"` // 企业ID
ScopeID string `gorm:"column:scope_id;type:char(32);not null;comment:角色范围" json:"scope_id"` // 角色范围
Code string `gorm:"column:code;type:varchar(64);not null;comment:代码" json:"code"` // 代码
Name string `gorm:"column:name;type:varchar(64);not null;index:idx_name,priority:1" json:"name"`
Desc string `gorm:"column:desc;type:varchar(64);not null;comment:描述" json:"desc"` // 描述
DeleteAt *gorm.DeletedAt `gorm:"column:delete_at;type:datetime;index:idx_delete_at,priority:1;comment:del标志" json:"delete_at"` // del标志
CreateAt time.Time `gorm:"column:create_at;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"create_at"` // 创建时间
CreateBy string `gorm:"column:create_by;type:varchar(255);not null;comment:创建人" json:"create_by"` // 创建人
UpdateAt time.Time `gorm:"column:update_at;type:datetime;not null;index:idx_delete_at,priority:2;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_at"` // 更新时间
UpdateBy string `gorm:"column:update_by;type:varchar(255);not null;comment:更新人" json:"update_by"` // 更新人
}
// TableName AdminRole's table name
func (*AdminRole) TableName() string {
return TableNameAdminRole
}

View File

@ -0,0 +1,32 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameAdminRolePrivilege = "admin_role_privilege"
// AdminRolePrivilege 租户角色-权限表
type AdminRolePrivilege struct {
ID string `gorm:"column:id;type:char(32);not null;uniqueIndex:id,priority:1;comment:业务ID" json:"id"` // 业务ID
TenantID string `gorm:"column:tenant_id;type:char(32);not null;default:system;comment:企业ID" json:"tenant_id"` // 企业ID
ScopeID string `gorm:"column:scope_id;type:char(32);not null;comment:范围" json:"scope_id"` // 范围
RoleID string `gorm:"column:role_id;type:char(32);not null;uniqueIndex:idx_uk,priority:1;comment:角色ID" json:"role_id"` // 角色ID
PrivilegeID string `gorm:"column:privilege_id;type:char(32);not null;uniqueIndex:idx_uk,priority:2;comment:权限ID" json:"privilege_id"` // 权限ID
DeleteAt *gorm.DeletedAt `gorm:"column:delete_at;type:datetime;index:idx_delete_at,priority:1;comment:del标志" json:"delete_at"` // del标志
CreateAt time.Time `gorm:"column:create_at;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"create_at"` // 创建时间
CreateBy string `gorm:"column:create_by;type:varchar(255);not null;comment:创建人" json:"create_by"` // 创建人
UpdateAt time.Time `gorm:"column:update_at;type:datetime;not null;index:idx_delete_at,priority:2;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_at"` // 更新时间
UpdateBy string `gorm:"column:update_by;type:varchar(255);not null;comment:更新人" json:"update_by"` // 更新人
}
// TableName AdminRolePrivilege's table name
func (*AdminRolePrivilege) TableName() string {
return TableNameAdminRolePrivilege
}

View File

@ -0,0 +1,39 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameAdminUser = "admin_user"
// AdminUser 用户表
type AdminUser struct {
ID string `gorm:"column:id;type:char(32);not null;uniqueIndex:id,priority:1;comment:业务ID" json:"id"` // 业务ID
TenantID string `gorm:"column:tenant_id;type:char(32);not null;default:system;comment:企业ID" json:"tenant_id"` // 企业ID
Username string `gorm:"column:username;type:varchar(64);not null;uniqueIndex:idx_name,priority:1;comment:用户名" json:"username"` // 用户名
Channel string `gorm:"column:channel;type:varchar(64);not null;comment:渠道ADMIN=后台维护 LDAP=统一管理获取 REGISTER=注册" json:"channel"` // 渠道ADMIN=后台维护 LDAP=统一管理获取 REGISTER=注册
ChannelCode string `gorm:"column:channel_code;type:varchar(255);not null;default:auth-center.prod" json:"channel_code"`
Nickname string `gorm:"column:nickname;type:varchar(64);not null;comment:昵称" json:"nickname"` // 昵称
Desc string `gorm:"column:desc;type:varchar(255);not null" json:"desc"`
State int32 `gorm:"column:state;type:int;not null;default:1;comment:1=启用 2=禁用" json:"state"` // 1=启用 2=禁用
Password string `gorm:"column:password;type:varchar(64);not null;comment:密码" json:"password"` // 密码
PassCipher string `gorm:"column:pass_cipher;type:varchar(64);not null;default:MD5;comment:加密方式" json:"pass_cipher"` // 加密方式
PassSalt string `gorm:"column:pass_salt;type:varchar(64);not null;comment:密码盐" json:"pass_salt"` // 密码盐
ExtID string `gorm:"column:ext_id;type:varchar(64);not null;comment:外部ID" json:"ext_id"` // 外部ID
DeleteAt *gorm.DeletedAt `gorm:"column:delete_at;type:datetime;index:idx_delete_at,priority:1;comment:del标志" json:"delete_at"` // del标志
CreateAt time.Time `gorm:"column:create_at;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"create_at"` // 创建时间
CreateBy string `gorm:"column:create_by;type:varchar(255);not null;comment:创建人" json:"create_by"` // 创建人
UpdateAt time.Time `gorm:"column:update_at;type:datetime;not null;index:idx_delete_at,priority:2;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_at"` // 更新时间
UpdateBy string `gorm:"column:update_by;type:varchar(255);not null;comment:更新人" json:"update_by"` // 更新人
}
// TableName AdminUser's table name
func (*AdminUser) TableName() string {
return TableNameAdminUser
}

View File

@ -0,0 +1,32 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameAdminUserRole = "admin_user_role"
// AdminUserRole 租户用户-角色表
type AdminUserRole struct {
ID string `gorm:"column:id;type:char(32);not null;uniqueIndex:id,priority:1;comment:业务ID" json:"id"` // 业务ID
TenantID string `gorm:"column:tenant_id;type:char(32);not null;default:system;comment:企业ID" json:"tenant_id"` // 企业ID
ScopeID string `gorm:"column:scope_id;type:char(32);not null;comment:范围" json:"scope_id"` // 范围
UserID string `gorm:"column:user_id;type:char(32);not null;uniqueIndex:idx_uk,priority:1;comment:用户ID" json:"user_id"` // 用户ID
RoleID string `gorm:"column:role_id;type:char(32);not null;uniqueIndex:idx_uk,priority:2;comment:角色ID" json:"role_id"` // 角色ID
DeleteAt *gorm.DeletedAt `gorm:"column:delete_at;type:datetime;index:idx_delete_at,priority:1;comment:del标志" json:"delete_at"` // del标志
CreateAt time.Time `gorm:"column:create_at;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"create_at"` // 创建时间
CreateBy string `gorm:"column:create_by;type:varchar(255);not null;comment:创建人" json:"create_by"` // 创建人
UpdateAt time.Time `gorm:"column:update_at;type:datetime;not null;index:idx_delete_at,priority:2;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_at"` // 更新时间
UpdateBy string `gorm:"column:update_by;type:varchar(255);not null;comment:更新人" json:"update_by"` // 更新人
}
// TableName AdminUserRole's table name
func (*AdminUserRole) TableName() string {
return TableNameAdminUserRole
}

View File

@ -0,0 +1,43 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameDataAttach = "data_attach"
// DataAttach 通用附件
type DataAttach struct {
ID string `gorm:"column:id;type:char(32);not null;uniqueIndex:id,priority:1;comment:业务ID" json:"id"` // 业务ID
TenantID string `gorm:"column:tenant_id;type:char(32);not null;default:system;comment:企业ID" json:"tenant_id"` // 企业ID
PartUploadID string `gorm:"column:part_upload_id;type:char(32);not null;comment:分片上传upload id" json:"part_upload_id"` // 分片上传upload id
TotalChunks int32 `gorm:"column:total_chunks;type:int;not null;comment:分片总数" json:"total_chunks"` // 分片总数
PartSize int32 `gorm:"column:part_size;type:int;not null;comment:分片大小-单位M" json:"part_size"` // 分片大小-单位M
Md5 string `gorm:"column:md5;type:char(32);not null;comment:文件md5" json:"md5"` // 文件md5
DataType string `gorm:"column:data_type;type:varchar(64);not null;comment:数据类型SATELLITE=卫星图,其他请使用时完善定义" json:"data_type"` // 数据类型SATELLITE=卫星图,其他请使用时完善定义
DataID string `gorm:"column:data_id;type:char(32);not null;comment:数据ID" json:"data_id"` // 数据ID
CommonType string `gorm:"column:common_type;type:varchar(64);not null;comment:通用类型IMAGE = 图片(jfif、png、jpg等)DOC = 文档excel、docs、ppt、pdf等BINARY = 二进制文件exe等" json:"common_type"` // 通用类型IMAGE = 图片(jfif、png、jpg等)DOC = 文档excel、docs、ppt、pdf等BINARY = 二进制文件exe等
FileType string `gorm:"column:file_type;type:varchar(64);not null;comment:文件类型" json:"file_type"` // 文件类型
FileSize int32 `gorm:"column:file_size;type:int;not null;comment:文件大小" json:"file_size"` // 文件大小
FileName string `gorm:"column:file_name;type:varchar(255);not null;comment:文件名称" json:"file_name"` // 文件名称
FilePath string `gorm:"column:file_path;type:varchar(255);not null;comment:文件路径" json:"file_path"` // 文件路径
FileThumbPath string `gorm:"column:file_thumb_path;type:varchar(255);not null;comment:缩略文件路径" json:"file_thumb_path"` // 缩略文件路径
FileThumbType string `gorm:"column:file_thumb_type;type:varchar(64);not null;comment:缩略图文件类型" json:"file_thumb_type"` // 缩略图文件类型
UploadState *int32 `gorm:"column:upload_state;type:int;default:1;comment:0未上传,1上传" json:"upload_state"` // 0未上传,1上传
DeleteAt *gorm.DeletedAt `gorm:"column:delete_at;type:datetime;index:idx_delete_at,priority:1;comment:del标志" json:"delete_at"` // del标志
CreateAt time.Time `gorm:"column:create_at;type:datetime;not null;index:idx_data_time,priority:1;default:CURRENT_TIMESTAMP;comment:创建时间" json:"create_at"` // 创建时间
CreateBy string `gorm:"column:create_by;type:varchar(255);not null;comment:创建人" json:"create_by"` // 创建人
UpdateAt time.Time `gorm:"column:update_at;type:datetime;not null;index:idx_delete_at,priority:2;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_at"` // 更新时间
UpdateBy string `gorm:"column:update_by;type:varchar(255);not null;comment:更新人" json:"update_by"` // 更新人
}
// TableName DataAttach's table name
func (*DataAttach) TableName() string {
return TableNameDataAttach
}

34
pkg/model/game.gen.go Normal file
View File

@ -0,0 +1,34 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameGame = "game"
// Game 交易游戏
type Game struct {
ID string `gorm:"column:id;type:char(32);not null;uniqueIndex:id,priority:1;comment:业务ID" json:"id"` // 业务ID
Name string `gorm:"column:name;type:varchar(255);not null;comment:游戏名称" json:"name"` // 游戏名称
Desc string `gorm:"column:desc;type:varchar(255);not null;comment:描述" json:"desc"` // 描述
Icon string `gorm:"column:icon;type:varchar(255);not null;comment:图标" json:"icon"` // 图标
Logo string `gorm:"column:logo;type:varchar(255);not null;comment:logo" json:"logo"` // logo
Sort int32 `gorm:"column:sort;type:int;not null;default:9999;comment:排序" json:"sort"` // 排序
State int32 `gorm:"column:state;type:int;not null;default:1;comment:状态1=启用 2=禁用" json:"state"` // 状态1=启用 2=禁用
DeleteAt *gorm.DeletedAt `gorm:"column:delete_at;type:datetime;comment:del标志" json:"delete_at"` // del标志
CreateAt time.Time `gorm:"column:create_at;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"create_at"` // 创建时间
CreateBy string `gorm:"column:create_by;type:varchar(255);not null;comment:创建人" json:"create_by"` // 创建人
UpdateAt time.Time `gorm:"column:update_at;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_at"` // 更新时间
UpdateBy string `gorm:"column:update_by;type:varchar(255);not null;comment:更新人" json:"update_by"` // 更新人
}
// TableName Game's table name
func (*Game) TableName() string {
return TableNameGame
}

View File

@ -0,0 +1,36 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameTradeCommodity = "trade_commodity"
// TradeCommodity 交易的商品
type TradeCommodity struct {
ID string `gorm:"column:id;type:char(32);not null;uniqueIndex:id,priority:1;comment:业务ID" json:"id"` // 业务ID
CommodityType string `gorm:"column:commodity_type;type:varchar(255);not null;comment:GAME_ROLE=游戏角色" json:"commodity_type"` // GAME_ROLE=游戏角色
UserID string `gorm:"column:user_id;type:char(32);not null;comment:用户ID" json:"user_id"` // 用户ID
CommodityID string `gorm:"column:commodity_id;type:char(32);not null;comment:如游戏角色IDuser_game_role的ID" json:"commodity_id"` // 如游戏角色IDuser_game_role的ID
Desc string `gorm:"column:desc;type:varchar(255);not null;comment:描述" json:"desc"` // 描述
Price int32 `gorm:"column:price;type:int;not null;comment:价格,单位为分" json:"price"` // 价格,单位为分
Icon string `gorm:"column:icon;type:varchar(255);not null;comment:图标" json:"icon"` // 图标
Logo string `gorm:"column:logo;type:varchar(255);not null;comment:logo" json:"logo"` // logo
State int32 `gorm:"column:state;type:int;not null;default:1;comment:状态1=启用 2=禁用" json:"state"` // 状态1=启用 2=禁用
DeleteAt *gorm.DeletedAt `gorm:"column:delete_at;type:datetime;comment:del标志" json:"delete_at"` // del标志
CreateAt time.Time `gorm:"column:create_at;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"create_at"` // 创建时间
CreateBy string `gorm:"column:create_by;type:varchar(255);not null;comment:创建人" json:"create_by"` // 创建人
UpdateAt time.Time `gorm:"column:update_at;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_at"` // 更新时间
UpdateBy string `gorm:"column:update_by;type:varchar(255);not null;comment:更新人" json:"update_by"` // 更新人
}
// TableName TradeCommodity's table name
func (*TradeCommodity) TableName() string {
return TableNameTradeCommodity
}

Some files were not shown because too many files have changed in this diff Show More