初始化项目

This commit is contained in:
皮蛋13361098506
2025-01-06 16:01:02 +08:00
commit 1b77f62820
575 changed files with 69193 additions and 0 deletions

8
trunk/goutil/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

9
trunk/goutil/.idea/goutil.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
trunk/goutil/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
trunk/goutil/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/goutil.iml" filepath="$PROJECT_DIR$/.idea/goutil.iml" />
</modules>
</component>
</project>

6
trunk/goutil/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,416 @@
// ************************************
// @package: app_environment
// @description: 模拟进程级别的环境变量功能
// 注意此文件非线程安全请注意使用设计上是在init的过程中存放在运行过程中只读取
// @author: byron
// @revision history:
// @create date: 2022-01-25 16:54:47
// ************************************
package app_environment
import (
"encoding/json"
"errors"
"fmt"
"strconv"
)
var (
kv map[string]string
)
func init() {
kv = make(map[string]string)
}
// Reset
// @description: 重置环境变量
// parameter:
// @kvmap:
// return:
// @error:
func Reset(kvmap map[string]string) error {
if kvmap == nil {
return fmt.Errorf("app_environment.Reset 不允许使用nil map做参数")
}
kv = kvmap
return nil
}
// Set
// @description: 设置环境变量如果存在会返回error如果要覆盖请使用 SetForCover 方法
// parameter:
// @k:key
// @v:value
// return:
// @error:如果已经存在则返回error
func Set(k, v string) error {
if _, eixsts := kv[k]; eixsts {
return errors.New(fmt.Sprintf("已经存在k=%s的缓存", k))
}
kv[k] = v
return nil
}
// SetMap
// @description: 设置环境变量如果存在会返回error如果要覆盖请使用 SetForCoverMap 方法
// parameter:
// @kvmap:
// return:
// @error:
func SetMap(kvmap map[string]string) error {
for k, v := range kvmap {
if err := Set(k, v); err != nil {
return err
}
}
return nil
}
// SetForCover
// @description: 设置环境变量,如果存在,则覆盖
// parameter:
// @k:key
// @v:value
// return:
func SetForCover(k, v string) {
kv[k] = v
}
// SetForCoverMap
// @description: 设置环境变量,如果存在,则覆盖
// parameter:
// @kvmap:
// return:
func SetForCoverMap(kvmap map[string]string) {
for k, v := range kvmap {
SetForCover(k, v)
}
}
// SetForNoExists
// @description: 如果不存在,则设置
// parameter:
// @k:key
// @v:value
// return:
func SetForNoExists(k, v string) {
if _, eixsts := kv[k]; eixsts {
return
}
kv[k] = v
}
// SetForNoExistsMap
// @description: 如果不存在,则设置
// parameter:
// @kvmap:要设置的字典
// return:
func SetForNoExistsMap(kvmap map[string]string) {
for k, v := range kvmap {
SetForNoExists(k, v)
}
}
// GetAll
// @description: 获取所有的环境变量
// parameter:
// return:
// @map[string]string:环境变量内容
func GetAll() map[string]string {
tempKv := make(map[string]string, len(kv))
for k, v := range kv {
tempKv[k] = v
}
return tempKv
}
// Get
// @description: 获取环境变量
// parameter:
// @k:配置项
// return:
// @string:value
// @bool:是否存在对应的值
func Get(k string) (string, bool) {
v, exists := kv[k]
return v, exists
}
// GetOrDefault
// @description: 获取环境变量,未配置情况下使用默认值
// parameter:
// @k:配置项
// @defaultValue:默认值
// return:
// @string:结果值
func GetOrDefault(k string, defaultValue string) string {
v, ok := Get(k)
if !ok {
return defaultValue
}
return v
}
// GetInt
// @description: 获取环境变量
// parameter:
// @k:key
// return:
// @int:value
// @bool:是否存在对应的值
// @error:配置值解析失败时返回
func GetInt(k string) (int, bool, error) {
v, isok := Get(k)
if isok == false {
return 0, false, nil
}
i, err := strconv.Atoi(v)
if err != nil {
return 0, true, err
}
return i, true, nil
}
// GetIntOrDefault
// @description: 获取环境变量,未配置情况下使用默认值
// parameter:
// @k:配置项
// @defaultValue:默认值
// return:
// @int:结果值
// @error:配置值解析失败时返回
func GetIntOrDefault(k string, defaultValue int) (int, error) {
val, ok, err := GetInt(k)
if err != nil || !ok {
return defaultValue, err
}
return val, nil
}
// GetInt32
// @description: 获取环境变量
// parameter:
// @k:key
// return:
// @int:value
// @bool:是否存在对应的值
// @error:配置值解析失败时返回
func GetInt32(k string) (int32, bool, error) {
v, isok := Get(k)
if isok == false {
return 0, false, nil
}
i, err := strconv.ParseInt(v, 10, 32)
if err != nil {
return 0, true, err
}
return int32(i), true, nil
}
// GetInt32OrDefault
// @description: 获取环境变量,未配置情况下使用默认值
// parameter:
// @k:配置项
// @defaultValue:默认值
// return:
// @int:结果值
// @error:配置值解析失败时返回
func GetInt32OrDefault(k string, defaultValue int32) (int32, error) {
val, ok, err := GetInt32(k)
if err != nil || !ok {
return defaultValue, err
}
return val, nil
}
// GetInt64
// @description: 获取环境变量
// parameter:
// @k:key
// return:
// @int64:value
// @bool:是否存在对应的值
// @error:过程是否出错
func GetInt64(k string) (int64, bool, error) {
v, isok := Get(k)
if isok == false {
return 0, false, nil
}
i64, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, true, err
}
return i64, true, nil
}
// GetInt64OrDefault
// @description: 获取环境变量,未配置情况下使用默认值
// parameter:
// @k:配置项
// @defaultValue:默认值
// return:
// @int:结果值
// @error:配置值解析失败时返回
func GetInt64OrDefault(k string, defaultValue int64) (int64, error) {
val, ok, err := GetInt64(k)
if err != nil || !ok {
return defaultValue, err
}
return val, nil
}
// GetFloat32
// @description: 获取环境变量
// parameter:
// @k:key
// return:
// @int64:value
// @bool:是否存在对应的值
// @error:过程是否出错
func GetFloat32(k string) (float32, bool, error) {
v, isok := Get(k)
if isok == false {
return 0, false, nil
}
f64, err := strconv.ParseFloat(v, 32)
if err != nil {
return 0, true, err
}
return float32(f64), true, nil
}
// GetFloat32OrDefault
// @description: 获取环境变量,未配置情况下使用默认值
// parameter:
// @k:配置项
// @defaultValue:默认值
// return:
// @int:结果值
// @error:配置值解析失败时返回
func GetFloat32OrDefault(k string, defaultValue float32) (float32, error) {
val, ok, err := GetFloat32(k)
if err != nil || !ok {
return defaultValue, err
}
return val, nil
}
// GetFloat64
// @description: 获取环境变量
// parameter:
// @k:key
// return:
// @int64:value
// @bool:是否存在对应的值
// @error:过程是否出错
func GetFloat64(k string) (float64, bool, error) {
v, isok := Get(k)
if isok == false {
return 0, false, nil
}
f64, err := strconv.ParseFloat(v, 64)
if err != nil {
return 0, true, err
}
return f64, true, nil
}
// GetFloat64OrDefault
// @description: 获取环境变量,未配置情况下使用默认值
// parameter:
// @k:配置项
// @defaultValue:默认值
// return:
// @int:结果值
// @error:配置值解析失败时返回
func GetFloat64OrDefault(k string, defaultValue float64) (float64, error) {
val, ok, err := GetFloat64(k)
if err != nil || !ok {
return defaultValue, err
}
return val, nil
}
// GetBool
// @description: 获取环境变量
// parameter:
// @k:key
// return:
// @bool:value
// @bool:是否存在对应的值
// @error:过程是否出错
func GetBool(k string) (bool, bool, error) {
v, isok := Get(k)
if isok == false {
return false, false, nil
}
i, err := strconv.ParseBool(v)
if err != nil {
return false, true, err
}
return i, true, nil
}
// GetBoolOrDefault
// @description: 获取环境变量,未配置情况下使用默认值
// parameter:
// @k:配置项
// @defaultValue:默认值
// return:
// @int:结果值
// @error:配置值解析失败时返回
func GetBoolOrDefault(k string, defaultValue bool) (bool, error) {
val, ok, err := GetBool(k)
if err != nil || !ok {
return defaultValue, err
}
return val, nil
}
// GetModel
// @description: 获取环境变量并将内容序列化到obj对象中返回
// parameter:
// @k:key
// @obj:value
// return:
// @bool:是否存在对应的值
// @error:过程是否出错
func GetModel(k string, obj interface{}) (bool, error) {
v, isok := Get(k)
if isok == false {
return false, nil
}
if err := json.Unmarshal([]byte(v), obj); err != nil {
return true, err
}
return true, nil
}

View File

@@ -0,0 +1,265 @@
package app_environment
import (
"encoding/json"
"fmt"
"testing"
)
func Test_Set(t *testing.T) {
var err error
err = Set("a", "a")
if err != nil {
t.Error(err)
}
err = Set("b", "b")
if err != nil {
t.Error(err)
}
err = Set("c", "1")
if err != nil {
t.Error(err)
}
err = Set("d", "2")
if err != nil {
t.Error(err)
}
err = Set("e", "true")
if err != nil {
t.Error(err)
}
err = Set("a", "a")
if err == nil {
t.Error("Set覆盖存在问题")
}
tm := &tempkv{
Name: "byron",
}
str, _ := json.Marshal(tm)
Set("str", string(str))
kvmap := make(map[string]string)
kvmap["a_1"] = "aaa"
err = SetMap(kvmap)
if err != nil {
t.Error(err)
}
err = SetMap(kvmap)
if err == nil {
t.Error("SetMap覆盖存在问题")
}
kvmap = make(map[string]string)
kvmap["b_aa"] = "b_aa"
SetForCoverMap(kvmap)
SetForCoverMap(kvmap)
SetForCover("a", "a")
}
func Test_Get(t *testing.T) {
m := GetAll()
if len(m) != 8 {
t.Error(fmt.Sprintf("getall错误 m=%v", len(m)))
}
v, isok := Get("a")
if isok == false {
t.Error("未获取到a")
}
if v != "a" {
t.Error("get值错误")
}
var i int
var err error
i, isok, err = GetInt("c")
if err != nil {
t.Error("GetInt 出错 err:", err)
}
if isok == false {
t.Error("GetInt 获取失败")
}
if i != 1 {
t.Error("GetInt值失败")
}
var i32 int32
i32, isok, err = GetInt32("c")
if err != nil {
t.Error("GetInt 出错 err:", err)
}
if isok == false {
t.Error("GetInt 获取失败")
}
if i32 != 1 {
t.Error("GetInt值失败")
}
var i64 int64
i64, isok, err = GetInt64("d")
if err != nil {
t.Error("GetInt64 出错 err:", err)
}
if isok == false {
t.Error("GetInt64 获取失败")
}
if i64 != 2 {
t.Error("GetInt64 值失败")
}
var b bool
b, isok, err = GetBool("e")
if err != nil {
t.Error("GetBool 出错 err:", err)
}
if isok == false {
t.Error("GetBool 获取失败")
}
if b == false {
t.Error("GetBool 值失败")
}
tm := &tempkv{}
isok, err = GetModel("str", tm)
if err != nil {
t.Error("GetModel 出错 err:", err)
}
if isok == false {
t.Error("GetModel 获取失败")
}
if tm.Name != "byron" {
t.Error("GetModel 值失败")
}
}
func Test_GetOrDefault(t *testing.T) {
v := GetOrDefault("notExists", "default")
if v != "default" {
t.Error("GetOrDefault 返回默认值不正确")
}
Set("stringTest", "ok")
v = GetOrDefault("stringTest", "default")
if v != "ok" {
t.Error("GetOrDefault 返回值不正确")
}
i, err := GetIntOrDefault("notExists", -99)
if err != nil {
t.Error(err)
}
if i != -99 {
t.Error("GetIntOrDefault 返回默认值不正确")
}
Set("intTest", "99")
i, err = GetIntOrDefault("intTest", -99)
if err != nil {
t.Error(err)
}
if i != 99 {
t.Error("GetIntOrDefault 返回值不正确")
}
Set("intTestError", "a99")
i, err = GetIntOrDefault("intTestError", -99)
if err == nil {
t.Error("GetIntOrDefault 解析时错误未抛出")
}
i32, err := GetInt32OrDefault("notExists", -99)
if err != nil {
t.Error(err)
}
if i32 != -99 {
t.Error("GetInt32OrDefault 返回默认值不正确")
}
Set("int32Test", "99")
i32, err = GetInt32OrDefault("int32Test", -99)
if err != nil {
t.Error(err)
}
if i32 != 99 {
t.Error("GetInt32OrDefault 返回值不正确")
}
Set("int32TestError", "a99")
i32, err = GetInt32OrDefault("int32TestError", -99)
if err == nil {
t.Error("GetInt32OrDefault 解析时错误未抛出")
}
i64, err := GetInt64OrDefault("notExists", -99)
if err != nil {
t.Error(err)
}
if i64 != -99 {
t.Error("GetInt64OrDefault 返回默认值不正确")
}
Set("int64Test", "99")
i64, err = GetInt64OrDefault("int64Test", -99)
if err != nil {
t.Error(err)
}
if i64 != 99 {
t.Error("GetInt64OrDefault 返回值不正确")
}
Set("int64TestError", "a99")
i64, err = GetInt64OrDefault("int64TestError", -99)
if err == nil {
t.Error("GetInt64OrDefault 解析时错误未抛出")
}
b, err := GetBoolOrDefault("notExists", true)
if err != nil {
t.Error(err)
}
if !b {
t.Error("GetBoolOrDefault 返回默认值不正确")
}
Set("boolTest", "true")
b, err = GetBoolOrDefault("boolTest", false)
if err != nil {
t.Error(err)
}
if !b {
t.Error("GetBoolOrDefault 返回值不正确")
}
Set("boolTestError", "sss")
b, err = GetBoolOrDefault("boolTestError", true)
if err == nil {
t.Error("GetBoolOrDefault 解析时错误未抛出")
}
}
func Test_Reset(t *testing.T) {
if err := Reset(nil); err == nil {
t.Error("Reset nil参数错误未抛出")
}
m := make(map[string]string)
if err := Reset(m); err != nil {
t.Error("Reset 错误的抛出错误")
}
}
type tempkv struct {
Name string
}

View File

@@ -0,0 +1,4 @@
/*
用于提供验证AppStore充值的逻辑
*/
package appChargeUtil

View File

@@ -0,0 +1,141 @@
package appChargeUtil
import (
"encoding/json"
"fmt"
"goutil/typeUtil"
)
// APP Store充值收据对象
type Receipt struct {
// Bvrs
Bvrs string
// BundleIdentifier
BundleIdentifier string
// 产品Id
ProductId string
// 交易Id
TransactionId string
// 数量
Quantity int
// 状态
Status int
}
// BundleIdentifier是否有效
// bundleIdentifierList配置的BundleIdentifier列表
// 返回值:
// 是否有效
func (this *Receipt) IsBundleIdentifierValid(bundleIdentifierList []string) bool {
for _, item := range bundleIdentifierList {
if this.BundleIdentifier == item {
return true
}
}
return false
}
// ProductId是否有效
// productId输入的ProductId
// 返回值:
// 是否有效
func (this *Receipt) IsProductIdValid(productId string) bool {
return this.ProductId == productId
}
// 转换为字符串
// 返回值:
// 字符串
func (this *Receipt) String() string {
return fmt.Sprintf("{Bvrs=%s,BundleIdentifier=%s,ProductId=%s,TransactionId=%s,Quantity=%d,Status=%d}", this.Bvrs, this.BundleIdentifier, this.ProductId, this.TransactionId, this.Quantity, this.Status)
}
// 创建新的收据对象
// receiptInfo收据信息
// 返回值:
// 收据对象
// 错误对象
/*
{
"receipt":
{
"original_purchase_date_pst":"2015-06-22 20:56:34 America/Los_Angeles", //购买时间,太平洋标准时间
"purchase_date_ms":"1435031794826", //购买时间毫秒
"unique_identifier":"5bcc5503dbcc886d10d09bef079dc9ab08ac11bb",//唯一标识符
"original_transaction_id":"1000000160390314", //原始交易ID
"bvrs":"1.0",//iPhone程序的版本号
"transaction_id":"1000000160390314", //交易的标识
"quantity":"1", //购买商品的数量
"unique_vendor_identifier":"AEEC55C0-FA41-426A-B9FC-324128342652", //开发商交易ID
"item_id":"1008526677",//App Store用来标识程序的字符串
"product_id":"cosmosbox.strikehero.gems60",//商品的标识
"purchase_date":"2015-06-23 03:56:34 Etc/GMT",//购买时间
"original_purchase_date":"2015-06-23 03:56:34 Etc/GMT", //原始购买时间
"purchase_date_pst":"2015-06-22 20:56:34 America/Los_Angeles",//太平洋标准时间
"bid":"com.cosmosbox.StrikeHero",//iPhone程序的bundle标识
"original_purchase_date_ms":"1435031794826"//毫秒
},
"status":0 //状态码,0为成功
}
*/
func newReceipt(receiptInfo string) (receiptObj *Receipt, err error) {
// 创建空对象
receiptObj = &Receipt{}
// 将接收的数据转化为map类型的对象
receiptDataMap := make(map[string]interface{})
err = json.Unmarshal([]byte(receiptInfo), &receiptDataMap)
if err != nil {
return
}
mapData := typeUtil.NewMapData(receiptDataMap)
// 定义、并判断返回状态
receiptObj.Status, err = mapData.Int("status")
if err != nil {
return
}
if receiptObj.Status != 0 {
err = fmt.Errorf("状态:%d不正确", receiptObj.Status)
return
}
// Receipt is actually a child
receiptDataMap, ok := mapData["receipt"].(map[string]interface{})
if !ok {
err = fmt.Errorf("receipt错误")
return
}
mapData = typeUtil.NewMapData(receiptDataMap)
// 用返回值对本对象的属性进行赋值
receiptObj.Bvrs, err = mapData.String("bvrs")
if err != nil {
return
}
receiptObj.BundleIdentifier, err = mapData.String("bid")
if err != nil {
return
}
receiptObj.ProductId, err = mapData.String("product_id")
if err != nil {
return
}
receiptObj.TransactionId, err = mapData.String("transaction_id")
if err != nil {
return
}
receiptObj.Quantity, err = mapData.Int("quantity")
if err != nil {
return
}
return
}

View File

@@ -0,0 +1,57 @@
package appChargeUtil
import (
"fmt"
"testing"
)
func TestValidateCharge(t *testing.T) {
bundleIdentifierList := make([]string, 0, 4)
productId := ""
receiptObj, isValid, _ := ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false)
fmt.Printf("1. receiptObj:%s, isValid:%t\n", receiptObj, isValid)
if isValid != false {
t.Errorf("it should be invalid, but now valid\n")
return
}
productId = "xh_6"
receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false)
fmt.Printf("2. receiptObj:%s, isValid:%t\n", receiptObj, isValid)
if isValid != false {
t.Errorf("it should be invalid, but now valid\n")
return
}
bundleIdentifierList = append(bundleIdentifierList, "com.mqkk.game.xhs")
receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false)
fmt.Printf("3. receiptObj:%s, isValid:%t\n", receiptObj, isValid)
if isValid != false {
t.Errorf("it should be invalid, but now valid\n")
return
}
bundleIdentifierList = append(bundleIdentifierList, "com.yh.game.xhs")
receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false)
fmt.Printf("4. receiptObj:%s, isValid:%t\n", receiptObj, isValid)
if isValid != true {
t.Errorf("it should be valid, but now invalid\n")
return
}
bundleIdentifierList = append(bundleIdentifierList, "com.yh.game.xhs")
receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", true)
fmt.Printf("5. receiptObj:%s, isValid:%t\n", receiptObj, isValid)
if isValid == false {
receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false)
fmt.Printf("6. receiptObj:%s, isValid:%t\n", receiptObj, isValid)
if isValid != true {
t.Errorf("it should be valid, but now invalid\n")
return
}
} else {
t.Errorf("it should be invalid, but now valid\n")
return
}
}

View File

@@ -0,0 +1,82 @@
package appChargeUtil
import (
"errors"
"fmt"
"goutil/webUtil"
)
const (
con_SandBoxUrl = "https://sandbox.itunes.apple.com/verifyReceipt"
con_ProductionUrl = "https://buy.itunes.apple.com/verifyReceipt"
)
var (
NetworkError = errors.New("NetWorkError")
)
// 验证充值是否有效
// bundleIdentifierList配置的BundleIdentifier列表
// productId输入的ProductId
// receiptData订单数据
// isSandBox是否为沙盒模式
// 返回值:
// 充值收据对象
// 是否有效
// 错误对象如果err==NetWorkError,则表明为网络错误)
func ValidateCharge(bundleIdentifierList []string, productId, receiptData string, isSandBox bool) (receiptObj *Receipt, isValid bool, err error) {
// 判断参数是否为空
if len(bundleIdentifierList) == 0 || productId == "" || receiptData == "" {
return
}
// 获取Receipt对象
receiptObj, err = getReceipt(receiptData, isSandBox)
if err != nil {
return
}
if receiptObj.IsBundleIdentifierValid(bundleIdentifierList) == false {
return
}
if receiptObj.IsProductIdValid(productId) == false {
return
}
isValid = true
return
}
func getReceipt(receiptData string, isSandBox bool) (receiptObj *Receipt, err error) {
weburl := con_ProductionUrl
if isSandBox {
weburl = con_SandBoxUrl
}
data := []byte(convertReceiptToPost(receiptData))
statusCode, returnBytes, err := webUtil.PostByteData2(weburl, data, webUtil.GetFormHeader(), nil)
if err != nil {
err = NetworkError
return
}
if statusCode != 200 {
err = fmt.Errorf("StatusCode is wrong:%d", statusCode)
return
}
if len(returnBytes) == 0 {
err = fmt.Errorf("返回的数据为空")
return
}
receiptObj, err = newReceipt(string(returnBytes))
return
}
func convertReceiptToPost(receiptData string) string {
return fmt.Sprintf("{\"receipt-data\":\"%s\"}", receiptData)
}

View File

