初始化项目
This commit is contained in:
8
trunk/goutil/.idea/.gitignore
generated
vendored
Normal file
8
trunk/goutil/.idea/.gitignore
generated
vendored
Normal 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
9
trunk/goutil/.idea/goutil.iml
generated
Normal 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
6
trunk/goutil/.idea/misc.xml
generated
Normal 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
8
trunk/goutil/.idea/modules.xml
generated
Normal 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
6
trunk/goutil/.idea/vcs.xml
generated
Normal 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>
|
||||
416
trunk/goutil/app-environment/env.go
Normal file
416
trunk/goutil/app-environment/env.go
Normal 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
|
||||
}
|
||||
265
trunk/goutil/app-environment/env_test.go
Normal file
265
trunk/goutil/app-environment/env_test.go
Normal 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
|
||||
}
|
||||
4
trunk/goutil/appChargeUtil/doc.go
Normal file
4
trunk/goutil/appChargeUtil/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
用于提供验证AppStore充值的逻辑
|
||||
*/
|
||||
package appChargeUtil
|
||||
141
trunk/goutil/appChargeUtil/receipt.go
Normal file
141
trunk/goutil/appChargeUtil/receipt.go
Normal 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
|
||||
}
|
||||
57
trunk/goutil/appChargeUtil/receipt_test.go
Normal file
57
trunk/goutil/appChargeUtil/receipt_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
82
trunk/goutil/appChargeUtil/verify.go
Normal file
82
trunk/goutil/appChargeUtil/verify.go
Normal 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)
|
||||
}
|
||||
119
trunk/goutil/baseUtil/base.go
Normal file
119
trunk/goutil/baseUtil/base.go
Normal 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")
|
||||
}
|
||||
394
trunk/goutil/baseUtil/base_test.go
Normal file
394
trunk/goutil/baseUtil/base_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
4
trunk/goutil/configUtil/doc.go
Normal file
4
trunk/goutil/configUtil/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
配置助手类,用于处理以JSON格式存储的配置文件
|
||||
*/
|
||||
package configUtil
|
||||
99
trunk/goutil/configUtil/json.go
Normal file
99
trunk/goutil/configUtil/json.go
Normal 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
|
||||
}
|
||||
109
trunk/goutil/configUtil/jsonArray.go
Normal file
109
trunk/goutil/configUtil/jsonArray.go
Normal 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
|
||||
}
|
||||
53
trunk/goutil/configUtil/jsonArray_test.go
Normal file
53
trunk/goutil/configUtil/jsonArray_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
53
trunk/goutil/configUtil/json_test.go
Normal file
53
trunk/goutil/configUtil/json_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
13
trunk/goutil/configUtil/testdata/jsonConfig.ini
vendored
Normal file
13
trunk/goutil/configUtil/testdata/jsonConfig.ini
vendored
Normal 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
|
||||
}
|
||||
18
trunk/goutil/configUtil/testdata/jsonConfigArray.ini
vendored
Normal file
18
trunk/goutil/configUtil/testdata/jsonConfigArray.ini
vendored
Normal 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
|
||||
}
|
||||
]
|
||||
299
trunk/goutil/configUtil/xmlConfig.go
Normal file
299
trunk/goutil/configUtil/xmlConfig.go
Normal 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{}
|
||||
}
|
||||
329
trunk/goutil/configUtil/xmlConfigList.go
Normal file
329
trunk/goutil/configUtil/xmlConfigList.go
Normal 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
|
||||
}
|
||||
135
trunk/goutil/configUtil/xmlConfigList_test.go
Normal file
135
trunk/goutil/configUtil/xmlConfigList_test.go
Normal 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
|
||||
}
|
||||
321
trunk/goutil/configUtil/xmlConfig_test.go
Normal file
321
trunk/goutil/configUtil/xmlConfig_test.go
Normal 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
|
||||
}
|
||||
2
trunk/goutil/coroutine-timer/.gitignore
vendored
Normal file
2
trunk/goutil/coroutine-timer/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Log/*
|
||||
logs/*
|
||||
47
trunk/goutil/coroutine-timer/cmd.go
Normal file
47
trunk/goutil/coroutine-timer/cmd.go
Normal 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
|
||||
}
|
||||
73
trunk/goutil/coroutine-timer/model.go
Normal file
73
trunk/goutil/coroutine-timer/model.go
Normal 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
|
||||
}
|
||||
20
trunk/goutil/coroutine-timer/readme.md
Normal file
20
trunk/goutil/coroutine-timer/readme.md
Normal file
@@ -0,0 +1,20 @@
|
||||
coroutine-timer支持如下工作:
|
||||
定时触发设定的回调,最小精度秒级
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 增加回调
|
||||
|
||||
> 1. 导入包
|
||||
> 2. 调用AddTimerx添加定时回调,传入相关参数
|
||||
|
||||
ps:
|
||||
> 1. AddTimer1,AddTimer2,AddTimer3是内部自动生成的id,内部保证唯一性。外部如果后续要删除该添加的timer,需要持有返回的id信息
|
||||
> 2. AddTimer4 需要外部传入id,外部需要保证id的唯一性。并且这个方法会在内部校验id是否已经存在,所以性能上会比其他AddTimer方法慢
|
||||
|
||||
### 删除回调
|
||||
|
||||
```go
|
||||
DeleteTimer(id)
|
||||
```
|
||||
|
||||
409
trunk/goutil/coroutine-timer/timer-mgr.go
Normal file
409
trunk/goutil/coroutine-timer/timer-mgr.go
Normal 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)
|
||||
}
|
||||
40
trunk/goutil/coroutine-timer/timer-obj.go
Normal file
40
trunk/goutil/coroutine-timer/timer-obj.go
Normal 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
|
||||
}
|
||||
114
trunk/goutil/coroutine-timer/timer_test.go
Normal file
114
trunk/goutil/coroutine-timer/timer_test.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
60
trunk/goutil/counter-util/counter.go
Normal file
60
trunk/goutil/counter-util/counter.go
Normal 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
|
||||
}
|
||||
33
trunk/goutil/counter-util/counter_test.go
Normal file
33
trunk/goutil/counter-util/counter_test.go
Normal 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()
|
||||
}
|
||||
35
trunk/goutil/counter-util/readme.md
Normal file
35
trunk/goutil/counter-util/readme.md
Normal 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()
|
||||
}
|
||||
|
||||
```
|
||||
104
trunk/goutil/dbUtil/dataRow.go
Normal file
104
trunk/goutil/dbUtil/dataRow.go
Normal 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,
|
||||
}
|
||||
}
|
||||
189
trunk/goutil/dbUtil/dataTable.go
Normal file
189
trunk/goutil/dbUtil/dataTable.go
Normal 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
|
||||
}
|
||||
195
trunk/goutil/dbUtil/valueConvert.go
Normal file
195
trunk/goutil/dbUtil/valueConvert.go
Normal 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)
|
||||
}
|
||||
66
trunk/goutil/debugUtil/debug.go
Normal file
66
trunk/goutil/debugUtil/debug.go
Normal 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...)
|
||||
}
|
||||
47
trunk/goutil/debugUtil/display.go
Normal file
47
trunk/goutil/debugUtil/display.go
Normal 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
|
||||
)
|
||||
4
trunk/goutil/debugUtil/doc.go
Normal file
4
trunk/goutil/debugUtil/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
提供调试功能的助手包
|
||||
*/
|
||||
package debugUtil
|
||||
81
trunk/goutil/deviceUtil/deviceUtil.go
Normal file
81
trunk/goutil/deviceUtil/deviceUtil.go
Normal 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
|
||||
}
|
||||
}
|
||||
131
trunk/goutil/deviceUtil/deviceUtil_test.go
Normal file
131
trunk/goutil/deviceUtil/deviceUtil_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
240
trunk/goutil/dfaExUtil/dfaEx.go
Normal file
240
trunk/goutil/dfaExUtil/dfaEx.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package dfaExUtil
|
||||
|
||||
/*
|
||||
* 扩展DFA算法
|
||||
*
|
||||
* 一种二层树实现类DFA算法(DFA为多层树结构;go语言特性中的map结构过于“重量级”导致内存占用很大;此外还可能存在大量相同字符结点)
|
||||
*
|
||||
* 第一层map为所有字母/汉字作为key;value为第二层map
|
||||
* 第二层map为第一层冲突字母/汉字的自定义hash作为key;value指示是否为敏感词结束标识
|
||||
*
|
||||
* 测试结果: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)
|
||||
}
|
||||
15
trunk/goutil/dfaExUtil/dfaEx_test.go
Normal file
15
trunk/goutil/dfaExUtil/dfaEx_test.go
Normal 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)
|
||||
}
|
||||
98
trunk/goutil/dfaExUtil/hash32/hash32.go
Normal file
98
trunk/goutil/dfaExUtil/hash32/hash32.go
Normal 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
|
||||
}
|
||||
98
trunk/goutil/dfaExUtil/hash64/hash64.go
Normal file
98
trunk/goutil/dfaExUtil/hash64/hash64.go
Normal 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
|
||||
}
|
||||
BIN
trunk/goutil/dfaExUtil/三维树转二维树.bmp
Normal file
BIN
trunk/goutil/dfaExUtil/三维树转二维树.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
219
trunk/goutil/dfaUtil/dfa.go
Normal file
219
trunk/goutil/dfaUtil/dfa.go
Normal 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
|
||||
}
|
||||
2088
trunk/goutil/dfaUtil/dfa_test.go
Normal file
2088
trunk/goutil/dfaUtil/dfa_test.go
Normal file
File diff suppressed because it is too large
Load Diff
23
trunk/goutil/dfaUtil/trieNode.go
Normal file
23
trunk/goutil/dfaUtil/trieNode.go
Normal 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),
|
||||
}
|
||||
}
|
||||
9
trunk/goutil/doc/Code Specification.txt
Normal file
9
trunk/goutil/doc/Code Specification.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
#go语言项目约定
|
||||
1、如果在函数内部出现错误,需记录该错误,然后抛出错误,直至回退到函数最外层进行处理。(对于项目初始化的函数,还需调用panic结束协程)
|
||||
2、对于包内逻辑独立完整的类型,才需建立子包单独存放;对于其他情况,请在同一级包内进行处理。(注意:包与文件夹概念不同,轻易建立子包会造成数据与接口的暴露)
|
||||
3、对于变量,一律以小写开头;如果该变量需要对包外提供,提供Get,Set接口进行数据访问。
|
||||
4、对于常量,如果该常量仅包内可见,前缀小写(con_);如果包外可见,前缀大写(Con_)。
|
||||
5、对于自定义类型,如果仅包内可见,小写开头;如果包外可见,大写开头。类型内部字段名默认小写开头,需要序列化的字段大写。
|
||||
6、对于通道(chan),如果仅包内可见,小写开头;如果包外可见,大写开头,此处不提供接口访问。
|
||||
7、在函数参数中出现的变量若与包内数据同名,统一以_开头加以区分。
|
||||
8、对于需要向包外提供的数据,如果不需要修改,统一返回副本,如果需要在包外修改,请返回指针。
|
||||
3
trunk/goutil/ensureSendUtil/.gitignore
vendored
Normal file
3
trunk/goutil/ensureSendUtil/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
DefaultLogPath/
|
||||
|
||||
/test_*/
|
||||
54
trunk/goutil/ensureSendUtil/baseSender.go
Normal file
54
trunk/goutil/ensureSendUtil/baseSender.go
Normal 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
|
||||
}
|
||||
54
trunk/goutil/ensureSendUtil/bytesSendUtil/baseSender.go
Normal file
54
trunk/goutil/ensureSendUtil/bytesSendUtil/baseSender.go
Normal 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
|
||||
}
|
||||
101
trunk/goutil/ensureSendUtil/bytesSendUtil/dataItem.go
Normal file
101
trunk/goutil/ensureSendUtil/bytesSendUtil/dataItem.go
Normal 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
|
||||
}
|
||||
29
trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSendUtil.go
Normal file
29
trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSendUtil.go
Normal 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) {
|
||||
*/
|
||||
24
trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSender.go
Normal file
24
trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSender.go
Normal 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{}
|
||||
}
|
||||
96
trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender.go
Normal file
96
trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender.go
Normal 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
|
||||
}
|
||||
86
trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender_test.go
Normal file
86
trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
58
trunk/goutil/ensureSendUtil/bytesSendUtil/saveData.go
Normal file
58
trunk/goutil/ensureSendUtil/bytesSendUtil/saveData.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
111
trunk/goutil/ensureSendUtil/bytesSendUtil/send.go
Normal file
111
trunk/goutil/ensureSendUtil/bytesSendUtil/send.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
208
trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender.go
Normal file
208
trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender.go
Normal 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
|
||||
}
|
||||
}
|
||||
95
trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender_test.go
Normal file
95
trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender_test.go
Normal 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()
|
||||
}
|
||||
101
trunk/goutil/ensureSendUtil/dataItem.go
Normal file
101
trunk/goutil/ensureSendUtil/dataItem.go
Normal 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
|
||||
}
|
||||
29
trunk/goutil/ensureSendUtil/ensureSendUtil.go
Normal file
29
trunk/goutil/ensureSendUtil/ensureSendUtil.go
Normal 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) {
|
||||
*/
|
||||
24
trunk/goutil/ensureSendUtil/ensureSender.go
Normal file
24
trunk/goutil/ensureSendUtil/ensureSender.go
Normal 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{}
|
||||
}
|
||||
96
trunk/goutil/ensureSendUtil/httpSender.go
Normal file
96
trunk/goutil/ensureSendUtil/httpSender.go
Normal 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
|
||||
}
|
||||
86
trunk/goutil/ensureSendUtil/httpSender_test.go
Normal file
86
trunk/goutil/ensureSendUtil/httpSender_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
58
trunk/goutil/ensureSendUtil/saveData.go
Normal file
58
trunk/goutil/ensureSendUtil/saveData.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
111
trunk/goutil/ensureSendUtil/send.go
Normal file
111
trunk/goutil/ensureSendUtil/send.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
208
trunk/goutil/ensureSendUtil/tcpSender.go
Normal file
208
trunk/goutil/ensureSendUtil/tcpSender.go
Normal 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
|
||||
}
|
||||
}
|
||||
95
trunk/goutil/ensureSendUtil/tcpSender_test.go
Normal file
95
trunk/goutil/ensureSendUtil/tcpSender_test.go
Normal 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()
|
||||
}
|
||||
113
trunk/goutil/esLogUtil/esIndexHandler.go
Normal file
113
trunk/goutil/esLogUtil/esIndexHandler.go
Normal 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())
|
||||
}
|
||||
}
|
||||
269
trunk/goutil/esLogUtil/esLogUtil.go
Normal file
269
trunk/goutil/esLogUtil/esLogUtil.go
Normal 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日志系统
|
||||
// 参数:
|
||||
//
|
||||
// esUrls:ES地址(多个地址使用,分割)
|
||||
// name:IndexName
|
||||
// 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
|
||||
35
trunk/goutil/esLogUtil/esLogUtil_test.go
Normal file
35
trunk/goutil/esLogUtil/esLogUtil_test.go
Normal 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()
|
||||
}
|
||||
39
trunk/goutil/esLogUtil/model.go
Normal file
39
trunk/goutil/esLogUtil/model.go
Normal 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(),
|
||||
//}
|
||||
}
|
||||
200
trunk/goutil/fileUtil/bigFile.go
Normal file
200
trunk/goutil/fileUtil/bigFile.go
Normal 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"))
|
||||
}
|
||||
37
trunk/goutil/fileUtil/bigFile_test.go
Normal file
37
trunk/goutil/fileUtil/bigFile_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
4
trunk/goutil/fileUtil/doc.go
Normal file
4
trunk/goutil/fileUtil/doc.go
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
文件助手类
|
||||
*/
|
||||
package fileUtil
|
||||
298
trunk/goutil/fileUtil/file.go
Normal file
298
trunk/goutil/fileUtil/file.go
Normal 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)
|
||||
}
|
||||
345
trunk/goutil/fileUtil/file_test.go
Normal file
345
trunk/goutil/fileUtil/file_test.go
Normal 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")
|
||||
}
|
||||
78
trunk/goutil/fileUtil/gzip.go
Normal file
78
trunk/goutil/fileUtil/gzip.go
Normal 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
|
||||
}
|
||||
54
trunk/goutil/fileUtil/gzip_test.go
Normal file
54
trunk/goutil/fileUtil/gzip_test.go
Normal 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)
|
||||
}
|
||||
55
trunk/goutil/fileUtil/netFile.go
Normal file
55
trunk/goutil/fileUtil/netFile.go
Normal 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
|
||||
}
|
||||
13
trunk/goutil/fileUtil/netFile_test.go
Normal file
13
trunk/goutil/fileUtil/netFile_test.go
Normal 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("成功了")
|
||||
}
|
||||
104
trunk/goutil/fileUtil/tar.go
Normal file
104
trunk/goutil/fileUtil/tar.go
Normal 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
|
||||
}
|
||||
73
trunk/goutil/fileUtil/tar_test.go
Normal file
73
trunk/goutil/fileUtil/tar_test.go
Normal 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
25
trunk/goutil/go.mod
Normal 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
147
trunk/goutil/go.sum
Normal 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
2
trunk/goutil/grpc-util/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Log/*
|
||||
logs/*
|
||||
63
trunk/goutil/grpc-util/client/client.go
Normal file
63
trunk/goutil/grpc-util/client/client.go
Normal 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
|
||||
}
|
||||
8
trunk/goutil/grpc-util/client/doc.go
Normal file
8
trunk/goutil/grpc-util/client/doc.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// ************************************
|
||||
// @package: client
|
||||
// @description: grpc客户端辅助包
|
||||
// @author: byron
|
||||
// @revision history:
|
||||
// @create date: 2022-01-19 16:50:24
|
||||
// ************************************
|
||||
package client
|
||||
29
trunk/goutil/grpc-util/client/readme.md
Normal file
29
trunk/goutil/grpc-util/client/readme.md
Normal 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
|
||||
20
trunk/goutil/grpc-util/readme.md
Normal file
20
trunk/goutil/grpc-util/readme.md
Normal 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相关辅助方法
|
||||
具体请直接看代码
|
||||
|
||||
135
trunk/goutil/grpc-util/util/pbUtil.go
Normal file
135
trunk/goutil/grpc-util/util/pbUtil.go
Normal 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
|
||||
}
|
||||
136
trunk/goutil/idUtil/idUtil_test.go
Normal file
136
trunk/goutil/idUtil/idUtil_test.go
Normal 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{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
108
trunk/goutil/idUtil/identifierConstructorSeed.go
Normal file
108
trunk/goutil/idUtil/identifierConstructorSeed.go
Normal 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
|
||||
}
|
||||
121
trunk/goutil/idUtil/identifierTimeSeed.go
Normal file
121
trunk/goutil/idUtil/identifierTimeSeed.go
Normal 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
|
||||
// identifier:Id的唯一标识值。取值范围必须可以用创建对象时指定的唯一标识值的位数来表示,否则会返回参数超出范围的错误
|
||||
// 返回值:
|
||||
// 新的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
|
||||
}
|
||||
121
trunk/goutil/idUtil/timeIdentifierSeed.go
Normal file
121
trunk/goutil/idUtil/timeIdentifierSeed.go
Normal 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
|
||||
// identifier:Id的唯一标识值。取值范围必须可以用创建对象时指定的唯一标识值的位数来表示,否则会返回参数超出范围的错误
|
||||
// 返回值:
|
||||
// 新的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
|
||||
}
|
||||
16
trunk/goutil/ini-config/ini.conf
Normal file
16
trunk/goutil/ini-config/ini.conf
Normal 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
Reference in New Issue
Block a user