@@ -0,0 +1,119 @@
/*
一个处理不同进制的工具包;用于将十进制和其它进制进行互相转换
*/
package baseUtil
import (
"fmt"
"math"
)
// 进制对象定义
type Base struct {
elementList []string
base uint64
}
// 将10进制的uint64类型数据转换为字符串形式
// source:10进制的uint64类型数据
// 返回值:
// 对应进制的字符串形式
func (this *Base) Transform(source uint64) (result string) {
quotient, remainder := uint64(0), source
for {
quotient, remainder = remainder/this.base, remainder%this.base
result = this.elementList[remainder] + result
if quotient == 0 {
break
}
remainder = quotient
}
return
}
// 将字符串解析为10进制的uint64类型
// source:对应进制的字符串形式
// 返回值:10进制的uint64类型数据
func (this *Base) Parse(source string) (result uint64) {
if source == "" {
return
}
sourceList := make([]string, 0, len(source))
for _, v := range source {
sourceList = append(sourceList, string(v))
}
for idx, exp := len(sourceList)-1, 0; idx >= 0; idx, exp = idx-1, exp+1 {
sourceItem := sourceList[idx]
// Find the source item in the elementList
for i, v := range this.elementList {
if sourceItem == v {
result += uint64(float64(i) * math.Pow(float64(this.base), float64(exp)))
break
}
}
}
return
}
// 以指定的任意非重复的数组,来指定基于的进制数
func New(elements string) (baseObj *Base, err error) {
if len(elements) == 0 {
err = fmt.Errorf("输入的字符数串为空")
return
}
elementList := make([]string, 0, len(elements))
elementMap := make(map[rune]struct{}, len(elements))
for _, v := range elements {
if _, exist := elementMap[v]; exist {
err = fmt.Errorf("输入的字符串中含有重复的字符:%s", string(v))
return
} else {
elementMap[v] = struct{}{}
elementList = append(elementList, string(v))
}
}
baseObj = &Base{
elementList: elementList,
base: uint64(len(elementList)),
}
return
}
// 包含01
func NewBase2() (baseObj *Base, err error) {
return New("01")
}
// 包含0-7
func NewBase8() (baseObj *Base, err error) {
return New("01234567")
}
// 包含0-9,a-x
func NewBase16() (baseObj *Base, err error) {
return New("0123456789abcdef")
}
// 包含a-z
func NewBase26() (baseObj *Base, err error) {
return New("abcdefghijklmnopqrstuvwxyz")
}
// 包含0-9,a-z
func NewBase36() (baseObj *Base, err error) {
return New("0123456789abcdefghijklmnopqrstuvwxyz")
}
// 包含0-9,a-z,A-Z
func NewBase62() (baseObj *Base, err error) {
return New("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
}

View File

@@ -0,0 +1,394 @@
package baseUtil
import (
"testing"
)
func TestNew(t *testing.T) {
elements := ""
_, err := New(elements)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
elements = "00"
_, err = New(elements)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
elements = "01"
_, err = New(elements)
if err != nil {
t.Errorf("There should be no error, but now there is one:%v.", err)
return
}
}
func TestNewBase2(t *testing.T) {
_, err := NewBase2()
if err != nil {
t.Errorf("There should be no error, but now there is one:%v.", err)
return
}
}
func TestNewBase8(t *testing.T) {
_, err := NewBase8()
if err != nil {
t.Errorf("There should be no error, but now there is one:%v.", err)
return
}
}
func TestNewBase16(t *testing.T) {
_, err := NewBase16()
if err != nil {
t.Errorf("There should be no error, but now there is one:%v.", err)
return
}
}
func TestNewBase26(t *testing.T) {
_, err := NewBase26()
if err != nil {
t.Errorf("There should be no error, but now there is one:%v.", err)
return
}
}
func TestNewBase36(t *testing.T) {
_, err := NewBase36()
if err != nil {
t.Errorf("There should be no error, but now there is one:%v.", err)
return
}
}
func TestNewBase62(t *testing.T) {
_, err := NewBase62()
if err != nil {
t.Errorf("There should be no error, but now there is one:%v.", err)
return
}
}
func TestTransform(t *testing.T) {
base2, _ := NewBase2()
base8, _ := NewBase8()
base16, _ := NewBase16()
base26, _ := NewBase26()
base36, _ := NewBase36()
base62, _ := NewBase62()
var source uint64 = 0
expected := "0"
got := base2.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "0"
got = base8.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "0"
got = base16.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "a"
got = base26.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "0"
got = base36.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "0"
got = base62.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
source = 1
expected = "1"
got = base2.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "1"
got = base8.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "1"
got = base16.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "b"
got = base26.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "1"
got = base36.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "1"
got = base62.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
source = 2
expected = "10"
got = base2.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "2"
got = base8.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "2"
got = base16.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "c"
got = base26.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "2"
got = base36.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "2"
got = base62.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
source = 100
expected = "1100100"
got = base2.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "144"
got = base8.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "64"
got = base16.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "dw"
got = base26.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "2s"
got = base36.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
expected = "1C"
got = base62.Transform(source)
if got != expected {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
}
func TestParse(t *testing.T) {
base2, _ := NewBase2()
base8, _ := NewBase8()
base16, _ := NewBase16()
base26, _ := NewBase26()
base36, _ := NewBase36()
base62, _ := NewBase62()
expected := uint64(0)
got := base2.Parse("0")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base8.Parse("0")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base16.Parse("0")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base26.Parse("a")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base36.Parse("0")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base62.Parse("0")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
expected = uint64(1)
got = base2.Parse("1")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base8.Parse("1")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base16.Parse("1")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base26.Parse("b")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base36.Parse("1")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base62.Parse("1")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
expected = uint64(2)
got = base2.Parse("10")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base8.Parse("2")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base16.Parse("2")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base26.Parse("c")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base36.Parse("2")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base62.Parse("2")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
expected = uint64(100)
got = base2.Parse("1100100")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base8.Parse("144")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base16.Parse("64")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base26.Parse("dw")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base36.Parse("2s")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
got = base62.Parse("1C")
if got != expected {
t.Errorf("Expected to get %d, but got %d", expected, got)
return
}
}

View File

@@ -0,0 +1,4 @@
/*
配置助手类用于处理以JSON格式存储的配置文件
*/
package configUtil

View File

@@ -0,0 +1,99 @@
package configUtil
import (
"encoding/json"
"fmt"
"io/ioutil"
)
// 读取JSON格式的配置文件
// config_file_path配置文件路径
// 返回值:
// 配置内容的map格式
// 错误对象
func ReadJsonConfig(config_file_path string) (config map[string]interface{}, err error) {
// 读取配置文件一次性读取整个文件则使用ioutil
bytes, err := ioutil.ReadFile(config_file_path)
if err != nil {
err = fmt.Errorf("读取配置文件的内容出错:%s", err)
return
}
// 使用json反序列化
config = make(map[string]interface{})
if err = json.Unmarshal(bytes, &config); err != nil {
err = fmt.Errorf("反序列化配置文件的内容出错:%s", err)
return
}
return
}
// 从config配置中获取int类型的配置值
// config从config文件中反序列化出来的map对象
// configName配置名称
// 返回值:
// 配置值
// 错误对象
func ReadIntJsonValue(config map[string]interface{}, configName string) (value int, err error) {
configValue, exist := config[configName]
if !exist {
err = fmt.Errorf("不存在名为%s的配置或配置为空", configName)
return
}
configValue_float, ok := configValue.(float64)
if !ok {
err = fmt.Errorf("%s必须为int型", configName)
return
}
value = int(configValue_float)
return
}
// 从config配置中获取string类型的配置值
// config从config文件中反序列化出来的map对象
// configName配置名称
// 返回值:
// 配置值
// 错误对象
func ReadStringJsonValue(config map[string]interface{}, configName string) (value string, err error) {
configValue, exist := config[configName]
if !exist {
err = fmt.Errorf("不存在名为%s的配置或配置为空", configName)
return
}
configValue_string, ok := configValue.(string)
if !ok {
err = fmt.Errorf("%s必须为string型", configName)
return
}
value = configValue_string
return
}
// 从config配置中获取string类型的配置值
// config从config文件中反序列化出来的map对象
// configName配置名称
// 返回值:
// 配置值
// 错误对象
func ReadBoolJsonValue(config map[string]interface{}, configName string) (value bool, err error) {
configValue, exist := config[configName]
if !exist {
err = fmt.Errorf("不存在名为%s的配置或配置为空", configName)
return
}
configValue_bool, ok := configValue.(bool)
if !ok {
err = fmt.Errorf("%s必须为bool型", configName)
return
}
value = configValue_bool
return
}

View File

@@ -0,0 +1,109 @@
package configUtil
import (
"encoding/json"
"fmt"
"io/ioutil"
)
// 读取JSON格式的配置文件
// config_file_path配置文件路径
// 返回值:
// 配置内容的map格式数组
// 错误对象
func ReadJsonConfig_Array(config_file_path string) ([]map[string]interface{}, error) {
// 读取配置文件一次性读取整个文件则使用ioutil
bytes, err := ioutil.ReadFile(config_file_path)
if err != nil {
return nil, fmt.Errorf("读取配置文件的内容出错:%s", err)
}
// 使用json反序列化
config := make([]map[string]interface{}, 0, 4)
if err = json.Unmarshal(bytes, &config); err != nil {
return nil, fmt.Errorf("反序列化配置文件的内容出错:%s", err)
}
return config, nil
}
func getConfigValue(config []map[string]interface{}, configName string) (configValue interface{}, err error) {
var exist bool
for _, configItem := range config {
if configValue, exist = configItem[configName]; exist {
break
}
}
if !exist {
err = fmt.Errorf("不存在名为%s的配置或配置为空", configName)
}
return
}
// 从config配置中获取int类型的配置值
// config从config文件中反序列化出来的map对象
// configName配置名称
// 返回值:
// 配置值
// 错误对象
func ReadIntJsonValue_Array(config []map[string]interface{}, configName string) (value int, err error) {
configValue, err := getConfigValue(config, configName)
if err != nil {
return
}
configValue_float, ok := configValue.(float64)
if !ok {
err = fmt.Errorf("%s必须为int型", configName)
return
}
value = int(configValue_float)
return
}
// 从config配置中获取string类型的配置值
// config从config文件中反序列化出来的map对象
// configName配置名称
// 返回值:
// 配置值
// 错误对象
func ReadStringJsonValue_Array(config []map[string]interface{}, configName string) (value string, err error) {
configValue, err := getConfigValue(config, configName)
if err != nil {
return
}
configValue_string, ok := configValue.(string)
if !ok {
err = fmt.Errorf("%s必须为string型", configName)
return
}
value = configValue_string
return
}
// 从config配置中获取string类型的配置值
// config从config文件中反序列化出来的map对象
// configName配置名称
// 返回值:
// 配置值
// 错误对象
func ReadBoolJsonValue_Array(config []map[string]interface{}, configName string) (value bool, err error) {
configValue, err := getConfigValue(config, configName)
if err != nil {
return
}
configValue_bool, ok := configValue.(bool)
if !ok {
err = fmt.Errorf("%s必须为bool型", configName)
return
}
value = configValue_bool
return
}

View File

@@ -0,0 +1,53 @@
package configUtil
import (
"testing"
)
var (
config_Array []map[string]interface{}
err_Array error
)
func TestReadJsonConfig_Array(t *testing.T) {
config_Array, err_Array = ReadJsonConfig_Array("testdata/jsonConfigArray.ini")
if err_Array != nil {
t.Errorf("读取JSON配置失败错误信息为%s", err_Array)
}
}
func TestReadIntJsonValue_Array(t *testing.T) {
actualValue, err_Array := ReadIntJsonValue_Array(config_Array, "ServerGroupId")
if err_Array != nil {
t.Errorf("读取JSON配置失败错误信息为%s", err_Array)
}
expectedValue := 1
if actualValue != expectedValue {
t.Errorf("期望的值为%d实际的值为%d", expectedValue, actualValue)
}
}
func TestReadStringJsonValue_Array(t *testing.T) {
actualValue, err_Array := ReadStringJsonValue_Array(config_Array, "ChatDBConnection")
if err_Array != nil {
t.Errorf("读取JSON配置失败错误信息为%s", err_Array)
}
expectedValue := "root:moqikaka@tcp(192.168.1.226:3306)/chatserver?charset=utf8&parseTime=true&loc=Local&timeout=30s"
if actualValue != expectedValue {
t.Errorf("期望的值为%s实际的值为%s", expectedValue, actualValue)
}
}
func TestReadBoolJsonValue_Array(t *testing.T) {
actualValue, err_Array := ReadBoolJsonValue_Array(config_Array, "IfRecordMessage")
if err_Array != nil {
t.Errorf("读取JSON配置失败错误信息为%s", err_Array)
}
expectedValue := true
if actualValue != expectedValue {
t.Errorf("期望的值为%v实际的值为%v", expectedValue, actualValue)
}
}

View File

@@ -0,0 +1,53 @@
package configUtil
import (
"testing"
)
var (
config map[string]interface{}
err error
)
func TestReadJsonConfig(t *testing.T) {
config, err = ReadJsonConfig("testdata/jsonConfig.ini")
if err != nil {
t.Errorf("读取JSON配置失败错误信息为%s", err)
}
}
func TestReadIntJsonValue(t *testing.T) {
actualValue, err := ReadIntJsonValue(config, "ServerGroupId")
if err != nil {
t.Errorf("读取JSON配置失败错误信息为%s", err)
}
expectedValue := 1
if actualValue != expectedValue {
t.Errorf("期望的值为%d实际的值为%d", expectedValue, actualValue)
}
}
func TestReadStringJsonValue(t *testing.T) {
actualValue, err := ReadStringJsonValue(config, "ChatDBConnection")
if err != nil {
t.Errorf("读取JSON配置失败错误信息为%s", err)
}
expectedValue := "root:moqikaka@tcp(192.168.1.226:3306)/chatserver?charset=utf8&parseTime=true&loc=Local&timeout=30s"
if actualValue != expectedValue {
t.Errorf("期望的值为%s实际的值为%s", expectedValue, actualValue)
}
}
func TestReadBoolJsonValue(t *testing.T) {
actualValue, err := ReadBoolJsonValue(config, "IfRecordMessage")
if err != nil {
t.Errorf("读取JSON配置失败错误信息为%s", err)
}
expectedValue := true
if actualValue != expectedValue {
t.Errorf("期望的值为%v实际的值为%v", expectedValue, actualValue)
}
}

View File

@@ -0,0 +1,13 @@
{
"ServerGroupId": 1,
"ChatDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/chatserver?charset=utf8&parseTime=true&loc=Local&timeout=30s",
"ChatDBMaxOpenConns": 10,
"ChatDBMaxIdleConns": 5,
"ModelDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/dzz_model_online?charset=utf8&parseTime=true&loc=Local&timeout=30s",
"ModelDBMaxOpenConns": 0,
"ModelDBMaxIdleConns": 0,
"GameDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/dzz_online?charset=utf8&parseTime=true&loc=Local&timeout=30s",
"GameDBMaxOpenConns": 10,
"GameDBMaxIdleConns": 5,
"IfRecordMessage": true
}

View File

@@ -0,0 +1,18 @@
[
{
"ServerGroupId": 1,
"ChatDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/chatserver?charset=utf8&parseTime=true&loc=Local&timeout=30s",
"ChatDBMaxOpenConns": 10,
"ChatDBMaxIdleConns": 5,
"ModelDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/dzz_model_online?charset=utf8&parseTime=true&loc=Local&timeout=30s",
"ModelDBMaxOpenConns": 0,
"ModelDBMaxIdleConns": 0,
"GameDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/dzz_online?charset=utf8&parseTime=true&loc=Local&timeout=30s",
"GameDBMaxOpenConns": 10,
"GameDBMaxIdleConns": 5
},
{
"AppId": "DZZ",
"IfRecordMessage": true
}
]

View File

@@ -0,0 +1,299 @@
package configUtil
import (
"fmt"
"goutil/typeUtil"
"goutil/xmlUtil"
"reflect"
"strings"
)
type XmlConfig struct {
root *xmlUtil.Node
}
// 从文件加载
// xmlFilePath:xml文件路径
// 返回值:
// error:错误信息
func (this *XmlConfig) LoadFromFile(xmlFilePath string) error {
if this.root != nil {
return fmt.Errorf("There has been an xml file loaded.")
}
root, err := xmlUtil.LoadFromFile(xmlFilePath)
if err != nil {
return err
}
this.root = root
return nil
}
// 从node节点加载会取其根节点
// xmlRoot:xml节点
// 返回值:
// error:错误信息
func (this *XmlConfig) LoadFromXmlNode(xmlRoot *xmlUtil.Node) error {
if this.root != nil {
return fmt.Errorf("There has been an xml file loaded.")
}
if xmlRoot == nil {
return fmt.Errorf("xmlRoot is nil")
}
this.root = xmlRoot
return nil
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// bool:结果
// error:错误信息
func (this *XmlConfig) Bool(xpath string, attrName string) (bool, error) {
value, err := this.getVal(xpath, attrName)
if err != nil {
return false, err
}
return typeUtil.Bool(value)
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
//  返回值:
// bool:结果
func (this *XmlConfig) DefaultBool(xpath string, attrName string, defaultVal bool) bool {
value, err := this.Bool(xpath, attrName)
if err != nil {
return defaultVal
}
return value
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// int:结果
// error:错误信息
func (this *XmlConfig) Int(xpath string, attrName string) (int, error) {
value, err := this.getVal(xpath, attrName)
if err != nil {
return 0, err
}
return typeUtil.Int(value)
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
//  返回值:
// int:结果
func (this *XmlConfig) DefaultInt(xpath string, attrName string, defaultVal int) int {
value, err := this.Int(xpath, attrName)
if err != nil {
return defaultVal
}
return value
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// int64:结果
// error:错误信息
func (this *XmlConfig) Int64(xpath string, attrName string) (int64, error) {
value, err := this.getVal(xpath, attrName)
if err != nil {
return 0, err
}
return typeUtil.Int64(value)
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
//  返回值:
// int64:结果
func (this *XmlConfig) DefaultInt64(xpath string, attrName string, defaultVal int64) int64 {
value, err := this.Int64(xpath, attrName)
if err != nil {
return defaultVal
}
return value
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// float64:结果
// error:错误信息
func (this *XmlConfig) Float(xpath string, attrName string) (float64, error) {
value, err := this.getVal(xpath, attrName)
if err != nil {
return 0, err
}
return typeUtil.Float64(value)
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
//  返回值:
// float64:结果
func (this *XmlConfig) DefaultFloat(xpath string, attrName string, defaultVal float64) float64 {
value, err := this.Float(xpath, attrName)
if err != nil {
return defaultVal
}
return value
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// string:结果
// error:错误信息
func (this *XmlConfig) String(xpath string, attrName string) (string, error) {
return this.getVal(xpath, attrName)
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
//  返回值:
// string:结果
func (this *XmlConfig) DefaultString(xpath string, attrName string, defaultVal string) string {
value, err := this.String(xpath, attrName)
if err != nil {
return defaultVal
}
return value
}
// 获取指定位置的节点
// xpath:xpath路径
// 返回值:
// []*xmlUtil.Node结果
func (this *XmlConfig) Nodes(xpath string) []*xmlUtil.Node {
return this.root.SelectElements(xpath)
}
// 获取指定位置的节点
// xpath:xpath路径
// 返回值:
// *xmlUtil.Node结果
func (this *XmlConfig) Node(xpath string) *xmlUtil.Node {
return this.root.SelectElement(xpath)
}
// 反序列化指定的整个节点
// xpath:xml的path
// data:反序列化得到的数据
// 返回值:
// error:错误信息
func (this *XmlConfig) Unmarshal(xpath string, data interface{}) error {
nodeItem := this.Node(xpath)
//不存在节点,这里直接返回空的就行了
if nodeItem == nil {
data = nil
return nil
//return fmt.Errorf("节点不存在,XPATH:%s", xpath)
}
value := reflect.ValueOf(data)
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
dataType := value.Type()
// 依次设置字段值
var err error
fieldCount := value.NumField()
for i := 0; i < fieldCount; i++ {
fieldItem := value.Field(i)
fieldName := dataType.Field(i).Name
// 读取数据
var valueString string
tmpXpath := fmt.Sprintf("%s/%s", xpath, fieldName)
if valueString, err = this.getVal(tmpXpath, ""); err != nil {
valueString, err = this.getVal(xpath, fieldName)
if err != nil {
// 压根儿无此字段的配置数据,则略过
continue
}
}
// 字符串转换成目标值
fieldValue, err := typeUtil.Convert(valueString, fieldItem.Kind())
if err != nil {
return fmt.Errorf("读取字段失败, DataType:%s FieldName:%s Value:%v 错误信息:%v ", dataType.Name(), fieldName, valueString, err)
}
// 设置到字段上面
valType := reflect.ValueOf(fieldValue)
if valType.Type() == fieldItem.Type() {
fieldItem.Set(valType)
} else {
fieldItem.Set(valType.Convert(fieldItem.Type()))
}
}
return nil
}
// 获取指定路径的之
// xpath:xpath路径
// attrName:要获取的属性值,如果为空,则返回内部文本
func (this *XmlConfig) getVal(xpath string, attrName string) (val string, err error) {
targetRoot := this.root.SelectElement(xpath)
if targetRoot == nil {
err = fmt.Errorf("no find target node:%v", xpath)
return
}
if attrName == "" {
val = strings.TrimSpace(targetRoot.InnerText())
return
}
exist := false
val, exist = targetRoot.SelectAttr(attrName)
if exist == false {
err = fmt.Errorf("no find target attr, node:%v attr:%v", xpath, attrName)
return
}
return
}
// 创建新的xml配置对象
func NewXmlConfig() *XmlConfig {
return &XmlConfig{}
}

View File

@@ -0,0 +1,329 @@
package configUtil
import (
"fmt"
"strings"
"goutil/typeUtil"
)
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// []bool:结果
// error:错误信息
func (this *XmlConfig) BoolList(xpath string, attrName string) (result []bool, err error) {
result = make([]bool, 0)
// 获取值列表
valList, err := this.getValList(xpath, attrName)
if err != nil {
return
}
// 转换成指定类型
for _, valItem := range valList {
resultItem, err1 := typeUtil.Bool(valItem)
if err1 != nil {
err = err1
return
}
result = append(result, resultItem)
}
return
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中
//  返回值:
// []bool:结果
func (this *XmlConfig) DefaultBoolList(xpath string, attrName string, defaultVal bool, ifAdddefaultVal bool) (result []bool) {
result = make([]bool, 0)
// 获取值列表
valList, err := this.getValList(xpath, attrName)
if err != nil {
if ifAdddefaultVal {
result = append(result, defaultVal)
}
return result
}
// 转换成指定类型
for _, valItem := range valList {
resultItem, err := typeUtil.Bool(valItem)
if err != nil {
if ifAdddefaultVal {
result = append(result, defaultVal)
}
continue
}
result = append(result, resultItem)
}
return result
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// []int:结果
// error:错误信息
func (this *XmlConfig) IntList(xpath string, attrName string) (result []int, err error) {
result = make([]int, 0)
// 获取值列表
valList, err := this.getValList(xpath, attrName)
if err != nil {
return result, err
}
// 转换成指定类型
for _, valItem := range valList {
resultItem, err := typeUtil.Int(valItem)
if err != nil {
return result, err
}
result = append(result, resultItem)
}
return result, nil
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中
//  返回值:
// []int:结果
func (this *XmlConfig) DefaultIntList(xpath string, attrName string, defaultVal int, ifAdddefaultVal bool) []int {
result := make([]int, 0)
// 获取值列表
valList, err := this.getValList(xpath, attrName)
if err != nil {
if ifAdddefaultVal {
result = append(result, defaultVal)
}
return result
}
// 转换成指定类型
for _, valItem := range valList {
resultItem, err := typeUtil.Int(valItem)
if err != nil {
if ifAdddefaultVal {
result = append(result, defaultVal)
}
continue
}
result = append(result, resultItem)
}
return result
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// []int64:结果
// error:错误信息
func (this *XmlConfig) Int64List(xpath string, attrName string) ([]int64, error) {
result := make([]int64, 0)
// 获取值列表
valList, err := this.getValList(xpath, attrName)
if err != nil {
return result, err
}
// 转换成指定类型
for _, valItem := range valList {
resultItem, err := typeUtil.Int64(valItem)
if err != nil {
return result, err
}
result = append(result, resultItem)
}
return result, nil
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中
//  返回值:
// []int64:结果
func (this *XmlConfig) DefaultInt64List(xpath string, attrName string, defaultVal int64, ifAdddefaultVal bool) []int64 {
result := make([]int64, 0)
// 获取值列表
valList, err := this.getValList(xpath, attrName)
if err != nil {
if ifAdddefaultVal {
result = append(result, defaultVal)
}
return result
}
// 转换成指定类型
for _, valItem := range valList {
resultItem, err := typeUtil.Int64(valItem)
if err != nil {
if ifAdddefaultVal {
result = append(result, defaultVal)
}
continue
}
result = append(result, resultItem)
}
return result
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// []float64:结果
// error:错误信息
func (this *XmlConfig) FloatList(xpath string, attrName string) ([]float64, error) {
result := make([]float64, 0)
// 获取值列表
valList, err := this.getValList(xpath, attrName)
if err != nil {
return result, err
}
// 转换成指定类型
for _, valItem := range valList {
resultItem, err := typeUtil.Float64(valItem)
if err != nil {
return result, err
}
result = append(result, resultItem)
}
return result, nil
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中
//  返回值:
// []float64:结果
func (this *XmlConfig) DefaultFloatList(xpath string, attrName string, defaultVal float64, ifAdddefaultVal bool) []float64 {
result := make([]float64, 0)
// 获取值列表
valList, err := this.getValList(xpath, attrName)
if err != nil {
if ifAdddefaultVal {
result = append(result, defaultVal)
}
return result
}
// 转换成指定类型
for _, valItem := range valList {
resultItem, err := typeUtil.Float64(valItem)
if err != nil {
if ifAdddefaultVal {
result = append(result, defaultVal)
}
continue
}
result = append(result, resultItem)
}
return result
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
//  返回值:
// string:结果
// error:错误信息
func (this *XmlConfig) StringList(xpath string, attrName string) ([]string, error) {
// 获取值列表
return this.getValList(xpath, attrName)
}
// 获取指定xpath路径下的值
// xpath:xpath路径
// attrName:属性名,如果为空,则返回节点的内部文本
// defaultVal:默认值
// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中
//  返回值:
// string:结果
func (this *XmlConfig) DefaultStringList(xpath string, attrName string, defaultVal string, ifAdddefaultVal bool) []string {
result := make([]string, 0)
// 获取值列表
valList, err := this.getValList(xpath, attrName)
if err != nil {
if ifAdddefaultVal {
result = append(result, defaultVal)
}
return result
} else {
return valList
}
}
// 获取指定路径的之
// xpath:xpath路径
// attrName:要获取的属性值,如果为空,则返回内部文本
func (this *XmlConfig) getValList(xpath string, attrName string) ([]string, error) {
result := make([]string, 0)
targetNodeList := this.root.SelectElements(xpath)
if targetNodeList == nil {
return result, fmt.Errorf("no find target node:%v", xpath)
}
// 依次获取各个节点
for _, nodeItem := range targetNodeList {
val := ""
if attrName == "" {
val = strings.TrimSpace(nodeItem.InnerText())
} else {
val, _ = nodeItem.SelectAttr(attrName)
}
result = append(result, val)
}
return result, nil
}

View File

@@ -0,0 +1,135 @@
package configUtil
import (
"fmt"
"testing"
"goutil/xmlUtil"
)
// bool值读取测试
func TestBoolList(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigListData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
booList, errMsg := xmlConfigData.BoolList("html/body/ul/li/a", "id")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
fmt.Println("TestBoolList读取到的值:", booList)
}
// int值读取测试
func TestIntList(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigListData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
valList, errMsg := xmlConfigData.IntList("html/body/ul/li/a", "id")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
fmt.Println("TestInt读取到的值:", valList)
}
// int64值读取测试
func TestInt64List(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigListData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
valList, errMsg := xmlConfigData.Int64List("html/body/ul/li/a", "id")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
fmt.Println("TestInt64读取到的值:", valList)
}
// Float值读取测试
func TestFloatList(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigListData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
valList, errMsg := xmlConfigData.FloatList("html/body/ul/li/a", "id")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
fmt.Println("TestFloat读取到的值:", valList)
}
// 字符串读取测试
func TestStringList(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigListData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
valList, errMsg := xmlConfigData.StringList("html/body/ul/li/a", "id")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
fmt.Println("TestString读取到的值:", valList)
}
func getxmlConfigListData() (xmlConfigData *XmlConfig, errMsg error) {
content := `
<html lang="en">
<head>
<title>Hello</title>
<meta name="language" content="en"/>
</head>
<body IsPost='true'>
<h1> This is a H1 </h1>
<ul>
<li><a id="1" dd='1.1' href="/">Home</a></li>
<li><a id="2" href="/about">about</a></li>
<li><a id="3" href="/account">login</a></li>
<li></li>
</ul>
<p>
Hello,This is an example for gxpath.
</p>
<footer>footer script</footer>
</body>
</html>
`
var root *xmlUtil.Node
root, errMsg = xmlUtil.LoadFromString(content)
if errMsg == nil {
xmlConfigData = NewXmlConfig()
xmlConfigData.LoadFromXmlNode(root)
}
return
}

View File

@@ -0,0 +1,321 @@
package configUtil
import (
"fmt"
"testing"
"goutil/xmlUtil"
)
// bool值读取测试
func TestBool(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
var ispost bool
ispost, errMsg = xmlConfigData.Bool("html/body", "IsPost")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
fmt.Println("读取到的值:", ispost)
if ispost == false {
t.Error("html/body的isPost读取错误")
t.Fail()
return
}
ispost = xmlConfigData.DefaultBool("html/body", "IsPost", false)
if ispost == false {
t.Error("html/body的isPost读取错误")
t.Fail()
}
}
// int值读取测试
func TestInt(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
var id int
id, errMsg = xmlConfigData.Int("html/body/ul/li/a[@id=1]", "id")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
if id != 1 {
t.Errorf("html/body的isPost读取错误读取到的值:%v", id)
t.Fail()
return
}
id = xmlConfigData.DefaultInt("html/body", "id", 2)
if id != 2 {
t.Error("TestInt html/body的id读取错误")
t.Fail()
}
}
// int64值读取测试
func TestInt64(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
var id int64
id, errMsg = xmlConfigData.Int64("html/body/ul/li/a[@id=1]", "id")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
if id != 1 {
t.Errorf("TestInt64 html/body/ul/li/a[@id=1]的id读取错误读取到的值:%v", id)
t.Fail()
return
}
id = xmlConfigData.DefaultInt64("html/body", "id", 2)
if id != 2 {
t.Error("TestInt64 html/body的id读取错误")
t.Fail()
}
}
// Float值读取测试
func TestFloat(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
var id float64
id, errMsg = xmlConfigData.Float("html/body/ul/li/a[@id=1]", "dd")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
if id != 1.1 {
t.Errorf("TestFloat html/body/ul/li/a[@id=1]的id读取错误读取到的值:%v", id)
t.Fail()
return
}
id = xmlConfigData.DefaultFloat("html/body", "id", 2)
if id != 2 {
t.Error("TestFloat html/body的id读取错误")
t.Fail()
}
}
// 字符串读取测试
func TestString(t *testing.T) {
xmlConfigData, errMsg := getxmlConfigData()
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
var id string
id, errMsg = xmlConfigData.String("html/body/ul/li/a[@id=1]", "dd")
if errMsg != nil {
t.Error(errMsg)
t.Fail()
return
}
if id != "1.1" {
t.Errorf("TestString html/body/ul/li/a[@id=1]的id读取错误读取到的值:%v", id)
t.Fail()
return
}
id = xmlConfigData.DefaultString("html/body", "id", "2")
if id != "2" {
t.Error("TestString html/body的id读取错误")
t.Fail()
}
}
type HelloStruct struct {
// 连接字符串
ConnectionString string
// 最大开启连接数量
MaxOpenConns int `xml:",attr"`
// 最大空闲连接数量
MaxIdleConns int `xml:",attr"`
}
func (this *HelloStruct) Equal(other *HelloStruct) bool {
return this.MaxOpenConns == other.MaxOpenConns && this.MaxIdleConns == other.MaxIdleConns
}
func TestUnmarshal(t *testing.T) {
data, _ := getxmlConfigData2(`
<DBConnection>
<GameServerCenterDB MaxOpenConns="10" MaxIdleConns="5">
<![CDATA[
root:moqikaka3312@tcp(10.1.0.10:3312)/2_gsc_develop?charset=utf8&parseTime=true&loc=Local&timeout=60s
]]>
</GameServerCenterDB>
</DBConnection>
`)
val := &HelloStruct{}
err := data.Unmarshal("/DBConnection/GameServerCenterDB", val)
if err != nil {
t.Error(err)
return
}
want := &HelloStruct{
ConnectionString: "root:moqikaka3312@tcp(10.1.0.10:3312)/2_gsc_develop?charset=utf8&parseTime=true&loc=Local&timeout=60s",
MaxOpenConns: 10,
MaxIdleConns: 5,
}
if want.Equal(val) == false {
t.Errorf("Expected %v, but now got %v", want, val)
}
}
type ConnsNum int
type Hello2Struct struct {
// 连接字符串
ConnectionString string
// 最大开启连接数量
MaxOpenConns ConnsNum
// 最大空闲连接数量
MaxIdleConns ConnsNum
}
func (this *Hello2Struct) Equal(other *Hello2Struct) bool {
return this.MaxOpenConns == other.MaxOpenConns && this.MaxIdleConns == other.MaxIdleConns
}
func TestUnmarshal2(t *testing.T) {
data, _ := getxmlConfigData2(`
<DBConnection>
<GameServerCenterDB MaxOpenConns="10" MaxIdleConns="5">
<![CDATA[
root:moqikaka3312@tcp(10.1.0.10:3312)/2_gsc_develop?charset=utf8&parseTime=true&loc=Local&timeout=60s
]]>
</GameServerCenterDB>
</DBConnection>
`)
val := &Hello2Struct{}
err := data.Unmarshal("/DBConnection/GameServerCenterDB", val)
if err != nil {
t.Error(err)
return
}
want := &Hello2Struct{
ConnectionString: "root:moqikaka3312@tcp(10.1.0.10:3312)/2_gsc_develop?charset=utf8&parseTime=true&loc=Local&timeout=60s",
MaxOpenConns: 10,
MaxIdleConns: 5,
}
if want.Equal(val) == false {
t.Errorf("Expected %v, but now got %v", want, val)
}
}
func TestUnmarshal3(t *testing.T) {
data, _ := getxmlConfigData2(`
<DBConnection>
<GameServerCenterDB MaxOpenConns="10">
<![CDATA[
root:moqikaka3312@tcp(10.1.0.10:3312)/2_gsc_develop?charset=utf8&parseTime=true&loc=Local&timeout=60s
]]>
</GameServerCenterDB>
</DBConnection>
`)
val := &HelloStruct{}
err := data.Unmarshal("/DBConnection/GameServerCenterDB", val)
if err != nil {
t.Error(err)
return
}
want := &HelloStruct{
ConnectionString: "root:moqikaka3312@tcp(10.1.0.10:3312)/2_gsc_develop?charset=utf8&parseTime=true&loc=Local&timeout=60s",
MaxOpenConns: 10,
MaxIdleConns: 0,
}
if want.Equal(val) == false {
t.Errorf("Expected %v, but now got %v", want, val)
}
}
func getxmlConfigData() (xmlConfigData *XmlConfig, errMsg error) {
content := `
<html lang="en">
<head>
<title>Hello</title>
<meta name="language" content="en"/>
</head>
<body IsPost='true'>
<h1> This is a H1 </h1>
<ul>
<li><a id="1" dd='1.1' href="/">Home</a></li>
<li><a id="2" href="/about">about</a></li>
<li><a id="3" href="/account">login</a></li>
<li></li>
</ul>
<p>
Hello,This is an example for gxpath.
</p>
<footer>footer script</footer>
</body>
</html>
`
var root *xmlUtil.Node
root, errMsg = xmlUtil.LoadFromString(content)
if errMsg == nil {
xmlConfigData = NewXmlConfig()
xmlConfigData.LoadFromXmlNode(root)
}
return
}
func getxmlConfigData2(xml string) (xmlConfigData *XmlConfig, errMsg error) {
var root *xmlUtil.Node
root, errMsg = xmlUtil.LoadFromString(xml)
if errMsg == nil {
xmlConfigData = NewXmlConfig()
xmlConfigData.LoadFromXmlNode(root)
}
return
}

View File

@@ -0,0 +1,2 @@
Log/*
logs/*

View File

@@ -0,0 +1,47 @@
package coroutine_timer
const (
// 添加
cmd_add = 1
// 删除
cmd_del = 2
)
// cmdModel
// @description: 命令对象
type cmdModel struct {
// cmd 指令
cmd int
// paramObj 指令参数
paramObj interface{}
// resObj 指令返回对象
resObj interface{}
// err 指令返回的错误
err error
// waitChan 等待channel
waitChan chan struct{}
}
// newCmdModel
// @description: 创建cmd模型对象
// parameter:
// @c:cmd命令
// @po:参数
// return:
// @*cmdModel:
func newCmdModel(c int, po interface{}) *cmdModel {
result := &cmdModel{
cmd: c,
paramObj: po,
resObj: nil,
err: nil,
waitChan: make(chan struct{}, 1),
}
return result
}

View File

@@ -0,0 +1,73 @@
package coroutine_timer
// timersModel
// @description: timer卡槽对象
type timersModel struct {
timers map[string]*timerObj
}
// newTimersModel
// @description: 构造timer卡槽对象
// parameter:
// return:
// @*timersModel:
func newTimersModel() *timersModel {
return &timersModel{timers: map[string]*timerObj{}}
}
// addTimer
// @description: 添加定时器
// parameter:
// @receiver this:
// @t:
// return:
func (this *timersModel) addTimer(t *timerObj) {
this.timers[t.id] = t
}
// delTimer
// @description: 删除定时器
// parameter:
// @receiver this:
// @id:
// return:
func (this *timersModel) delTimer(id string) {
delete(this.timers, id)
}
// exist
// @description: 判断id是否存在
// parameter:
// @receiver this:
// @id:
// return:
// @exist:
func (this *timersModel) exist(id string) (exist bool) {
_, exist = this.timers[id]
return
}
// getAllTimers
// @description: 获取所有定时器
// parameter:
// @receiver this:
// return:
// @map[string]*timerObj:
func (this *timersModel) getAllTimers() map[string]*timerObj {
return this.timers
}
// getAllTimers2
// @description: 获取所有定时器
// parameter:
// @receiver this:
// return:
// @result:
func (this *timersModel) getAllTimers2() (result []*timerObj) {
result = make([]*timerObj, 0, len(this.timers))
for _, v := range this.timers {
result = append(result, v)
}
return
}

View File

@@ -0,0 +1,20 @@
coroutine-timer支持如下工作
定时触发设定的回调,最小精度秒级
## 使用方式
### 增加回调
> 1. 导入包
> 2. 调用AddTimerx添加定时回调传入相关参数
ps:
> 1. AddTimer1AddTimer2AddTimer3是内部自动生成的id内部保证唯一性。外部如果后续要删除该添加的timer需要持有返回的id信息
> 2. AddTimer4 需要外部传入id外部需要保证id的唯一性。并且这个方法会在内部校验id是否已经存在所以性能上会比其他AddTimer方法慢
### 删除回调
```go
DeleteTimer(id)
```

View File

@@ -0,0 +1,409 @@
package coroutine_timer
import (
"fmt"
"math"
"time"
"goutil/logUtil"
"goutil/stringUtil"
)
const (
// 启动暂停时间
con_STAR_SLEEP_NUM = 3
// 秒级定时器卡槽数量
con_SECOND_SLOT_NUM = 60
//分钟级定时器卡槽数量
con_MINUTES_SLOT_NUM = 60
)
var (
// 秒级定时器下标
secIndex = 0
// 秒级定时器当前开始时间
secondStarTime int64
// 秒级定时器槽
secondsTimers [con_SECOND_SLOT_NUM]*timersModel
// 分钟级定时器下标
minIndex = 0
// 分钟级定时器当前开始时间
minStarTime int64
// 分钟级定时器槽
minutesTimers [con_MINUTES_SLOT_NUM]*timersModel
// 其他定时器存放槽
otherTimers *timersModel
// 操作通道
cmdChan chan *cmdModel
)
func init() {
for i := 0; i < con_SECOND_SLOT_NUM; i++ {
secondsTimers[i] = newTimersModel()
}
for i := 0; i < con_MINUTES_SLOT_NUM; i++ {
minutesTimers[i] = newTimersModel()
}
otherTimers = newTimersModel()
cmdChan = make(chan *cmdModel, 1000)
secondStarTime = time.Now().Unix()
minStarTime = secondStarTime + con_SECOND_SLOT_NUM
go chanHandler()
}
// AddTimer
// @description: 添加定时回调
// parameter:
//
// @afterSecond:延后多少时间执行
// @exfun:执行方法
// @obj:执行传入的参数
//
// return:
//
// @string:
func AddTimer(afterSecond int, exfun func(interface{}), obj interface{}) string {
tick := time.Now().Unix() + int64(afterSecond)
return AddTimer3(tick, exfun, obj)
}
// AddTimer2
// @description: 添加定时回调
// parameter:
//
// @t:执行时间点
// @exfun:执行方法
// @obj:执行传入的参数
//
// return:
//
// @string:
func AddTimer2(t time.Time, exfun func(interface{}), obj interface{}) string {
tick := t.Unix()
return AddTimer3(tick, exfun, obj)
}
// AddTimer3
// @description: 添加定时回调
// parameter:
//
// @tick:执行时间点
// @exfun:执行方法
// @obj:执行传入的参数
//
// return:
//
// @newId:
func AddTimer3(tick int64, exfun func(interface{}), obj interface{}) (newId string) {
newId = stringUtil.GetNewUUID()
newObj := newTimerObj(newId, tick, exfun, obj)
cnm := newCmdModel(cmd_add, newObj)
cmdChan <- cnm
return
}
// AddTimer4
// @description: 添加定时回调(此方法会在内部校验id所以性能会比其他AddTimer方法低)
// parameter:
//
// @id:定时id(外部需要自行保证id唯一)
// @tick:执行时间点
// @exfun:执行方法
// @obj:执行传入的参数
//
// return:
//
// @err:
func AddTimer4(id string, tick int64, exfun func(interface{}), obj interface{}) (err error) {
newObj := newTimerObj(id, tick, exfun, obj)
newObj.needCheckId = true
// 加入处理队列
cnm := newCmdModel(cmd_add, newObj)
cmdChan <- cnm
// 等待处理结束
<-cnm.waitChan
// 返回处理结果
err = cnm.err
return
}
// DeleteTimer
// @description: 删除定时器
// parameter:
//
// @id:
//
// return:
func DeleteTimer(id string) {
cnm := newCmdModel(cmd_del, id)
cmdChan <- cnm
}
// chanHandler
// @description: channel处理
// parameter:
// return:
func chanHandler() {
defer func() {
if err := recover(); err != nil {
logUtil.ErrorLog("coroutine-timer.excute err:%s", err)
}
}()
// 暂停一下再处理,避免启动立即处理,其他数据还没准备好
time.Sleep(con_STAR_SLEEP_NUM * time.Second)
at := time.After(time.Second * 1)
for {
select {
case cm := <-cmdChan:
switch cm.cmd {
case cmd_add:
cmdAdd(cm)
case cmd_del:
cmdDel(cm)
}
case <-at:
// byron:需要处理时间后调导致跳时间的问题:调整后应该马上执行的
// 计算需要执行的次数
n := time.Now().Unix() - secondStarTime - int64(secIndex)
if n > 0 {
// 执行对应次数的方法 --- 正常应该只执行1此调时间后此处会追时间
var i int64
for i = 0; i < n; i++ {
cmdRun()
}
}
at = time.After(time.Second * 1)
}
}
}
// cmdAdd
// @description: 添加定时器
// parameter:
//
// @cm:
//
// return:
func cmdAdd(cm *cmdModel) {
newObj := cm.paramObj.(*timerObj)
if newObj.needCheckId && checkTimerExist(newObj.id) {
cm.err = fmt.Errorf("已经存在id=%s的timer", newObj.id)
cm.waitChan <- struct{}{}
return
}
// 如果执行时间比当前时间小,则放入最近的调度卡槽,以便尽快执行
tick := newObj.tick
if tick <= (secondStarTime + int64(secIndex)) {
tick = (secondStarTime + int64(secIndex)) + 1
}
// 落在秒钟级别定时器上
if tick < (secondStarTime + con_SECOND_SLOT_NUM) {
index := (int)(tick - secondStarTime)
secondsTimers[index].addTimer(newObj)
cm.waitChan <- struct{}{}
return
}
// 落在分钟级别定时器上
if tick < (minStarTime + con_MINUTES_SLOT_NUM*con_SECOND_SLOT_NUM) {
index := (int)(tick-minStarTime) / con_SECOND_SLOT_NUM
minutesTimers[index].addTimer(newObj)
cm.waitChan <- struct{}{}
return
}
//落在小时级别定时器上
otherTimers.addTimer(newObj)
// 返回操作完成
cm.waitChan <- struct{}{}
}
// cmdDel
// @description: 删除timer
// parameter:
//
// @cm:
//
// return:
func cmdDel(cm *cmdModel) {
id := cm.paramObj.(string)
// 移除秒级别定时器
for _, item := range secondsTimers {
item.delTimer(id)
}
// 移除分种级定时器
for _, item := range minutesTimers {
item.delTimer(id)
}
// 移除时钟级定时器
otherTimers.delTimer(id)
// 返回操作完成
cm.waitChan <- struct{}{}
}
// cmdRun
// @description: 运行定时器
// parameter:
// return:
func cmdRun() {
defer func() {
if err := recover(); err != nil {
logUtil.ErrorLog("coroutine-timer.inExcute err:%s", err)
}
}()
// 执行秒级定时器
timers := getSencondTimers()
if len(timers) == 0 {
return
}
for _, t := range timers {
go safeRun(t)
}
}
// checkTimerExist
// @description: 校验timer是否存在
// parameter:
//
// @id:id
//
// return:
//
// @bool:
func checkTimerExist(id string) bool {
// 秒级别定时器检测
for _, item := range secondsTimers {
if item.exist(id) {
return true
}
}
// 分种级定时器检测
for _, item := range minutesTimers {
if item.exist(id) {
return true
}
}
// 时钟级定时器检测
return otherTimers.exist(id)
}
// getSencondTimers
// @description: 获取秒级定时器
// parameter:
// return:
//
// @result:
func getSencondTimers() (result []*timerObj) {
// 获取对应slot里面的定时对象
result = secondsTimers[secIndex].getAllTimers2()
secondsTimers[secIndex] = newTimersModel()
secIndex++
// 如果达到最大,则重新填装新的调度对象
if secIndex == con_SECOND_SLOT_NUM {
secIndex = 0
secondStarTime = secondStarTime + con_SECOND_SLOT_NUM
minTaskList := getMinutesTasks()
for _, t := range minTaskList {
index := t.tick - secondStarTime
secondsTimers[index].addTimer(t)
}
}
return
}
// getMinutesTasks
// @description: 获取分钟级定时器
// parameter:
// return:
//
// @result:
func getMinutesTasks() (result []*timerObj) {
// 获取对应slot里面的定时对象
result = minutesTimers[minIndex].getAllTimers2()
minutesTimers[minIndex] = newTimersModel()
minIndex++
// 如果达到最大,则重新填装新的调度对象
if minIndex == con_MINUTES_SLOT_NUM {
reInputMin()
}
return
}
// reInputMin
// @description: 重新填入分钟级定时器
// parameter:
// return:
func reInputMin() {
minIndex = 0
minStarTime = minStarTime + con_MINUTES_SLOT_NUM*con_SECOND_SLOT_NUM
delMap := make(map[string]struct{})
for _, t := range otherTimers.getAllTimers() {
index := (t.tick - minStarTime) / con_SECOND_SLOT_NUM
if index > math.MaxInt || index >= con_MINUTES_SLOT_NUM {
continue
}
minutesTimers[index].addTimer(t)
delMap[t.id] = struct{}{}
}
if len(delMap) > 0 {
for k := range delMap {
otherTimers.delTimer(k)
}
}
}
// safeRun
// @description: 安全运行定时器回调
// parameter:
//
// @t:
//
// return:
func safeRun(t *timerObj) {
defer func() {
if err := recover(); err != nil {
logUtil.ErrorLog("coroutine-timer.safeRun id:%s err:%s", t.id, err)
}
}()
t.excuteAction(t.paramObj)
}

View File

@@ -0,0 +1,40 @@
package coroutine_timer
// timerObj
// @description: 定时调度对象
type timerObj struct {
// id 调度id
id string
// tick 执行时间
tick int64
// excuteAction 执行方法
excuteAction func(interface{})
// paramObj 携带的参数
paramObj interface{}
// needCheckId 是否需要校验id
needCheckId bool
}
// newTimerObj
// @description: 构造调度对象
// parameter:
// @_id:id
// @t:调度时间
// @ea:调度方法
// @pm:调度参数
// return:
// @*timerObj:
func newTimerObj(_id string, t int64, ea func(interface{}), pm interface{}) *timerObj {
result := &timerObj{
id: _id,
tick: t,
excuteAction: ea,
paramObj: pm,
}
return result
}

View File

@@ -0,0 +1,114 @@
package coroutine_timer
import (
"sync"
"testing"
"time"
"goutil/mathUtil"
"goutil/stringUtil"
)
func init() {
}
func Test_Method1(t *testing.T) {
imap := make(map[int]struct{})
var lockObj sync.Mutex
cb := func(obj interface{}) {
i := obj.(int)
lockObj.Lock()
defer lockObj.Unlock()
if _, exist := imap[i]; exist == false {
t.Error(i, "应该删除,不应该回调 Test_Method1")
}
delete(imap, i)
}
for i := 0; i < 20000; i++ {
tick := i % 20
isdel := false
if tick > 1 {
isdel = mathUtil.GetRand().GetRandInt(100) < 50
}
if isdel == false {
lockObj.Lock()
imap[i] = struct{}{}
lockObj.Unlock()
}
id := AddTimer(tick, cb, i)
if isdel {
DeleteTimer(id)
}
}
newN := 10000000
newId := stringUtil.GetNewUUID()
lockObj.Lock()
imap[newN] = struct{}{}
lockObj.Unlock()
err := AddTimer4(newId, 3, cb, newN)
if err != nil {
t.Error(err)
}
err = AddTimer4(newId, 3, cb, newN)
if err == nil {
t.Error("未检测到重复id")
}
for {
if len(imap) == 0 {
break
}
t.Log("剩余回调次数:", len(imap))
time.Sleep(time.Second)
}
}
func Test_Method2(t *testing.T) {
imap := make(map[int64]struct{})
var lockObj sync.Mutex
cb := func(obj interface{}) {
i := obj.(int64)
n := time.Now().Unix()
x := n - i
// 此处因为启动有暂停5s所以启动后最近的执行偏差在5s内
if x > 6 || x < -6 {
t.Errorf("错误的时间执行了回调函数 tick:%v now:%v", i, n)
}
lockObj.Lock()
defer lockObj.Unlock()
if _, exist := imap[i]; exist == false {
t.Error(i, "应该删除,不应该回调 Test_Method2")
}
delete(imap, i)
}
for i := 0; i < 20; i++ {
tick := time.Now().Unix() + int64(i)
imap[tick] = struct{}{}
AddTimer3(tick, cb, tick)
}
for {
if len(imap) == 0 {
break
}
t.Log("剩余回调次数:", len(imap))
time.Sleep(time.Second)
}
}

View File

@@ -0,0 +1,60 @@
package counter_util
import (
"time"
)
// CounterUtil
// @description: 固定窗口计数器辅助类
type CounterUtil struct {
tag string // tag counter标识
num int // num 当前计数
warnNum int // warnNum 警告数量
windowsTime time.Time // windowsTime 窗口时间
checkSameWindowsFun func(t1, t2 time.Time) bool // checkSameWindowsFun 比较是否同一个时间窗口
warnAction func(tag string, num int, t time.Time) // warnAction 监控回调方法
}
// NewCounterUtil
// @description: 构造计数器
// parameter:
// @_tag:tag标识会在WarnAction中传递回来
// @_warnNum:告警数量
// @_checkWindowFun:比较是否同一个时间窗口
// @_warnAction:指定数量触发回调
// return:
// @*CounterUtil:固定窗口计数器辅助类
func NewCounterUtil(_tag string, _warnNum int, _checkWindowFun func(t1, t2 time.Time) bool, _warnAction func(tag string, num int, t time.Time)) *CounterUtil {
r := &CounterUtil{
tag: _tag,
warnNum: _warnNum,
windowsTime: time.Now(),
checkSameWindowsFun: _checkWindowFun,
warnAction: _warnAction,
}
return r
}
// AddNum
// @description: 添加数量
// parameter:
// @receiver c:计数器
// @n:增加的数量
// return:
// @int:计数器当前的数量
func (c *CounterUtil) AddNum(n int) int {
if !c.checkSameWindowsFun(c.windowsTime, time.Now()) {
c.num = 0
c.windowsTime = time.Now()
}
// 增加次数
c.num += n
if c.num >= c.warnNum {
c.warnAction(c.tag, c.num, time.Now())
c.num = 0
}
return c.num
}

View File

@@ -0,0 +1,33 @@
package counter_util
import (
"fmt"
"testing"
"time"
)
func TestInfoLog(t *testing.T) {
var iserr bool = true
c := NewCounterUtil("test", 2, checkId, func(tag string, num int, ti time.Time) {
msg := fmt.Sprintf("tag:%s 当前数量为num:%v ti:%v", tag, num, ti)
if iserr {
t.Error(msg)
} else {
t.Log(msg)
}
})
c.AddNum(1)
iserr = false
c.AddNum(1)
time.Sleep(time.Second * 1)
iserr = true
c.AddNum(1)
time.Sleep(time.Second * 1)
c.AddNum(1)
}
func checkId(t1, t2 time.Time) bool {
return t1.Second() == t2.Second()
}

View File

@@ -0,0 +1,35 @@
### 窗口周期计数器
窗口周期计数类,用于记录一个窗口周期数量,并且触发某个操作的场景。
在下一个窗口周期会自动重置次数
#### =======================>使用方法说明<=========================
1.引入包
2.构造对象并次有
3.调用对象的增加次数方法
```go
package demo
import (
"time"
"goutil/counter_util"
)
func main() {
// 构造名字叫test的窗口间隔为1s计数达到2就会触发警告的窗口计数器
c := counter_util.NewCounterUtil("test", 2, checkId, func(tag string, num int, ti time.Time) {
//自定义触发动作
})
c.AddNum(1)
c.AddNum(10)
}
// 窗口周期设定为1s
func checkId(t1, t2 time.Time) bool {
return t1.Second() == t2.Second()
}
```

View File

@@ -0,0 +1,104 @@
package dbUtil
import (
"errors"
"time"
)
// 数据行结果
type DataRow struct {
// 所属数据表
table *DataTable
// 行的所有值
cells []interface{}
}
// 行的所有原始值
func (this *DataRow) CellOriginValues() []interface{} {
return this.cells
}
// 值的个数
func (this *DataRow) Len() int {
return len(this.cells)
}
// 单元格的字符串值可能为nil,如果有设置连接字符串parseTime=true则会有time.Time
// celIndex:单元格序号
// 返回值:
// interface{}:单元格的字符串值
// error:错误信息
func (this *DataRow) CellValue(celIndex int) (interface{}, error) {
if len(this.cells) <= celIndex {
return nil, errors.New("cell out of range")
}
// 检查是否为nil
if this.cells[celIndex] == nil {
return nil, nil
}
// 转换为字符串
switch this.cells[celIndex].(type) {
case []byte:
return string(this.cells[celIndex].([]byte)), nil
case string:
return this.cells[celIndex].(string), nil
case time.Time:
return this.cells[celIndex].(time.Time), nil
}
return nil, errors.New("unknown value type")
}
// 单元格的字符串值可能为nil,如果有设置连接字符串parseTime=true则会有time.Time
// cellName:单元格名称
// 返回值:
// interface{}:单元格的字符串值
// error:错误信息
func (this *DataRow) CellValueByName(cellName string) (interface{}, error) {
celIndex := this.table.cellIndex(cellName)
if celIndex < 0 {
return nil, errors.New("cell name no exist")
}
return this.CellValue(celIndex)
}
// 单元格的原始值
// celIndex:单元格序号
// 返回值:
// interface{}:单元格的字符串值
// error:错误信息
func (this *DataRow) OriginCellValue(celIndex int) (interface{}, error) {
if len(this.cells) <= celIndex {
return nil, errors.New("cell out of range")
}
return this.cells[celIndex], nil
}
// 单元格的原始值
// cellName:单元格名称
// 返回值:
// interface{}:单元格的字符串值
// error:错误信息
func (this *DataRow) OriginCellValueByName(cellName string) (interface{}, error) {
celIndex := this.table.cellIndex(cellName)
if celIndex < 0 {
return nil, errors.New("cell name no exist")
}
return this.OriginCellValue(celIndex)
}
// 创建单元格对象
// _table:所属表对象
// _cells:单元格的值集合
func newDataRow(_table *DataTable, _cells []interface{}) *DataRow {
return &DataRow{
table: _table,
cells: _cells,
}
}

View File

@@ -0,0 +1,189 @@
package dbUtil
import (
"database/sql"
"errors"
)
// 数据表结构
type DataTable struct {
// 行对象集合
rowData []*DataRow
// 列名称集合
columnNames map[string]int
}
// 数据表初始化
// rows:原始的数据行信息
// 返回值:
// error:初始化的错误信息
func (this *DataTable) init(rows *sql.Rows) error {
defer func() {
rows.Close()
}()
// 读取列信息和保存列名称
tmpColumns, errMsg := rows.Columns()
if errMsg != nil {
return errMsg
}
this.columnNames = make(map[string]int)
for index, val := range tmpColumns {
this.columnNames[val] = index
}
// 读取行数据
this.rowData = make([]*DataRow, 0)
columnCount := len(this.columnNames)
args := make([]interface{}, columnCount)
for rows.Next() {
values := make([]interface{}, columnCount)
for i := 0; i < columnCount; i++ {
args[i] = &values[i]
}
rows.Scan(args...)
this.rowData = append(this.rowData, newDataRow(this, values))
}
return nil
}
// 获取原始单元格值(一般为:string或[]byte
// rowIndex:行序号
// cellIndex:单元格序号
// 返回值:
// interface{}:原始单元格值(一般为:string或[]byte
// error:获取错误信息
func (this *DataTable) OriginCellValueByIndex(rowIndex int, cellIndex int) (interface{}, error) {
if len(this.rowData) <= rowIndex {
return nil, errors.New("row out of range")
}
rowItem := this.rowData[rowIndex]
if len(rowItem.cells) <= cellIndex {
return nil, errors.New("column out of range")
}
return rowItem.OriginCellValue(cellIndex)
}
// 获取原始单元格值(一般为:string或[]byte
// rowIndex:行序号
// cellIndex:单元格序号
// 返回值:
// interface{}:原始单元格值(一般为:string或[]byte
// error:获取错误信息
func (this *DataTable) OriginCellValueByCellName(rowIndex int, cellName string) (interface{}, error) {
if len(this.rowData) <= rowIndex {
return nil, errors.New("row out of range")
}
rowItem := this.rowData[rowIndex]
return rowItem.OriginCellValueByName(cellName)
}
// 获取字符串的单元格值有可能为nil
// rowIndex:行序号
// cellIndex:单元格序号
// 返回值:
// interface{}:字符串的单元格值有可能为nil
// error:获取错误信息
func (this *DataTable) CellValueByIndex(rowIndex int, cellIndex int) (interface{}, error) {
if len(this.rowData) <= rowIndex {
return nil, errors.New("row out of range")
}
rowItem := this.rowData[rowIndex]
if len(rowItem.cells) <= cellIndex {
return nil, errors.New("column out of range")
}
return rowItem.CellValue(cellIndex)
}
// 获取字符串的单元格值有可能为nil
// rowIndex:行序号
// cellIndex:单元格序号
// 返回值:
// interface{}:字符串的单元格值有可能为nil
// error:获取错误信息
func (this *DataTable) CellValueByName(rowIndex int, cellName string) (interface{}, error) {
if len(this.rowData) <= rowIndex {
return nil, errors.New("row out of range")
}
rowItem := this.rowData[rowIndex]
return rowItem.CellValueByName(cellName)
}
// 获取行对象
// rowIndex:行序号
// 返回值:
// *DataRow:行对象
// error:错误信息
func (this *DataTable) Row(rowIndex int) (*DataRow, error) {
if len(this.rowData) <= rowIndex {
return nil, errors.New("row out of range")
}
return this.rowData[rowIndex], nil
}
// 根据列名获取列序号
// cellName:列名
// 返回值:
// int:列序号
func (this *DataTable) cellIndex(cellName string) int {
cellIndex, isExist := this.columnNames[cellName]
if isExist == false {
return -1
}
return cellIndex
}
// 获取所有列的名字
// 返回值:
// []string:列字段名集合
func (this *DataTable) Columns() []string {
result := make([]string, len(this.columnNames))
for key, val := range this.columnNames {
result[val] = key
}
return result
}
// 获取列数量
// 返回值:
// int:列数量
func (this *DataTable) ColumnCount() int {
return len(this.columnNames)
}
// 获取数据行数
// 返回值:
// int:行数
func (this *DataTable) RowCount() int {
return len(this.rowData)
}
// 新建数据表对象
// rows:数据行对象
// 返回值:
// *DataTable:数据表对象
// error:错误信息
func NewDataTable(rows *sql.Rows) (*DataTable, error) {
table := &DataTable{}
errMsg := table.init(rows)
if errMsg != nil {
return nil, errMsg
}
return table, nil
}

View File

@@ -0,0 +1,195 @@
package dbUtil
import (
"fmt"
"time"
"goutil/typeUtil"
)
// 类型转换为byte
// 返回值:
// byte:结果
// error:错误数据
func Byte(row *DataRow, key string) (byte, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return 0, errMsg
}
if val == nil {
return 0, fmt.Errorf("value is nil")
}
return typeUtil.Byte(val)
}
// 类型转换为int
// 返回值:
// int:结果
// error:错误数据
func Int32(row *DataRow, key string) (int32, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return 0, errMsg
}
if val == nil {
return 0, fmt.Errorf("value is nil")
}
return typeUtil.Int32(val)
}
// 类型转换为uint32
// 返回值:
// int:结果
// error:错误数据
func Uint32(row *DataRow, key string) (uint32, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return 0, errMsg
}
if val == nil {
return 0, fmt.Errorf("value is nil")
}
return typeUtil.Uint32(val)
}
// 类型转换为int
// 返回值:
// int:结果
// error:错误数据
func Int(row *DataRow, key string) (int, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return 0, errMsg
}
if val == nil {
return 0, fmt.Errorf("value is nil")
}
return typeUtil.Int(val)
}
// 类型转换为int
// 返回值:
// int:结果
// error:错误数据
func Uint(row *DataRow, key string) (uint, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return 0, errMsg
}
if val == nil {
return 0, fmt.Errorf("value is nil")
}
return typeUtil.Uint(val)
}
// 类型转换为int
// 返回值:
// int:结果
// error:错误数据
func Int64(row *DataRow, key string) (int64, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return 0, errMsg
}
if val == nil {
return 0, fmt.Errorf("value is nil")
}
return typeUtil.Int64(val)
}
// 类型转换为int
// 返回值:
// int:结果
// error:错误数据
func Uint64(row *DataRow, key string) (uint64, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return 0, errMsg
}
if val == nil {
return 0, fmt.Errorf("value is nil")
}
return typeUtil.Uint64(val)
}
// 类型转换为int
// 返回值:
// float64:结果
// error:错误数据
func Float64(row *DataRow, key string) (float64, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return 0, errMsg
}
if val == nil {
return 0, fmt.Errorf("value is nil")
}
return typeUtil.Float64(val)
}
// 类型转换为bool
// 返回值:
// bool:结果
// error:错误信息
func Bool(row *DataRow, key string) (bool, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return false, errMsg
}
if val == nil {
return false, fmt.Errorf("value is nil")
}
return typeUtil.Bool(val)
}
// 类型转换为字符串
// 返回值:
// string:结果
// error:错误信息
func String(row *DataRow, key string) (string, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return "", errMsg
}
if val == nil {
return "", fmt.Errorf("value is nil")
}
return typeUtil.String(val)
}
// 转换为时间格式如果是字符串则要求内容格式形如2017-02-14 05:20:00
// 返回值:
// bool:结果
// error:错误信息
func DateTime(row *DataRow, key string) (time.Time, error) {
val, errMsg := row.CellValueByName(key)
if errMsg != nil {
return time.Time{}, errMsg
}
if val == nil {
return time.Time{}, fmt.Errorf("value is nil")
}
return typeUtil.DateTime(val)
}

View File

@@ -0,0 +1,66 @@
package debugUtil
import (
"github.com/fatih/color"
)
var (
isDebug = false
code Code = Code_Bold
foregroundColor ForegroundColor = Foreground_Purple
backgroundColor BackgroundColor = BackgroundColor_Black
colorObj = color.New(foregroundColor, code)
)
// 设置DEBUG状态
// _isDebug是否是DEBUG
func SetDebug(_isDebug bool) {
isDebug = _isDebug
}
// 是否处于调试状态
func IsDebug() bool {
return isDebug
}
// 设置显示信息
func SetDisplayInfo(_code Code, _foregroundColor ForegroundColor, _backgroundColor BackgroundColor) {
code = _code
foregroundColor = _foregroundColor
backgroundColor = _backgroundColor
colorObj = color.New(foregroundColor, code)
}
// Print formats using the default formats for its operands and writes to standard output.
// Spaces are added between operands when neither is a string.
// It returns the number of bytes written and any write error encountered.
func Print(a ...interface{}) {
if !isDebug {
return
}
_, _ = colorObj.Print(a...)
}
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) {
if !isDebug {
return
}
_, _ = colorObj.Printf(format, a...)
}
// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Println(a ...interface{}) {
if !isDebug {
return
}
_, _ = colorObj.Println(a...)
}

View File

@@ -0,0 +1,47 @@
package debugUtil
import "github.com/fatih/color"
// Code 显示代码
type Code = color.Attribute
const (
Code_Reset Code = color.Reset
Code_Bold Code = color.Bold
Code_Faint Code = color.Faint
Code_Italic Code = color.Italic
Code_Underline Code = color.Underline
Code_BlinkSlow Code = color.BlinkSlow
Code_BlinkRapid Code = color.BlinkRapid
Code_ReverseVideo Code = color.ReverseVideo
Code_Concealed Code = color.Concealed
Code_CrossedOut Code = color.CrossedOut
)
// ForegroundColor 前景色
type ForegroundColor = color.Attribute
const (
Foreground_Black ForegroundColor = color.FgBlack
Foreground_Red ForegroundColor = color.FgRed
Foreground_Green ForegroundColor = color.FgGreen
Foreground_Yellow ForegroundColor = color.FgYellow
Foreground_Blue ForegroundColor = color.FgBlue
Foreground_Purple ForegroundColor = color.FgMagenta
Foreground_Cyan ForegroundColor = color.FgCyan
Foreground_White ForegroundColor = color.FgWhite
)
// BackgroundColor 背景色
type BackgroundColor = color.Attribute
const (
BackgroundColor_Black = color.BgBlack
BackgroundColor_Red = color.BgRed
BackgroundColor_Green = color.BgGreen
BackgroundColor_Yellow = color.BgYellow
BackgroundColor_Blue = color.BgBlue
BackgroundColor_Purple = color.BgMagenta
BackgroundColor_Cyan = color.BgCyan
BackgroundColor_White = color.BgWhite
)

View File

@@ -0,0 +1,4 @@
/*
提供调试功能的助手包
*/
package debugUtil

View File

@@ -0,0 +1,81 @@
package deviceUtil
import (
"strings"
)
// 将MAC地址转化为标准格式
func ConvertMacToStandardFormat(mac string) string {
if mac == "" || mac == "00:00:00:00:00:00" || mac == "02:00:00:00:00:00" {
return ""
}
//如果mac的长度不为12或17则是不正确的格式
if len(mac) != 12 && len(mac) != 17 {
return ""
}
//转化为大写
mac = strings.ToUpper(mac)
//如果mac地址的长度为17已经有:),则直接返回
if len(mac) == 17 {
return mac
}
//如果没有分隔符,则添加分隔符
newMac := make([]rune, 0, 17)
for i, v := range []rune(mac) {
newMac = append(newMac, v)
if i < len(mac) - 1 && i % 2 == 1 {
newMac = append(newMac, ':')
}
}
return string(newMac)
}
func ConvertIdfaToStandardFormat(idfa string) string {
//如果是空或默认值则返回String.Empty
if idfa == "" || idfa == "00000000-0000-0000-0000-000000000000" {
return ""
}
//如果idfa的长度不为32或36则代表是Android的数据则可以直接返回
if len(idfa) != 32 && len(idfa) != 36 {
return idfa
}
//转化为大写
idfa = strings.ToUpper(idfa);
//如果idfa地址的长度为36已经有:),则直接返回
if len(idfa) == 36 {
return idfa
}
//如果没有分隔符,则添加分隔符
newIdfa := make([]rune, 0, 36)
for i, v := range []rune(idfa) {
newIdfa = append(newIdfa, v)
if i == 7 || i == 11 || i == 15 || i == 19 {
newIdfa = append(newIdfa, '-')
}
}
return string(newIdfa)
}
// 根据MAC和IDFA获取唯一标识
func GetIdentifier(mac, idfa string) string {
mac = ConvertMacToStandardFormat(mac)
idfa = ConvertIdfaToStandardFormat(idfa);
//如果idfa不为空则使用idfa否则使用mac
if idfa != "" {
return idfa
} else {
return mac
}
}

View File

@@ -0,0 +1,131 @@
package deviceUtil
import (
"testing"
)
func TestConvertMacToStarndardFormat(t *testing.T) {
mac := ""
expected := ""
got := ConvertMacToStandardFormat(mac)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
mac = "00:00:00:00:00:00"
expected = ""
got = ConvertMacToStandardFormat(mac)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
mac = "02:00:00:00:00:00"
expected = ""
got = ConvertMacToStandardFormat(mac)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
mac = "02:00:00:00:00"
expected = ""
got = ConvertMacToStandardFormat(mac)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
mac = "020000000020"
expected = "02:00:00:00:00:20"
got = ConvertMacToStandardFormat(mac)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
mac = "02:00:00:00:00:20"
expected = "02:00:00:00:00:20"
got = ConvertMacToStandardFormat(mac)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
}
func TestConvertIdfaToStandardFormat(t *testing.T) {
idfa := ""
expected := ""
got := ConvertIdfaToStandardFormat(idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
idfa = "00000000-0000-0000-0000-000000000000"
expected = ""
got = ConvertIdfaToStandardFormat(idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
idfa = "00000000-0000-0000-0000-000000000000-123"
expected = "00000000-0000-0000-0000-000000000000-123"
got = ConvertIdfaToStandardFormat(idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
idfa = "00000000-1234-5678-0000-000000000000"
expected = "00000000-1234-5678-0000-000000000000"
got = ConvertIdfaToStandardFormat(idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
idfa = "00000000123456780000000000000000"
expected = "00000000-1234-5678-0000-000000000000"
got = ConvertIdfaToStandardFormat(idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
}
func TestGetIdentifier(t *testing.T) {
mac := ""
idfa := ""
expected := ""
got := GetIdentifier(mac, idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
mac = "00:00:00:00:00:00"
expected = ""
got = GetIdentifier(mac, idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
mac = "02:00:00:00:00:00"
expected = ""
got = GetIdentifier(mac, idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
mac = "020000000020"
expected = "02:00:00:00:00:20"
got = GetIdentifier(mac, idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
mac = "02:00:00:00:00:20"
expected = "02:00:00:00:00:20"
got = GetIdentifier(mac, idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
idfa = "00000000123456780000000000000000"
expected = "00000000-1234-5678-0000-000000000000"
got = GetIdentifier(mac, idfa)
if got != expected {
t.Errorf("Expected: %s, but got:%s", expected, got)
}
}

View File

@@ -0,0 +1,240 @@
package dfaExUtil
/*
* 扩展DFA算法
*
* 一种二层树实现类DFA算法DFA为多层树结构go语言特性中的map结构过于“重量级”导致内存占用很大此外还可能存在大量相同字符结点
*
* 第一层map为所有字母/汉字作为keyvalue为第二层map
* 第二层map为第一层冲突字母/汉字的自定义hash作为keyvalue指示是否为敏感词结束标识
*
* 测试结果50万+的敏感词:
* 构造树耗时稍优于原始DFA
* 内存使用为原DFA的不到1/4原DFA占用495M内存此算法使用111M
* 查询效率比原DFA低10%~20%左右主要是多一次map查询和多一次hash计算
*
*/
/*
* 注意使用[]rune的问题(此问题已通过使用固定位数hash解决)
* []rune中文使用的是unicode编码若“中”编码为#4E2D而#4E2D对应“N-”;
* 即:"N-"与"中"unicode编码均为#4E2D即会产生hash冲突
*
*/
import (
"fmt"
"strings"
hash "goutil/dfaExUtil/hash64"
)
// hash使用的类型(uint64对应hash64函数uint32对应hash32函数)
type hashType = uint64
type DFAEx struct {
// 忽略大小写true-忽略大小写false-大小写敏感
ignoreCase bool
// hash冲突个数
hashCollisions int
// 树根
// 字符/hash/uint8(b10000000)(最高字符表示是否结束/低7位表示字符位置)
root map[rune]map[hashType]uint8
}
// 新建敏感词对象
// wordList - 敏感词列表
// ignoreCase - [可选默认false] 是否忽略大小写
func NewDFAEx(wordList []string, ignoreCase ...bool) (dfaEx *DFAEx) {
var iCase bool
var mapSize int
if len(ignoreCase) > 0 {
iCase = ignoreCase[0]
}
mapSize = len(wordList) * 10
// 防止过小
if mapSize < 1_000 {
mapSize = 1_000
}
// 通常各语言的单rune非重复数不超过1万
if mapSize > 10_000 {
mapSize = 10_000
}
dfaEx = &DFAEx{
ignoreCase: iCase,
root: make(map[rune]map[hashType]uint8, mapSize),
}
for _, v := range wordList {
word := v
if iCase {
// 忽略大小写;所有字母转大写
word = strings.ToUpper(word)
}
wordRune := []rune(word)
if len(wordRune) > 0 {
dfaEx.InsertWord(wordRune)
}
}
return dfaEx
}
// 添加敏感词
func (dfaEx *DFAEx) InsertWord(word []rune) {
var hs hashType
var lastWord rune
var lastHash hashType
for i, v := range word {
lastWord = v
lastHash = hs
if wdInfo, ok := dfaEx.root[v]; ok {
// "字"已存在
if hsV, ok := wdInfo[hs]; !ok {
// hash不存在添加hash
wdInfo[hs] = uint8(i & 0x7F) // 第i位
} else {
// hash已存在检测是否冲突
if (hsV & 0x7F) != uint8(i) {
// hash冲突
dfaEx.hashCollisions++
// fmt.Printf("hash冲突 %s %016X %d %d\n", string(v), hs, i+1, hsV&0x7F+1)
}
}
} else {
// "字"不存在,添加"字"和hash
wdInfo = make(map[hashType]uint8)
wdInfo[hs] = uint8(i & 0x7F) // 第i位
dfaEx.root[v] = wdInfo
}
hs = hash.FastSumByRune2(v, hs) // hash更新
}
// 敏感词结束标志(uint8最高位置1)
dfaEx.root[lastWord][lastHash] |= 0x80
}
// 字符串查找敏感词
func (dfaEx *DFAEx) IsMatch(str string) bool {
starts, _ := dfaEx.SearchSentence(str, true)
return len(starts) > 0
}
// 指定字符替换敏感词
func (dfaEx *DFAEx) HandleWord(str string, replace rune) string {
starts, ends := dfaEx.SearchSentence(str)
if len(starts) == 0 {
return str
}
strRune := []rune(str)
for i := 0; i < len(starts); i++ {
for idx := starts[i]; idx <= ends[i]; idx++ {
strRune[idx] = replace
}
}
return string(strRune)
}
// 字符串查找敏感词
func (dfaEx *DFAEx) SearchSentence(str string, firstOpt ...bool) (starts, ends []int) {
var first bool // 是否首次匹配就返回
if len(firstOpt) > 0 {
first = firstOpt[0]
}
strBak := str
if dfaEx.ignoreCase {
// 忽略大小写;所有字母转大写
strBak = strings.ToUpper(str)
}
runeStr := []rune(strBak)
for i := 0; i < len(runeStr); {
end := dfaEx.searchByStart(i, runeStr)
if end < 0 {
// 继续下一个进行匹配
i++
} else {
// 记录匹配位置;从匹配到的下一个位置继续
starts = append(starts, i)
ends = append(ends, end)
if first {
// 首次匹配就返回
break
}
i = end + 1
}
}
return
}
// 从指定的开始位置搜索语句
// start - 开始匹配的位置
// str - 待检测字符串
// 返回:匹配到的结束位置,未匹配到返回-1
func (dfaEx *DFAEx) searchByStart(start int, runeStr []rune) (end int) {
var hs hashType
end = -1 // 未匹配到返回值
for i := start; i < len(runeStr); i++ {
wd := runeStr[i]
wdInfo, ok := dfaEx.root[wd]
if !ok {
// "字"不存在
break
}
hsV, ok := wdInfo[hs]
if !ok {
// hash不存在
break
}
// 检测是否句尾
if (hsV & 0x80) != 0 {
// 找到句尾,继续匹配,直到匹配到最长敏感词为止
end = i
}
hs = hash.FastSumByRune2(wd, hs) // hash更新
}
return
}
// 获取hash冲突数
func (dfaEx *DFAEx) GetHashCollisions() int {
return dfaEx.hashCollisions
}
// 调试接口
func (dfaEx *DFAEx) Print() {
fmt.Println(dfaEx)
}
// 调试接口
func (dfaEx *DFAEx) PrintFmt(verbose bool) {
var keys int
var hashs int
for k, v := range dfaEx.root {
keys++
if verbose {
fmt.Println("---------------------------")
fmt.Println(string(k))
}
for kk, vv := range v {
hashs++
if verbose {
fmt.Printf("%016X %02X\n", kk, vv)
}
}
}
fmt.Println("================================")
fmt.Println("keys:", keys, "hashs", hashs, "map count", keys+1, "hashCollisions Count", dfaEx.hashCollisions)
}

View File

@@ -0,0 +1,15 @@
package dfaExUtil
import "testing"
func TestHandleWord(t *testing.T) {
strs := []string{"ABC", "1234", "测试", "测试代码", "测试一下"}
dfaEx1 := NewDFAEx(strs)
str := dfaEx1.HandleWord("abc按了数字12345来测试代码是否正常结果测试出了bug", '*')
t.Log(str)
dfaEx2 := NewDFAEx(strs, true)
str = dfaEx2.HandleWord("abc按了数字12345来测试代码是否正常结果测试出了bug", '*')
t.Log(str)
}

View File

@@ -0,0 +1,98 @@
package hash32
/*
* ***注意***
*
* Sum 使用的是[]byte参数string中文是utf-8编码
* SumByRune 使用的是[]rune参数中文使用的是unicode编码
*
* 两种参数中文编码不同同一个string调用两个接口得到的hash是不同的 这是要特别注意的
*
* 对string进行for range操作会自动被[]rune化一定要注意
*
*/
const shiftBit = 11 // 每个字节移位数(测试经验值11)
const reverseBit = 32 - shiftBit
// 快速Hash计算(*** 注意只取了rune低16位进行hash计算计算结果与SumByRune不一致 ***)
func FastSumByRune2(in rune, hs uint32) (out uint32) {
out = ((hs << shiftBit) | (hs >> reverseBit)) + uint32(byte(in>>8))
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in))
return
}
// 快速Hash计算(*** 此计算结果与SumByRune一致 ***)
func FastSumByRune4(in rune, hs uint32) (out uint32) {
out = ((hs << shiftBit) | (hs >> reverseBit)) + uint32(byte(in>>24))
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in>>16))
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in>>8))
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in))
return
}
// 原Hash值参数重
func hsArg(hsOpt ...uint32) (out uint32) {
out = uint32(0)
if len(hsOpt) > 0 {
out = hsOpt[0]
}
return
}
// Hash计算
// in - 待hash串
// hsOpt - 原hash值(在此基础上继续hash)
func Sum(in []byte, hsOpt ...uint32) (out uint32) {
out = hsArg(hsOpt...)
for _, v := range in {
out = ((out << shiftBit) | (out >> reverseBit)) + uint32(v)
}
return
}
// Hash计算
func SumByRune(in rune, hsOpt ...uint32) (out uint32) {
// rune转[]byte
inVal := make([]byte, 4)
inVal[0] = byte(in >> 24)
inVal[1] = byte(in >> 16)
inVal[2] = byte(in >> 8)
inVal[3] = byte(in)
// *** 经实际测试:不加以下代码运行效率更高 ***
// 去除前面多余的\x00
// for {
// if len(inVal) <= 1 {
// // 以免全0异常至少要保留1位
// break
// }
// if inVal[0] == 0 {
// inVal = inVal[1:]
// } else {
// break
// }
// }
// 规避hash冲突"N-"与"中"unicode编码均为#4E2D即会产生hash冲突
// 若长度>1(即非常规ASCII)所有字节最高位置1
// if len(inVal) > 1 {
// for i := 0; i < len(inVal); i++ {
// inVal[i] |= 0x80
// }
// }
out = Sum(inVal, hsOpt...)
return
}
// Hash计算
func SumByRunes(in []rune, hsOpt ...uint32) (out uint32) {
out = hsArg(hsOpt...)
for _, v := range in {
out = SumByRune(v, out)
}
return
}

View File

@@ -0,0 +1,98 @@
package hash64
/*
* ***注意***
*
* Sum 使用的是[]byte参数string中文是utf-8编码
* SumByRune 使用的是[]rune参数中文使用的是unicode编码
*
* 两种参数中文编码不同同一个string调用两个接口得到的hash是不同的 这是要特别注意的
*
* 对string进行for range操作会自动被[]rune化一定要注意
*
*/
const shiftBit = 23 // 每个字节移位数(测试经验值23)
const reverseBit = 64 - shiftBit
// 快速Hash计算(*** 注意只取了rune低16位进行hash计算计算结果与SumByRune不一致 ***)
func FastSumByRune2(in rune, hs uint64) (out uint64) {
out = ((hs << shiftBit) | (hs >> reverseBit)) + uint64(byte(in>>8))
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in))
return
}
// 快速Hash计算(*** 此计算结果与SumByRune一致 ***)
func FastSumByRune4(in rune, hs uint64) (out uint64) {
out = ((hs << shiftBit) | (hs >> reverseBit)) + uint64(byte(in>>24))
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in>>16))
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in>>8))
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in))
return
}
// 原Hash值参数重
func hsArg(hsOpt ...uint64) (out uint64) {
out = uint64(0)
if len(hsOpt) > 0 {
out = hsOpt[0]
}
return
}
// Hash计算
// in - 待hash串
// hsOpt - 原hash值(在此基础上继续hash)
func Sum(in []byte, hsOpt ...uint64) (out uint64) {
out = hsArg(hsOpt...)
for _, v := range in {
out = ((out << shiftBit) | (out >> reverseBit)) + uint64(v)
}
return
}
// Hash计算
func SumByRune(in rune, hsOpt ...uint64) (out uint64) {
// rune转[]byte
inVal := make([]byte, 4)
inVal[0] = byte(in >> 24)
inVal[1] = byte(in >> 16)
inVal[2] = byte(in >> 8)
inVal[3] = byte(in)
// *** 经实际测试:不加以下代码运行效率更高 ***
// 去除前面多余的\x00
// for {
// if len(inVal) <= 1 {
// // 以免全0异常至少要保留1位
// break
// }
// if inVal[0] == 0 {
// inVal = inVal[1:]
// } else {
// break
// }
// }
// 规避hash冲突"N-"与"中"unicode编码均为#4E2D即会产生hash冲突
// 若长度>1(即非常规ASCII)所有字节最高位置1
// if len(inVal) > 1 {
// for i := 0; i < len(inVal); i++ {
// inVal[i] |= 0x80
// }
// }
out = Sum(inVal, hsOpt...)
return
}
// Hash计算
func SumByRunes(in []rune, hsOpt ...uint64) (out uint64) {
out = hsArg(hsOpt...)
for _, v := range in {
out = SumByRune(v, out)
}
return
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

219
trunk/goutil/dfaUtil/dfa.go Normal file
View File

@@ -0,0 +1,219 @@
package dfaUtil
import "strings"
/*
DFA util, is used to verify whether a sentence has invalid words.
The underlying data structure is trie.
https://en.wikipedia.org/wiki/Trie
*/
// dfa util
type DFAUtil struct {
// The root node
root *trieNode
}
// 搜索语句
// 由于go不支持tuple所以为了避免定义多余的struct特别使用两个list来分别返回匹配的索引的上界和下界
// 在处理此方法的返回值时,需要两者配合使用
// 参数:
//
// sentence语句字符串
//
// 返回:
//
// 搜索到的开始位置列表
// 搜索到的结束位置列表
func (this *DFAUtil) SearchSentence(sentence string) (startIndexList, endIndexList []int) {
sentenceRuneList := []rune(sentence)
for i := 0; i < len(sentenceRuneList); {
//按序匹配每个字
end := this.searchSentenceByStart(i, sentenceRuneList)
if end < 0 {
//匹配失败,继续匹配下一个字
i++
} else {
//匹配成功,记录索引位置
startIndexList = append(startIndexList, i)
endIndexList = append(endIndexList, end)
//从匹配到的字后面开始找
i = end + 1
}
}
return
}
// 从指定的开始位置搜索语句
// 参数:
//
// start开始匹配的位置
// sentenceRuneList语句字列表
//
// 返回:
//
// 匹配到的结束位置,未匹配到返回-1
func (this *DFAUtil) searchSentenceByStart(start int, sentenceRuneList []rune) (endIndex int) {
//当前节点,从根节点开始找
currNode := this.root
//是否匹配到
var isMatched bool
//按顺序匹配字
for i := start; i < len(sentenceRuneList); {
child, exists := currNode.children[sentenceRuneList[i]]
//未匹配到则结束,跳出循环(可能匹配到过词结尾)
if !exists {
break
}
//是否是词末尾,如果是则先记录下来,因为还可能匹配到更长的词
//比如["金鳞"、"金鳞岂是池中物"] => 匹配"金鳞岂是池中物",匹配到"金鳞"不应该停下来,应继续匹配更长的词
if child.isEndOfWord {
endIndex = i
isMatched = true
}
//是否已经到词末尾
if len(child.children) == 0 {
return endIndex
} else {
//继续与后面的字匹配
currNode = child
}
//增加索引匹配下一个位置
i++
}
//匹配结束,若曾经匹配到词末尾,则直接返回匹配到的位置
if isMatched {
return endIndex
} else {
//没有匹配到词末尾,则返回匹配失败
return -1
}
}
// Insert new word into object
func (this *DFAUtil) InsertWord(word []rune) {
currNode := this.root
for _, c := range word {
if cildNode, exist := currNode.children[c]; !exist {
cildNode = newtrieNode()
currNode.children[c] = cildNode
currNode = cildNode
} else {
currNode = cildNode
}
}
currNode.isEndOfWord = true
}
// Check if there is any word in the trie that starts with the given prefix.
func (this *DFAUtil) StartsWith(prefix []rune) bool {
currNode := this.root
for _, c := range prefix {
if childNode, exist := currNode.children[c]; !exist {
return false
} else {
currNode = childNode
}
}
return true
}
// Judge if input sentence contains some special caracter
// Return:
// Matc or not
func (this *DFAUtil) IsMatch(sentence string) bool {
startIndexList, _ := this.SearchSentence(sentence)
return len(startIndexList) > 0
}
// Handle sentence. Use specified caracter to replace those sensitive caracters.
// input: Input sentence
// replaceCh: candidate
// Return:
// Sentence after manipulation
func (this *DFAUtil) HandleWord(sentence string, replaceCh rune) string {
startIndexList, endIndexList := this.SearchSentence(sentence)
if len(startIndexList) == 0 {
return sentence
}
// Manipulate
sentenceList := []rune(sentence)
for i := 0; i < len(startIndexList); i++ {
for index := startIndexList[i]; index <= endIndexList[i]; index++ {
sentenceList[index] = replaceCh
}
}
return string(sentenceList)
}
// Handle sentence. Use specified caracter to replace those sensitive caracters.
// input: Input sentence
// replaceCh: candidate
// Return:
// Sentence after manipulation
func (this *DFAUtil) HandleWordUseStr(input string, replaceCh string) string {
input2 := strings.ToUpper(input)
startIndexList, endIndexList := this.SearchSentence(input2)
if len(startIndexList) == 0 {
return input
}
// Manipulate
inputRune := []rune(input)
replaceChList := []rune(replaceCh)
//上一次替换掉的数量
lastReplaceCount := 0
for i := 0; i < len(startIndexList); i++ {
//替换字的索引
index := len(replaceChList)
//开始位置--加上替换的词的索引
starIndex := startIndexList[i] + (i * index) - lastReplaceCount
//结束位置
endIndex := endIndexList[i] + (i * index) - lastReplaceCount
//结束字符串
sentenceAttr := string(inputRune[endIndex+1:])
//替换范围字符串
inputRune = append(inputRune[:starIndex], replaceChList...)
inputRune = append(inputRune, []rune(sentenceAttr)...)
lastReplaceCount = endIndex + 1 - starIndex
}
return string(inputRune)
}
// Create new DfaUtil object
// wordList:word list
func NewDFAUtil(wordList []string) *DFAUtil {
this := &DFAUtil{
root: newtrieNode(),
}
for _, word := range wordList {
wordRuneList := []rune(word)
if len(wordRuneList) > 0 {
this.InsertWord(wordRuneList)
}
}
return this
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
package dfaUtil
const (
INIT_TRIE_CHILDREN_NUM = 0
)
// trieNode data structure
// trieNode itself doesn't have any value. The value is represented on the path
type trieNode struct {
// if this node is the end of a word
isEndOfWord bool
// the collection of children of this node
children map[rune]*trieNode
}
// Create new trieNode
func newtrieNode() *trieNode {
return &trieNode{
isEndOfWord: false,
children: make(map[rune]*trieNode, INIT_TRIE_CHILDREN_NUM),
}
}

View File

@@ -0,0 +1,9 @@
#go语言项目约定
1、如果在函数内部出现错误需记录该错误然后抛出错误直至回退到函数最外层进行处理。(对于项目初始化的函数还需调用panic结束协程
2、对于包内逻辑独立完整的类型才需建立子包单独存放;对于其他情况,请在同一级包内进行处理。(注意:包与文件夹概念不同,轻易建立子包会造成数据与接口的暴露)
3、对于变量一律以小写开头如果该变量需要对包外提供提供Get,Set接口进行数据访问。
4、对于常量如果该常量仅包内可见前缀小写con_如果包外可见前缀大写Con_
5、对于自定义类型如果仅包内可见小写开头如果包外可见大写开头。类型内部字段名默认小写开头需要序列化的字段大写。
6、对于通道chan,如果仅包内可见,小写开头;如果包外可见,大写开头,此处不提供接口访问。
7、在函数参数中出现的变量若与包内数据同名统一以_开头加以区分。
8、对于需要向包外提供的数据如果不需要修改统一返回副本如果需要在包外修改请返回指针。

View File

@@ -0,0 +1,3 @@
DefaultLogPath/
/test_*/

View File

@@ -0,0 +1,54 @@
package ensureSendUtil
import (
"fmt"
)
/*
实现sender接口
*/
type baseSender struct {
// 待发送的数据channel
waitingDataChan chan dataItem
// 失败数据缓存
cachedDataChan chan dataItem
// 用于停止协程
done chan struct{}
}
func newBaseSender() *baseSender {
return &baseSender{
waitingDataChan: make(chan dataItem, 1024),
cachedDataChan: make(chan dataItem, 1024000),
done: make(chan struct{}),
}
}
// Sender接口
// Send:
func (this *baseSender) Send() error {
// baseSender不实现发送
// 由tcpSender和httpSender实现发送
return fmt.Errorf("baseSender dose not have Send Method")
}
// Sender接口
// Data: 返回待发送的数据channel
func (this *baseSender) Data() <-chan dataItem {
return this.waitingDataChan
}
// Sender接口
// Cache返回失败数据缓存channel
func (this *baseSender) Cache() chan dataItem {
return this.cachedDataChan
}
// Sender接口
// Done返回channel用于判断是否关闭
func (this *baseSender) Done() <-chan struct{} {
return this.done
}

View File

@@ -0,0 +1,54 @@
package bytesSendUtil
import (
"fmt"
)
/*
实现sender接口
*/
type baseSender struct {
// 待发送的数据channel
waitingDataChan chan dataItem
// 失败数据缓存
cachedDataChan chan dataItem
// 用于停止协程
done chan struct{}
}
func newBaseSender() *baseSender {
return &baseSender{
waitingDataChan: make(chan dataItem, 1024),
cachedDataChan: make(chan dataItem, 1024000),
done: make(chan struct{}),
}
}
// Sender接口
// Send:
func (this *baseSender) Send() error {
// baseSender不实现发送
// 由tcpSender和httpSender实现发送
return fmt.Errorf("baseSender dose not have Send Method")
}
// Sender接口
// Data: 返回待发送的数据channel
func (this *baseSender) Data() <-chan dataItem {
return this.waitingDataChan
}
// Sender接口
// Cache返回失败数据缓存channel
func (this *baseSender) Cache() chan dataItem {
return this.cachedDataChan
}
// Sender接口
// Done返回channel用于判断是否关闭
func (this *baseSender) Done() <-chan struct{} {
return this.done
}

View File

@@ -0,0 +1,101 @@
package bytesSendUtil
import (
"goutil/zlibUtil"
)
type dataItem interface {
// 返回原始数据
OriginData() []byte
// 返回发送字节流
Bytes() []byte
// 设置发送次数
SetCount(uint)
// 返回发送次数
Count() uint
}
/////////////////////////////////////////////////
// httpDataItem
type httpDataItem struct {
// 数据
data []byte
// 发送次数
count uint
}
func newHTTPData(_data []byte) dataItem {
return &httpDataItem{
data: _data,
count: 0,
}
}
// 返回原始数据
func (this *httpDataItem) OriginData() []byte {
return this.data
}
// 返回原始数据用于发送
func (this *httpDataItem) Bytes() []byte {
return this.data
}
func (this *httpDataItem) SetCount(cnt uint) {
this.count = cnt
}
func (this *httpDataItem) Count() uint {
return this.count
}
/////////////////////////////////////////////////
// tcpDataItem
type tcpDataItem struct {
// 原始数据
origin []byte
// 压缩后数据
data []byte
// 重试次数
count uint
}
func newTCPDataItem(_data []byte) (dataItem, error) {
compressed, err := zlibUtil.Compress([]byte(_data), 5)
if err != nil {
return nil, err
}
item := &tcpDataItem{
origin: _data,
data: compressed,
count: 0,
}
return item, nil
}
// 返回原始数据
func (this *tcpDataItem) OriginData() []byte {
return this.origin
}
// 返回压缩数据用于发送
func (this *tcpDataItem) Bytes() []byte {
return this.data
}
func (this *tcpDataItem) SetCount(cnt uint) {
this.count = cnt
}
func (this *tcpDataItem) Count() uint {
return this.count
}

View File

@@ -0,0 +1,29 @@
package bytesSendUtil
/*
ensureSendUtil 用于推送数据
支持TCP和HTTP两种形式在发送失败时会缓存数据并在一定时间间隔后重试
通过NewTCPSender和NewHTTPSender两个接口分别创建TCP和HTTP模式的EnsureSender
type EnsureSender interface {
// 用于发送数据
Write([]byte) error
// 用于停止发送,此时会自动保存未发送数据
Close() error
}
// 创建一个tcp数据发送器
// 参数:
// _dataFolder 数据存放目录
// _address 连接地址
func NewTCPSender(_dataFolder, _address string) (EnsureSender, error) {
// 创建一个http数据发送器
// 参数:
// _dataFolder 数据存放目录
// _url 发送地址
func NewHTTPSender(_dataFolder, _url string) (EnsureSender, error) {
*/

View File

@@ -0,0 +1,24 @@
package bytesSendUtil
type EnsureSender interface {
// use Write to send data
Write([]byte) error
// stop sender
Close() error
}
// resend和dataSaver通过此接口调用tcpSender与httpSender
type sender interface {
// 发送数据
Send(dataItem) error
// 返回待发送的数据channel
Data() <-chan dataItem
// 返回失败数据缓存channel
Cache() chan dataItem
// 用于判断是否关闭
Done() <-chan struct{}
}

View File

@@ -0,0 +1,96 @@
package bytesSendUtil
import (
"fmt"
"goutil/webUtil"
)
// 实现 EnsureSender和sender接口
type httpSender struct {
// 需要实现的接口
EnsureSender
// 包含sender接口部分实现
*baseSender
// 数据目录
dataFolder string
// 发送地址
url string
// 用于sendLoop和resendLoop发送退出信号
closeSignal chan struct{}
}
// 创建一个http数据发送器
// 参数:
//
// _dataFolder 数据存放目录
// _url 发送地址
func NewHTTPSender(_dataFolder, _url string) (EnsureSender, error) {
this := &httpSender{
dataFolder: _dataFolder,
url: _url,
baseSender: newBaseSender(),
closeSignal: make(chan struct{}),
}
// 新开协程发送数据
go sendLoop(this, this.closeSignal)
// 定时重发
go resendLoop(this, _dataFolder, this.closeSignal)
return this, nil
}
// EnsureSender接口
// Write写入数据
func (this *httpSender) Write(data []byte) error {
item := newHTTPData(data)
this.waitingDataChan <- item
return nil
}
// EnsureSender接口
// Close关闭
func (this *httpSender) Close() error {
close(this.done)
// 等待sendLoop和resendLoop退出
<-this.closeSignal
<-this.closeSignal
// 保存数据
_, e1 := saveData(this.Cache(), this.dataFolder)
_, e2 := saveData(this.Data(), this.dataFolder)
if e2 != nil {
if e1 != nil {
return fmt.Errorf("%s %s", e1, e2)
}
return e2
} else {
return e1
}
}
// sender接口
// Send发送数据
func (this *httpSender) Send(item dataItem) error {
statusCode, _, err := webUtil.PostByteData2(this.url, item.Bytes(), nil, nil)
if err != nil || statusCode != 200 {
if err == nil {
err = fmt.Errorf("StatusCode is not 200")
}
// 发送失败时发送次数+1
item.SetCount(item.Count() + 1)
}
return err
}

View File

@@ -0,0 +1,86 @@
package bytesSendUtil
import (
"fmt"
"io/ioutil"
"net/http"
"testing"
"time"
"goutil/debugUtil"
)
// 保存接收的数据用于校验
var http_recv_msg = make([]byte, 0)
func init() {
debugUtil.SetDebug(true)
}
type httpHandler struct {
cnt int
}
func (ctx *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
result, _ := ioutil.ReadAll(r.Body)
if string(result) == "http-msg-failed" {
http.NotFound(w, r)
} else {
ctx.cnt++
// 模拟一次失败
if ctx.cnt == 2 {
http.NotFound(w, r)
} else {
http_recv_msg = append(http_recv_msg, result...)
}
}
}
func Test_http(t *testing.T) {
http.Handle("/test", new(httpHandler))
go http.ListenAndServe("127.0.0.1:9560", nil)
httpSender, err := NewHTTPSender("./test_http", "http://127.0.0.1:9560/test")
if err != nil {
t.Error(err)
}
time.Sleep(time.Millisecond * 50)
// 第一次应该成功
httpSender.Write([]byte("http-msg-1"))
time.Sleep(time.Millisecond)
// 发送消息此数据会多次失败被丢弃到giveup目录
httpSender.Write([]byte("http-msg-failed"))
time.Sleep(time.Second * 4)
// 第二次应该失败
httpSender.Write([]byte("http-msg-2"))
time.Sleep(time.Millisecond)
// 保存数据
httpSender.Close()
// 重启之后应该会重发数据
httpSender, err = NewHTTPSender("./test_http", "http://127.0.0.1:9560/test")
if err != nil {
t.Error(err)
}
time.Sleep(time.Second * 2)
httpSender.Close()
if string(http_recv_msg) != "http-msg-1http-msg-2" {
t.Error("message error. got " + string(http_recv_msg))
} else {
fmt.Println("HTTP OK")
}
}

View File

@@ -0,0 +1,58 @@
package bytesSendUtil
import (
"fmt"
"goutil/fileUtil"
"goutil/logUtil"
"goutil/stringUtil"
)
// 从目录加载缓存数据并发送
func loadData(s EnsureSender, folder string) error {
if fileList, err := fileUtil.GetFileList(folder); err != nil {
return err
} else {
for _, filename := range fileList {
// 读取发送内容
if fileContent, err := fileUtil.ReadFileBytes(filename); err != nil {
// 打印错误
log := fmt.Sprintf("ensureSendUtil.loadData: Failed To Read File: %s %s\n", err, filename)
logUtil.NormalLog(log, logUtil.Error)
} else if err = fileUtil.DeleteFile(filename); err != nil {
// 删除文件,如果成功则将内容添加到通道中,否则不处理
log := fmt.Sprintf("ensureSendUtil.loadData: Failed To Delete File: %s %s", err, filename)
logUtil.NormalLog(log, logUtil.Error)
} else {
// 发送数据
s.Write(fileContent)
}
}
}
return nil
}
// 保存数据到文件中(通常在退出时调用)
func saveData(datas <-chan dataItem, folder string) (failed []dataItem, err error) {
defer func() {
if len(failed) > 0 {
err = fmt.Errorf("保存数据时有%d个失败数据", len(failed))
}
}()
for {
select {
case v := <-datas:
filename := stringUtil.GetNewGUID()
if e := fileUtil.WriteFile4Byte(folder, filename, false, v.OriginData()); e != nil {
failed = append(failed, v)
log := fmt.Sprintf("ensureSendUtil.saveData: 写入错误\n目录%s文件%s错误信息为%s, Data Len:%v",
folder, filename, err, len(v.OriginData()))
logUtil.NormalLog(log, logUtil.Error)
}
default:
return
}
}
}

View File

@@ -0,0 +1,111 @@
package bytesSendUtil
import (
"fmt"
"time"
"goutil/debugUtil"
"goutil/logUtil"
)
// 负责发送数据的协程
func sendLoop(s sender, closeSignal chan struct{}) {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
}
}()
for {
select {
case <-s.Done():
closeSignal <- struct{}{}
return
case v := <-s.Data():
if err := s.Send(v); err != nil {
// 发送失败存入缓存
s.Cache() <- v
}
}
}
}
// 定时重发失败的数据
func resendLoop(s sender, folder string, closeSignal chan struct{}) {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
}
}()
// debug模式每秒重试1次
var delay time.Duration
if debugUtil.IsDebug() {
delay = time.Second
} else {
delay = time.Minute * 5
}
// 定时重发失败数据
for {
select {
case <-s.Done():
closeSignal <- struct{}{}
return
case <-time.After(delay):
sendCacheData(s, folder)
loadData(s.(EnsureSender), folder)
}
}
}
// 从sender获取失败数据重发
func sendCacheData(s sender, folder string) {
failed := make([]dataItem, 0)
length := len(s.Cache())
defer func() {
// 用于记录多次失败后放弃发送的数据
giveUpItems := make(chan dataItem, len(failed))
for _, v := range failed {
if v.Count() >= 3 {
// 失败次数太多的数据准备存放到磁盘中
giveUpItems <- v
} else {
s.Cache() <- v
}
}
giveUpLen := len(giveUpItems)
if giveUpLen > 0 {
// 将多次失败的数据保存到文件中
if folder[len(folder)-1] == '/' {
folder = folder[:len(folder)-1]
}
saveData(giveUpItems, folder+"_giveup")
if giveUpLen >= 5 {
log := fmt.Sprintf("ensureSendUtil: 有%d条数据多次发送失败", giveUpLen)
logUtil.NormalLog(log, logUtil.Error)
}
}
// 输出信息
log := fmt.Sprintf("ensureSendUtil: 重发%d条数据失败%d条存盘%d条\n", length, len(failed), giveUpLen)
logUtil.NormalLog(log, logUtil.Info)
}()
for {
select {
case v := <-s.Cache():
// 重发数据
if e := s.Send(v); e != nil {
// 记录失败的数据
failed = append(failed, v)
}
default:
return
}
}
}

View File

@@ -0,0 +1,208 @@
package bytesSendUtil
import (
"encoding/binary"
"fmt"
"net"
"sync"
"time"
"goutil/intAndBytesUtil"
"goutil/logUtil"
)
var (
errConnectEmpty = fmt.Errorf("scoket reconnecting...")
byterOrder = binary.LittleEndian
)
// 实现 EnsureSender和sender接口
type tcpSender struct {
// 需要实现的接口
EnsureSender
// 包含sender接口部分实现
*baseSender
// 数据目录
dataFolder string
// 服务器地址
address string
// 连接
conn net.Conn
// 用于重连时互斥
mutex sync.Mutex
// 用于sendLoop和resendLoop发送退出信号
closeSignal chan struct{}
}
// 创建一个tcp数据发送器
// 参数:
//
// _dataFolder 数据存放目录
// _address 连接地址
func NewTCPSender(_dataFolder, _address string) (EnsureSender, error) {
// 连接服务器
conn, err := net.DialTimeout("tcp", _address, 5*time.Second)
if err != nil {
return nil, err
}
this := &tcpSender{
dataFolder: _dataFolder,
baseSender: newBaseSender(),
address: _address,
conn: conn,
closeSignal: make(chan struct{}),
}
// 新开协程发送数据
go sendLoop(this, this.closeSignal)
// 定时重发
go resendLoop(this, _dataFolder, this.closeSignal)
// 发送心跳包
go this.heartBeat()
return this, nil
}
// 每隔15秒发送心跳包
func (this *tcpSender) heartBeat() {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
}
}()
tick := time.Tick(time.Second * 15)
for {
select {
case <-this.Done():
return
case <-tick:
this.sendBytes([]byte{})
}
}
}
// EnsureSender接口
// Write写入数据
func (this *tcpSender) Write(data []byte) error {
item, err := newTCPDataItem(data)
if err != nil {
return err
}
this.waitingDataChan <- item
return nil
}
// EnsureSender接口
// Close关闭
func (this *tcpSender) Close() error {
close(this.done)
// 关闭socket连接
conn := this.conn
if conn != nil {
conn.Close()
}
// 等待sendLoop和resendLoop退出
<-this.closeSignal
<-this.closeSignal
// 保存数据
_, e1 := saveData(this.Cache(), this.dataFolder)
_, e2 := saveData(this.Data(), this.dataFolder)
if e2 != nil {
if e1 != nil {
return fmt.Errorf("%s %s", e1, e2)
}
return e2
} else {
return e1
}
}
// Sender接口
// Send发送dataItem
func (this *tcpSender) Send(item dataItem) error {
err := this.sendBytes(item.Bytes())
if err != nil && err != errConnectEmpty {
// 发送失败时发送次数+1
item.SetCount(item.Count() + 1)
}
return err
}
// 发送字节数据
// 发送格式:[lenght+data]
func (this *tcpSender) sendBytes(data []byte) error {
conn := this.conn
if conn == nil {
return errConnectEmpty
}
// 将长度转化为字节数组
header := intAndBytesUtil.Int32ToBytes(int32(len(data)), byterOrder)
if len(data) > 0 {
data = append(header, data...)
} else {
data = header
}
_, err := conn.Write(data)
if err != nil {
this.mutex.Lock()
// 发送失败
// 检查失败的conn是否this.conn避免多个线程失败后均调用reconnect
// 是则关闭并重连
if conn == this.conn {
this.conn.Close()
this.conn = nil
this.mutex.Unlock()
// 重连
go this.reconnect()
} else {
this.mutex.Unlock()
}
}
return err
}
// 重连服务器
func (this *tcpSender) reconnect() error {
// lock-it
this.mutex.Lock()
defer this.mutex.Unlock()
for {
// 检查是否已经重连
if this.conn != nil {
return nil
}
conn, err := net.DialTimeout("tcp", this.address, 5*time.Second)
if err != nil {
// 连接失败5秒后重试
<-time.After(time.Second * 5)
continue
}
this.conn = conn
}
}

View File

@@ -0,0 +1,95 @@
package bytesSendUtil
import (
"fmt"
"net"
"testing"
"time"
"goutil/debugUtil"
"goutil/zlibUtil"
)
// 保存接收的数据用于校验
var tcp_recv_msg = make([]byte, 0)
func init() {
debugUtil.SetDebug(true)
}
// 创建socket服务器保存收到的数据
func server(addr string) net.Listener {
listener, err := net.Listen("tcp", addr)
if err != nil {
panic(err)
}
go func() {
for {
conn, err := listener.Accept()
if err != nil {
return
}
for {
buff := make([]byte, 512)
_, err := conn.Read(buff)
if err != nil {
break
} else {
decompressed, err := zlibUtil.Decompress(buff[4:])
if err != nil {
panic(err)
} else {
tcp_recv_msg = append(tcp_recv_msg, decompressed...)
}
}
}
}
}()
return listener
}
func Test_tcp(t *testing.T) {
// 开启服务器
l := server("127.0.0.1:9559")
tcp, err := NewTCPSender("./test_tcp", "127.0.0.1:9559")
if err != nil {
t.Error(err)
}
// 发送消息
tcp.Write([]byte("tcp-msg-1"))
time.Sleep(time.Millisecond * 50) // 等待协程发送数据
// 关闭连接和服务器
l.Close()
(tcp.(*tcpSender)).conn.Close()
// 发送消息,此数据会失败
tcp.Write([]byte("tcp-msg-2"))
// time.Sleep(time.Millisecond * 50)
// 保存数据
tcp.Close()
// 重启检查是否重发tcp-msg-2
l = server("127.0.0.1:9559")
tcp, err = NewTCPSender("./test_tcp", "127.0.0.1:9559")
if err != nil {
t.Error(err)
}
time.Sleep(time.Second * 2)
if string(tcp_recv_msg) != "tcp-msg-1tcp-msg-2" {
t.Error("message error. got " + string(tcp_recv_msg))
} else {
fmt.Println("TCP OK")
}
tcp.Close()
l.Close()
}

View File

@@ -0,0 +1,101 @@
package ensureSendUtil
import (
"goutil/zlibUtil"
)
type dataItem interface {
// 返回原始数据
String() string
// 返回发送字节流
Bytes() []byte
// 设置发送次数
SetCount(uint)
// 返回发送次数
Count() uint
}
/////////////////////////////////////////////////
// httpDataItem
type httpDataItem struct {
// 数据
data string
// 发送次数
count uint
}
func newHTTPData(_data string) dataItem {
return &httpDataItem{
data: _data,
count: 0,
}
}
// 返回原始数据
func (this *httpDataItem) String() string {
return this.data
}
// 返回原始数据用于发送
func (this *httpDataItem) Bytes() []byte {
return []byte(this.data)
}
func (this *httpDataItem) SetCount(cnt uint) {
this.count = cnt
}
func (this *httpDataItem) Count() uint {
return this.count
}
/////////////////////////////////////////////////
// tcpDataItem
type tcpDataItem struct {
// 原始数据
origin string
// 压缩后数据
data []byte
// 重试次数
count uint
}
func newTCPDataItem(_data string) (dataItem, error) {
compressed, err := zlibUtil.Compress([]byte(_data), 5)
if err != nil {
return nil, err
}
item := &tcpDataItem{
origin: _data,
data: compressed,
count: 0,
}
return item, nil
}
// 返回原始数据
func (this *tcpDataItem) String() string {
return this.origin
}
// 返回压缩数据用于发送
func (this *tcpDataItem) Bytes() []byte {
return this.data
}
func (this *tcpDataItem) SetCount(cnt uint) {
this.count = cnt
}
func (this *tcpDataItem) Count() uint {
return this.count
}

View File

@@ -0,0 +1,29 @@
package ensureSendUtil
/*
ensureSendUtil 用于推送数据
支持TCP和HTTP两种形式在发送失败时会缓存数据并在一定时间间隔后重试
通过NewTCPSender和NewHTTPSender两个接口分别创建TCP和HTTP模式的EnsureSender
type EnsureSender interface {
// 用于发送数据
Write(string) error
// 用于停止发送,此时会自动保存未发送数据
Close() error
}
// 创建一个tcp数据发送器
// 参数:
// _dataFolder 数据存放目录
// _address 连接地址
func NewTCPSender(_dataFolder, _address string) (EnsureSender, error) {
// 创建一个http数据发送器
// 参数:
// _dataFolder 数据存放目录
// _url 发送地址
func NewHTTPSender(_dataFolder, _url string) (EnsureSender, error) {
*/

View File

@@ -0,0 +1,24 @@
package ensureSendUtil
type EnsureSender interface {
// use Write to send data
Write(string) error
// stop sender
Close() error
}
// resend和dataSaver通过此接口调用tcpSender与httpSender
type sender interface {
// 发送数据
Send(dataItem) error
// 返回待发送的数据channel
Data() <-chan dataItem
// 返回失败数据缓存channel
Cache() chan dataItem
// 用于判断是否关闭
Done() <-chan struct{}
}

View File

@@ -0,0 +1,96 @@
package ensureSendUtil
import (
"fmt"
"goutil/webUtil"
)
// 实现 EnsureSender和sender接口
type httpSender struct {
// 需要实现的接口
EnsureSender
// 包含sender接口部分实现
*baseSender
// 数据目录
dataFolder string
// 发送地址
url string
// 用于sendLoop和resendLoop发送退出信号
closeSignal chan struct{}
}
// 创建一个http数据发送器
// 参数:
//
// _dataFolder 数据存放目录
// _url 发送地址
func NewHTTPSender(_dataFolder, _url string) (EnsureSender, error) {
this := &httpSender{
dataFolder: _dataFolder,
url: _url,
baseSender: newBaseSender(),
closeSignal: make(chan struct{}),
}
// 新开协程发送数据
go sendLoop(this, this.closeSignal)
// 定时重发
go resendLoop(this, _dataFolder, this.closeSignal)
return this, nil
}
// EnsureSender接口
// Write写入数据
func (this *httpSender) Write(data string) error {
item := newHTTPData(data)
this.waitingDataChan <- item
return nil
}
// EnsureSender接口
// Close关闭
func (this *httpSender) Close() error {
close(this.done)
// 等待sendLoop和resendLoop退出
<-this.closeSignal
<-this.closeSignal
// 保存数据
_, e1 := saveData(this.Cache(), this.dataFolder)
_, e2 := saveData(this.Data(), this.dataFolder)
if e2 != nil {
if e1 != nil {
return fmt.Errorf("%s %s", e1, e2)
}
return e2
} else {
return e1
}
}
// sender接口
// Send发送数据
func (this *httpSender) Send(item dataItem) error {
statusCode, _, err := webUtil.PostByteData2(this.url, item.Bytes(), nil, nil)
if err != nil || statusCode != 200 {
if err == nil {
err = fmt.Errorf("StatusCode is not 200")
}
// 发送失败时发送次数+1
item.SetCount(item.Count() + 1)
}
return err
}

View File

@@ -0,0 +1,86 @@
package ensureSendUtil
import (
"fmt"
"io/ioutil"
"net/http"
"testing"
"time"
"goutil/debugUtil"
)
// 保存接收的数据用于校验
var http_recv_msg = make([]byte, 0)
func init() {
debugUtil.SetDebug(true)
}
type httpHandler struct {
cnt int
}
func (ctx *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
result, _ := ioutil.ReadAll(r.Body)
if string(result) == "http-msg-failed" {
http.NotFound(w, r)
} else {
ctx.cnt++
// 模拟一次失败
if ctx.cnt == 2 {
http.NotFound(w, r)
} else {
http_recv_msg = append(http_recv_msg, result...)
}
}
}
func Test_http(t *testing.T) {
http.Handle("/test", new(httpHandler))
go http.ListenAndServe("127.0.0.1:9560", nil)
httpSender, err := NewHTTPSender("./test_http", "http://127.0.0.1:9560/test")
if err != nil {
t.Error(err)
}
time.Sleep(time.Millisecond * 50)
// 第一次应该成功
httpSender.Write("http-msg-1")
time.Sleep(time.Millisecond)
// 发送消息此数据会多次失败被丢弃到giveup目录
httpSender.Write("http-msg-failed")
time.Sleep(time.Second * 4)
// 第二次应该失败
httpSender.Write("http-msg-2")
time.Sleep(time.Millisecond)
// 保存数据
httpSender.Close()
// 重启之后应该会重发数据
httpSender, err = NewHTTPSender("./test_http", "http://127.0.0.1:9560/test")
if err != nil {
t.Error(err)
}
time.Sleep(time.Second * 2)
httpSender.Close()
if string(http_recv_msg) != "http-msg-1http-msg-2" {
t.Error("message error. got " + string(http_recv_msg))
} else {
fmt.Println("HTTP OK")
}
}

View File

@@ -0,0 +1,58 @@
package ensureSendUtil
import (
"fmt"
"goutil/fileUtil"
"goutil/logUtil"
"goutil/stringUtil"
)
// 从目录加载缓存数据并发送
func loadData(s EnsureSender, folder string) error {
if fileList, err := fileUtil.GetFileList(folder); err != nil {
return err
} else {
for _, filename := range fileList {
// 读取发送内容
if fileContent, err := fileUtil.ReadFileContent(filename); err != nil {
// 打印错误
log := fmt.Sprintf("ensureSendUtil.loadData: Failed To Read File: %s %s\n", err, filename)
logUtil.NormalLog(log, logUtil.Error)
} else if err = fileUtil.DeleteFile(filename); err != nil {
// 删除文件,如果成功则将内容添加到通道中,否则不处理
log := fmt.Sprintf("ensureSendUtil.loadData: Failed To Delete File: %s %s", err, filename)
logUtil.NormalLog(log, logUtil.Error)
} else {
// 发送数据
s.Write(fileContent)
}
}
}
return nil
}
// 保存数据到文件中(通常在退出时调用)
func saveData(datas <-chan dataItem, folder string) (failed []dataItem, err error) {
defer func() {
if len(failed) > 0 {
err = fmt.Errorf("保存数据时有%d个失败数据", len(failed))
}
}()
for {
select {
case v := <-datas:
filename := stringUtil.GetNewGUID()
if e := fileUtil.WriteFile(folder, filename, false, v.String()); e != nil {
failed = append(failed, v)
log := fmt.Sprintf("ensureSendUtil.saveData: 写入错误\n目录%s文件%s错误信息为%s, Data:%s",
folder, filename, err, v.String())
logUtil.NormalLog(log, logUtil.Error)
}
default:
return
}
}
}

View File

@@ -0,0 +1,111 @@
package ensureSendUtil
import (
"fmt"
"time"
"goutil/debugUtil"
"goutil/logUtil"
)
// 负责发送数据的协程
func sendLoop(s sender, closeSignal chan struct{}) {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
}
}()
for {
select {
case <-s.Done():
closeSignal <- struct{}{}
return
case v := <-s.Data():
if err := s.Send(v); err != nil {
// 发送失败存入缓存
s.Cache() <- v
}
}
}
}
// 定时重发失败的数据
func resendLoop(s sender, folder string, closeSignal chan struct{}) {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
}
}()
// debug模式每秒重试1次
var delay time.Duration
if debugUtil.IsDebug() {
delay = time.Second
} else {
delay = time.Minute * 5
}
// 定时重发失败数据
for {
select {
case <-s.Done():
closeSignal <- struct{}{}
return
case <-time.After(delay):
sendCacheData(s, folder)
loadData(s.(EnsureSender), folder)
}
}
}
// 从sender获取失败数据重发
func sendCacheData(s sender, folder string) {
failed := make([]dataItem, 0)
length := len(s.Cache())
defer func() {
// 用于记录多次失败后放弃发送的数据
giveUpItems := make(chan dataItem, len(failed))
for _, v := range failed {
if v.Count() >= 3 {
// 失败次数太多的数据准备存放到磁盘中
giveUpItems <- v
} else {
s.Cache() <- v
}
}
giveUpLen := len(giveUpItems)
if giveUpLen > 0 {
// 将多次失败的数据保存到文件中
if folder[len(folder)-1] == '/' {
folder = folder[:len(folder)-1]
}
saveData(giveUpItems, folder+"_giveup")
if giveUpLen >= 5 {
log := fmt.Sprintf("ensureSendUtil: 有%d条数据多次发送失败", giveUpLen)
logUtil.NormalLog(log, logUtil.Error)
}
}
// 输出信息
log := fmt.Sprintf("ensureSendUtil: 重发%d条数据失败%d条存盘%d条\n", length, len(failed), giveUpLen)
logUtil.NormalLog(log, logUtil.Info)
}()
for {
select {
case v := <-s.Cache():
// 重发数据
if e := s.Send(v); e != nil {
// 记录失败的数据
failed = append(failed, v)
}
default:
return
}
}
}

View File

@@ -0,0 +1,208 @@
package ensureSendUtil
import (
"encoding/binary"
"fmt"
"net"
"sync"
"time"
"goutil/intAndBytesUtil"
"goutil/logUtil"
)
var (
errConnectEmpty = fmt.Errorf("scoket reconnecting...")
byterOrder = binary.LittleEndian
)
// 实现 EnsureSender和sender接口
type tcpSender struct {
// 需要实现的接口
EnsureSender
// 包含sender接口部分实现
*baseSender
// 数据目录
dataFolder string
// 服务器地址
address string
// 连接
conn net.Conn
// 用于重连时互斥
mutex sync.Mutex
// 用于sendLoop和resendLoop发送退出信号
closeSignal chan struct{}
}
// 创建一个tcp数据发送器
// 参数:
//
// _dataFolder 数据存放目录
// _address 连接地址
func NewTCPSender(_dataFolder, _address string) (EnsureSender, error) {
// 连接服务器
conn, err := net.DialTimeout("tcp", _address, 5*time.Second)
if err != nil {
return nil, err
}
this := &tcpSender{
dataFolder: _dataFolder,
baseSender: newBaseSender(),
address: _address,
conn: conn,
closeSignal: make(chan struct{}),
}
// 新开协程发送数据
go sendLoop(this, this.closeSignal)
// 定时重发
go resendLoop(this, _dataFolder, this.closeSignal)
// 发送心跳包
go this.heartBeat()
return this, nil
}
// 每隔15秒发送心跳包
func (this *tcpSender) heartBeat() {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
}
}()
tick := time.Tick(time.Second * 15)
for {
select {
case <-this.Done():
return
case <-tick:
this.sendBytes([]byte{})
}
}
}
// EnsureSender接口
// Write写入数据
func (this *tcpSender) Write(data string) error {
item, err := newTCPDataItem(data)
if err != nil {
return err
}
this.waitingDataChan <- item
return nil
}
// EnsureSender接口
// Close关闭
func (this *tcpSender) Close() error {
close(this.done)
// 关闭socket连接
conn := this.conn
if conn != nil {
conn.Close()
}
// 等待sendLoop和resendLoop退出
<-this.closeSignal
<-this.closeSignal
// 保存数据
_, e1 := saveData(this.Cache(), this.dataFolder)
_, e2 := saveData(this.Data(), this.dataFolder)
if e2 != nil {
if e1 != nil {
return fmt.Errorf("%s %s", e1, e2)
}
return e2
} else {
return e1
}
}
// Sender接口
// Send发送dataItem
func (this *tcpSender) Send(item dataItem) error {
err := this.sendBytes(item.Bytes())
if err != nil && err != errConnectEmpty {
// 发送失败时发送次数+1
item.SetCount(item.Count() + 1)
}
return err
}
// 发送字节数据
// 发送格式:[lenght+data]
func (this *tcpSender) sendBytes(data []byte) error {
conn := this.conn
if conn == nil {
return errConnectEmpty
}
// 将长度转化为字节数组
header := intAndBytesUtil.Int32ToBytes(int32(len(data)), byterOrder)
if len(data) > 0 {
data = append(header, data...)
} else {
data = header
}
_, err := conn.Write(data)
if err != nil {
this.mutex.Lock()
// 发送失败
// 检查失败的conn是否this.conn避免多个线程失败后均调用reconnect
// 是则关闭并重连
if conn == this.conn {
this.conn.Close()
this.conn = nil
this.mutex.Unlock()
// 重连
go this.reconnect()
} else {
this.mutex.Unlock()
}
}
return err
}
// 重连服务器
func (this *tcpSender) reconnect() error {
// lock-it
this.mutex.Lock()
defer this.mutex.Unlock()
for {
// 检查是否已经重连
if this.conn != nil {
return nil
}
conn, err := net.DialTimeout("tcp", this.address, 5*time.Second)
if err != nil {
// 连接失败5秒后重试
<-time.After(time.Second * 5)
continue
}
this.conn = conn
}
}

View File

@@ -0,0 +1,95 @@
package ensureSendUtil
import (
"fmt"
"net"
"testing"
"time"
"goutil/debugUtil"
"goutil/zlibUtil"
)
// 保存接收的数据用于校验
var tcp_recv_msg = make([]byte, 0)
func init() {
debugUtil.SetDebug(true)
}
// 创建socket服务器保存收到的数据
func server(addr string) net.Listener {
listener, err := net.Listen("tcp", addr)
if err != nil {
panic(err)
}
go func() {
for {
conn, err := listener.Accept()
if err != nil {
return
}
for {
buff := make([]byte, 512)
_, err := conn.Read(buff)
if err != nil {
break
} else {
decompressed, err := zlibUtil.Decompress(buff[4:])
if err != nil {
panic(err)
} else {
tcp_recv_msg = append(tcp_recv_msg, decompressed...)
}
}
}
}
}()
return listener
}
func Test_tcp(t *testing.T) {
// 开启服务器
l := server("127.0.0.1:9559")
tcp, err := NewTCPSender("./test_tcp", "127.0.0.1:9559")
if err != nil {
t.Error(err)
}
// 发送消息
tcp.Write("tcp-msg-1")
time.Sleep(time.Millisecond * 50) // 等待协程发送数据
// 关闭连接和服务器
l.Close()
(tcp.(*tcpSender)).conn.Close()
// 发送消息,此数据会失败
tcp.Write("tcp-msg-2")
// time.Sleep(time.Millisecond * 50)
// 保存数据
tcp.Close()
// 重启检查是否重发tcp-msg-2
l = server("127.0.0.1:9559")
tcp, err = NewTCPSender("./test_tcp", "127.0.0.1:9559")
if err != nil {
t.Error(err)
}
time.Sleep(time.Second * 2)
if string(tcp_recv_msg) != "tcp-msg-1tcp-msg-2" {
t.Error("message error. got " + string(tcp_recv_msg))
} else {
fmt.Println("TCP OK")
}
tcp.Close()
l.Close()
}

View File

@@ -0,0 +1,113 @@
package esLogUtil
import (
"bytes"
"encoding/json"
"sync"
"time"
"github.com/elastic/go-elasticsearch/v8/esutil"
"golang.org/x/net/context"
"goutil/logUtil"
)
var (
indexMutex sync.Mutex
indexer esutil.BulkIndexer
)
func timedReindex() {
go func() {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
timedReindex()
time.Sleep(time.Second * 1)
}
}()
currentIndexName := getIndexName()
for {
//设置休眠
time.Sleep(time.Second * 1)
newIndexName := getIndexName()
if currentIndexName != getIndexName() {
newIndexer(newIndexName)
currentIndexName = newIndexName
}
}
}()
}
func newIndexer(newIndexName string) {
if indexer != nil {
closeIndex()
}
//wait until get the lock
indexMutex.Lock()
defer indexMutex.Unlock()
var err error
indexer, err = esutil.NewBulkIndexer(esutil.BulkIndexerConfig{
Index: getIndexName(), // The default index name
Client: esClient, // The Elasticsearch client
FlushInterval: time.Second, // The periodic flush interval
})
if err != nil {
logUtil.ErrorLog("[%s]: Creating the indexer err: %s", serverModuleName, err)
}
}
func closeIndex() {
//wait until get the lock
indexMutex.Lock()
defer indexMutex.Unlock()
if indexer == nil {
return
}
err := indexer.Close(context.Background())
if err != nil {
logUtil.ErrorLog("[%s]:Close err%s", serverModuleName, err.Error())
}
indexer = nil
}
// 批量保存到在线日志系统
// 参数:
//
// 数量
//
// 返回值:
//
// 日志列表对象
func bulkSendHandler(logObj EsLog) {
if esClient == nil || indexer == nil {
return
}
//try to get the lock in 10000 milliseconds,if cant obtain it,return false
indexMutex.Lock()
defer indexMutex.Unlock()
message, err := json.Marshal(logObj)
if err != nil {
logUtil.ErrorLog("[%s]: Marshal failed. Err:%s", serverModuleName, err)
return
}
err = indexer.Add(
context.Background(),
esutil.BulkIndexerItem{
Action: "index",
Body: bytes.NewReader(message),
})
if err != nil {
logUtil.ErrorLog("[%s]: Add data err%s", serverModuleName, err.Error())
}
}

View File

@@ -0,0 +1,269 @@
package esLogUtil
import (
"fmt"
"sync"
"time"
"github.com/elastic/go-elasticsearch/v8"
"goutil/logUtil"
"goutil/stringUtil"
)
var (
serverModuleName = "esLog"
esClient *elasticsearch.Client
indexName string //Index名
strServerGroupId string //区服
isStop bool
logChan = make(chan EsLog, 2048)
warnCount = 2000
closedChan = make(chan struct{})
logPool = sync.Pool{
New: func() interface{} {
return &EsLog{}
},
}
)
// 启动ES日志系统
// 参数:
//
// esUrlsES地址(多个地址使用,分割)
// nameIndexName
// serverGroupId服务器组Id
//
// 返回值:
//
// 结果状态
func Start(esUrls string, name string, serverGroupId int32) {
if stringUtil.IsEmpty(esUrls) {
return
}
//构造Es客户端
var err error
esClient, err = elasticsearch.NewClient(elasticsearch.Config{
Addresses: stringUtil.Split(esUrls, []string{","}),
// Retry on 429 TooManyRequests statuses
//
RetryOnStatus: []int{502, 503, 504, 429},
// A simple incremental backoff function
//
RetryBackoff: func(i int) time.Duration { return time.Duration(i) * 100 * time.Millisecond },
// Retry up to 5 attempts
//
MaxRetries: 5,
})
if err != nil {
panic(fmt.Sprintf("构造es对象出错err:%s", err.Error()))
}
indexName = name
strServerGroupId = fmt.Sprintf("%d", serverGroupId)
//初始化ES
newIndexer(getIndexName())
timedReindex()
startSendProcessor()
guardProcessor()
return
}
// 停止服务
func Stop() {
//停止接受日志
isStop = true
if indexer == nil {
return
}
if len(logChan) == 0 {
close(logChan)
}
<-closedChan
}
//#region 内部方法
func guardProcessor() {
go func() {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
time.Sleep(1 * time.Second)
guardProcessor()
}
}()
for {
time.Sleep(5 * time.Second)
count := len(logChan)
if count < warnCount {
continue
}
logUtil.NormalLog(fmt.Sprintf("ES日志通道中当前有%d条消息待消费。", count), logUtil.Warn)
}
}()
}
func startSendProcessor() {
go func() {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
time.Sleep(1 * time.Second)
startSendProcessor()
}
}()
for {
select {
case logObj, ok := <-logChan:
if ok {
bulkSendHandler(logObj) // 执行刷新
}
if len(logChan) == 0 && isStop {
// is closed
closeIndex()
closedChan <- struct{}{}
return
}
}
}
}()
}
func getIndexName() string {
//获取当天日期
return fmt.Sprintf("%s_%s", indexName, time.Now().Format("20060102"))
}
// 写入在线日志
// 参数:
//
// 日志信息对象
//
// 返回值:
//
// 无
func writeLog(logObj *EsLog) {
if isStop || indexer == nil {
return
}
logChan <- *logObj
}
// 组装ES日志对象
// 参数:
//
// logType 日志类型
// format 日志格式
// args 参数列表
//
// 返回值:
//
// 结果状态
func buildLog(logType, format string, args ...interface{}) (newLogObj *EsLog) {
msg := format
if len(args) > 0 {
msg = fmt.Sprintf(format, args...)
}
//构造新的日志对象
newLogObj = new(logType, msg, strServerGroupId)
return
}
//#endregion
//#region 外部方法
// 日志记录
//
// format:日志格式
// logType:日志类型
// args:参数列表
//
// 返回值
//
// 无
func NormalLog(format string, logType logUtil.LogType, args ...interface{}) {
writeLog(buildLog(logType.String(), format, args...))
}
// 消息日志记录
//
// format:日志格式
// args:参数列表
//
// 返回值
//
// 无
func InfoLog(format string, args ...interface{}) {
writeLog(buildLog("Info", format, args...))
}
// 警告日志记录
//
// format:日志格式
// args:参数列表
//
// 返回值
//
// 无
func WarnLog(format string, args ...interface{}) {
writeLog(buildLog("Warn", format, args...))
}
// 调试日志记录
//
// format:日志格式
// args:参数列表
//
// 返回值
//
// 无
func DebugLog(format string, args ...interface{}) {
writeLog(buildLog("Debug", format, args...))
}
// 错误日志记录
//
// format:日志格式
// args:参数列表
//
// 返回值
//
// 无
func ErrorLog(format string, args ...interface{}) {
writeLog(buildLog("Error", format, args...))
}
// 致命错误日志记录
//
// format:日志格式
// args:参数列表
//
// 返回值
//
// 无
func FatalLog(format string, args ...interface{}) {
writeLog(buildLog("Fatal", format, args...))
}
//#endregion

View File

@@ -0,0 +1,35 @@
package esLogUtil
import (
"testing"
"time"
)
func TestWrite(t *testing.T) {
Start("http://10.1.0.86:9200", "dzg_gs_log_gmc2", 20008)
for i := 0; i < 10000; i++ {
InfoLog("ES在线日志测试")
WarnLog("ES在线日志测试")
DebugLog("ES在线日志测试")
ErrorLog("ES在线日志测试")
FatalLog("ES在线日志测试")
}
Stop()
}
func BenchmarkWrite(b *testing.B) {
Start("http://106.52.100.147:14001", "20008_gs_log", 20008)
b.ResetTimer()
for i := 0; i < b.N; i++ {
InfoLog("ES在线日志测试%d", i)
WarnLog("ES在线日志测试%d", i)
DebugLog("ES在线日志测试%d", i)
ErrorLog("ES在线日志测试%d", i)
FatalLog("ES在线日志测试%d", i)
}
b.StopTimer()
time.Sleep(30 * time.Second)
Stop()
}

View File

@@ -0,0 +1,39 @@
package esLogUtil
import (
"time"
)
// 日志对象
type EsLog struct {
// 日志类型
LogType string
// 日志消息
Message string
// 程序标识
InnerId string
// 日志时间
CrTime time.Time
}
func new(logType string, msg string, innerId string) *EsLog {
logObj := logPool.Get().(*EsLog)
logObj.CrTime = time.Now()
logObj.LogType = logType
logObj.Message = msg
logObj.InnerId = innerId
return logObj
//
//return EsLog{
// LogType: logType,
// Message: msg,
// InnerId: innerId,
// CrTime: time.Now(),
//}
}

View File

@@ -0,0 +1,200 @@
package fileUtil
import (
"fmt"
"os"
"path/filepath"
"time"
"goutil/timeUtil"
)
// 大文件对象,可用于连续写入内容而不关闭文件,直到达到指定的大小
type BigFile struct {
// 文件夹名称
path string
// 当前文件名称
fileName string
// 文件名称前缀
fileNamePrefix string
// 当前文件大小单位Byte
fileSize int
// 最大的文件大小单位Byte
maxFileSize int
// 文件对象
file *os.File
// 获得新文件名称的方法
newFileNameFunc func(string, string) string
}
// 获取文件的完整路径
func (this *BigFile) getFullPath() string {
return filepath.Join(this.path, this.fileName)
}
// 初始化文件对象
func (this *BigFile) initFile() error {
// 初始化文件名称
this.fileName = this.newFileNameFunc(this.fileNamePrefix, this.fileName)
// 初始化文件大小
this.fileSize = 0
// 打开文件
file, err := os.OpenFile(this.getFullPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm|os.ModeTemporary)
if err != nil {
return fmt.Errorf("打开文件%s错误错误信息为%s", this.getFullPath(), err)
} else {
this.file = file
}
return nil
}
// 返回当前文件名称
// 返回值:
// 当前文件名称
func (this *BigFile) FileName() string {
return this.fileName
}
// 保存消息
// message消息内容
// 返回值:错误对象
func (this *BigFile) SaveMessage(message string) error {
if this.file == nil {
return fmt.Errorf("文件对象为空path:%s", this.getFullPath())
}
// 增加文件大小
this.fileSize += len([]byte(message))
// 写入消息(在结尾处增加一个换行符\n
message = fmt.Sprintf("%s\n", message)
if _, err := this.file.WriteString(message); err != nil {
return fmt.Errorf("向文件%s写入信息错误错误信息为%s", this.getFullPath(), err)
}
// 如果达到了文件的上限,则关闭文件并重新打开一个新文件
if this.fileSize >= this.maxFileSize {
this.Close()
this.initFile()
}
return nil
}
// 写入字节消息
// message消息内容
// 返回值:错误对象
func (this *BigFile) WriteMessage(message []byte) error {
if this.file == nil {
return fmt.Errorf("文件对象为空path:%s", this.getFullPath())
}
// 增加文件大小
this.fileSize += len(message)
// 写入消息
if _, err := this.file.Write(message); err != nil {
return fmt.Errorf("向文件%s写入信息错误错误信息为%s", this.getFullPath(), err)
}
// 如果达到了文件的上限,则关闭文件并重新打开一个新文件
if this.fileSize >= this.maxFileSize {
this.Close()
this.initFile()
}
return nil
}
// 关闭对象
// 返回值:无
func (this *BigFile) Close() {
if this.file != nil {
this.file.Close()
this.file = nil
}
}
// 创建新的大文件对象(obsolete)
// _path:文件夹路径
// _maxFileSize:单个文件大小的最大值单位Byte
// 返回值:
// 大文件对象
// 错误对象
func NewBigFile(_path string, _maxFileSize int) (*BigFile, error) {
return NewBigFileWithNewFileNameFunc(_path, "default", _maxFileSize, newFileName)
}
// 创建新的大文件对象
// _path:文件夹路径
// _fileNamePrefix:文件名称前缀
// _maxFileSize:单个文件大小的最大值单位Byte
// 返回值:
// 大文件对象
// 错误对象
func NewBigFile2(_path, _fileNamePrefix string, _maxFileSize int) (*BigFile, error) {
return NewBigFileWithNewFileNameFunc(_path, _fileNamePrefix, _maxFileSize, newFileName)
}
// 创建新的大文件对象
// _path:文件夹路径
// _fileNamePrefix:文件名称前缀
// _maxFileSize:单个文件大小的最大值单位Byte
// _newFileNameFunc:创建新文件名称的方法
// 返回值:
// 大文件对象
// 错误对象
func NewBigFileWithNewFileNameFunc(_path, _fileNamePrefix string, _maxFileSize int, _newFileNameFunc func(string, string) string) (*BigFile, error) {
return NewBigFileWithNewFileNameFunc2(_path, _fileNamePrefix, "default", _maxFileSize, _newFileNameFunc)
}
// 创建新的大文件对象
// _path:文件夹路径
// _fileNamePrefix:文件名称前缀
// _fileName:文件名称
// _maxFileSize:单个文件大小的最大值单位Byte
// _newFileNameFunc:创建新文件名称的方法
// 返回值:
// 大文件对象
// 错误对象
func NewBigFileWithNewFileNameFunc2(_path, _fileNamePrefix, _fileName string, _maxFileSize int, _newFileNameFunc func(string, string) string) (*BigFile, error) {
// 判断文件夹是否存在,如果不存在则创建
if !IsDirExists(_path) {
os.MkdirAll(_path, os.ModePerm|os.ModeTemporary)
}
// 初始化对象
obj := &BigFile{
path: _path,
fileNamePrefix: _fileNamePrefix,
fileName: _fileName,
maxFileSize: _maxFileSize,
newFileNameFunc: _newFileNameFunc,
}
// 初始化文件对象
if err := obj.initFile(); err != nil {
obj.Close()
return nil, err
}
return obj, nil
}
// 创建新的文件名称
// prefix:前缀
// currFileName:当前文件名称
// 返回值:
// 新的文件名称
func newFileName(prefix, currFileName string) string {
return fmt.Sprintf("%s_%s.data", prefix, timeUtil.Format(time.Now(), "yyyyMMddHHmmss"))
}

View File

@@ -0,0 +1,37 @@
package fileUtil
import (
"fmt"
"testing"
)
func BenchmarkSaveMessage(b *testing.B) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
bigFileObj, err := NewBigFile(path, 1024*1024*1024)
if err != nil {
b.Errorf("there should no err, but not there is:%s", err)
}
for i := 0; i < b.N; i++ {
bigFileObj.SaveMessage(fmt.Sprintf("line %d", i))
}
}
func TestSaveMessage(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
bigFileObj, err := NewBigFile(path, 1024)
if err != nil {
t.Errorf("there should no err, but not there is:%s", err)
}
for i := 0; i < 100000; i++ {
bigFileObj.SaveMessage(fmt.Sprintf("line %d", i))
}
fileList, err := GetFileList(path)
for _, item := range fileList {
fmt.Printf("file:%s\n", item)
}
}

View File

@@ -0,0 +1,4 @@
/*
文件助手类
*/
package fileUtil

View File

@@ -0,0 +1,298 @@
package fileUtil
import (
"bufio"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
)
var (
mutex sync.Mutex
)
// 文件是否存在
// 文件路径
// 返回值:
// 是否存在
// 错误对象
func IsFileExists(path string) (bool, error) {
file, err := os.Stat(path)
if err == nil {
return file.IsDir() == false, nil
} else {
if os.IsNotExist(err) {
return false, nil
}
}
return true, err
}
// 文件夹是否存在
// 文件夹路径
// 返回值:
// 是否存在
// 错误对象
func IsDirectoryExists(path string) (bool, error) {
file, err := os.Stat(path)
if err == nil {
return file.IsDir(), nil
} else {
if os.IsNotExist(err) {
return false, nil
}
}
return true, err
}
// 文件夹是否存在(obsolete)
// 文件夹路径
// 返回值:
// 是否存在
func IsDirExists(path string) bool {
file, err := os.Stat(path)
if err != nil {
return false
} else {
return file.IsDir()
}
}
// 获取当前路径
// 返回值:
// 当前路径
func GetCurrentPath() string {
file, _ := exec.LookPath(os.Args[0])
fileAbsPath, _ := filepath.Abs(file)
return filepath.Dir(fileAbsPath)
}
// 获取目标文件列表(完整路径)
// path文件夹路径
// 返回值:文件列表(完整路径)
func GetFileList(path string) (fileList []string, err error) {
if exists, err1 := IsDirectoryExists(path); err1 != nil {
err = err1
return
} else if !exists {
return
}
// 遍历目录,获取所有文件列表
err = filepath.Walk(path, func(fileName string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
// 忽略目录
if fi.IsDir() {
return nil
}
// 添加到列表
fileList = append(fileList, fileName)
return nil
})
return
}
// 获取目标文件列表(完整路径)
// path文件夹路径
// prefix文件前缀
// suffix文件后缀
// 返回值:文件列表(完整路径)
func GetFileList2(path, prefix, suffix string) (fileList []string, err error) {
if exists, err1 := IsDirectoryExists(path); err1 != nil {
err = err1
return
} else if !exists {
return
}
// 遍历目录,获取所有文件列表
err = filepath.Walk(path, func(fileName string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
// 忽略目录
if fi.IsDir() {
return nil
}
// 添加到列表
baseName := filepath.Base(fileName)
if prefix != "" && strings.HasPrefix(baseName, prefix) == false {
return nil
}
if suffix != "" && strings.HasSuffix(baseName, suffix) == false {
return nil
}
fileList = append(fileList, fileName)
return nil
})
return
}
// 按行读取每一个文件的内容
// fileName:文件的绝对路径
// 返回值:
// 行内容列表
// 错误信息
func ReadFileLineByLine(fileName string) (lineList []string, err error) {
// 打开文件
file, err1 := os.Open(fileName)
if err1 != nil {
err = err1
return
}
defer file.Close()
// 读取文件
buf := bufio.NewReader(file)
for {
// 按行读取
line, _, err2 := buf.ReadLine()
if err2 == io.EOF {
break
}
//将byte[]转换为string并添加到列表中
lineList = append(lineList, string(line))
}
return
}
// 读取文件内容(字符串)
// fileName文件的绝对路径
// 返回值:
// 文件内容
// 错误信息
func ReadFileContent(fileName string) (content string, err error) {
bytes, err1 := ioutil.ReadFile(fileName)
if err1 != nil {
err = err1
return
}
content = string(bytes)
return
}
// 读取文件内容(字符数组)
// fileName文件的绝对路径
// 返回值:
// 文件内容
// 错误信息
func ReadFileBytes(fileName string) (content []byte, err error) {
content, err = ioutil.ReadFile(fileName)
return
}
// 写入文件
// filePath文件夹路径
// fileName文件名称
// ifAppend是否追加内容
// args可变参数
// 返回值:
// error:错误信息
func WriteFile(filePath, fileName string, ifAppend bool, args ...string) error {
// 得到最终的fileName
fileName = filepath.Join(filePath, fileName)
// 判断文件夹是否存在,如果不存在则创建
mutex.Lock()
if !IsDirExists(filePath) {
os.MkdirAll(filePath, os.ModePerm|os.ModeTemporary)
}
mutex.Unlock()
// 打开文件(如果文件存在就以写模式打开,并追加写入;如果文件不存在就创建,然后以写模式打开。)
var f *os.File
var err error
if ifAppend == false {
f, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm|os.ModeTemporary)
} else {
f, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm|os.ModeTemporary)
}
if err != nil {
return err
}
defer f.Close()
// 写入内容
for _, arg := range args {
_, err = f.WriteString(arg)
if err != nil {
return err
}
}
return nil
}
// 写入文件
// filePath文件夹路径
// fileName文件名称
// ifAppend是否追加内容
// args可变参数
// 返回值:
// error:错误信息
func WriteFile4Byte(filePath, fileName string, ifAppend bool, args ...[]byte) error {
// 得到最终的fileName
fileName = filepath.Join(filePath, fileName)
// 判断文件夹是否存在,如果不存在则创建
mutex.Lock()
if !IsDirExists(filePath) {
os.MkdirAll(filePath, os.ModePerm|os.ModeTemporary)
}
mutex.Unlock()
// 打开文件(如果文件存在就以写模式打开,并追加写入;如果文件不存在就创建,然后以写模式打开。)
var f *os.File
var err error
if ifAppend == false {
f, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm|os.ModeTemporary)
} else {
f, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm|os.ModeTemporary)
}
if err != nil {
return err
}
defer f.Close()
// 写入内容
for _, arg := range args {
_, err = f.Write(arg)
if err != nil {
return err
}
}
return nil
}
// 删除文件
// fileName文件的绝对路径
// 返回值:
// 错误对象
func DeleteFile(fileName string) error {
return os.Remove(fileName)
}

View File

@@ -0,0 +1,345 @@
package fileUtil
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"testing"
"time"
)
func BenchmarkWriteFile(b *testing.B) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
for i := 0; i < b.N; i++ {
WriteFile(path, "test.txt", true, fmt.Sprintf("line %d", i))
}
}
func TestIsFileExists(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName := fmt.Sprintf("%s/%s", path, "test.txt")
fmt.Printf("FileName:%s\n", fileName)
if exists, err := IsFileExists(fileName); err != nil || exists {
t.Errorf("the file %s should not be exists, but now it's exists", fileName)
}
if err := WriteFile(path, "test.txt", true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if exists, err := IsFileExists(fileName); err != nil || !exists {
t.Errorf("the file %s should be exists, but now it's not exists", fileName)
}
if content, err := ReadFileContent(fileName); err != nil {
t.Errorf("there should be no error, but now err:%s", err)
} else {
fmt.Printf("Content:%s\n", content)
}
DeleteFile(fileName)
}
func TestIsDirectoryExists(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
filePath := filepath.Join(path, "Parent")
if exists, err := IsDirectoryExists(filePath); err != nil || exists {
t.Errorf("the file %s should not be exists, but now it's exists", filePath)
}
fileName := fmt.Sprintf("%s/%s", filePath, "test.txt")
if err := WriteFile(filePath, "test.txt", true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if exists, err := IsDirectoryExists(filePath); err != nil || !exists {
t.Errorf("the file %s should be exists, but now it's not exists", filePath)
}
if content, err := ReadFileContent(fileName); err != nil {
t.Errorf("there should be no error, but now err:%s", err)
} else {
fmt.Printf("Content:%s\n", content)
}
DeleteFile(fileName)
}
func TestIsDirExists(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
filePath := filepath.Join(path, "Parent2")
if IsDirExists(filePath) {
t.Errorf("the file %s should not be exists, but now it's exists", filePath)
}
fileName := fmt.Sprintf("%s/%s", filePath, "test.txt")
if err := WriteFile(filePath, "test.txt", true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if IsDirExists(filePath) == false {
t.Errorf("the file %s should be exists, but now it's not exists", filePath)
}
if content, err := ReadFileContent(fmt.Sprintf("%s/%s", filePath, "test.txt")); err != nil {
t.Errorf("there should be no error, but now err:%s", err)
} else {
fmt.Printf("Content:%s\n", content)
}
DeleteFile(fileName)
}
func TestGetFileList(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName1 := "2017-09-12-12.txt"
fileName2 := "2017-09-12-13.txt"
fileName3 := "2017-09-12-14.txt"
fileName4 := "2017-09-12.tar.bz2"
seperator := "\\"
if runtime.GOOS != "windows" {
seperator = "/"
}
filePath1 := fmt.Sprintf("%s%s%s", path, seperator, fileName1)
filePath2 := fmt.Sprintf("%s%s%s", path, seperator, fileName2)
filePath3 := fmt.Sprintf("%s%s%s", path, seperator, fileName3)
filePath4 := fmt.Sprintf("%s%s%s", path, seperator, fileName4)
if err := WriteFile(path, fileName1, true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := WriteFile(path, fileName2, true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := WriteFile(path, fileName3, true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := WriteFile(path, fileName4, true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
fileList, err := GetFileList(path)
if err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if fileList[0] != filePath1 {
t.Errorf("Expected:%s, now got:%s", filePath1, fileList[0])
}
if fileList[1] != filePath2 {
t.Errorf("Expected:%s, now got:%s", filePath2, fileList[1])
}
if fileList[2] != filePath3 {
t.Errorf("Expected:%s, now got:%s", filePath3, fileList[2])
}
if fileList[3] != filePath4 {
t.Errorf("Expected:%s, now got:%s", filePath4, fileList[3])
}
DeleteFile(filePath1)
DeleteFile(filePath2)
DeleteFile(filePath3)
DeleteFile(filePath4)
}
func TestGetFileList2(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName1 := "2017-09-12-12.txt"
fileName2 := "2017-09-12-13.txt"
fileName3 := "2017-09-12-14.txt"
fileName4 := "2017-09-12.tar.bz2"
seperator := "\\"
if runtime.GOOS != "windows" {
seperator = "/"
}
filePath1 := fmt.Sprintf("%s%s%s", path, seperator, fileName1)
filePath2 := fmt.Sprintf("%s%s%s", path, seperator, fileName2)
filePath3 := fmt.Sprintf("%s%s%s", path, seperator, fileName3)
filePath4 := fmt.Sprintf("%s%s%s", path, seperator, fileName4)
if err := WriteFile(path, fileName1, true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := WriteFile(path, fileName2, true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := WriteFile(path, fileName3, true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := WriteFile(path, fileName4, true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
fileList, err := GetFileList2(path, "2017-09-12", "txt")
if err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
fmt.Printf("fileList:%v\n", fileList)
if fileList[0] != filePath1 {
t.Errorf("Expected:%s, now got:%s", filePath1, fileList[0])
}
if fileList[1] != filePath2 {
t.Errorf("Expected:%s, now got:%s", filePath2, fileList[1])
}
if fileList[2] != filePath3 {
t.Errorf("Expected:%s, now got:%s", filePath3, fileList[2])
}
fileList2, err := GetFileList2(path, "2017-09-12", "tar.bz2")
if err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
fmt.Printf("fileList2:%v\n", fileList2)
if fileList2[0] != filePath4 {
t.Errorf("Expected:%s, now got:%s", filePath4, fileList2[0])
}
DeleteFile(filePath1)
DeleteFile(filePath2)
DeleteFile(filePath3)
DeleteFile(filePath4)
}
func TestReadFileLineByLine(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName := fmt.Sprintf("%s/%s", path, "test.txt")
if err := WriteFile(path, "test.txt", true, "first line\n"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := WriteFile(path, "test.txt", true, "second line\n"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
expectedFirstLine := "first line"
expectedSecondLine := "second line"
lineList, err := ReadFileLineByLine(fileName)
if err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if lineList[0] != expectedFirstLine {
t.Errorf("Expected:%s, but now got:%s", expectedFirstLine, lineList[0])
}
if lineList[1] != expectedSecondLine {
t.Errorf("Expected:%s, but now got:%s", expectedSecondLine, lineList[1])
}
if err := DeleteFile(fileName); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
}
}
func TestReadFileContent(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName := fmt.Sprintf("%s/%s", path, "test.txt")
if err := WriteFile(path, "test.txt", true, "first line\n"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := WriteFile(path, "test.txt", true, "second line\n"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
expectedContent := "first line\nsecond line\n"
if content, err := ReadFileContent(fileName); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
} else if content != expectedContent {
t.Errorf("Expected:%s, but now got:%s", expectedContent, content)
}
if err := DeleteFile(fileName); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
}
}
func TestDeleteFile(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName := fmt.Sprintf("%s/%s", path, "test.txt")
if err := WriteFile(path, "test.txt", true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := DeleteFile(fileName); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
}
}
func TestReadWriteSimultaneously(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName := fmt.Sprintf("%s/%s", path, "test.txt")
file1, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm|os.ModeTemporary)
if err != nil {
t.Errorf("1:there should be no err, but now err:%s", err)
}
// for i := 0; i < 10; i++ {
// file1.WriteString(fmt.Sprintf("line %d\n", i))
// }
go func() {
for i := 0; i < 10; i++ {
file1.WriteString(fmt.Sprintf("line %d\n", i))
time.Sleep(time.Second)
}
}()
file2, err := os.OpenFile(fileName, os.O_RDONLY, os.ModePerm|os.ModeTemporary)
if err != nil {
t.Errorf("2:there should be no err, but now err:%s", err)
}
go func() {
offset := 0
// 读取文件
buf := bufio.NewReader(file2)
for {
// 按行读取
line, _, err2 := buf.ReadLine()
if err2 == io.EOF {
time.Sleep(500 * time.Millisecond)
continue
}
if len(line) == 0 {
continue
}
//将byte[]转换为string并添加到列表中
fmt.Printf("line %d:%s\n", offset, string(line))
offset += 1
if offset >= 10 {
break
}
}
}()
time.Sleep(30 * time.Second)
fmt.Println("end")
}

View File

@@ -0,0 +1,78 @@
package fileUtil
import (
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
)
// 对文件进行gzip压缩
// source:源文件完整路径
// target:目标文件文件夹(如果传空字符串,则为当前文件夹)
// 返回值
// 错误对象
func Gzip(source, target string) error {
reader, err := os.Open(source)
if err != nil {
return err
}
defer reader.Close()
// 给目标文件夹赋值,如果传空,则默认为当前文件夹
if target == "" {
target = filepath.Dir(source)
}
fileName := filepath.Base(source)
targetFilePath := filepath.Join(target, fmt.Sprintf("%s.gz", fileName))
writer, err := os.Create(targetFilePath)
if err != nil {
return err
}
defer writer.Close()
archiver := gzip.NewWriter(writer)
archiver.Name = fileName
defer archiver.Close()
_, err = io.Copy(archiver, reader)
return err
}
// 对文件进行gzip解压缩
// source:源文件完整路径
// target:目标文件文件夹(解压缩文件的名字是内部自动赋值)
// 返回值
// 错误对象
func UnGzip(source, target string) error {
reader, err := os.Open(source)
if err != nil {
return err
}
defer reader.Close()
archive, err := gzip.NewReader(reader)
if err != nil {
return err
}
defer archive.Close()
// 给目标文件夹赋值,如果传空,则默认为当前文件夹
if target == "" {
target = filepath.Dir(source)
}
targetFilePath := filepath.Join(target, archive.Name)
writer, err := os.Create(targetFilePath)
if err != nil {
return err
}
defer writer.Close()
_, err = io.Copy(writer, archive)
return err
}

View File

@@ -0,0 +1,54 @@
package fileUtil
import (
"fmt"
"testing"
)
func TestGzip(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName := fmt.Sprintf("%s/%s", path, "test.txt")
if err := WriteFile(path, "test.txt", true, "first line\nHello world"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := Gzip(fileName, ""); err != nil {
// if err := Gzip(fileName, path); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
}
if fileList, err := GetFileList(path); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
} else {
for _, item := range fileList {
fmt.Printf("item:%s\n", item)
}
}
DeleteFile(fileName)
}
func TestUnGzip(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName := fmt.Sprintf("%s/%s", path, "test.txt.gz")
if err := UnGzip(fileName, ""); err != nil {
// if err := UnGzip(fileName, path); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
}
content, err := ReadFileContent(fmt.Sprintf("%s/%s", path, "test.txt"))
if err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
} else {
fmt.Printf("content:%s\n", content)
}
DeleteFile(fileName)
fileName = fmt.Sprintf("%s/%s", path, "test.txt")
DeleteFile(fileName)
}

View File

@@ -0,0 +1,55 @@
package fileUtil
import (
"io"
"net/http"
"os"
"path"
)
// 下载网络文件
// netUrl网络文件地址
// saveDir存储位置
// saveFileName:存储的文件名
// ifTruncate:如果文件存在了,是否覆盖此文件
// 返回值:
// err:错误对象
func DownLoadNetFile(netUrl string, saveDir string, saveFileName string, ifTruncate bool) (err error) {
resp, err := http.Get(netUrl)
defer func() {
if resp != nil {
resp.Body.Close()
}
}()
if err != nil {
return
}
// 创建文件夹
if IsDirExists(saveDir) == false {
os.MkdirAll(saveDir, os.ModePerm|os.ModeTemporary)
}
// 创建文件
filePath := path.Join(saveDir, saveFileName)
var fileObj *os.File
if ifTruncate {
fileObj, err = os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm|os.ModeTemporary)
} else {
// 如果文件已经存在,则不能打开
fileObj, err = os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, os.ModePerm|os.ModeTemporary)
}
defer func() {
if fileObj != nil {
fileObj.Close()
}
}()
if err != nil {
return
}
// 写入文件数据
_, err = io.Copy(fileObj, resp.Body)
return
}

View File

@@ -0,0 +1,13 @@
package fileUtil
import "testing"
func TestDownLoadNetFile(t *testing.T) {
err := DownLoadNetFile("https://www.baidu.com/img/bd_logo1.png", "./", "baidu.png", false)
if err != nil {
t.Error(err)
return
}
t.Log("成功了")
}

View File

@@ -0,0 +1,104 @@
package fileUtil
import (
"archive/tar"
"io"
"os"
"path/filepath"
)
// 对一组文件进行tar打包
// sourceList:源文件完整路径列表
// target:目标文件名称
// 返回值
// 错误对象
func Tar(sourceList []string, target string) error {
tarFile, err := os.Create(target)
if err != nil {
return err
}
defer tarFile.Close()
tarball := tar.NewWriter(tarFile)
defer tarball.Close()
// 对源文件遍历处理
for _, item := range sourceList {
info, err := os.Stat(item)
if err != nil || info.IsDir() {
continue
}
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
header.Name = filepath.Base(item)
if err := tarball.WriteHeader(header); err != nil {
return err
}
file, err := os.Open(item)
if err != nil {
return err
}
defer file.Close()
if _, err = io.Copy(tarball, file); err != nil {
return err
}
}
return nil
}
// 对一组文件进行tar解包
// source:源文件完整路径
// target:目标文件名称
// 返回值
// 错误对象
func Untar(source, target string) error {
reader, err := os.Open(source)
if err != nil {
return err
}
defer reader.Close()
tarReader := tar.NewReader(reader)
// 给目标文件夹赋值,如果传空,则默认为当前文件夹
if target == "" {
target = filepath.Dir(source)
}
for {
header, err := tarReader.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
targetFilePath := filepath.Join(target, header.Name)
info := header.FileInfo()
if info.IsDir() {
if err = os.MkdirAll(targetFilePath, info.Mode()); err != nil {
return err
}
continue
}
file, err := os.OpenFile(targetFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, tarReader)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,73 @@
package fileUtil
import (
"fmt"
"strings"
"testing"
)
func TestTar(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
fileName1 := fmt.Sprintf("%s/%s", path, "test1.txt")
fileName2 := fmt.Sprintf("%s/%s", path, "test2.txt")
if err := WriteFile(path, "test1.txt", true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
if err := WriteFile(path, "test2.txt", true, "first line"); err != nil {
t.Errorf("there should be no error, but now it is:%s", err)
}
sourceList := make([]string, 0, 2)
sourceList = append(sourceList, fileName1)
sourceList = append(sourceList, fileName2)
target := fmt.Sprintf("%s/%s", path, "test.tar")
if err := Tar(sourceList, target); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
}
if fileList, err := GetFileList(path); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
} else {
for _, item := range fileList {
fmt.Printf("item:%s\n", item)
}
}
DeleteFile(fileName1)
DeleteFile(fileName2)
}
func TestUntar(t *testing.T) {
path := GetCurrentPath()
fmt.Printf("CurrPath:%s\n", path)
source := fmt.Sprintf("%s/%s", path, "test.tar")
// target := path
target := ""
if err := Untar(source, target); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
}
if fileList, err := GetFileList(path); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
} else {
for _, item := range fileList {
fmt.Printf("item:%s\n", item)
if strings.HasSuffix(item, "txt") {
if content, err := ReadFileContent(item); err != nil {
t.Errorf("There should be no error, but now it has:%s", err)
} else {
fmt.Printf("content:%s\n", content)
}
DeleteFile(item)
}
}
DeleteFile(source)
}
}

25
trunk/goutil/go.mod Normal file
View File

@@ -0,0 +1,25 @@
module goutil
go 1.22.10
require (
github.com/bkaradzic/go-lz4 v1.0.0
github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4
github.com/fatih/color v1.15.0
github.com/go-sql-driver/mysql v1.5.0
github.com/gomodule/redigo v1.8.9
github.com/gorilla/websocket v1.4.2
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
google.golang.org/grpc v1.45.0
google.golang.org/protobuf v1.26.0
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
)
require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
)

147
trunk/goutil/go.sum Normal file
View File

@@ -0,0 +1,147 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 h1:OoL469zqSNrTLSz5zeVF/I6VOO7fiw2bzSzQe4J557c=
github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4/go.mod h1:xe9a/L2aeOgFKKgrO3ibQTnMdpAeL0GC+5/HpGScSa4=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

2
trunk/goutil/grpc-util/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
Log/*
logs/*

View File

@@ -0,0 +1,63 @@
package client
import (
"context"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var (
// 缓存连接 key:rpc地址 value:连接指针对象
rpcConMap sync.Map
// 连接超时时间
defaultTimeOut = time.Second * 1
)
// GetClientConn
// @description: 获取grpc连接对象
// parameter:
// @rpcAddres:连接地址
// return:
// @*grpc.ClientConn:连接对象
// @error:错误对象
func GetClientConn(rpcAddres string) (*grpc.ClientConn, error) {
// 从缓存获取
if con1, isok := rpcConMap.Load(rpcAddres); isok {
return con1.(*grpc.ClientConn), nil
}
// 构造新对象
//ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*1))
var ctx, _ = context.WithTimeout(context.Background(), defaultTimeOut)
con, err := grpc.DialContext(ctx, rpcAddres, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, err
}
// 加入缓存
rpcConMap.Store(rpcAddres, con)
return con, nil
}
// ClearClientConn
// @description: 根据连接地址,删除缓存的连接对象
// parameter:
// @rpcAddres:连接地址
// return:
func ClearClientConn(rpcAddres string) {
rpcConMap.Delete(rpcAddres)
}
// SetDefaultTimeOut
// @description: 设置连接的默认超时
// parameter:
// @outTime:超时时间
// return:
func SetDefaultTimeOut(outTime time.Duration) {
defaultTimeOut = outTime
}

View File

@@ -0,0 +1,8 @@
// ************************************
// @package: client
// @description: grpc客户端辅助包
// @author: byron
// @revision history:
// @create date: 2022-01-19 16:50:24
// ************************************
package client

View File

@@ -0,0 +1,29 @@
client简单封装了grpc相关调用方法缓存了connection连接
整体文档入口请参考:[传送门](../readme.md)
## 使用方式如下:
### 1.导入包
```go
import (
"vast.com/goutil/grpc-util/client"
)
```
### 2.获取连接
```go
con, err := client.GetClientConn(host)
```
### 3.清理某个连接
```go
client.ClearClientConn("ip:port")
```
### 4.设置连接的默认超时时间
```go
client.SetDefaultTimeOut(time.Second * 3)
```
注意:
1.设置后对后续创建的连接生效,已经创建的不生效
2.默认的超时 时间为1s

View File

@@ -0,0 +1,20 @@
grpc-util做了如下工作
### server(废弃):
1.实现服务的启动和相关方法的注册
2.提供了公共对象和接口的实现
3.提供公共req对象到res对象build辅助方法
具体参考server文档 [传送门](server/readme.md)
### client:
1.缓存了grpc连接对象使其可复用(可单独使用)
2.基于提供的公共对象和接口提供了grpc的4种基本用法的调用(建议需要搭配server使用)
具体参考client文档 [传送门](client/readme.md)
### util:
1.提供pb相关辅助方法
具体请直接看代码

View File

@@ -0,0 +1,135 @@
package util
import (
"context"
"fmt"
"net"
"strings"
"google.golang.org/grpc/peer"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
var (
moOpt protojson.MarshalOptions
uoOpt protojson.UnmarshalOptions
)
func init() {
moOpt = protojson.MarshalOptions{
// Multiline: true,
AllowPartial: true,
UseProtoNames: true,
UseEnumNumbers: true,
EmitUnpopulated: true,
}
uoOpt = protojson.UnmarshalOptions{
DiscardUnknown: true,
}
}
// PbCopy
// @description: 将from对象的内容copy给to对象
// parameter:
// @from:proto.Message
// @to:proto.Message
// return:
// @error:如果失败则返回错误否则返回nil
func PbCopy(from, to proto.Message) error {
data, err := proto.Marshal(from)
if err != nil {
return err
}
return proto.Unmarshal(data, to)
}
// Pb2Json
// @description: 将pb对象转换为json字符串
// parameter:
// @m:
// return:
// @string:pb对象的json标识形式
// @error:
func Pb2Json(m proto.Message) (string, error) {
str, err := moOpt.Marshal(m)
if err != nil {
return "", err
}
return string(str), nil
}
// Json2Pb
// @description: 将json字符串转换成对应pb对象
// parameter:
// @js:
// @m:
// return:
// @error:
func Json2Pb(js string, m proto.Message) error {
return uoOpt.Unmarshal([]byte(js), m)
}
// Marshal
// @description: 序列化pb对象
// parameter:
// @m:
// return:
// @[]byte:
// @error:
func Marshal(m proto.Message) ([]byte, error) {
data, err := proto.Marshal(m)
if err != nil {
return nil, err
}
return data, nil
}
// Marshal_Panic
// @description: 序列化pb对象
// parameter:
// @m:
// return:
// @[]byte:
func Marshal_Panic(m proto.Message) []byte {
data, err := Marshal(m)
if err != nil {
panic(fmt.Sprintf("pbUtil Marshal pb错误err:%s", err))
}
return data
}
// Unmarshal
// @description: 将数据转换为pb对象
// parameter:
// @data:
// @m:
// return:
func Unmarshal(data []byte, m proto.Message) error {
err := proto.Unmarshal(data, m)
return err
}
// GetClientIP
// @description: 获取客户端Ip
// parameter:
// @ctx:grpc底层传递过来的上下文对象
// return:
// @string:客户端ip
// @error:错误对象
func GetClientIP(ctx context.Context) (string, error) {
pr, ok := peer.FromContext(ctx)
if !ok {
return "", fmt.Errorf("GetClietIP未获取到客户端ip")
}
if pr.Addr == net.Addr(nil) {
return "", fmt.Errorf("GetClientIP 获取到的peer.Addr=nil")
}
addSlice := strings.Split(pr.Addr.String(), ":")
return addSlice[0], nil
}

View File

@@ -0,0 +1,136 @@
package idUtil
import (
"testing"
)
func TestNewTimeIdentifierSeedGenerator(t *testing.T) {
generator, err := NewTimeIdentifierSeedGenerator(40, 1, 7, 20)
if err == nil {
t.Errorf("there should be err, but now not.")
}
generator, err = NewTimeIdentifierSeedGenerator(10, int64(1), 7, 20)
if err != nil {
t.Errorf("there should be no err, but now there is.")
}
_, err = generator.GenerateNewId()
if err == nil {
t.Errorf("there should be err, but now not.")
}
generator, err = NewTimeIdentifierSeedGenerator(40, int64(127), 3, 20)
if err == nil {
t.Errorf("there should be err, but now not.")
}
count := 1048575
idMap := make(map[int64]struct{}, count)
for identifier := 0; identifier < 8; identifier++ {
generator, err = NewTimeIdentifierSeedGenerator(40, int64(identifier), 3, 20)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
for i := 0; i < count; i++ {
id, err := generator.GenerateNewId()
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if _, exist := idMap[id]; exist {
t.Errorf("Id:%d is duplicated.", id)
return
}
idMap[id] = struct{}{}
}
}
}
func TestNewIdentifierTimeSeedGenerator(t *testing.T) {
generator, err := NewIdentifierTimeSeedGenerator(int64(1), 7, 40, 20)
if err == nil {
t.Errorf("there should be err, but now not.")
}
generator, err = NewIdentifierTimeSeedGenerator(int64(1), 7, 10, 20)
if err != nil {
t.Errorf("there should be no err, but now there is.")
}
_, err = generator.GenerateNewId()
if err == nil {
t.Errorf("there should be err, but now not.")
}
generator, err = NewIdentifierTimeSeedGenerator(int64(127), 3, 40, 20)
if err == nil {
t.Errorf("there should be err, but now not.")
}
count := 1048575
idMap := make(map[int64]struct{}, count)
for identifier := 0; identifier < 8; identifier++ {
generator, err = NewIdentifierTimeSeedGenerator(int64(identifier), 3, 40, 20)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
for i := 0; i < count; i++ {
id, err := generator.GenerateNewId()
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if _, exist := idMap[id]; exist {
t.Errorf("Id:%d is duplicated.", id)
return
}
idMap[id] = struct{}{}
}
}
}
func TestNewIdentifierConstructCountSeedGenerator(t *testing.T) {
generator, err := NewIdentifierConstructCountSeedGenerator(int64(1), 27, int64(1), 20, 23)
if err == nil {
t.Errorf("there should be err, but now not.")
}
generator, err = NewIdentifierConstructCountSeedGenerator(int64(32768), 15, int64(1), 18, 30)
if err == nil {
t.Errorf("there should be err, but now not.")
}
count := 1048575
idMap := make(map[int64]struct{}, count)
for identifier := 0; identifier < 8; identifier++ {
for constructCount := 0; constructCount < 5; constructCount++ {
generator, err = NewIdentifierConstructCountSeedGenerator(int64(identifier), 15, int64(constructCount), 18, 30)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
for i := 0; i < count; i++ {
id, err := generator.GenerateNewId()
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if _, exist := idMap[id]; exist {
t.Errorf("Id:%d is duplicated.", id)
return
}
idMap[id] = struct{}{}
}
}
}
}

View File

@@ -0,0 +1,108 @@
/*
用于生成唯一的、递增的Id。生成的规则如下
1、生成的Id包含一个固定前缀值
2、为了生成尽可能多的不重复数字所以使用int64来表示一个数字其中
0 000000000000000 0000000000000000000000000000 00000000000000000000
第一部分1位固定为0
第二部分共IdentifierBit位表示固定唯一标识(机器号或者服务器Id等)。范围为[0, math.Pow(2, IdentifierBit))
第三部分共ConstructCountBit位表示对象被构造的次数。范围为[0, math.Pow(2, ConstructCountBit))以2020-1-1 00:00:00为基准
第四部分共SeedBit位表示自增种子。范围为[0, math.Pow(2, SeedBit))
3、总体而言此规则支持总共创建math.Pow(2, ConstructCountBit)次对象并且每次对象构造期间生成math.Pow(2, SeedBit)个不同的数字
*/
/*
修改记录:
2020-03-04 14:30:00 调整了时间和唯一标识在Id中的位置以便生成递增的Id
2020-04-20 21:10:00 同步了C版本的逻辑
*/
package idUtil
import (
"fmt"
"math"
"sync"
)
type IdentifierConstructCountSeedGenerator struct {
identifier int64 // 唯一标识
identifierBit int32 // 唯一标识(机器号或者服务器Id等)所占的位数
identifierBitOffset int32 // 唯一标识的偏移位数
constructCount int64 // 对象构造的次数
constructCountBit int32 // 对象构造的次数所占的位数
constructCountBitOffset int32 // 时间戳对象构造的次数的偏移位数的偏移位数
seed int64 // 当前种子值
seedBit int32 // 自增种子所占的位数
seedBitOffset int32 // 自增种子的偏移位数
maxSeed int64 // 最大的种子值
mutex sync.Mutex // 锁对象
}
func (this *IdentifierConstructCountSeedGenerator) getNewSeed() (int64, error) {
this.mutex.Lock()
defer this.mutex.Unlock()
if this.seed >= this.maxSeed {
return 0, fmt.Errorf("Seed's value is out of scope")
}
this.seed += 1
return this.seed, nil
}
// 生成新的Id
// 返回值:
// 新的Id
// 错误对象
func (this *IdentifierConstructCountSeedGenerator) GenerateNewId() (int64, error) {
seed, err := this.getNewSeed()
if err != nil {
return 0, err
}
id := (this.identifier << this.identifierBitOffset) | (this.constructCount << this.constructCountBitOffset) | (seed << this.seedBitOffset)
return id, nil
}
// 创建新的Id生成器对象为了保证Id的唯一需要保证生成的对象全局唯一
// identifierBit + constructCountBit + seedBit <= 63
// identifier:id唯一标识
// identifierBit:id唯一标识(机器号或者服务器Id等)的位数
// constructCount:对象构造次数
// constructCountBit:对象构造次数的位数
// seedBit:自增种子的位数
// 返回值:
// 新的Id生成器对象
// 错误对象
func NewIdentifierConstructCountSeedGenerator(identifier int64, identifierBit int32, constructCount int64, constructCountBit, seedBit int32) (*IdentifierConstructCountSeedGenerator, error) {
// 之所以使用63位而不是64是为了保证值为正数
if identifierBit+constructCountBit+seedBit > 63 {
return nil, fmt.Errorf("总位数%d超过63位请调整所有值的合理范围。", identifierBit+constructCountBit+seedBit)
}
if identifier < 0 || identifier > int64(math.Pow(2, float64(identifierBit)))-1 {
return nil, fmt.Errorf("唯一标识值溢出有效范围为【0,%d】", int64(math.Pow(2, float64(identifierBit)))-1)
}
if constructCount < 0 || constructCount > int64(math.Pow(2, float64(constructCountBit)))-1 {
return nil, fmt.Errorf("对象构造次数的值溢出有效范围为【0,%d】", int64(math.Pow(2, float64(constructCountBit)))-1)
}
obj := &IdentifierConstructCountSeedGenerator{
identifier: identifier,
identifierBit: identifierBit,
constructCount: constructCount,
constructCountBit: constructCountBit,
seed: 0,
seedBit: seedBit,
maxSeed: int64(math.Pow(2, float64(seedBit)) - 1),
}
obj.seedBitOffset = 0
obj.constructCountBitOffset = obj.seedBitOffset + obj.seedBit
obj.identifierBitOffset = obj.constructCountBitOffset + obj.constructCountBit
return obj, nil
}

View File

@@ -0,0 +1,121 @@
/*
用于生成唯一的、递增的Id。生成的规则如下
1、生成的Id包含一个固定前缀值
2、为了生成尽可能多的不重复数字所以使用int64来表示一个数字其中
0 000000000000000 0000000000000000000000000000 00000000000000000000
第一部分1位固定为0
第二部分共IdentifierBit位表示固定唯一标识(机器号或者服务器Id等)。范围为[0, math.Pow(2, IdentifierBit))
第三部分共TimeBit位表示当前时间距离基础时间的秒数。范围为[0, math.Pow(2, TimeBit))以2020-1-1 00:00:00为基准
第四部分共SeedBit位表示自增种子。范围为[0, math.Pow(2, SeedBit))
3、总体而言此规则支持每秒生成math.Pow(2, SeedBit)个不同的数字并且在math.Pow(2, TimeBit)/60/60/24/365年的时间范围内有效
*/
/*
修改记录:
2020-03-04 14:30:00 调整了时间和唯一标识在Id中的位置以便生成递增的Id
2020-04-20 21:10:00 同步了C版本的逻辑
*/
package idUtil
import (
"fmt"
"math"
"sync"
"time"
)
type IdentifierTimeSeedGenerator struct {
identifier int64 // 唯一标识
identifierBit int32 // 唯一标识(机器号或者服务器Id等)所占的位数
identifierBitOffset int32 // 唯一标识的偏移位数
timeBit int32 // 时间戳所占的位数
timeBitOffset int32 // 时间戳的偏移位数
startTimeStamp int64 // 起始时间戳
endTimeStamp int64 // 结束时间戳
seedBit int32 // 自增种子所占的位数
seedBitOffset int32 // 自增种子的偏移位数
minSeed int64 // 最小的种子值
maxSeed int64 // 最大的种子值
currSeed int64 // 当前种子值
mutex sync.Mutex // 锁对象
}
func (this *IdentifierTimeSeedGenerator) getTimeStamp() (int64, error) {
if time.Now().Unix() > this.endTimeStamp {
return 0, fmt.Errorf("Time's value is out of scope")
}
return time.Now().Unix() - this.startTimeStamp, nil
}
func (this *IdentifierTimeSeedGenerator) getNewSeed() int64 {
this.mutex.Lock()
defer this.mutex.Unlock()
if this.currSeed >= this.maxSeed {
this.currSeed = this.minSeed
} else {
this.currSeed = this.currSeed + 1
}
return this.currSeed
}
// 生成新的Id
// identifierId的唯一标识值。取值范围必须可以用创建对象时指定的唯一标识值的位数来表示否则会返回参数超出范围的错误
// 返回值:
// 新的Id
// 错误对象
func (this *IdentifierTimeSeedGenerator) GenerateNewId() (int64, error) {
timestamp, err := this.getTimeStamp()
if err != nil {
return 0, err
}
seed := this.getNewSeed()
id := (this.identifier << this.identifierBitOffset) | (timestamp << this.timeBitOffset) | (seed << this.seedBitOffset)
return id, nil
}
// 创建新的Id生成器对象为了保证Id的唯一需要保证生成的对象全局唯一
// identifierBit + timeBit + seedBit <= 63
// identifier:id唯一标识
// identifierBit:id唯一标识(机器号或者服务器Id等)的位数
// timeBit:时间的位数
// seedBit:自增种子的位数
// 返回值:
// 新的Id生成器对象
// 错误对象
func NewIdentifierTimeSeedGenerator(identifier int64, identifierBit, timeBit, seedBit int32) (*IdentifierTimeSeedGenerator, error) {
// 之所以使用63位而不是64是为了保证值为正数
if identifierBit+timeBit+seedBit > 63 {
return nil, fmt.Errorf("总位数%d超过63位请调整所有值的合理范围。", identifierBit+timeBit+seedBit)
}
if identifier < 0 || identifier > int64(math.Pow(2, float64(identifierBit)))-1 {
return nil, fmt.Errorf("唯一标识值溢出有效范围为【0,%d】", int64(math.Pow(2, float64(identifierBit)))-1)
}
startTimeStamp := time.Date(2019, time.January, 1, 0, 0, 0, 0, time.Local).Unix()
obj := &IdentifierTimeSeedGenerator{
identifier: identifier,
identifierBit: identifierBit,
timeBit: timeBit,
startTimeStamp: startTimeStamp,
endTimeStamp: startTimeStamp + int64(math.Pow(2, float64(timeBit))) - 1,
seedBit: seedBit,
minSeed: 0,
maxSeed: int64(math.Pow(2, float64(seedBit))) - 1,
currSeed: 0,
}
obj.seedBitOffset = 0
obj.timeBitOffset = obj.seedBitOffset + obj.seedBit
obj.identifierBitOffset = obj.timeBitOffset + obj.timeBit
return obj, nil
}

View File

@@ -0,0 +1,121 @@
/*
用于生成唯一的、递增的Id。生成的规则如下
1、生成的Id包含一个固定前缀值
2、为了生成尽可能多的不重复数字所以使用int64来表示一个数字其中
0 000000000000000 0000000000000000000000000000 00000000000000000000
第一部分1位固定为0
第二部分共TimeBit位表示当前时间距离基础时间的秒数。范围为[0, math.Pow(2, TimeBit))以2020-1-1 00:00:00为基准
第三部分共IdentifierBit位表示固定唯一标识(机器号或者服务器Id等)。范围为[0, math.Pow(2, IdentifierBit))
第四部分共SeedBit位表示自增种子。范围为[0, math.Pow(2, SeedBit))
3、总体而言此规则支持每秒生成math.Pow(2, SeedBit)个不同的数字并且在math.Pow(2, TimeBit)/60/60/24/365年的时间范围内有效
*/
/*
修改记录:
2020-03-04 14:30:00 调整了时间和唯一标识在Id中的位置以便生成递增的Id
2020-04-20 21:10:00 同步了C版本的逻辑
*/
package idUtil
import (
"fmt"
"math"
"sync"
"time"
)
type TimeIdentifierSeedGenerator struct {
timeBit int32 // 时间戳所占的位数
timeBitOffset int32 // 时间戳的偏移位数
startTimeStamp int64 // 起始时间戳
endTimeStamp int64 // 结束时间戳
identifier int64 // 唯一标识
identifierBit int32 // 唯一标识(机器号或者服务器Id等)所占的位数
identifierBitOffset int32 // 唯一标识的偏移位数
seedBit int32 // 自增种子所占的位数
seedBitOffset int32 // 自增种子的偏移位数
minSeed int64 // 最小的种子值
maxSeed int64 // 最大的种子值
currSeed int64 // 当前种子值
mutex sync.Mutex // 锁对象
}
func (this *TimeIdentifierSeedGenerator) getTimeStamp() (int64, error) {
if time.Now().Unix() > this.endTimeStamp {
return 0, fmt.Errorf("Time's value is out of scope")
}
return time.Now().Unix() - this.startTimeStamp, nil
}
func (this *TimeIdentifierSeedGenerator) getNewSeed() int64 {
this.mutex.Lock()
defer this.mutex.Unlock()
if this.currSeed >= this.maxSeed {
this.currSeed = this.minSeed
} else {
this.currSeed = this.currSeed + 1
}
return this.currSeed
}
// 生成新的Id
// identifierId的唯一标识值。取值范围必须可以用创建对象时指定的唯一标识值的位数来表示否则会返回参数超出范围的错误
// 返回值:
// 新的Id
// 错误对象
func (this *TimeIdentifierSeedGenerator) GenerateNewId() (int64, error) {
timestamp, err := this.getTimeStamp()
if err != nil {
return 0, err
}
seed := this.getNewSeed()
id := (timestamp << this.timeBitOffset) | (this.identifier << this.identifierBitOffset) | (seed << this.seedBitOffset)
return id, nil
}
// 创建新的Id生成器对象为了保证Id的唯一需要保证生成的对象全局唯一
// timeBit + identifierBit + seedBit <= 63
// timeBit:时间的位数
// identifier:id唯一标识
// identifierBit:id唯一标识(机器号或者服务器Id等)的位数
// seedBit:自增种子的位数
// 返回值:
// 新的Id生成器对象
// 错误对象
func NewTimeIdentifierSeedGenerator(timeBit int32, identifier int64, identifierBit, seedBit int32) (*TimeIdentifierSeedGenerator, error) {
// 之所以使用63位而不是64是为了保证值为正数
if timeBit+identifierBit+seedBit > 63 {
return nil, fmt.Errorf("总位数%d超过63位请调整所有值的合理范围。", timeBit+identifierBit+seedBit)
}
if identifier < 0 || identifier > int64(math.Pow(2, float64(identifierBit)))-1 {
return nil, fmt.Errorf("唯一标识值溢出有效范围为【0,%d】", int64(math.Pow(2, float64(identifierBit)))-1)
}
startTimeStamp := time.Date(2019, time.January, 1, 0, 0, 0, 0, time.Local).Unix()
obj := &TimeIdentifierSeedGenerator{
timeBit: timeBit,
startTimeStamp: startTimeStamp,
endTimeStamp: startTimeStamp + int64(math.Pow(2, float64(timeBit))) - 1,
identifier: identifier,
identifierBit: identifierBit,
seedBit: seedBit,
minSeed: 0,
maxSeed: int64(math.Pow(2, float64(seedBit))) - 1,
currSeed: 0,
}
obj.seedBitOffset = 0
obj.identifierBitOffset = obj.seedBitOffset + obj.seedBit
obj.timeBitOffset = obj.identifierBitOffset + obj.identifierBit
return obj, nil
}

View File

@@ -0,0 +1,16 @@
#配置项说明
log.es.enable=false #(false,默认值,关闭es日志记录,后续配置可不填写; true 打开日志记录)
log.es.url= #(es服务地址)
log.es.indexName=1 #(es服务中Index名)
log.es.level=info #(debug|info|warn|error|fatal等级,等于或高于配置项则记录)
log.file.enable=false #(默认false)
log.file.path=log #(运行目录下log目录,默认logs)
log.file.pre=log #(文件名前缀,默认log)
log.file.enableHour=true #(文件以小时划分,格式:yyyyMMddHH,默认true,false 一天一个文件,格式:yyyyMMdd)
log.file.level=info
log.console.enable=false #(默认false)
log.console.level=info
modelcenter.modelDBConnStr=root:moqikaka3306@tcp(10.255.0.10:3306)/xj_model_mr?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=10||MaxIdleConns=5 #(model数据库信息)

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