Apply .gitignore rules

This commit is contained in:
皮蛋13361098506
2025-01-06 16:21:36 +08:00
parent 1b77f62820
commit ccd2c530cf
580 changed files with 69806 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/goProject.iml" filepath="$PROJECT_DIR$/.idea/goProject.iml" />
</modules>
</component>
</project>
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="ALL" />
</component>
<component name="ChangeListManager">
<list default="true" id="1cbe937e-e3b5-473c-ae0b-961313bb7e86" name="更改" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="GOROOT" url="file://$PROJECT_DIR$/../../../Program Files/Go" />
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 2
}]]></component>
<component name="ProjectId" id="2qn71YfCvRaIgWjkDctgPm123Ac" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.go.formatter.settings.were.checked": "true",
"RunOnceActivity.go.migrated.go.modules.settings": "true",
"RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true",
"go.import.settings.migrated": "true",
"go.sdk.automatically.set": "true",
"last_opened_file_path": "D:/workspace/e2023/goProject/trunk",
"nodejs_package_manager_path": "npm"
}
}]]></component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-gosdk-5df93f7ad4aa-df9ad98b711f-org.jetbrains.plugins.go.sharedIndexes.bundled-GO-242.22855.85" />
<option value="bundled-js-predefined-d6986cc7102b-5c90d61e3bab-JavaScript-GO-242.22855.85" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VgoProject">
<settings-migrated>true</settings-migrated>
</component>
</project>
+1
View File
@@ -0,0 +1 @@
12
+1
View File
@@ -0,0 +1 @@
12
@@ -0,0 +1,34 @@
package webServer
import (
"reflect"
)
// methodAndInOutTypes
// @description: 反射的方法和输入、输出参数类型组合类型
type methodAndInOutTypes struct {
// 反射出来的对应方法对象
Method reflect.Value
// 反射出来的方法的输入参数的类型集合
InTypes []reflect.Type
// 反射出来的方法的输出参数的类型集合
OutTypes []reflect.Type
}
// newmethodAndInOutTypes
// @description: newmethodAndInOutTypes
// parameter:
// @_method: _method
// @_inTypes: _inTypes
// @_outTypes: _outTypes
// return:
// @*methodAndInOutTypes: methodAndInOutTypes
func newmethodAndInOutTypes(_method reflect.Value, _inTypes []reflect.Type, _outTypes []reflect.Type) *methodAndInOutTypes {
return &methodAndInOutTypes{
Method: _method,
InTypes: _inTypes,
OutTypes: _outTypes,
}
}
@@ -0,0 +1,110 @@
gxpath
====
gxpath is XPath packages for the Go, that lets you extract data from the custom documents using XPath expression.
**[XQuery](https://github.com/antchfx/xquery)** : gxpath implemented, lets you extract data from HTML/XML documents using XPath.
### Features
#### The basic XPath patterns.
> The basic XPath patterns cover 90% of the cases that most stylesheets will need.
- `node` : Selects all child elements with nodeName of node.
- `*` : Selects all child elements.
- `@attr` : Selects the attribute attr.
- `@*` : Selects all attributes.
- `node()` : Matches an org.w3c.dom.Node.
- `text()` : Matches a org.w3c.dom.Text node.
- `comment()` : Matches a comment.
- `.` : Selects the current node.
- `..` : Selects the parent of current node.
- `/` : Selects the document node.
- `a[expr]` : Select only those nodes matching a which also satisfy the expression expr.
- `a[n]` : Selects the nth matching node matching a When a filter's expression is a number, XPath selects based on position.
- `a/b` : For each node matching a, add the nodes matching b to the result.
- `a//b` : For each node matching a, add the descendant nodes matching b to the result.
- `//b` : Returns elements in the entire document matching b.
- `a|b` : All nodes matching a or b.
#### Node Axes
- `child::*` : The child axis selects children of the current node.
- `descendant::*` : The descendant axis selects descendants of the current node. It is equivalent to '//'.
- `descendant-or-self::*` : Selects descendants including the current node.
- `attribute::*` : Selects attributes of the current element. It is equivalent to @*
- `following-sibling::*` : Selects nodes after the current node.
- `preceding-sibling::*` : Selects nodes before the current node.
- `following::*` : Selects the first matching node following in document order, excluding descendants.
- `preceding::*` : Selects the first matching node preceding in document order, excluding ancestors.
- `parent::*` : Selects the parent if it matches. The '..' pattern from the core is equivalent to 'parent::node()'.
- `ancestor::*` : Selects matching ancestors.
- `ancestor-or-self::*` : Selects ancestors including the current node.
- `self::*` : Selects the current node. '.' is equivalent to 'self::node()'.
#### Expressions
The gxpath supported three types: number, boolean, string.
- `path` : Selects nodes based on the path.
- `a = b` : Standard comparisons.
* a = b True if a equals b.
* a != b True if a is not equal to b.
* a < b True if a is less than b.
* a <= b True if a is less than or equal to b.
* a > b True if a is greater than b.
* a >= b True if a is greater than or equal to b.
- `a + b` : Arithmetic expressions.
* `- a` Unary minus
* a + b Add
* a - b Substract
* a * b Multiply
* a div b Divide
* a mod b Floating point mod, like Java.
- `(expr)` : Parenthesized expressions.
- `fun(arg1, ..., argn)` : Function calls.
* position()
* last()
* count(node-set)
* name()
* starts-with(string,string)
* normalize-space(string)
* substring(string,start[,length])
* come more
- `a or b` : Boolean or.
- `a and b` : Boolean and.
@@ -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) {
*/
@@ -0,0 +1,129 @@
/*
url的格式如下:https://cmq-{$type}-{$region}.api.{$network}.com
其最终内容受到以下因素的影响:地域、网络、消息队列模型
地域
gz(广州)、sh(上海)、bj(北京)、shjr(上海金融)、szjr(深圳金融)、hk(中国香港)、cd(成都)、ca(北美)、usw(美西)、use(美东)、in(印度)、th(泰国)、sg(新加坡)
网络
外网接口请求域名后缀:api.qcloud.com
内网接口请求域名后缀:api.tencentyun.com
队列模型
请参照下面说明将域名中的 {$region} 替换成相应地域:
外网接口请求域名:https://cmq-queue-{$region}.api.qcloud.com
内网接口请求域名:http://cmq-queue-{$region}.api.tencentyun.com
主题模型
请参照下面说明将域名中的 {$region} 替换成相应地域:
外网接口请求域名:https://cmq-topic-{$region}.api.qcloud.com
内网接口请求域名:http://cmq-topic-{$region}.api.tencentyun.com
*/
package mqMgr
import (
"fmt"
"strings"
"goutil/securityUtil"
"goutil/stringUtil"
"goutil/webUtil"
)
func getPrefix(network string) string {
if network == MQ_NETWORK_INTERNAL {
return "http://"
}
return "https://"
}
// // 获取请求url
// // region:地域
// // network:网络类型:内网、外网
// // _type:消息队列类型:消息队列、消息主题
// // 返回:
// // 请求url
// func getHost(region, network, _type string) string {
// url := "cmq-{$type}-{$region}.api.{$network}.com"
// url = strings.Replace(url, "{$region}", region, 1)
// url = strings.Replace(url, "{$network}", network, 1)
// url = strings.Replace(url, "{$type}", _type, 1)
// return url
// }
// 获取请求url todo:切换成tdmq之后需要用这个方法
// region:地域
// network:网络类型:内网、外网
// _type:消息队列类型:消息队列、消息主题
// 返回:
// 请求url
func getHost(region, network, _type string) string {
var url string = ""
if network == MQ_NETWORK_INTERNAL {
url = "{$region}.mqadapter.cmq.{$network}.com"
} else {
url = "cmq-{$region}.public.{$network}.com"
}
url = strings.Replace(url, "{$region}", region, 1)
url = strings.Replace(url, "{$network}", network, 1)
return url
}
func getPath() string {
return "/v2/index.php"
}
func getMethod() string {
return "POST"
}
// AssembleUrl 组装请求url
// 参数
// region:地域
// network:网络类型:内网、外网
// _type:消息队列类型:消息队列、消息主题
// secretKey:密钥的key
// paramMap:参数字典
// 返回值
// string:组装好的请求url
// string:签名
// error:错误
func AssembleUrl(region, network, _type, secretKey string, paramMap map[string]string) (url, signature string, err error) {
// 1. 申请安全凭证(已经得到)
// 2. 生成签名串
// 2.1、对参数排序
// 2.2、拼接请求字符串
// 注意:
// “参数值”为原始值而非 url 编码后的值。
// 若输入参数中包含下划线,则需要将其转换为“.”。(指的是参数的名称,不是参数的值)
paramStr := webUtil.AssembleRequestParamSort(paramMap, true)
// 2.3、拼接签名原文字符串
host := getHost(region, network, _type)
path := getPath()
signatureSource := fmt.Sprintf("%s%s%s?%s", getMethod(), host, path, paramStr)
// 2.4、生成签名串
data, err := securityUtil.HmacSha256(signatureSource, secretKey)
if err != nil {
return
}
signature = string(stringUtil.Base64Encode2(data))
// 3. 签名串编码
// 注意:
// 生成的签名串并不能直接作为请求参数,需要对其进行 URL 编码。
// 如果用户的请求方法是 GET,则对所有请求参数值均需要做 URL 编码。
// 如果是POST,则不用进行URL编码
// signature = url.QueryEscape(signature)
// 将签名添加到参数集合中
url = fmt.Sprintf("%s%s%s", getPrefix(network), host, path)
return
}
@@ -0,0 +1,36 @@
package timeUtil
import (
"testing"
"time"
)
func TestGetTime(t *testing.T) {
timeVal := time.Date(2018, 4, 25, 9, 36, 1, 0, time.Local)
timeStr1 := ToDateTimeString2(timeVal)
utcTime := GetUTCTime(timeVal)
timeStr2 := ToDateTimeString2(utcTime)
if timeStr1 != timeStr2 {
t.Errorf("获取UTC时间出错,两个时间不对等")
}
utcTime2 := GetUTCTime(utcTime)
timeStr3 := ToDateTimeString2(utcTime2)
if timeStr1 != timeStr3 {
t.Errorf("两次的UTC时间不对等")
}
utcTime4 := GetLocalTime(utcTime)
timeStr4 := ToDateTimeString2(utcTime4)
if timeStr4 != timeStr1 {
t.Errorf("local变更了时间 time1:%v time4:%v", timeStr1, timeStr4)
}
utcTime5 := GetLocalTime(utcTime)
timeStr5 := ToDateTimeString2(utcTime5)
if timeStr4 != timeStr5 {
t.Errorf("两次的local时间不对等")
}
}
@@ -0,0 +1,658 @@
package query
import (
"reflect"
"goutil/xmlUtil/gxpath/xpath"
)
// An XPath query interface.
type Query interface {
// Select traversing Iterator returns a query matched node xpath.NodeNavigator.
Select(Iterator) xpath.NodeNavigator
// Evaluate evaluates query and returns values of the current query.
Evaluate(Iterator) interface{}
// Test checks a specified xpath.NodeNavigator can passed by the current query.
//Test(xpath.NodeNavigator) bool
}
// ContextQuery is returns current node on the Iterator object query.
type ContextQuery struct {
count int
Root bool // Moving to root-level node in the current context Iterator.
}
func (c *ContextQuery) Select(t Iterator) (n xpath.NodeNavigator) {
if c.count == 0 {
c.count++
n = t.Current().Copy()
if c.Root {
n.MoveToRoot()
}
}
return n
}
func (c *ContextQuery) Evaluate(Iterator) interface{} {
c.count = 0
return c
}
// AncestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*)
type AncestorQuery struct {
iterator func() xpath.NodeNavigator
Self bool
Input Query
Predicate func(xpath.NodeNavigator) bool
}
func (a *AncestorQuery) Select(t Iterator) xpath.NodeNavigator {
for {
if a.iterator == nil {
node := a.Input.Select(t)
if node == nil {
return nil
}
first := true
a.iterator = func() xpath.NodeNavigator {
if first && a.Self {
first = false
if a.Predicate(node) {
return node
}
}
for node.MoveToParent() {
if !a.Predicate(node) {
break
}
return node
}
return nil
}
}
if node := a.iterator(); node != nil {
return node
}
a.iterator = nil
}
}
func (a *AncestorQuery) Evaluate(t Iterator) interface{} {
a.Input.Evaluate(t)
return a
}
func (a *AncestorQuery) Test(n xpath.NodeNavigator) bool {
return a.Predicate(n)
}
// AttributeQuery is an XPath attribute node query.(@*)
type AttributeQuery struct {
iterator func() xpath.NodeNavigator
Input Query
Predicate func(xpath.NodeNavigator) bool
}
func (a *AttributeQuery) Select(t Iterator) xpath.NodeNavigator {
for {
if a.iterator == nil {
node := a.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
a.iterator = func() xpath.NodeNavigator {
for {
onAttr := node.MoveToNextAttribute()
if !onAttr {
return nil
}
if a.Predicate(node) {
return node
}
}
}
}
if node := a.iterator(); node != nil {
return node
}
a.iterator = nil
}
}
func (a *AttributeQuery) Evaluate(t Iterator) interface{} {
a.Input.Evaluate(t)
a.iterator = nil
return a
}
func (a *AttributeQuery) Test(n xpath.NodeNavigator) bool {
return a.Predicate(n)
}
// ChildQuery is an XPath child node query.(child::*)
type ChildQuery struct {
posit int
iterator func() xpath.NodeNavigator
Input Query
Predicate func(xpath.NodeNavigator) bool
}
func (c *ChildQuery) Select(t Iterator) xpath.NodeNavigator {
for {
if c.iterator == nil {
c.posit = 0
node := c.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
first := true
c.iterator = func() xpath.NodeNavigator {
for {
if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) {
return nil
}
first = false
if c.Predicate(node) {
return node
}
}
}
}
if node := c.iterator(); node != nil {
c.posit++
return node
}
c.iterator = nil
}
}
func (c *ChildQuery) Evaluate(t Iterator) interface{} {
c.Input.Evaluate(t)
c.iterator = nil
return c
}
func (c *ChildQuery) Test(n xpath.NodeNavigator) bool {
return c.Predicate(n)
}
// position returns a position of current xpath.NodeNavigator.
func (c *ChildQuery) position() int {
return c.posit
}
// DescendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*)
type DescendantQuery struct {
iterator func() xpath.NodeNavigator
Self bool
Input Query
Predicate func(xpath.NodeNavigator) bool
}
func (d *DescendantQuery) Select(t Iterator) xpath.NodeNavigator {
for {
if d.iterator == nil {
node := d.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
level := 0
first := true
d.iterator = func() xpath.NodeNavigator {
if first && d.Self {
first = false
if d.Predicate(node) {
return node
}
}
for {
if node.MoveToChild() {
level++
} else {
for {
if level == 0 {
return nil
}
if node.MoveToNext() {
break
}
node.MoveToParent()
level--
}
}
if d.Predicate(node) {
return node
}
}
}
}
if node := d.iterator(); node != nil {
return node
}
d.iterator = nil
}
}
func (d *DescendantQuery) Evaluate(t Iterator) interface{} {
d.Input.Evaluate(t)
d.iterator = nil
return d
}
func (d *DescendantQuery) Test(n xpath.NodeNavigator) bool {
return d.Predicate(n)
}
// FollowingQuery is an XPath following node query.(following::*|following-sibling::*)
type FollowingQuery struct {
iterator func() xpath.NodeNavigator
Input Query
Sibling bool // The matching sibling node of current node.
Predicate func(xpath.NodeNavigator) bool
}
func (f *FollowingQuery) Select(t Iterator) xpath.NodeNavigator {
for {
if f.iterator == nil {
node := f.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
if f.Sibling {
f.iterator = func() xpath.NodeNavigator {
for {
if !node.MoveToNext() {
return nil
}
if f.Predicate(node) {
return node
}
}
}
} else {
var q Query // descendant query
f.iterator = func() xpath.NodeNavigator {
for {
if q == nil {
for !node.MoveToNext() {
if !node.MoveToParent() {
return nil
}
}
q = &DescendantQuery{
Self: true,
Input: &ContextQuery{},
Predicate: f.Predicate,
}
t.Current().MoveTo(node)
}
if node := q.Select(t); node != nil {
return node
}
q = nil
}
}
}
}
if node := f.iterator(); node != nil {
return node
}
f.iterator = nil
}
}
func (f *FollowingQuery) Evaluate(t Iterator) interface{} {
f.Input.Evaluate(t)
return f
}
func (f *FollowingQuery) Test(n xpath.NodeNavigator) bool {
return f.Predicate(n)
}
// PrecedingQuery is an XPath preceding node query.(preceding::*)
type PrecedingQuery struct {
iterator func() xpath.NodeNavigator
Input Query
Sibling bool // The matching sibling node of current node.
Predicate func(xpath.NodeNavigator) bool
}
func (p *PrecedingQuery) Select(t Iterator) xpath.NodeNavigator {
for {
if p.iterator == nil {
node := p.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
if p.Sibling {
p.iterator = func() xpath.NodeNavigator {
for {
for !node.MoveToPrevious() {
return nil
}
if p.Predicate(node) {
return node
}
}
}
} else {
var q Query
p.iterator = func() xpath.NodeNavigator {
for {
if q == nil {
for !node.MoveToPrevious() {
if !node.MoveToParent() {
return nil
}
}
q = &DescendantQuery{
Self: true,
Input: &ContextQuery{},
Predicate: p.Predicate,
}
t.Current().MoveTo(node)
}
if node := q.Select(t); node != nil {
return node
}
q = nil
}
}
}
}
if node := p.iterator(); node != nil {
return node
}
p.iterator = nil
}
}
func (p *PrecedingQuery) Evaluate(t Iterator) interface{} {
p.Input.Evaluate(t)
return p
}
func (p *PrecedingQuery) Test(n xpath.NodeNavigator) bool {
return p.Predicate(n)
}
// ParentQuery is an XPath parent node query.(parent::*)
type ParentQuery struct {
Input Query
Predicate func(xpath.NodeNavigator) bool
}
func (p *ParentQuery) Select(t Iterator) xpath.NodeNavigator {
for {
node := p.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
if node.MoveToParent() && p.Predicate(node) {
return node
}
}
}
func (p *ParentQuery) Evaluate(t Iterator) interface{} {
p.Input.Evaluate(t)
return p
}
func (p *ParentQuery) Test(n xpath.NodeNavigator) bool {
return p.Predicate(n)
}
// SelfQuery is an Self node query.(self::*)
type SelfQuery struct {
Input Query
Predicate func(xpath.NodeNavigator) bool
}
func (s *SelfQuery) Select(t Iterator) xpath.NodeNavigator {
for {
node := s.Input.Select(t)
if node == nil {
return nil
}
if s.Predicate(node) {
return node
}
}
}
func (s *SelfQuery) Evaluate(t Iterator) interface{} {
s.Input.Evaluate(t)
return s
}
func (s *SelfQuery) Test(n xpath.NodeNavigator) bool {
return s.Predicate(n)
}
// FilterQuery is an XPath query for predicate filter.
type FilterQuery struct {
Input Query
Predicate Query
}
func (f *FilterQuery) do(t Iterator) bool {
val := reflect.ValueOf(f.Predicate.Evaluate(t))
switch val.Kind() {
case reflect.Bool:
return val.Bool()
case reflect.String:
return len(val.String()) > 0
case reflect.Float64:
pt := float64(getNodePosition(f.Input))
return int(val.Float()) == int(pt)
default:
if q, ok := f.Predicate.(Query); ok {
return q.Select(t) != nil
}
}
return false
}
func (f *FilterQuery) Select(t Iterator) xpath.NodeNavigator {
for {
node := f.Input.Select(t)
if node == nil {
return node
}
node = node.Copy()
//fmt.Println(node.LocalName())
t.Current().MoveTo(node)
if f.do(t) {
return node
}
}
}
func (f *FilterQuery) Evaluate(t Iterator) interface{} {
f.Input.Evaluate(t)
return f
}
// FunctionQuery is an XPath function that call a function to returns
// value of current xpath.NodeNavigator node.
type XPathFunction struct {
Input Query // Node Set
Func func(Query, Iterator) interface{} // The xpath function.
}
func (f *XPathFunction) Select(t Iterator) xpath.NodeNavigator {
return nil
}
// Evaluate call a specified function that will returns the
// following value type: number,string,boolean.
func (f *XPathFunction) Evaluate(t Iterator) interface{} {
return f.Func(f.Input, t)
}
// XPathConstant is an XPath constant operand.
type XPathConstant struct {
Val interface{}
}
func (c *XPathConstant) Select(t Iterator) xpath.NodeNavigator {
return nil
}
func (c *XPathConstant) Evaluate(t Iterator) interface{} {
return c.Val
}
// LogicalExpr is an XPath logical expression.
type LogicalExpr struct {
Left, Right Query
Do func(Iterator, interface{}, interface{}) interface{}
}
func (l *LogicalExpr) Select(t Iterator) xpath.NodeNavigator {
// When a XPath expr is logical expression.
node := t.Current().Copy()
val := l.Evaluate(t)
switch val.(type) {
case bool:
if val.(bool) == true {
return node
}
}
return nil
}
func (l *LogicalExpr) Evaluate(t Iterator) interface{} {
m := l.Left.Evaluate(t)
n := l.Right.Evaluate(t)
return l.Do(t, m, n)
}
// NumericExpr is an XPath numeric operator expression.
type NumericExpr struct {
Left, Right Query
Do func(interface{}, interface{}) interface{}
}
func (n *NumericExpr) Select(t Iterator) xpath.NodeNavigator {
return nil
}
func (n *NumericExpr) Evaluate(t Iterator) interface{} {
m := n.Left.Evaluate(t)
k := n.Right.Evaluate(t)
return n.Do(m, k)
}
type BooleanExpr struct {
IsOr bool
Left, Right Query
iterator func() xpath.NodeNavigator
}
func (b *BooleanExpr) Select(t Iterator) xpath.NodeNavigator {
if b.iterator == nil {
var list []xpath.NodeNavigator
i := 0
root := t.Current().Copy()
if b.IsOr {
for {
node := b.Left.Select(t)
if node == nil {
break
}
node = node.Copy()
list = append(list, node)
}
t.Current().MoveTo(root)
for {
node := b.Right.Select(t)
if node == nil {
break
}
node = node.Copy()
list = append(list, node)
}
} else {
var m []xpath.NodeNavigator
var n []xpath.NodeNavigator
for {
node := b.Left.Select(t)
if node == nil {
break
}
node = node.Copy()
list = append(m, node)
}
t.Current().MoveTo(root)
for {
node := b.Right.Select(t)
if node == nil {
break
}
node = node.Copy()
list = append(n, node)
}
for _, k := range m {
for _, j := range n {
if k == j {
list = append(list, k)
}
}
}
}
b.iterator = func() xpath.NodeNavigator {
if i >= len(list) {
return nil
}
node := list[i]
i++
return node
}
}
return b.iterator()
}
func (b *BooleanExpr) Evaluate(t Iterator) interface{} {
m := b.Left.Evaluate(t)
if m.(bool) == b.IsOr {
return m
}
return b.Right.Evaluate(t)
}
func getNodePosition(q Query) int {
type Position interface {
position() int
}
if count, ok := q.(Position); ok {
return count.position()
}
return 1
}
@@ -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)
}
}
@@ -0,0 +1,35 @@
package logUtilPlus
import (
"testing"
"time"
)
func TestWrite(t *testing.T) {
Start("http://10.254.0.242:9200", "20008_gs_log", 20008)
InfoLog("日志测试")
WarnLog("日志测试")
DebugLog("日志测试")
ErrorLog("日志测试")
FatalLog("日志测试")
time.Sleep(1 * time.Second)
Stop()
}
func BenchmarkWrite(b *testing.B) {
Start("http://10.254.0.242:9200", "20008_gs_log", 20008)
b.ResetTimer()
for i := 0; i < b.N; i++ {
InfoLog("日志测试%d", i)
WarnLog("日志测试%d", i)
DebugLog("日志测试%d", i)
ErrorLog("日志测试%d", i)
FatalLog("日志测试%d", i)
}
b.StopTimer()
time.Sleep(1 * time.Second)
Stop()
}
@@ -0,0 +1,88 @@
package ini_config
import (
"fmt"
"os"
"path"
"testing"
)
func Test_File(t *testing.T) {
foder, _ := os.Getwd()
filePath := path.Join(foder, "ini.conf")
kvmap, err := ParseFile(filePath)
if err != nil {
t.Error(err)
return
}
if len(kvmap) != 12 {
t.Error("读取的内容数量不正确")
return
}
if v, exists := kvmap["log.es.enable"]; exists == false || v != "false" {
t.Error("log.es.enable读取的值不正确")
}
if v, exists := kvmap["log.es.url"]; exists == false || v != "" {
t.Error("log.es.url读取的值不正确")
}
}
func Test_String(t *testing.T) {
k, v, err := Parse("log.es.enable")
if err == nil {
t.Error(fmt.Errorf("解析格式错误"))
return
}
k, v, err = Parse("log.es.enable=false #(false,默认值,关闭es日志记录,后续配置可不填写; true 打开日志记录)")
if err != nil {
t.Error(err)
return
}
if k != "log.es.enable" || v != "false" {
t.Error("解析的值不正确")
}
k, v, err = Parse("dbconnection=root:moqikaka3309!#@tcp(10.252.0.62:3309)/liangjian2_groupserver_auto_master?charset=utf8&parseTime=true&loc=Local&timeout=30s#数据库连接")
if err != nil {
t.Error(err)
return
}
if k != "dbconnection" || v != "root:moqikaka3309!#@tcp(10.252.0.62:3309)/liangjian2_groupserver_auto_master?charset=utf8&parseTime=true&loc=Local&timeout=30s" {
t.Error("解析的值不正确")
}
}
func Test_Content(t *testing.T) {
content := "#配置项说明\nlog.es.enable #(false,默认值,关闭es日志记录,后续配置可不填写; true 打开日志记录)\nlog.es.url= #(es服务地址)\nlog.es.indexName=1 #(es服务中Index名)\nlog.es.level=info #(debug|info|warn|error|fatal等级,等于或高于配置项则记录)\n\nlog.file.enable=false #(默认false)\nlog.file.path=log #(运行目录下log目录,默认logs)\nlog.file.pre=log #(文件名前缀,默认log)\nlog.file.enableHour=true #(文件以小时划分,格式:yyyyMMddHH,默认true,false 一天一个文件,格式:yyyyMMdd)\nlog.file.level=info\n\nlog.console.enable=false #(默认false)\nlog.console.level=info"
kvmap, err := ParseMultipleLines(content)
if err == nil {
t.Error(fmt.Errorf("配置格式不正确"))
return
}
content = "#配置项说明\nlog.es.enable=false #(false,默认值,关闭es日志记录,后续配置可不填写; true 打开日志记录)\nlog.es.url= #(es服务地址)\nlog.es.indexName=1 #(es服务中Index名)\nlog.es.level=info #(debug|info|warn|error|fatal等级,等于或高于配置项则记录)\n\nlog.file.enable=false #(默认false)\nlog.file.path=log #(运行目录下log目录,默认logs)\nlog.file.pre=log #(文件名前缀,默认log)\nlog.file.enableHour=true #(文件以小时划分,格式:yyyyMMddHH,默认true,false 一天一个文件,格式:yyyyMMdd)\nlog.file.level=info\n\nlog.console.enable=false #(默认false)\nlog.console.level=info"
kvmap, err = ParseMultipleLines(content)
if err != nil {
t.Error(err)
return
}
if len(kvmap) != 11 {
t.Error("读取的内容数量不正确")
return
}
if v, exists := kvmap["log.es.enable"]; exists == false || v != "false" {
t.Error("log.es.enable读取的值不正确")
}
if v, exists := kvmap["log.es.url"]; exists == false || v != "" {
t.Error("log.es.url读取的值不正确")
}
}
@@ -0,0 +1,98 @@
package ipMgr
import (
"fmt"
"testing"
)
func TestQuery(t *testing.T) {
IP_SERVICE_URL = "http://ipip.7qule.com/query"
appId := "unittest"
appId_wrong := "wrong"
appSecret := "c5746980-5d52-4ba9-834f-13d0066ad1d0"
appSecret_wrong := "wrong"
ip := "117.139.247.210"
ip_wrong := "wrong"
isDomestic := true
continent := ""
country := "中国"
region := "四川"
city := "成都"
timeout := 3
// Test with wrong AppId
ipInfoObj, err := Query(appId_wrong, appSecret, ip, isDomestic, timeout)
if err == nil {
t.Errorf("There should be an error, but now there is no error")
return
}
// Test with wrong AppSecret
ipInfoObj, err = Query(appId, appSecret_wrong, ip, isDomestic, timeout)
if err == nil {
t.Errorf("There should be an error, but now there is no error")
return
}
// Test with wrong IP
ipInfoObj, err = Query(appId, appSecret, ip_wrong, isDomestic, timeout)
if err == nil {
t.Errorf("There should be an error, but now there is no error")
return
}
// Test with correct information and domestic ip
ipInfoObj, err = Query(appId, appSecret, ip, isDomestic, timeout)
if err != nil {
t.Errorf("There should be no error, but now there is one:%s", err)
return
}
fmt.Printf("ipInfoObj:%v\n", ipInfoObj)
if ipInfoObj.Continent != continent {
t.Errorf("Expected continent %s, but got %s", continent, ipInfoObj.Continent)
}
if ipInfoObj.Country != country {
t.Errorf("Expected country %s, but got %s", country, ipInfoObj.Country)
}
if ipInfoObj.Region != region {
t.Errorf("Expected region %s, but got %s", region, ipInfoObj.Region)
}
if ipInfoObj.City != city {
t.Errorf("Expected city %s, but got %s", city, ipInfoObj.City)
}
// Test with correct information and foreign ip
isDomestic = false
continent = "亚洲"
country = "中国"
region = "四川省"
city = "成都"
ipInfoObj, err = Query(appId, appSecret, ip, isDomestic, timeout)
if err != nil {
t.Errorf("There should be no error, but now there is one:%s", err)
return
}
fmt.Printf("ipInfoObj:%v\n", ipInfoObj)
if ipInfoObj.Continent != continent {
t.Errorf("Expected continent %s, but got %s", continent, ipInfoObj.Continent)
}
if ipInfoObj.Country != country {
t.Errorf("Expected country %s, but got %s", country, ipInfoObj.Country)
}
if ipInfoObj.Region != region {
t.Errorf("Expected region %s, but got %s", region, ipInfoObj.Region)
}
if ipInfoObj.City != city {
t.Errorf("Expected city %s, but got %s", city, ipInfoObj.City)
}
}
@@ -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
}
@@ -0,0 +1,67 @@
package logUtil
import (
"os"
"os/exec"
"path/filepath"
"testing"
"time"
impl_console "goutil/logUtil/impl-console"
impl_es "goutil/logUtil/impl-es"
impl_localfile "goutil/logUtil/impl-localfile"
)
func TestAllLog(t *testing.T) {
file, _ := exec.LookPath(os.Args[0])
path, _ := filepath.Abs(file)
logPath := filepath.Dir(path)
GetLocalFileLog().SetLogPath(logPath)
//添加控制台日志
consoleLog := impl_console.NewLogger()
//添加es日志
urls := []string{"http://10.1.0.71:9101/"}
eslog, err := impl_es.NewLogger(urls, "", "", "es_log_test", "les_log_test_innerid", nil)
if err != nil {
t.Error("esLog 创建失败")
}
SettingLogs([]ILog{consoleLog, eslog, impl_localfile.NewLogger()})
for i := 1; i < 10; i++ {
InfoLog("Info记录")
InfoLog("Info记录2:%v %v", i, time.Now())
DebugLog("Debug记录")
DebugLog("Debug记录2:%v %v", i, time.Now())
WarnLog("Warn记录")
WarnLog("Warn记录2:%v %v", i, time.Now())
ErrorLog("Error记录")
ErrorLog("ErrorLog记录2:%v %v", i, time.Now())
FatalLog("Fatal记录")
FatalLog("Fatal记录2:%v %v", i, time.Now())
}
time.Sleep(time.Second * 5)
Close(true)
}
func BenchmarkInfoLog(b *testing.B) {
file, _ := exec.LookPath(os.Args[0])
path, _ := filepath.Abs(file)
logPath := filepath.Dir(path)
GetLocalFileLog().SetLogPath(logPath)
for i := 0; i < b.N; i++ {
DebugLog("Debug 记录")
InfoLog("info记录 :%v", time.Now())
}
Close(true)
}
@@ -0,0 +1,25 @@
package managecenterMgr
// ManageCenter数据获取开关(每一类数据一个开关)
type ManageCenterDataSwitch struct {
// 获取所有数据开关(只要这个开关值为true,则不论各类数据的开关是否打开,都获取数据)
AllDataSwitch bool
// 获取合作商数据开关
PartnerDataSwitch bool
// 获取服务器数据开关
ServerDataSwitch bool
// 获取服务器组数开关
ServerGroupDataSwitch bool
// 获取资源包版本数据开关
ResourceVersionDataSwitch bool
// 获取白名单数据开关
WhiteListDataSwitch bool
// 获取大区数据开关
AreaDataSwitch bool
}
@@ -0,0 +1,38 @@
module admincenter
go 1.22.2
replace (
common => ../common
framework => ../../framework
goutil => ../../goutil
)
require (
common v0.0.0-00010101000000-000000000000
goutil v0.0.0-20230425160006-b2d0b0a0b0b0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
framework v0.0.0-20230425160006-b2d0b0a0b0b0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/gomodule/redigo v1.8.9 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/jinzhu/gorm v1.9.12 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/gorm v1.25.12 // indirect
)
@@ -0,0 +1,259 @@
package timeUtil
import (
"fmt"
"strconv"
"strings"
"time"
"goutil/stringUtil"
)
// format time like java, such as: yyyy-MM-dd HH:mm:ss
// t:时间
// format:格式化字符串
// 返回值:
// 格式化后的字符串
func Format(t time.Time, format string) string {
//year
if strings.ContainsAny(format, "y") {
year := strconv.Itoa(t.Year())
if strings.Count(format, "yy") == 1 && strings.Count(format, "y") == 2 {
format = strings.Replace(format, "yy", year[2:], 1)
} else if strings.Count(format, "yyyy") == 1 && strings.Count(format, "y") == 4 {
format = strings.Replace(format, "yyyy", year, 1)
} else {
panic("format year error! please 'yyyy' or 'yy'")
}
}
//month
if strings.ContainsAny(format, "M") {
var month string
if int(t.Month()) < 10 {
month = "0" + strconv.Itoa(int(t.Month()))
} else {
month = strconv.Itoa(int(t.Month()))
}
if strings.Count(format, "MM") == 1 && strings.Count(format, "M") == 2 {
format = strings.Replace(format, "MM", month, 1)
} else {
panic("format month error! please 'MM'")
}
}
//day
if strings.ContainsAny(format, "d") {
var day string
if t.Day() < 10 {
day = "0" + strconv.Itoa(t.Day())
} else {
day = strconv.Itoa(t.Day())
}
if strings.Count(format, "dd") == 1 && strings.Count(format, "d") == 2 {
format = strings.Replace(format, "dd", day, 1)
} else {
panic("format day error! please 'dd'")
}
}
//hour
if strings.ContainsAny(format, "H") {
var hour string
if t.Hour() < 10 {
hour = "0" + strconv.Itoa(t.Hour())
} else {
hour = strconv.Itoa(t.Hour())
}
if strings.Count(format, "HH") == 1 && strings.Count(format, "H") == 2 {
format = strings.Replace(format, "HH", hour, 1)
} else {
panic("format hour error! please 'HH'")
}
}
//minute
if strings.ContainsAny(format, "m") {
var minute string
if t.Minute() < 10 {
minute = "0" + strconv.Itoa(t.Minute())
} else {
minute = strconv.Itoa(t.Minute())
}
if strings.Count(format, "mm") == 1 && strings.Count(format, "m") == 2 {
format = strings.Replace(format, "mm", minute, 1)
} else {
panic("format minute error! please 'mm'")
}
}
//second
if strings.ContainsAny(format, "s") {
var second string
if t.Second() < 10 {
second = "0" + strconv.Itoa(t.Second())
} else {
second = strconv.Itoa(t.Second())
}
if strings.Count(format, "ss") == 1 && strings.Count(format, "s") == 2 {
format = strings.Replace(format, "ss", second, 1)
} else {
panic("format second error! please 'ss'")
}
}
return format
}
// 转换成日期字符串
// timeVal:待转换的时间
// 返回值:
// string:格式形如:2016-10-10
/*
前面是含义,后面是 go 的表示值,多种表示,逗号","分割
月份 1,01,Jan,January
日  2,02,_2
时  3,03,15,PM,pm,AM,am
分  4,04
秒  5,05
年  06,2006
时区 -07,-0700,Z0700,Z07:00,-07:00,MST
周几 Mon,Monday
*/
func ToDateString(timeVal time.Time) string {
return timeVal.Local().Format("2006-01-02")
}
// 忽略时区,转换成日期字符串
// timeVal:待转换的时间
// 返回值:
// string:格式形如:2016-10-10
/*
前面是含义,后面是 go 的表示值,多种表示,逗号","分割
月份 1,01,Jan,January
日  2,02,_2
时  3,03,15,PM,pm,AM,am
分  4,04
秒  5,05
年  06,2006
时区 -07,-0700,Z0700,Z07:00,-07:00,MST
周几 Mon,Monday
*/
func ToDateString2(timeVal time.Time) string {
return timeVal.Format("2006-01-02")
}
// 以本地时区为准,转换成时间字符串
// timeVal:待转换的时间
// 返回值:
// string:格式形如:2016-10-10 10:10:10
/*
前面是含义,后面是 go 的表示值,多种表示,逗号","分割
月份 1,01,Jan,January
日  2,02,_2
时  3,03,15,PM,pm,AM,am
分  4,04
秒  5,05
年  06,2006
时区 -07,-0700,Z0700,Z07:00,-07:00,MST
周几 Mon,Monday
*/
func ToDateTimeString(timeVal time.Time) string {
return ToDateTimeStringEx(timeVal, false)
}
func ToDateTimeStringEx(timeVal time.Time, flagT bool) string {
if flagT {
val := timeVal.Local().Format("2006-01-02 15:04:05")
return strings.Replace(val, " ", "T", -1)
}
return timeVal.Local().Format("2006-01-02 15:04:05")
}
// 忽略时区,转换成时间字符串
// timeVal:待转换的时间
// 返回值:
// string:格式形如:2016-10-10 10:10:10
/*
前面是含义,后面是 go 的表示值,多种表示,逗号","分割
月份 1,01,Jan,January
日  2,02,_2
时  3,03,15,PM,pm,AM,am
分  4,04
秒  5,05
年  06,2006
时区 -07,-0700,Z0700,Z07:00,-07:00,MST
周几 Mon,Monday
*/
func ToDateTimeString2(timeVal time.Time) string {
return ToDateTimeStringEx2(timeVal, false)
}
// 日期和时间中间带T方式
func ToDateTimeStringEx2(timeVal time.Time, flagT bool) string {
if flagT {
val := timeVal.Format("2006-01-02 15:04:05")
return strings.Replace(val, " ", "T", -1)
}
return timeVal.Format("2006-01-02 15:04:05")
}
// 转换成日期格式
func ToDateTime(timeVal string) (time.Time, error) {
if stringUtil.IsEmpty(timeVal) {
return time.Time{}, fmt.Errorf("timeval is empty")
}
return time.ParseInLocation("2006-01-02 15:04:05", timeVal, time.Local)
}
// 以指定时区,转换成日期格式
func ToDateTime2(timeVal string, location *time.Location) (time.Time, error) {
if stringUtil.IsEmpty(timeVal) {
return time.Time{}, fmt.Errorf("timeval is empty")
}
return time.ParseInLocation("2006-01-02 15:04:05", timeVal, location)
}
// 转换成时间格式
func ToDate(timeVal string) (time.Time, error) {
if stringUtil.IsEmpty(timeVal) {
return time.Time{}, fmt.Errorf("timeval is empty")
}
return time.ParseInLocation("2006-01-02", timeVal, time.Local)
}
// 转换成时间格式
func ToDate2(timeVal string, location *time.Location) (time.Time, error) {
if stringUtil.IsEmpty(timeVal) {
return time.Time{}, fmt.Errorf("timeval is empty")
}
return time.ParseInLocation("2006-01-02", timeVal, location)
}
// 转换成yyyyMMddHHmmssms的格式
func ToInt64(timeVal time.Time) int64 {
year := timeVal.Year()
month := int(timeVal.Month())
day := timeVal.Day()
hour := timeVal.Hour()
minute := timeVal.Minute()
second := timeVal.Second()
return int64(int64(year)*1e10) + int64(month*1e8) + int64(day*1e6) + int64(hour*1e4) + int64(minute*1e2) + int64(second)
}
@@ -0,0 +1,19 @@
package game
import "common/connection"
func init() {
//注册数据库
connection.RegisterDBModel(&Game{})
}
type Game struct {
GameID int64 `gorm:"column:game_id;primary_key;comment:用户id;autoIncrementIncrement" json:"gameid"`
//账号
Account string `gorm:"column:account;comment:账号" json:"account"`
}
func (Game) TableName() string {
return "user"
}
@@ -0,0 +1,80 @@
package gameLogMgr
import (
"fmt"
"github.com/Shopify/sarama"
"goutil/debugUtil"
"goutil/logUtilPlus"
)
var (
producer sarama.AsyncProducer
)
// 启动生产者
// 参数:
// brokerList:Broker地址
// userId:用户名(可默认为空字符串)
// passWard:密码(可默认为空字符串)
// 返回值:
// 无
func Start(brokerList []string, userId string, passWard string) {
/*
设置 acks = all。acks 是 Producer 的一个参数,代表了你对“已提交”消息的定义。如果设置成 all,则表明所有副本 Broker 都要接收到消息,该消息才算是“已提交”。这是最高等级的“已提交”定义。
对于游戏日志,设置为WaitForLocal即可;如果是游戏数据,则应设置为WaitForAll
设置 retries 为一个较大的值。这里的 retries 同样是 Producer 的参数,对应前面提到的 Producer 自动重试。当出现网络的瞬时抖动时,消息发送可能会失败,此时配置了 retries > 0 的 Producer 能够自动重试消息发送,避免消息丢失。
*/
var err error
config := sarama.NewConfig()
config.Net.SASL.User = userId
config.Net.SASL.Password = passWard
config.Producer.Return.Successes = false
config.Producer.Return.Errors = true
config.Producer.Retry.Max = 10
config.Producer.RequiredAcks = sarama.WaitForLocal
producer, err = sarama.NewAsyncProducer(brokerList, config)
if err != nil {
panic(fmt.Errorf("Kafka Start failed. Error: %v\n", err))
}
go func() {
for err := range producer.Errors() {
debugUtil.Printf("Send message to kafka failed. Error: %v\n", err.Err)
logUtilPlus.ErrorLog("Send message to kafka failed. Error: %v\n", err.Err)
}
}()
}
func Stop() {
if producer != nil {
err := producer.Close()
if err != nil {
debugUtil.Printf("Stop kafka failed. Error: %v\n", err)
logUtilPlus.ErrorLog("Stop kafka failed. Error: %v\n", err)
}
}
}
// 写入游戏日志
// 参数:
// serverGroupId: 游戏服务器组Id
// key: 标识
// message: 日志
// 返回值: 无
func Write(topic string, serverGroupId int32, message string) {
if producer == nil {
debugUtil.Printf("Send message to kafka failed. producer is nil")
logUtilPlus.ErrorLog("Send message to kafka failed. producer is nil")
return
}
msg := &sarama.ProducerMessage{}
msg.Topic = topic
msg.Key = sarama.StringEncoder(fmt.Sprintf("%d", serverGroupId))
msg.Value = sarama.ByteEncoder(message)
// Send to kafka
producer.Input() <- msg
}
@@ -0,0 +1,14 @@
package gameLogMgr
// 游戏日志对象
type GameLog struct {
ServerGroupId int32 // 服务器组Id
LogSql string // 日志Sql
}
func newGameLog(serverGroupId int32, logSql string) *GameLog {
return &GameLog{
ServerGroupId: serverGroupId,
LogSql: logSql,
}
}
@@ -0,0 +1,13 @@
package mysqlSync
/*
提供数据同步到mysql的方法。基本逻辑如下:
1、对外接收数据,以追加的方式保存到大文件中。数据的格式为:header(4bytes)+content。
2、启动独立的goroutine来从大文件中读取数据,并保存到数据库中。
3、使用syncInfo.txt文件保存当前已经处理的文件的路径,以及下一次将要读取的文件的Offset。为了降低向syncInfo.txt文件中写入失败,
导致需要从头开始同步数据,所以采用了在指定数目的范围内以追加形式来写入数据的方式;只有达到了指定数量才会将整个文件清空。
对于错误的处理方式,分为以下两种:
1、文件错误:由于文件系统是本系统的核心,所以如果出现文件的读写出错,则需要终止整个进程,所以需要抛出panic。
2、数据库错误:当数据库不可访问时,为了不影响整个外部进程的运行,故而不抛出panic,而只是通过monitorNewMgr.Report的方式来报告故障。
*/
@@ -0,0 +1,18 @@
package mqMgr
// 地域
const (
MQ_REGION_GUANGZHOU = "gz"
MQ_REGION_SAHNGHAI = "sh"
MQ_REGION_BEIJING = "bj"
MQ_REGION_SHANGHAIJINRONG = "shjr"
MQ_REGION_SHENZHENJINRONG = "szjr"
MQ_REGION_HONGKONG = "hk"
MQ_REGION_CHENGDU = "cd"
MQ_REGION_CANADA = "ca"
MQ_REGION_UNITED_STATES_EAST = "use"
MQ_REGION_UNITED_STATES_WEST = "usw"
MQ_REGION_INDIA = "in"
MQ_REGION_THILAND = "th"
MQ_REGION_SINGAPORE = "sg"
)
@@ -0,0 +1,265 @@
package typeUtil
import (
"fmt"
"time"
)
// KeyValue数据集合
type MapData map[string]interface{}
// 创建新的MapData
// mapData:原有的map数据
// 返回
// 新的Map对象
func NewMapData(mapData map[string]interface{}) MapData {
return MapData(mapData)
}
// 类型转换为byte
// 返回值:
// byte:结果
// error:错误数据
func (this MapData) Byte(key string) (value byte, err error) {
return this.Uint8(key)
}
// 类型转换为int
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Int(key string) (value int, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Int(val)
return
}
// 类型转换为int8
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Int8(key string) (value int8, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Int8(val)
return
}
// 类型转换为int16
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Int16(key string) (value int16, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Int16(val)
return
}
// 类型转换为int32
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Int32(key string) (value int32, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Int32(val)
return
}
// 类型转换为int64
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Int64(key string) (value int64, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Int64(val)
return
}
// 类型转换为uint
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Uint(key string) (value uint, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Uint(val)
return
}
// 类型转换为uint8
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Uint8(key string) (value uint8, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Uint8(val)
return
}
// 类型转换为uint16
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Uint16(key string) (value uint16, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Uint16(val)
return
}
// 类型转换为uint32
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Uint32(key string) (value uint32, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Uint32(val)
return
}
// 类型转换为uint64
// 返回值:
// int:结果
// error:错误数据
func (this MapData) Uint64(key string) (value uint64, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Uint64(val)
return
}
// 类型转换为float32
// 返回值:
// float64:结果
// error:错误数据
func (this MapData) Float32(key string) (value float32, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Float32(val)
return
}
// 类型转换为float64
// 返回值:
// float64:结果
// error:错误数据
func (this MapData) Float64(key string) (value float64, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Float64(val)
return
}
// 类型转换为bool
// 返回值:
// bool:结果
// error:错误信息
func (this MapData) Bool(key string) (value bool, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = Bool(val)
return
}
// 类型转换为字符串
// 返回值:
// string:结果
// error:错误信息
func (this MapData) String(key string) (value string, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = String(val)
return
}
// 转换为时间格式,如果是字符串,则要求内容格式形如:2017-02-14 05:20:00
// 返回值:
// bool:结果
// error:错误信息
func (this MapData) DateTime(key string) (value time.Time, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value, err = DateTime(val)
return
}
// 获取指定的值
// 返回值:
// interface{}:结果
// error:错误信息
func (this MapData) Interface(key string) (value interface{}, err error) {
val, exist := this[key]
if exist == false || val == nil {
err = fmt.Errorf("Target key: [%s] doesn't exist", key)
return
}
value = val
return
}
@@ -0,0 +1,73 @@
package shortUrlMgr
import (
"encoding/json"
"fmt"
"time"
"goutil/securityUtil"
"goutil/stringUtil"
"goutil/webUtil"
)
var (
ShortUrl_SERVICE_URL = "http://a.app366.com/get"
)
// 服务器的响应对象
type QueryResponse struct {
// 响应结果的状态值
ResultStatus string
// 响应结果的数据
Data string
}
// 获取短链
// appId: 为应用分配的唯一标识
// appSecret: 为应用分配的密钥
// fullUrl: 完整的Url
// timeout:超时时间(单位:秒)
// 返回值:
// ipInfoObj: IP地址信息对象
// err: 错误对象
func GetShortUrl(appId, appSecret, fullUrl string, timeout int) (shortUrl string, err error) {
timeStamp := fmt.Sprintf("%d", time.Now().Unix())
fullUrl = stringUtil.Base64Encode(fullUrl)
rawString := fmt.Sprintf("AppId=%s&FullUrl=%s&Timestamp=%s&AppSecret=%s", appId, fullUrl, timeStamp, appSecret)
sign := securityUtil.Md5String(rawString, true)
postData := make(map[string]string, 5)
postData["AppId"] = appId
postData["FullUrl"] = fullUrl
postData["Timestamp"] = timeStamp
postData["Sign"] = sign
header := webUtil.GetFormHeader()
transport := webUtil.NewTransport()
transport.DisableKeepAlives = true
transport = webUtil.GetTimeoutTransport(transport, timeout)
statusCode, result, err := webUtil.PostMapData(ShortUrl_SERVICE_URL, postData, header, transport)
if err != nil {
return
}
if statusCode != 200 {
err = fmt.Errorf("StatusCode:%d is wrong.", statusCode)
return
}
var queryResponseObj *QueryResponse
err = json.Unmarshal(result, &queryResponseObj)
if err != nil {
return
}
if queryResponseObj.ResultStatus != "" {
err = fmt.Errorf("Query result:%s", queryResponseObj.ResultStatus)
return
}
shortUrl = queryResponseObj.Data
return
}
@@ -0,0 +1,39 @@
package reloadMgr
import (
"fmt"
"goutil/logUtil"
)
var (
reloadFuncMap = make(map[string]func() error)
)
// RegisterReloadFunc ...注册Reload方法
// funcName:方法名称
// reloadFuncreload方法
func RegisterReloadFunc(funcName string, reloadFunc func() error) {
if _, exists := reloadFuncMap[funcName]; exists {
panic(fmt.Sprintf("%s已经存在,请重新取名", funcName))
}
reloadFuncMap[funcName] = reloadFunc
logUtil.InfoLog(fmt.Sprintf("RegisterReloadFunc funcName:%s,当前共有%d个注册", funcName, len(reloadFuncMap)))
}
// Reload ...重新加载
// 返回值:
// 错误列表
func Reload() (errList []error) {
for funcName, reloadFunc := range reloadFuncMap {
if err := reloadFunc(); err == nil {
logUtil.InfoLog(fmt.Sprintf("Call ReloadFunc:%s Success.", funcName))
} else {
logUtil.ErrorLog(fmt.Sprintf("Call ReloadFunc:%s Fail, Error:%s", funcName, err))
errList = append(errList, err)
}
}
return
}
@@ -0,0 +1,55 @@
package stringUtil
import (
"encoding/base64"
)
const (
base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
// const encodeStd = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
// const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
)
var coder = base64.NewEncoding(base64Table)
// 对字符串进行Base64编码
func Base64Encode(src string) string {
if src == "" {
return src
}
return base64.StdEncoding.EncodeToString([]byte(src))
}
// 对字符串进行Base64解码
func Base64Encode2(src []byte) []byte {
if len(src) == 0 {
return src
}
return []byte(base64.StdEncoding.EncodeToString(src))
}
// 对字符数组进行Base64编码
func Base64Decode(src string) (string, error) {
if src == "" {
return src, nil
}
bytes, err := coder.DecodeString(src)
if err != nil {
return "", err
}
return string(bytes), nil
}
// 对字符数组进行Base64解码
func Base64Decode2(src []byte) ([]byte, error) {
if len(src) == 0 {
return src, nil
}
return coder.DecodeString(string(src))
}
@@ -0,0 +1,41 @@
// ************************************
// @package: handleMgr
// @description: 反射类-反射的方法和输入、输出参数类型组合类型
// @author:
// @revision history:
// @create date: 2022-02-23 16:33:43
// ************************************
package handleMgr
import (
"reflect"
)
// ReflectMethod 反射的方法和输入、输出参数类型组合类型
type ReflectMethod struct {
// 反射出来的对应方法对象
Method reflect.Value
// 反射出来的方法的输入参数的类型集合
InTypes []reflect.Type
// 反射出来的方法的输出参数的类型集合
OutTypes []reflect.Type
}
// NewReflectMethod
// @description:创建反射的方法和输入、输出参数类型组合类型
// parameter:
// @_method:反射出来的对应方法对象
// @_inTypes:反射出来的方法的输入参数的类型集合
// @_outTypes:反射出来的方法的输出参数的类型集合
// return:
// @*ReflectMethod:
func NewReflectMethod(_method reflect.Value, _inTypes []reflect.Type, _outTypes []reflect.Type) *ReflectMethod {
return &ReflectMethod{
Method: _method,
InTypes: _inTypes,
OutTypes: _outTypes,
}
}
@@ -0,0 +1,4 @@
/*
提供时间相关的一些助手方法
*/
package timeUtil
@@ -0,0 +1,33 @@
package exitMgr
import (
"fmt"
"goutil/logUtil"
)
var (
exitFuncMap = make(map[string]func())
)
// RegisterExitFunc ...注册Exit方法
// funcName:方法名称
// exitFuncexit方法
func RegisterExitFunc(funcName string, exitFunc func()) {
if _, exists := exitFuncMap[funcName]; exists {
panic(fmt.Sprintf("%s已经存在,请重新取名", funcName))
}
exitFuncMap[funcName] = exitFunc
logUtil.InfoLog("RegisterExitFunc funcName:%s,当前共有%d个注册", funcName, len(exitFuncMap))
}
// Exit ...退出程序
// 返回值:
// 无
func Exit() {
for funcName, exitFunc := range exitFuncMap {
exitFunc()
logUtil.InfoLog("Call ExitFunc:%s Finish.", funcName)
}
}
@@ -0,0 +1,7 @@
package mqMgr
// 消息队列类型:消息队列、消息主题
const (
MQ_TYPE_QUEUE = "queue"
MQ_TYPE_TOPIC = "topic"
)
@@ -0,0 +1,44 @@
package intUtil
import (
"fmt"
"math/rand"
"time"
)
// ShuffleIntDigits 打乱整数的各个数字位置并返回新的整数
func ShuffleIntDigits(num int64) (int64, error) {
if num < 0 {
return 0, fmt.Errorf("number must be non-negative")
}
var digits []int64
for num > 0 {
digits = append(digits, num%10)
num /= 10
}
// 如果原始数字是0,直接返回
if len(digits) == 0 {
return 0, nil
}
// 反转切片以保持原来的数字顺序
for i, j := 0, len(digits)-1; i < j; i, j = i+1, j-1 {
digits[i], digits[j] = digits[j], digits[i]
}
// 打乱数字切片
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(digits), func(i, j int) {
digits[i], digits[j] = digits[j], digits[i]
})
// 重新组合为新的整数
result := int64(0)
for _, digit := range digits {
result = result*10 + digit
}
return result, nil
}
@@ -0,0 +1,19 @@
package qcloud
// 发送模板短信字段
type tmplSmsField struct {
// 签名 (前缀)
Sign string `json:"sign,omitempty"`
// 模板id
Tpl_id int `json:"tpl_id,omitempty"`
// 模板参数
Params []string `json:"params,omitempty"`
}
func newTmplSmsField(sign string, id int, params []string) *tmplSmsField {
return &tmplSmsField{
Sign: sign,
Tpl_id: id,
Params: params,
}
}
@@ -0,0 +1,41 @@
package monitorNewMgr
// 监控信息传输对象
type MonitorModel struct {
//状态码 0 是心跳,非零为错误信息
Code int `json:"Code"`
//组Id
GroupId string `json:"GroupId"`
//组密钥
ProjectId string `json:"ProjectId"`
//项目Id
ServerIp string `json:"ServerIp"`
// 监控使用的服务器IP
ServerName string `json:"ServerName"`
// 监控使用的服务器名称
Content string `json:"Content"`
// 消息产生时的时间戳
Timestamp int64 `json:"Timestamp"`
// 签名
Sign string `json:"Sign"`
}
func newMonitorModel(code int, groupId, projectId, serverIp, serverName, content string, timestamp int64, sign string) *MonitorModel {
return &MonitorModel{
Code: code,
GroupId: groupId,
ProjectId: projectId,
ServerIp: serverIp,
ServerName: serverName,
Content: content,
Timestamp: timestamp,
Sign: sign,
}
}
@@ -0,0 +1,43 @@
package main
import (
"common/connection"
"sync"
_ "common/resultStatus"
"common/webServer"
_ "logincenter/internal/user"
)
var (
wg sync.WaitGroup
)
func init() {
// 设置WaitGroup需要等待的数量,只要有一个服务器出现错误都停止服务器
wg.Add(1)
}
func main() {
//加载配置
loadConfig()
// 启动webserver
go webServer.Start(&wg)
// 阻塞等待,以免main线程退出
wg.Wait()
}
// loadConfig 用于加载配置信息。
// 该函数会读取配置文件或环境变量中的设置,并根据这些设置初始化程序所需的配置。
// 目前函数的实现为空,需要根据实际的配置加载逻辑进行填充。
func loadConfig() {
//设置数据类型
connection.SetModelDB(connection.GetUserDB())
//构建数据库
connection.BuildDB()
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>
@@ -0,0 +1,155 @@
package gameServerMgr
import (
"crypto/tls"
"encoding/json"
"fmt"
"strconv"
. "Framework/managecenterModel"
"goutil/webUtil"
"goutil/zlibUtil"
)
// 区服激活地址后缀
const ActivateServer_URL_SUFFIX string = "/API/ServerActivate.ashx"
var (
mManageCenterServerAPIUrl string
mIsInit bool = true
)
// 解析从ManagecenterServer中获取的服务器相关数据
func ParseInfoFromManageCenterServer(serverGroupId int32, data string) {
var deserializedData map[string]interface{}
err := json.Unmarshal([]byte(data), &deserializedData)
if err != nil {
return
}
//解析服务器组
var serverGroup *ServerGroup
err = json.Unmarshal([]byte(deserializedData["ServerGroupInfo"].(string)), &serverGroup)
if err != nil {
return
}
//解析合作商
var partnerList []*Partner
err = json.Unmarshal([]byte(deserializedData["PartnerList"].(string)), &partnerList)
if err != nil {
return
}
//解析服务器列表
var serverList []*Server
err = json.Unmarshal([]byte(deserializedData["ServerList"].(string)), &serverList)
if err != nil {
return
}
//解析资源包
var resourceList []*ResourceVersion
err = json.Unmarshal([]byte(deserializedData["ResourceVersionList"].(string)), &resourceList)
if err != nil {
return
}
//解析大区
var areaList []*Area
err = json.Unmarshal([]byte(deserializedData["AreaList"].(string)), &areaList)
if err != nil {
return
}
//判断是否需要更新数据(如果ServerGroupId不匹配,则不解析数据)
if serverGroupId != serverGroup.Id {
return
}
//缓存服务器组
ParseServerGroupInfo(serverGroup)
//缓存合作商
ParsePartnerInfo(partnerList)
//缓存合作商对应的充值配置
ParseChargeConfigInfo(partnerList)
//缓存服务器
ParseServerInfo(serverList)
//缓存资源包
ParseResourceVersionInfo(resourceList)
//缓存大区
ParseAreaInfo(areaList)
}
// 激活服务器
func ActiveServer(manageCenterServerAPIUrl string, serverGroupId int32) error {
if len(manageCenterServerAPIUrl) == 0 {
return fmt.Errorf("ManageCenterServerAPI地址不能为空")
}
mManageCenterServerAPIUrl = manageCenterServerAPIUrl
//定义参数
requestParamMap := make(map[string]string, 0)
requestParamMap["ServerGroupID"] = strconv.Itoa(int(serverGroupId))
//构造请求url
url := fmt.Sprintf("%s/%s", mManageCenterServerAPIUrl, ActivateServer_URL_SUFFIX)
//请求url,请求头
header := webUtil.GetFormHeader()
transport := webUtil.NewTransport()
transport.DisableKeepAlives = true
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true, //关闭证书校验
}
transport = webUtil.GetTimeoutTransport(transport, 30)
statusCode, returnBytes, err := webUtil.PostMapData(url, requestParamMap, header, transport)
//statusCode, returnBytes, err := webUtil.PostMapData(url, requestParamMap, header, nil)
if err != nil {
return err
}
if statusCode != 200 {
return fmt.Errorf("StatusCode:%d", statusCode)
}
//解压缩
retBytes, err1 := zlibUtil.Decompress(returnBytes)
if err1 != nil {
return err1
}
// 解析返回值
returnObj := new(ReturnObject)
if err = json.Unmarshal(retBytes, &returnObj); err != nil {
return err
}
// 判断返回状态是否为成功
if returnObj.Code != 0 {
return fmt.Errorf("code:%d,Message:%s", returnObj.Code, returnObj.Message)
}
//解析得到的数据
ParseInfoFromManageCenterServer(serverGroupId, returnObj.Data.(string))
//获取白名单
GetWhiteListFromManageCenterServer()
//如果是初始化,则开启白名单刷新线程。避免游戏客户端刷新数据的时候重复开启线程
if mIsInit {
//启动白名单数据刷新线程
StartRefreshWhiteListTread()
//启动刷新MC系统配置
StartRefreshSysConfigTread()
//初始化修改为fasle
mIsInit = false
}
return nil
}
@@ -0,0 +1,64 @@
package main
import (
"sync"
)
type player struct {
// 玩家id
Id string `gorm:"column:Id;primary_key"`
// 玩家名称
Name string `gorm:"column:Name"`
}
func (this *player) resetName(name string) {
this.Name = name
}
func (this *player) tableName() string {
return "player"
}
func newPlayer(id, name string) *player {
return &player{
Id: id,
Name: name,
}
}
type playerMgr struct {
playerMap map[string]*player
mutex sync.Mutex
}
func (this *playerMgr) insert(obj *player) {
this.mutex.Lock()
defer this.mutex.Unlock()
this.playerMap[obj.Id] = obj
}
func (this *playerMgr) delete(obj *player) {
this.mutex.Lock()
defer this.mutex.Unlock()
delete(this.playerMap, obj.Id)
}
func (this *playerMgr) randomSelect() *player {
this.mutex.Lock()
defer this.mutex.Unlock()
for _, obj := range this.playerMap {
return obj
}
return nil
}
func newPlayerMgr() *playerMgr {
return &playerMgr{
playerMap: make(map[string]*player),
}
}
@@ -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")
}
}
@@ -0,0 +1,17 @@
v1.0版本,支持以下功能:
1、项目中的各种基础功能;
2、其中的managecenterMgr兼容旧版本的ManageCenter2019-12-01之前)。
3、新增日志记录功能(LogMgr 2020-03-09
4、新增监控功能(MonitorNewMgr 2020-03-09
5、新增短链功能(ShortUrlMgr 2020-03-09
v2.0版本,支持以下功能:
1、新的ManageCenter版本(2019-12-01之后)
v2.0.0.1
LogMgr里面Log日志消息先进行base64编码之后再发送到mq,因为原消息有特殊符号,
直接发送消息会导致消息返送之后,在腾讯mq收到消息之后数据会丢失,导致验签失败
v2.0.1.1
新增屏蔽字处理forbidWordsMgr
新增gameServerMgr
@@ -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)
}
}
@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
@@ -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
}
@@ -0,0 +1,330 @@
package stringUtil
import (
"fmt"
"strconv"
"strings"
"goutil/mathUtil"
)
// 使用多分隔符来进行分割(默认分隔符为:",", ";", ":", "|", "||")
// eg:1,2;3|4||5,6;7|8||9
// 返回值:
// []string
func Split(s string, seps []string) []string {
retList := make([]string, 0, 32)
// 如果seps为nil,则使用默认值
if seps == nil {
seps = []string{",", ";", ":", "|", "||"}
}
// 根据所有的分隔符来一点一点地切割字符串,直到不可切割为止
for {
startIndex := len(s) - 1
endIndex := 0
exists := false
// 遍历,找到第一个分割的位置
for _, sep := range seps {
index := strings.Index(s, sep)
// 如果找到有匹配项,则寻找最小的pos,如果有多个相同的pos,则使用长度最长的分隔符
if index > -1 {
exists = true
// 说明有多个有效的分隔符,如|和||
if index < startIndex {
startIndex = index
endIndex = startIndex + len(sep) - 1
} else if index == startIndex {
if startIndex+len(sep)-1 > endIndex {
endIndex = startIndex + len(sep) - 1
}
}
}
}
// 如果没有找到匹配的pos,则分割过程结束
if !exists {
retList = append(retList, s)
break
}
// 切割字符串
sub := s[:startIndex]
if sub != "" {
retList = append(retList, sub)
}
s = s[endIndex+1:]
}
return retList
}
// 将字符串切割为[]int
// str:输入字符串
// 返回值:
// []int
// error
func SplitToIntSlice(s, sep string) ([]int, error) {
// 先按照分隔符进行切割
strSlice := strings.Split(s, sep)
// 定义int slice
intSlice := make([]int, 0, len(strSlice))
for _, value := range strSlice {
// 去除空格
if value = strings.TrimSpace(value); value == "" {
continue
}
if value_int, err := strconv.Atoi(value); err != nil {
return nil, err
} else {
intSlice = append(intSlice, value_int)
}
}
return intSlice, nil
}
// 将字符串切割为[]int32
// s:输入字符串
// 返回值:
// []int
// error
func SplitToInt32Slice(s, sep string) ([]int32, error) {
// 先获得int slice
count := 0
intSlice, err := SplitToIntSlice(s, sep)
if err != nil {
return nil, err
} else {
count = len(intSlice)
}
// 定义int32 slice
int32Slice := make([]int32, 0, count)
for _, item := range intSlice {
int32Slice = append(int32Slice, int32(item))
}
return int32Slice, nil
}
// 将字符串切割为[]int64
// s:输入字符串
// 返回值:
// []int64
// error
func SplitToInt64Slice(s, sep string) ([]int64, error) {
// 先获得int slice
count := 0
intSlice, err := SplitToIntSlice(s, sep)
if err != nil {
return nil, err
} else {
count = len(intSlice)
}
// 定义int32 slice
int64Slice := make([]int64, 0, count)
for _, item := range intSlice {
int64Slice = append(int64Slice, int64(item))
}
return int64Slice, nil
}
// 将字符串切割为[]float64
// s:输入字符串
// 返回值:
// []float64
// error
func SplitToFloat64Slice(s, sep string) ([]float64, error) {
// 先按照分隔符进行切割
strSlice := strings.Split(s, sep)
// 定义float64 slice
floatSlice := make([]float64, 0, len(strSlice))
for _, value := range strSlice {
// 去除空格
if value = strings.TrimSpace(value); value == "" {
continue
}
if value_float, err := strconv.ParseFloat(value, 64); err != nil {
return nil, err
} else {
floatSlice = append(floatSlice, value_float)
}
}
return floatSlice, nil
}
// 将字符串切割为map[int32]int32
// s:输入字符串
// 返回值:
// map[int32]float32
// error
func SplitToDict_KintVint(s, outerSep, innerSep string) (map[int32]int32, error) {
// 先按照分隔符进行切割
outerSlice := strings.Split(s, outerSep)
// 定义map[int32]float32
floatMap := make(map[int32]int32, len(outerSlice))
for _, itemStr := range outerSlice {
innerSlice := strings.Split(strings.TrimSpace(itemStr), innerSep)
key := strings.TrimSpace(innerSlice[0])
value := strings.TrimSpace(innerSlice[1])
key_int, err := strconv.Atoi(key)
if err != nil {
return nil, err
}
value_int, err := strconv.Atoi(value)
if err != nil {
return nil, err
}
floatMap[int32(key_int)] = int32(value_int)
}
return floatMap, nil
}
// 将字符串切割为map[int32]string
// s:输入字符串
// 返回值:
// map[int32]string
// error
func SplitToDict_KintVstring(s, outerSep, innerSep string) (map[int32]string, error) {
// 先按照分隔符进行切割
outerSlice := strings.Split(s, outerSep)
// 定义map[int32]string
resultMap := make(map[int32]string, len(outerSlice))
for _, itemStr := range outerSlice {
innerSlice := strings.Split(strings.TrimSpace(itemStr), innerSep)
key := strings.TrimSpace(innerSlice[0])
value := strings.TrimSpace(innerSlice[1])
key_int, err := strconv.Atoi(key)
if err != nil {
return nil, err
}
resultMap[int32(key_int)] = value
}
return resultMap, nil
}
// 将字符串切割为map[int32]float32
// s:输入字符串
// 返回值:
// map[int32]float32
// error
func SplitToDict_KintVfloat(s, outerSep, innerSep string) (map[int32]float32, error) {
// 先按照分隔符进行切割
outerSlice := strings.Split(s, outerSep)
// 定义map[int32]float32
floatMap := make(map[int32]float32, len(outerSlice))
for _, itemStr := range outerSlice {
innerSlice := strings.Split(strings.TrimSpace(itemStr), innerSep)
key := strings.TrimSpace(innerSlice[0])
value := strings.TrimSpace(innerSlice[1])
key_int, err := strconv.Atoi(key)
if err != nil {
return nil, err
}
value_float, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, err
}
floatMap[int32(key_int)] = float32(value_float)
}
return floatMap, nil
}
// 将字符串切割为map[int32]float32
// s:输入字符串
// 返回值:
// map[int32]float32
// error
func SplitToDict_KintVfloat64(s, outerSep, innerSep string) (map[int32]float64, error) {
// 先按照分隔符进行切割
outerSlice := strings.Split(s, outerSep)
// 定义map[int32]float32
floatMap := make(map[int32]float64, len(outerSlice))
for _, itemStr := range outerSlice {
innerSlice := strings.Split(strings.TrimSpace(itemStr), innerSep)
key := strings.TrimSpace(innerSlice[0])
value := strings.TrimSpace(innerSlice[1])
key_int, err := strconv.Atoi(key)
if err != nil {
return nil, err
}
value_float, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, err
}
floatMap[int32(key_int)] = float64(value_float)
}
return floatMap, nil
}
// 将字符串切割为IntRegion列表
// s:输入字符串,形如:1-200,201-400,401-1000
// outerSep:外部分隔符
// innerSep:内部分隔符
// 返回值:
// IntRegion列表
// 错误对象
func SplitToIntRegion(s, outerSep, innerSep string) (intRegionList []*mathUtil.IntRegion, err error) {
if s == "" {
err = fmt.Errorf("Input is empty")
return
}
outerRegionList := make([]string, 0, 4)
outerRegionList = strings.Split(s, outerSep)
if len(outerRegionList) == 0 {
err = fmt.Errorf("%s:Format invalid. Such as:1-100,101-200", s)
return
}
for _, item := range outerRegionList {
innerRegionList := make([]string, 0, 2)
innerRegionList = strings.Split(item, innerSep)
if len(innerRegionList) != 2 {
err = fmt.Errorf("%s:Format invalid. Such as:1-100", item)
return
}
var lower, upper int
lower, err = strconv.Atoi(innerRegionList[0])
if err != nil {
return
}
upper, err = strconv.Atoi(innerRegionList[1])
if err != nil {
return
}
if lower > upper {
err = fmt.Errorf("lower:%d should less than upper:%d", lower, upper)
return
}
intRegionList = append(intRegionList, mathUtil.NewIntRegion(lower, upper))
}
return
}
@@ -0,0 +1,206 @@
// ************************************
// @package: websocketServer
// @description:
// @author:
// @revision history:
// @create date: 2022-02-16 18:13:45
// ************************************
package websocketServer
import (
"time"
"github.com/gorilla/websocket"
webServer "Framework/webServer"
logUtil "goutil/logUtil"
)
// 事件回调函数
type EventCallbackFuncs struct {
// websocket连接事件
OnConnFunc func(ctx *Context)
// websocket关闭事件
OnCloseFunc func(ctx *Context)
// websocket接收事件
OnMsgFunc func(ctx *Context, msgType int, msgData []byte)
}
// 用户自定义数据结构
type userDatas struct {
// WsServer 或 WssServer 指针
server interface{}
// 事件回调函数
eventCallback *EventCallbackFuncs
}
// iServerMgr
// @description: WsServer/WssServer内部管理接口,以便hookHandler中统一访问
type iServerMgr interface {
// 升级为websocket
upgrade(ctx *Context) (conn *websocket.Conn, err error)
// 将连接从连接池删除
delConn(conn *websocket.Conn)
// 获取接收到Ping消息时,是否自动回复Pong信息
GetAutoPong() bool
}
// 回调勾子,将http/https升级为websocket
func hookHandler(webServerCtx *webServer.Context) {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
}
}()
userDataI := webServerCtx.GetUserData()
if userDataI == nil {
return
}
userData, ok := userDataI.(*userDatas)
if !ok {
logUtil.ErrorLog("userData type error")
return
}
// 通信结束信号
cls := make(chan struct{})
ctx := &Context{
webServerCtx: webServerCtx, // web_server环境
cls: cls, // 关闭连接信号
}
var serverMgr iServerMgr
var conn *websocket.Conn
var err error
// 转为iServerMgr
switch userData.server.(type) {
case *WsServer:
if svr, ok := userData.server.(*WsServer); ok {
serverMgr = svr
} else {
logUtil.ErrorLog("server type not WsServer")
return
}
case *WssServer:
if svr, ok := userData.server.(*WssServer); ok {
serverMgr = svr
} else {
logUtil.ErrorLog("server type not WssServer")
return
}
default:
logUtil.ErrorLog("server type not WsServer or WssServer")
return
}
// 升级为websocket
conn, err = serverMgr.upgrade(ctx)
if err != nil {
if err.Error() == "connManager(disableNewConn)" {
// 禁用新连接。正常功能,直接返回
return
}
logUtil.ErrorLog("websocket Upgrade failed: %v", err)
return
}
// 将连接从连接池删除
defer serverMgr.delConn(conn)
// 关闭连接
defer conn.Close()
// 默认情况下,ReadMessage不会读取到ping/pong/close消息(内部有专门的处理函数)
// 设置心跳包处理函数
conn.SetPingHandler(func(msg string) error {
// 只要收到消息,都需要更新最近一次收到心跳包的时间
ctx.heartbeat = time.Now()
if serverMgr.GetAutoPong() {
// 自动回应一个Pong心跳
go ctx.SendMessage(websocket.PongMessage, []byte(msg))
}
// 接收消息回调
if userData.eventCallback.OnMsgFunc != nil {
userData.eventCallback.OnMsgFunc(ctx, websocket.PingMessage, []byte(msg))
}
return nil
})
// 设置关闭包处理函数
conn.SetCloseHandler(func(code int, text string) error {
// 所有向cls写入,都使用select超时结构,以保证这儿不会一直阻塞,确保此协程能退出
select {
case <-time.After(time.Millisecond * 10):
case cls <- struct{}{}:
}
return nil
})
// 设置最近一次收到心跳包的时间
ctx.heartbeat = time.Now()
// 新连接回调
if userData.eventCallback.OnConnFunc != nil {
userData.eventCallback.OnConnFunc(ctx)
}
// 开启读协程
go func() {
defer func() {
if r := recover(); r != nil {
logUtil.LogUnknownError(r)
// 有异常出现(可能是用户回调中出现异常);执行到这儿,需要关闭连接
// 所有向cls写入,都使用select超时结构,以保证这儿不会一直阻塞,确保此协程能退出
select {
case <-time.After(time.Millisecond * 10):
case cls <- struct{}{}:
}
}
}()
for {
// 注意:ReadMessage不会读取到心跳包和关闭包数据;心跳包/关闭包需要设置专门的处理函数
// 但内部对心跳包/关闭包的处理,也是由ReadMessage函数触发的(也就是不调用ReadMessage函数,可能也不会触发对心跳包/关闭包的处理);
// 经测试和内部代码确认:调用心跳包/关闭包处理函数时,ReadMessage不会返回;心跳包/关闭包处理函数调用完毕后ReadMessage才可能返回
mt, msg, err := conn.ReadMessage()
if err != nil {
// 所有向cls写入,都使用select超时结构,以保证这儿不会一直阻塞,确保此协程能退出
select {
case <-time.After(time.Millisecond * 10):
case cls <- struct{}{}:
}
break
}
// 只要收到消息,都需要更新最近一次收到心跳包的时间
ctx.heartbeat = time.Now()
// 接收消息回调
if userData.eventCallback.OnMsgFunc != nil {
userData.eventCallback.OnMsgFunc(ctx, mt, msg)
}
}
}()
// 等待退出
<-cls
// 设置已关闭标志
ctx.isClosed = true
// 关闭回调
if userData.eventCallback.OnCloseFunc != nil {
userData.eventCallback.OnCloseFunc(ctx)
}
}
@@ -0,0 +1,109 @@
/*
请求结构简介
最近更新时间:2019-08-01 19:14:44
编辑 查看pdf
在这篇文章中:
服务地址
通信协议
请求方法
请求参数
字符编码
对腾讯云的 API 接口的调用是通过向腾讯云 API 的服务端地址发送请求,并按照接口说明在请求中加入相应的请求参数来完成的。腾讯云 API 的请求结构由:服务地址、通信协议、请求方法、请求参数和字符编码组成。具体描述如下:
服务地址
腾讯云 API 的服务接入地址与具体模块相关,详细请参见各接口相关描述。
通信协议
腾讯云 API 的大部分接口都通过 HTTPS 进行通信,为您提供高安全性的通信通道。
请求方法
腾讯云 API 同时支持 POST 和 GET 请求。
注意:
POST 和 GET 请求不能混合使用,若使用 GET 方式,则参数均从 Querystring 取得;
若使用 POST 方式,则参数均从 Request Body 中取得,而 Querystring 中的参数将忽略。
两种请求方式的参数格式规则相同,一般情况下使用 GET 请求,当参数字符串过长时推荐使用 POST。
如果用户的请求方法是 GET,则对所有请求参数值均需要做 URL 编码,若为 POST,则无需对参数编码。
GET 请求的最大长度根据不同的浏览器和服务器设置有所不同,例如,传统 IE 浏览器限制为 2K,Firefox 限制为 8K;对于一些参数较多、长度较长的 API 请求,建议您使用 POST 方法以免在请求过程中会由于字符串超过最大长度而导致请求失败。
对于 POST 请求,您需要使用 x-www-form-urlencoded 的形式传参,因为云 API 侧是从 $_POST 中取出请求参数的。
请求参数
腾讯云 API 的每个请求都需要指定两类参数:公共请求参数以及接口请求参数。其中公共请求参数是每个接口都要用到的请求参数,具体可参见 公共请求参数,而接口请求参数是各个接口所特有的,具体见各个接口的“请求参数”描述。
字符编码
腾讯云 API 的请求及返回结果均使用 UTF-8 字符集进行编码。
*/
package model
import (
"fmt"
"time"
"goutil/mathUtil"
)
// CommonRequest 公共请求参数对象
type CommonRequest struct {
// Action 指令接口名称(必须)
Action string
// 地域参数(必须)
Region string
// Timestamp 当前UNIX时间戳(必须)
Timestamp uint64
// Nonce 随机正整数(必须)
Nonce uint32
// SecretId 在云API密钥上申请的标识身份的SecretId(必须)
SecretId string
// 请求签名,用来验证此次请求的合法性,需要用户根据实际的输入参数计算得出。
Signature string
// 签名方式,目前支持 HmacSHA256 和 HmacSHA1。只有指定此参数为 HmacSHA256 时,才使用 HmacSHA256 算法验证签名,其他情况均使用 HmacSHA1 验证签名。
SignatureMethod string
// 队列名称(此属性虽然不是API文档中的公共属性,但是在队列模型中确实事实上的公共属性,所以将其转移到此处)
queueName string
}
// AssembleParamMap 组装请求参数字典
// 返回值
// map[string]interface{}:请求参数字
func (this *CommonRequest) AssembleParamMap() map[string]string {
result := make(map[string]string)
// 组装参数
result["Action"] = this.Action
result["Region"] = this.Region
result["Timestamp"] = fmt.Sprintf("%d", this.Timestamp)
result["Nonce"] = fmt.Sprintf("%d", this.Nonce)
result["SecretId"] = this.SecretId
result["SignatureMethod"] = this.SignatureMethod
result["queueName"] = this.queueName
return result
}
// NewCommonRequest 新建公共请求参数对象
// 参数
// action:指令接口名称
// region:地域
// secretId:在云API密钥上申请的标识身份的SecretId
// queueName:队列名称
// 返回值
// *CommonRequest:公共请求参数对象
func NewCommonRequest(action, region, secretId, queueName string) *CommonRequest {
return &CommonRequest{
Action: action,
Region: region,
Timestamp: uint64(time.Now().Unix()),
Nonce: mathUtil.GetRand().Uint32(),
SecretId: secretId,
SignatureMethod: "HmacSHA256",
queueName: queueName,
}
}
@@ -0,0 +1,61 @@
package configYaml
import (
"framework/configMgr"
"gopkg.in/yaml.v3"
"goutil/logUtil"
"goutil/yamlUtil"
"log"
)
var (
// 配置对象
configManager = configMgr.NewConfigManager()
)
// init
//
// @description: init
//
// parameter:
// return:
func init() {
// 设置日志文件的存储目录
logUtil.SetLogPath("LOG")
if err := reloadConfig(); err != nil {
panic(err)
}
//加载配置
initBaseConfig()
initDbConfig()
initFunctionConfig()
initLogMgrConfig()
}
// reloadConfig
//
// @description: reloadConfig
//
// parameter:
// return:
//
// @error: 错误信息
func reloadConfig() error {
yamlFile, err := yamlUtil.LoadFromFile("config.yaml")
if err != nil {
return err
}
// 解析 YAML 文件
err = yaml.Unmarshal(yamlFile, &ConfigYaml)
if err != nil {
log.Fatalf("Error unmarshalling config file: %v", err)
return err
}
return nil
}
@@ -0,0 +1,71 @@
package webUtil
import (
"testing"
"time"
)
func TestGet(t *testing.T) {
client := NewClient(nil)
result, err := client.Get("https://www.baidu.com", nil)
if err != nil {
t.Errorf("测试错误,返回的结果为:%s", err)
}
if len(result) == 0 {
t.Errorf("返回的数据为空,期望不为空")
}
//t.Log(string(result))
}
func TestGetTimeout(t *testing.T) {
transportOPT := &TransportOPT{
Timeout: 3 * time.Second,
}
opt := make(map[string]interface{})
opt["Timeout"] = 3 * time.Second
client := NewClient(transportOPT)
_, err := client.Get("https://www.google.com", nil)
if err != nil {
t.Log(err)
return
}
t.Errorf("测试异常")
}
func TestPostWithMap(t *testing.T) {
client := NewClient(nil)
data := make(map[string]string)
data["test1"] = "value1"
data["test2"] = "value2"
result, err := client.PostWithMap("http://www.baidu.com", data, nil)
if err != nil {
t.Errorf("测试错误,返回的结果为:%s", err)
}
if len(result) == 0 {
t.Errorf("返回的数据为空,期望不为空")
}
//t.Log(string(result))
}
func TestPostWithByte(t *testing.T) {
client := NewClient(nil)
result, err := client.PostWithByte("http://www.baidu.com", []byte("test=abc"), nil)
if err != nil {
t.Errorf("测试错误,返回的结果为:%s", err)
}
if len(result) == 0 {
t.Errorf("返回的数据为空,期望不为空")
}
//t.Log(string(result))
}
@@ -0,0 +1,13 @@
package qcloud
type telField struct {
Nationcode string `json:"nationcode"`
Mobile string `json:"mobile"`
}
func newTelField(nation, mobile string) *telField {
return &telField{
Nationcode: nation,
Mobile: mobile,
}
}
@@ -0,0 +1,105 @@
package gameServerMgr
import (
"encoding/json"
"errors"
"fmt"
"time"
"Framework/goroutineMgr"
. "Framework/managecenterModel"
"goutil/logUtil"
"goutil/webUtil"
)
const SYSCONF_URL_SUFFIX string = "/API/SysConfig.ashx"
var (
mSysConfig *SysConfig
)
// 获取MC系统配置
func GetSysConfigFromManageCenterServer() error {
//定义参数
requestParamMap := make(map[string]string, 0)
requestParamMap["IsResultCompressed"] = "false"
//构造url
url := fmt.Sprintf("%s/%s", mManageCenterServerAPIUrl, SYSCONF_URL_SUFFIX)
//请求url,请求头
header := webUtil.GetFormHeader()
transport := webUtil.NewTransport()
transport.DisableKeepAlives = true
transport = webUtil.GetTimeoutTransport(transport, 30)
statusCode, returnBytes, err := webUtil.PostMapData(url, requestParamMap, header, transport)
//statusCode, returnBytes, err := webUtil.PostMapData(url, requestParamMap, header, nil)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("获取MC系统配置出错,url:%s,错误信息为:%s", url, err))
return err
}
if statusCode != 200 {
logUtil.ErrorLog(fmt.Sprintf("获取MC系统配置出错,url:%s,错误码为:%d", url, statusCode))
return err
}
// 解析返回值
returnObj := new(ReturnObject)
if err = json.Unmarshal(returnBytes, &returnObj); err != nil {
logUtil.ErrorLog(fmt.Sprintf("获取MC系统配置出错,反序列化返回值出错,错误信息为:%s, str:%s", err, string(returnBytes)))
return err
}
// 判断返回状态是否为成功
if returnObj.Code != 0 {
// 数据没有变化,所以没有获取到新的数据,不能算错误。
if returnObj.Code == 47 || returnObj.Message == "DataNotChanged" {
return nil
} else {
msg := fmt.Sprintf("获取MC系统配置出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message)
logUtil.ErrorLog(msg)
return errors.New(msg)
}
}
// 解析Data
var tmpSysConfig *SysConfig
if data, ok := returnObj.Data.(string); !ok {
msg := "获取MC系统配置出错,返回的数据不是string类型"
logUtil.ErrorLog(msg)
return errors.New(msg)
} else {
if err = json.Unmarshal([]byte(data), &tmpSysConfig); err != nil {
logUtil.ErrorLog(fmt.Sprintf("获取MC系统配置出错出错,反序列化数据出错,错误信息为:%s", err))
return err
}
}
// 赋值给最终的sysconfig
mSysConfig = tmpSysConfig
return nil
}
// 定时刷新MC系统配置
func StartRefreshSysConfigTread() {
// 定时刷新数据
go func() {
goroutineName := "gameServerMgr.StartRefreshSysConfigTread"
goroutineMgr.Monitor(goroutineName)
defer goroutineMgr.ReleaseMonitor(goroutineName)
for {
// 每30秒刷新一次
time.Sleep(30 * time.Second)
// MC系统配置
GetSysConfigFromManageCenterServer()
}
}()
}
// 获取系统配置
func GetSysConfig() *SysConfig {
return mSysConfig
}
@@ -0,0 +1,54 @@
package initMgr
import (
"fmt"
"testing"
)
func TestRegister(t *testing.T) {
Register("first", 1, first)
Register("second", 2, second)
Register("third", 3, third)
Register("fourth", 4, fourth)
}
func TestCallOne(t *testing.T) {
name := "first"
if err := CallOne(name); err != nil {
t.Errorf("there should be no error, but now it has:%s", err)
}
}
func TestCallAny(t *testing.T) {
errList := CallAny("second", "third")
if len(errList) != 1 {
t.Errorf("there should be 1 error, but now:%d", len(errList))
}
}
func TestCallAll(t *testing.T) {
errList := CallAll()
if len(errList) != 2 {
t.Errorf("there should be 1 error, but now:%d", len(errList))
}
}
func first() error {
fmt.Println("first")
return nil
}
func second() error {
fmt.Println("second")
return fmt.Errorf("the second error")
}
func third() error {
fmt.Println("third")
return nil
}
func fourth() error {
fmt.Println("fourth")
return fmt.Errorf("the fourth error")
}
@@ -0,0 +1,89 @@
package user
import (
"common/cache"
"common/connection"
"goutil/logUtilPlus"
"sync"
)
// 用户缓存对象
var userMap = make(map[int64]*User)
var rwmu sync.RWMutex
// GetUserByID 根据用户id获取用户信息
func GetUserByID(UserID int64) (*User, error) {
//判断缓存是否存在
var user *User
func() *User {
rwmu.RLock()
defer rwmu.RUnlock()
ok := true
if user, ok = userMap[UserID]; ok {
return user
}
return nil
}()
if user != nil {
return user, nil
}
result := connection.GetUserDB().First(&user, UserID)
if result.Error != nil {
return nil, result.Error
}
//添加缓存
func() {
rwmu.Lock()
defer rwmu.Unlock()
user.Cache = cache.NewCache()
userMap[user.ID] = user
}()
return user, nil
}
// AddUserCache 添加用户缓存
func AddUserCache(user *User) {
rwmu.Lock()
defer rwmu.Unlock()
user.Cache = cache.NewCache()
userMap[user.ID] = user
}
// AddUser 添加用户
// AddUser 添加新的用户到数据库中。
// 参数 User: 包含用户信息的对象。
// 返回值: 插入操作影响的行数和可能发生的错误。
func AddUser(User *User) (int64, error) {
//处理一些验证
//写入缓存
AddUserCache(User)
// 写入到数据库
result := connection.GetUserDB().Create(&User) // 通过数据的指针来创建
if result.Error != nil {
logUtilPlus.ErrorLog("添加用户失败 错误信息:", result.Error.Error())
}
return User.ID, nil
}
// Login 用户登录
func Login(account string, password string) (*User, error) {
//这里优先验证缓存数据
var User User
result := connection.GetUserDB().Where("account = ? AND password = ?", account, password).First(&User)
if result.Error != nil {
return nil, result.Error
}
return &User, nil
}
@@ -0,0 +1,20 @@
<html lang="en">
<head>
<title> <!-- 阿斯大三大四的 -->
这里有中文哦</title>
<meta name="language" content="en"/>
</head>
<body>
<h1> This is a H1 </h1>
<ul>
<li><a id="1" href="/">阿萨德</a></li>
<li><a id="2" href="/about">哈哈哈</a></li>
<li><a id="3" href="/account">他写的</a></li>
<li></li>
</ul>
<p>
Hello,This is an example for gxpath.
</p>
<footer>footer script</footer>
</body>
</html>
@@ -0,0 +1,76 @@
package monitorNewMgr
import (
"time"
)
//服务器节点信息
type ServerNodeMessage struct {
//通用信息
CommInfo CommInfoModel
// indexMap 指标对象
IndexSlice []Index
}
type Index struct {
// IndexName 指标名字(eg:可以为cpu,mem,numGoroutine,proxy
IndexName string
// moduleName 模块名字
ModuleName string
// methodName 方法名字
MethodName string
// value 指标值(eg:indexName为cpu时,value 为cpu使用率,indexName为内存时,value为内存使用率)
Value float64
}
//服务监控中心信息
type MonitorCenterMessage struct {
//通用信息
CommInfo CommInfoModel
// Cpu 核数
Cpu int32
// Mem 内存大小
Mem int32
// Status
Status int32
}
// 通用信息
type CommInfoModel struct {
// 项目组ID
GroupId string
// projectId 项目Id (eg:迪士尼)
ProjectId string
// clusterId 集群Id(一个集群相当于一个大区)
ClusterId string
// 组密钥
ProjectSecret string
// 服务器IP
IP string
// Port 服务端口
Port int32
// 服务名(eg:玩家服务player,城市服务:city,代理服务:proxy
ServiceName string
// instanceId 服务实例Id
InstanceId string
// tll 时间戳
Tll time.Duration
// 签名
Sign string
}
@@ -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)
}
}
@@ -0,0 +1,127 @@
package logUtil
import (
"fmt"
"runtime"
"strings"
)
const (
con_MIN_SKIP = 1
con_MAX_SKIP = 10
)
// InfoLog 信息日志记录
// format:日志格式
// args:参数列表
func InfoLog(format string, args ...interface{}) {
for _, log := range logs {
log.InfoLog(format, args...)
}
}
// WarnLog 警告日志记录
// format:日志格式
// args:参数列表
func WarnLog(format string, args ...interface{}) {
for _, log := range logs {
log.WarnLog(format, args...)
}
}
// DebugLog 调试日志记录
// format:日志格式
// args:参数列表
func DebugLog(format string, args ...interface{}) {
for _, log := range logs {
log.DebugLog(format, args...)
}
}
// ErrorLog 错误日志记录
// format:日志格式
// args:参数列表
func ErrorLog(format string, args ...interface{}) {
for _, log := range logs {
log.ErrorLog(format, args...)
}
}
// FatalLog 致命错误日志记录
// format:日志格式
// args:参数列表
func FatalLog(format string, args ...interface{}) {
for _, log := range logs {
log.FatalLog(format, args...)
}
}
// Close
// @description: 关闭日志
// parameter:
// @waitFinish:是否等待关闭完成
// return:
func Close(waitFinish bool) {
for _, log := range logs {
log.CloseLog(waitFinish)
}
}
//--------------------------Deprecated methods start----------------------------
// Log 日志记录
// Deprecated: use XXXLog api instead
func Log(logInfo string, lv LogType, ifIncludeHour bool) {
switch lv {
case Info:
InfoLog(logInfo)
case Warn:
WarnLog(logInfo)
case Debug:
DebugLog(logInfo)
case Error:
ErrorLog(logInfo)
case Fatal:
FatalLog(logInfo)
}
}
// NormalLog 日志记录
// Deprecated: use XXXLog api instead
func NormalLog(logInfo string, level LogType) {
Log(logInfo, level, true)
}
// LogAndPrint 日志记录
// Deprecated: use XXXLog api instead
func LogAndPrint(logInfo string, level LogType) {
NormalLog(logInfo, level)
fmt.Println(logInfo)
}
// LogUnknownError 日志记录
func LogUnknownError(r interface{}, args ...string) {
buf := strings.Builder{}
buf.WriteString(fmt.Sprintf("通过recover捕捉到的未处理异常:%v \n", r))
// 获取附加信息
if len(args) > 0 {
buf.WriteString("附加信息:")
buf.WriteString(strings.Join(args, "-"))
buf.WriteString("\n")
}
// 获取堆栈信息
for skip := con_MIN_SKIP; skip <= con_MAX_SKIP; skip++ {
_, file, line, ok := runtime.Caller(skip)
if !ok {
break
}
buf.WriteString(fmt.Sprintf("skip = %d, file = %s, line = %d \n", skip, file, line))
buf.WriteString("\n")
}
ErrorLog(buf.String())
}
//--------------------------Deprecated methods end----------------------------
@@ -0,0 +1,76 @@
// ************************************
// @package: websocketServer
// @description: WsServer/WssServer接口,以便统一调用
// @author:
// @revision history:
// @create date: 2022-02-22 16:07:27
// ************************************
package websocketServer
import (
"github.com/gorilla/websocket"
webServer "Framework/webServer"
"sync"
"time"
)
// IServer
// @description: WsServer/WssServer接口,以便统一调用
type IServer interface {
//-------------------------------------
// HttpServer方法
// HttpServer接口
webServer.IWebServer
// 设置地址
SetAddr(addr string)
// 启动HttpServer
Start(wg *sync.WaitGroup)
//-------------------------------------
// websocket方法
// 注册websocket回调
RegisterWebsocketHandler(path string, eventCallback *EventCallbackFuncs, configObj *webServer.HandlerConfig)
// 注册正则websocket回调
RegisterRegexWebsocketHandler(path string, eventCallback *EventCallbackFuncs, configObj *webServer.HandlerConfig)
// 设置websocket参数结构
SetUpgrader(upgrader *websocket.Upgrader)
// 获取websocket参数结构
GetUpgrader() *websocket.Upgrader
// 设置接收到Ping消息时,是否自动回复Pong信息
SetAutoPong(autuPong bool)
// 获取接收到Ping消息时,是否自动回复Pong信息
GetAutoPong() bool
// 设置心跳检测信息
SetHeartbeatDetectInfo(heartbeatCloseCount int, heartbeatCycle time.Duration)
// 获取心跳检测信息
GetHeartbeatDetectInfo() (heartbeatCloseCount int, heartbeatCycle time.Duration)
// 设置广播并发数
SetBroadcastConcurrent(n int)
// 允许新连接
EnableNewConn()
// 禁用新连接
DisableNewConn()
// 多播消息(给指定多用户发送消息)
MulticastMessage(ctxs []*Context, messageType int, data []byte) (err error)
// 消息广播
BroadcastMessage(messageType int, data []byte) (err error)
// 关闭所有连接
CloseAll()
}
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html>
<head>
<title>go websocket</title>
<meta charset="utf-8" />
</head>
<body>
<script type="text/javascript">
var wsUri ="ws://127.0.0.1:22222/websocket";
var output;
function init() {
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) {
onOpen(evt)
};
websocket.onclose = function(evt) {
onClose(evt)
};
websocket.onmessage = function(evt) {
onMessage(evt)
};
websocket.onerror = function(evt) {
onError(evt)
};
}
function onOpen(evt) {
writeToScreen("CONNECTED");
// doSend("WebSocket rocks");
}
function onClose(evt) {
writeToScreen("DISCONNECTED");
}
function onMessage(evt) {
writeToScreen('<span style="color: blue;">RESPONSE: '+ evt.data+'</span>');
// websocket.close();
}
function onError(evt) {
writeToScreen('<span style="color: red;">ERROR:</span> '+ evt.data);
}
function doSend(message) {
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
function sendBtnClick(){
var msg = document.getElementById("input").value;
doSend(msg);
document.getElementById("input").value = '';
}
function closeBtnClick(){
websocket.close();
}
</script>
<h2>WebSocket Test</h2>
<input type="text" id="input"></input>
<button onclick="sendBtnClick()" >send</button>
<button onclick="closeBtnClick()" >close</button>
<div id="output"></div>
</body>
</html>
@@ -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)
}
@@ -0,0 +1,93 @@
/*
提供统一的ip验证的逻辑;包括两部分:
1、ManageCenter中配置到ServerGroup中的IP;系统内部自动处理,外部无需关注(暂时不用);
2、各个应用程序中配置的ip;通过调用Init或InitString方法进行初始化;
*/
package ipMgr
import (
"sync"
"goutil/stringUtil"
)
var (
ipMap = make(map[string]struct{}, 32) // 本地ip集合
ipCheckFuncList = make([]func(string) bool, 0, 16) // ip检查函数列表
mutex sync.RWMutex
)
// 初始化IP列表
func Init(ipList []string) {
mutex.Lock()
defer mutex.Unlock()
// 先清空再初始化
ipMap = make(map[string]struct{}, 32)
for _, item := range ipList {
ipMap[item] = struct{}{}
}
}
// 初始化ip字符串(以分隔符分割的,分隔符为:",", ";", ":", "|", "||"
func InitString(ipStr string) {
mutex.Lock()
defer mutex.Unlock()
// 先清空再初始化
ipMap = make(map[string]struct{}, 32)
for _, item := range stringUtil.Split(ipStr, nil) {
ipMap[item] = struct{}{}
}
}
func AddString(ipStr string) {
mutex.Lock()
defer mutex.Unlock()
// 先清空再初始化
if ipMap == nil {
ipMap = make(map[string]struct{}, 32)
}
for _, item := range stringUtil.Split(ipStr, nil) {
ipMap[item] = struct{}{}
}
}
// RegisterIpCheckFunc 注册Ip检查函数
func RegisterIpCheckFunc(funcName string, funcItem func(string) bool) {
mutex.Lock()
defer mutex.Unlock()
ipCheckFuncList = append(ipCheckFuncList, funcItem)
}
func isLocalValid(ip string) bool {
mutex.RLock()
defer mutex.RUnlock()
_, exist := ipMap[ip]
return exist
}
func isCheckFuncValid(ip string) bool {
mutex.RLock()
defer mutex.RUnlock()
for _, funcItem := range ipCheckFuncList {
if funcItem(ip) {
return true
}
}
return false
}
// 判断传入的Ip是否有效
// ip:ip
// 返回值:
// 是否有效
func IsIpValid(ip string) bool {
return isLocalValid(ip) || isCheckFuncValid(ip)
}
@@ -0,0 +1,210 @@
package managecenterModel
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
"goutil/stringUtil"
"goutil/typeUtil"
)
// 游戏版本
type ResourceVersion struct {
// 资源版本唯一标识
Id int32 `json:"ResourceVersionID"`
// 资源版本名称
Name string `json:"ResourceVersionName"`
// 资源版本的url地址
Url string `json:"ResourceVersionUrl"`
// 资源大小
Size int32 `json:"Size"`
// 资源文件MD5加密的结果
MD5 string `json:"MD5"`
//大区Id
AreaID int32 `json:"AreaID"`
// 资源生效时间
StartTime string `json:"StartTime"`
StartTimeTick int64 `json:"StartTimeTick"`
// 资源失效时间
EndTime string `json:"EndTime"`
EndTimeTick int64 `json:"EndTimeTick"`
// 添加时间
Crdate string `json:"Crdate"`
CrdateTick int64 `json:"CrdateTick"`
// 更新时间
UpdateTime string `json:"UpdateTime"`
UpdateTimeTick int64 `json:"UpdateTimeTick"`
// 是否重启客户端
IfRestart int32 `json:"IfRestart"`
// 是否禁用
IfDelete int32 `json:"IfDelete"`
// 是否审核服下载
IfAuditServiceDownload int32 `json:"IfAuditServiceDownload"`
// 资源所属的合作商ID集合
PartnerIds string `json:"PartnerIDs"`
// 资源所属的游戏版本ID集合
GameVersionIds string `json:"GameVersionIDs"`
}
// 判断资源是否包含指定合作商
// partnerId:合作商Id
// 返回值
// 是否包含
func (this *ResourceVersion) ContainsPartner(partnerId int32) bool {
partnerIdList, _ := stringUtil.SplitToInt32Slice(this.PartnerIds, ",")
for _, item := range partnerIdList {
if item == partnerId {
return true
}
}
return false
}
// 判断资源是否包含指定游戏版本
// gameVersionId:游戏版本Id
// 返回值
// 是否包含
func (this *ResourceVersion) ContainsGameVersion(gameVersionId int32) bool {
gameVersionIdList, _ := stringUtil.SplitToInt32Slice(this.GameVersionIds, ",")
for _, item := range gameVersionIdList {
if item == gameVersionId {
return true
}
}
return false
}
// 获取有效资源包
func GetAvailableResource(resourceVersionList []*ResourceVersion, partnerId, gameVersionId int32, resourceVersionName string, offTest OfficialOrTest) (availableResourceVersion map[string]interface{}) {
if len(resourceVersionList) == 0 {
return
}
//判断资源是否有效
_, hashCode, isVaild := IsResourceVersionNameValid(resourceVersionName)
if !isVaild {
return
}
//根据合作商Id和游戏版本Id和开始时间来过滤
var targetResourceVersionList []*ResourceVersion
for _, resourceVersion := range resourceVersionList {
startime, err := typeUtil.DateTime(resourceVersion.StartTimeTick)
if resourceVersion.ContainsPartner(partnerId) && resourceVersion.ContainsGameVersion(gameVersionId) && err == nil && startime.Before(time.Now()) {
targetResourceVersionList = append(targetResourceVersionList, resourceVersion)
}
}
if len(targetResourceVersionList) == 0 {
return
}
//组装数据
//按照资源Id进行降序排列
sort.Slice(targetResourceVersionList, func(i, j int) bool {
return targetResourceVersionList[i].SortByIdDesc(targetResourceVersionList[j])
})
//取出资源号最大的资源,如果与传入的资源名称相等,则表示没有新资源
availableResourceVersion = make(map[string]interface{}, 0)
newResource := targetResourceVersionList[0]
availableResourceVersion["ResourceVersionName"] = newResource.Name
availableResourceVersion["Url"] = strings.Replace(newResource.Url, ".zip", "", -1)
if newResource.Name == resourceVersionName {
//是否有新资源,如果为fasle,也需要返回project.manifest.temp.zip 用于子包验证
availableResourceVersion["IsNewResource"] = false
return
}
//判断资源号中的HashCode是否相等,如果相等,则表示没有新资源;如果传入的timeTick>最新的timeTick说明服务器没有被刷新,表示没有新资源
_, newHashCode, newIsVaild := IsResourceVersionNameValid(newResource.Name)
if !newIsVaild {
return
}
if compareRes := strings.Compare(hashCode, newHashCode); compareRes == 0 {
//是否有新资源,如果为fasle,也需要返回project.manifest.temp.zip 用于子包验证
availableResourceVersion["IsNewResource"] = false
return
}
if offTest == Con_Test && newResource.IfAuditServiceDownload == 0 {
return nil
}
availableResourceVersion["IsNewResource"] = true
return
}
// 按照Id进行升序排序
// target:另一个资源对象
// 是否是小于
func (this *ResourceVersion) SortByIdAsc(target *ResourceVersion) bool {
return this.Id < target.Id
}
// 按照Id进行降序排序
// target:另一个资源对象
// 是否是大于
func (this *ResourceVersion) SortByIdDesc(target *ResourceVersion) bool {
return this.Id > target.Id
}
//判断资源版本是否有效
func IsResourceVersionNameValid(resourceVersionName string) (timeTick int64, hashCode string, isValid bool) {
if len(resourceVersionName) == 0 {
isValid = false
return
}
if index := strings.Index(resourceVersionName, "_"); index == -1 {
resourceVersionName = fmt.Sprintf("0_%s", resourceVersionName)
}
resourceList := stringUtil.Split(resourceVersionName, []string{"_"})
if len(resourceList) != 2 {
isValid = false
return
}
//解析timeTick
timeTick, err := strconv.ParseInt(resourceList[0], 10, 64)
if err != nil {
isValid = false
return
}
hashCode = resourceList[1]
isValid = true
return
}
@@ -0,0 +1,73 @@
package redisUtil
import (
"github.com/gomodule/redigo/redis"
)
func (this *RedisPool) Int(reply interface{}) (int, error) {
return redis.Int(reply, nil)
}
func (this *RedisPool) Int64(reply interface{}) (int64, error) {
return redis.Int64(reply, nil)
}
func (this *RedisPool) Uint64(reply interface{}) (uint64, error) {
return redis.Uint64(reply, nil)
}
func (this *RedisPool) Float64(reply interface{}) (float64, error) {
return redis.Float64(reply, nil)
}
func (this *RedisPool) String(reply interface{}) (string, error) {
return redis.String(reply, nil)
}
func (this *RedisPool) Bytes(reply interface{}) ([]byte, error) {
return redis.Bytes(reply, nil)
}
func (this *RedisPool) Bool(reply interface{}) (bool, error) {
return redis.Bool(reply, nil)
}
func (this *RedisPool) Values(reply interface{}) ([]interface{}, error) {
return redis.Values(reply, nil)
}
func (this *RedisPool) Ints(reply interface{}) ([]int, error) {
return redis.Ints(reply, nil)
}
func (this *RedisPool) Int64s(reply interface{}) ([]int64, error) {
return redis.Int64s(reply, nil)
}
func (this *RedisPool) Float64s(reply interface{}) ([]float64, error) {
return redis.Float64s(reply, nil)
}
func (this *RedisPool) Strings(reply interface{}) ([]string, error) {
return redis.Strings(reply, nil)
}
func (this *RedisPool) ByteSlices(reply interface{}) ([][]byte, error) {
return redis.ByteSlices(reply, nil)
}
func (this *RedisPool) IntMap(reply interface{}) (map[string]int, error) {
return redis.IntMap(reply, nil)
}
func (this *RedisPool) Int64Map(reply interface{}) (map[string]int64, error) {
return redis.Int64Map(reply, nil)
}
func (this *RedisPool) StringMap(reply interface{}) (map[string]string, error) {
return redis.StringMap(reply, nil)
}
func (this *RedisPool) Positions(reply interface{}) ([]*[2]float64, error) {
return redis.Positions(reply, nil)
}
@@ -0,0 +1,68 @@
package intAndBytesUtil
import (
"encoding/binary"
"testing"
)
func TestInt16ToBytes(t *testing.T) {
var expectedBigEndian []byte = []byte{1, 0}
var expectedLittleEndian []byte = []byte{0, 1}
var givenInt int16 = 256
result := Int16ToBytes(givenInt, binary.BigEndian)
if equal(result, expectedBigEndian) == false {
t.Errorf("IntToBytes(%v) failed.Got %v, expected %v", givenInt, result, expectedBigEndian)
}
result = Int16ToBytes(givenInt, binary.LittleEndian)
if equal(result, expectedLittleEndian) == false {
t.Errorf("IntToBytes(%v) failed.Got %v, expected %v", givenInt, result, expectedLittleEndian)
}
}
func TestInt32ToBytes(t *testing.T) {
var expectedBigEndian []byte = []byte{0, 0, 1, 0}
var expectedLittleEndian []byte = []byte{0, 1, 0, 0}
var givenInt int32 = 256
result := Int32ToBytes(givenInt, binary.BigEndian)
if equal(result, expectedBigEndian) == false {
t.Errorf("IntToBytes(%v) failed.Got %v, expected %v", givenInt, result, expectedBigEndian)
}
result = Int32ToBytes(givenInt, binary.LittleEndian)
if equal(result, expectedLittleEndian) == false {
t.Errorf("IntToBytes(%v) failed.Got %v, expected %v", givenInt, result, expectedLittleEndian)
}
}
func TestInt64ToBytes(t *testing.T) {
var expectedBigEndian []byte = []byte{0, 0, 0, 0, 0, 0, 1, 0}
var expectedLittleEndian []byte = []byte{0, 1, 0, 0, 0, 0, 0, 0}
var givenInt int64 = 256
result := Int64ToBytes(givenInt, binary.BigEndian)
if equal(result, expectedBigEndian) == false {
t.Errorf("IntToBytes(%v) failed.Got %v, expected %v", givenInt, result, expectedBigEndian)
}
result = Int64ToBytes(givenInt, binary.LittleEndian)
if equal(result, expectedLittleEndian) == false {
t.Errorf("IntToBytes(%v) failed.Got %v, expected %v", givenInt, result, expectedLittleEndian)
}
}
func equal(b1, b2 []byte) bool {
if len(b1) != len(b2) {
return false
}
for i := 0; i < len(b1); i++ {
if b1[i] != b2[i] {
return false
}
}
return true
}
@@ -0,0 +1,2 @@
Log/*
logs/*
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,114 @@
package redisUtil
import (
"testing"
"time"
)
var (
redisPoolObj *RedisPool
)
func init() {
redisPoolObj = NewRedisPool("testPool", "10.1.0.21:6379", "redis_pwd", 5, 500, 200, 10*time.Second, 5*time.Second)
}
func TestGetName(t *testing.T) {
expected := "testPool"
got := redisPoolObj.GetName()
if expected != got {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
}
func TestGetAddress(t *testing.T) {
expected := "10.1.0.21:6379"
got := redisPoolObj.GetAddress()
if expected != got {
t.Errorf("Expected to get %s, but got %s", expected, got)
return
}
}
func converInterfaceSliceToStringSlice(sourceList []interface{}) []string {
targetList := make([]string, 0, len(sourceList))
for _, item := range sourceList {
if item == nil {
targetList = append(targetList, "")
} else if item_str, ok := item.(string); ok {
targetList = append(targetList, item_str)
} else if item_bytes, ok2 := item.([]byte); ok2 {
targetList = append(targetList, string(item_bytes))
}
}
return targetList
}
func isTwoOrderedSliceEqual(list1, list2 []string) bool {
if list1 == nil && list2 == nil {
return true
}
if list1 == nil || list2 == nil {
return false
}
if len(list1) != len(list2) {
return false
}
for i := 0; i < len(list1); i++ {
if list1[i] != list2[i] {
return false
}
}
return true
}
func isTwoUnorderedSliceEqual(list1, list2 []string) bool {
if list1 == nil && list2 == nil {
return true
}
if list1 == nil || list2 == nil {
return false
}
if len(list1) != len(list2) {
return false
}
map1 := make(map[string]struct{})
map2 := make(map[string]struct{})
for _, item := range list1 {
map1[item] = struct{}{}
}
for _, item := range list2 {
map2[item] = struct{}{}
}
for k := range map1 {
if _, exist := map2[k]; !exist {
return false
}
}
return true
}
func getDistinctKeyList(keyList []string) []string {
distinctKeyList := make([]string, 0, len(keyList))
keyMap := make(map[string]struct{})
for _, key := range keyList {
if _, exist := keyMap[key]; !exist {
distinctKeyList = append(distinctKeyList, key)
keyMap[key] = struct{}{}
}
}
return distinctKeyList
}
@@ -0,0 +1,587 @@
package typeUtil
import (
"testing"
"time"
)
func TestMapDataByte(t *testing.T) {
TestMapDataUint8(t)
}
func TestMapDataInt(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected int = 0
// Test when key doesn't exist
got, err := mapData.Int(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Int(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Int(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataInt8(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected int8 = 0
// Test when key doesn't exist
got, err := mapData.Int8(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Int8(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Int8(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataInt16(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected int16 = 0
// Test when key doesn't exist
got, err := mapData.Int16(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Int16(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Int16(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataInt32(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected int32 = 0
// Test when key doesn't exist
got, err := mapData.Int32(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Int32(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Int32(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataInt64(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected int64 = 0
// Test when key doesn't exist
got, err := mapData.Int64(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Int64(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Int64(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataUint(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected uint = 0
// Test when key doesn't exist
got, err := mapData.Uint(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Uint(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Uint(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataUint8(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected uint8 = 0
// Test when key doesn't exist
got, err := mapData.Uint8(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Uint8(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Uint8(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataUint16(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected uint16 = 0
// Test when key doesn't exist
got, err := mapData.Uint16(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Uint16(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Uint16(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataUint32(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected uint32 = 0
// Test when key doesn't exist
got, err := mapData.Uint32(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Uint32(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Uint32(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataUint64(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected uint64 = 0
// Test when key doesn't exist
got, err := mapData.Uint64(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Uint64(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Uint64(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %d, but got %d", expected, got)
return
}
}
func TestMapDataFloat32(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected float32 = 0
// Test when key doesn't exist
got, err := mapData.Float32(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Float32(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Float32(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %f, but got %f", expected, got)
return
}
}
func TestMapDataFloat64(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected float64 = 0
// Test when key doesn't exist
got, err := mapData.Float64(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Float64(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = 1
expected = 1
got, err = mapData.Float64(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %f, but got %f", expected, got)
return
}
}
func TestMapDataBool(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected bool = true
// Test when key doesn't exist
got, err := mapData.Bool(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = "abc"
got, err = mapData.Bool(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = true
expected = true
got, err = mapData.Bool(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %t, but got %t", expected, got)
return
}
}
func TestMapDataString(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected string = ""
// Test when key doesn't exist
got, err := mapData.String(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist, but type doesn't match
mapData[key] = 123
expected = "123"
got, err = mapData.String(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %s, but got %s", expected, got)
return
}
// Test when key exist and value matches
mapData[key] = "hello"
expected = "hello"
got, err = mapData.String(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %s, but got %s", expected, got)
return
}
}
// Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time
func TestMapDataDateTime(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected time.Time = time.Date(2019, time.December, 25, 12, 0, 0, 0, time.UTC)
// Test when key doesn't exist
got, err := mapData.DateTime(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist but value doesn't match
mapData[key] = "123"
expected = time.Date(2019, time.December, 25, 12, 0, 0, 0, time.UTC)
got, err = mapData.DateTime(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = time.Date(2019, time.December, 25, 12, 0, 0, 0, time.UTC)
expected = time.Date(2019, time.December, 25, 12, 0, 0, 0, time.UTC)
got, err = mapData.DateTime(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if got != expected {
t.Errorf("Expected %s, but got %s", expected, got)
return
}
}
func TestMapDataInterface(t *testing.T) {
data := make(map[string]interface{})
mapData := NewMapData(data)
key := "key"
var expected *Person
// Test when key doesn't exist
got, err := mapData.Interface(key)
if err == nil {
t.Errorf("There should be an error, but now there isn't.")
return
}
// Test when key exist and value matches
mapData[key] = NewPerson("Jordan", 34)
expected = NewPerson("Jordan", 34)
got, err = mapData.Interface(key)
if err != nil {
t.Errorf("There should be no error, but now there is:%s", err)
return
}
if gotPersonObj, ok := got.(*Person); !ok {
t.Errorf("Expected type *Person")
} else if gotPersonObj.SameAs(expected) == false {
t.Errorf("Expected %v, but got %v", expected, got)
return
}
}
type Person struct {
Name string
Age int
}
func (this *Person) SameAs(other *Person) bool {
return this.Name == other.Name && this.Age == other.Age
}
func NewPerson(name string, age int) *Person {
return &Person{
Name: name,
Age: age,
}
}
@@ -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
}
@@ -0,0 +1,83 @@
package notify_util
import (
"fmt"
"goutil/syncUtil"
)
var (
// key:初始化成功的标志名称 val:占位符
registerNC = make(map[string]*notifyCenter)
mutex = syncUtil.NewRWLocker()
)
// getItemOrAdd
// @description: 获取注册的通知对象
// parameter:
// @chanGroup:
// return:
// @*notifyCenter:
func getItemOrAdd(chanGroup string) *notifyCenter {
if isOk, prevStack, currStack := mutex.Lock(deathLockTime); isOk == false {
//记日志
errMsg := fmt.Sprintf("Lock timeout! \n上一个堆栈:\n%s \n当前堆栈:\n%s", prevStack, currStack)
panic(errMsg)
}
defer mutex.Unlock()
nc, exists := registerNC[chanGroup]
if exists {
return nc
}
nc = newNotifyCenter()
registerNC[chanGroup] = nc
return nc
}
// Register
// @description: 注册需要被通知的对象
// parameter:
// @chanGroup:通知的分组标识
// @chanName:唯一标识
// @cf:回调方法
// return:
func Register(chanGroup string, chanName string, cf func()) {
nc := getItemOrAdd(chanGroup)
nc.register(chanName, cf)
}
// Unregister
// @description: 取消启动成功通知注册
// parameter:
// @chanGroup:分组标识
// @name:唯一标识
// return:
func Unregister(chanGroup string, name string) {
nc := getItemOrAdd(chanGroup)
nc.unregister(name)
}
// Notify
// @description: 通知分组所有已注册的对象
// parameter:
// @chanGroup:分组标识
// return:
func Notify(chanGroup string) {
nc := getItemOrAdd(chanGroup)
nc.notify()
}
// Notify2
// @description: 通知所有已注册的对象,该方法会在捕获第一个err的时候停止后续的通知。多用于系统启动的判定
// parameter:
// @chanGroup:分组标识
// return:
// @err:
func Notify2(chanGroup string) (err error) {
nc := getItemOrAdd(chanGroup)
err = nc.notify2()
return
}
@@ -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()
}
```
@@ -0,0 +1,40 @@
package main
import (
"common/connection"
"sync"
_ "common/resultStatus"
"common/webServer"
_ "logincenter/internal/user"
)
var (
wg sync.WaitGroup
)
func init() {
// 设置WaitGroup需要等待的数量,只要有一个服务器出现错误都停止服务器
wg.Add(1)
}
func main() {
//加载配置
loadConfig()
// 启动webserver
go webServer.Start(&wg)
// 阻塞等待,以免main线程退出
wg.Wait()
}
// loadConfig 用于加载配置信息。
// 该函数会读取配置文件或环境变量中的设置,并根据这些设置初始化程序所需的配置。
// 目前函数的实现为空,需要根据实际的配置加载逻辑进行填充。
func loadConfig() {
//构建数据库
connection.BuildDB()
}
@@ -0,0 +1,36 @@
module logincenter
go 1.22.10
replace (
common => ../common
framework => ../../framework
goutil => ../../goutil
)
require common v0.0.0-00010101000000-000000000000
require (
filippo.io/edwards25519 v1.1.0 // indirect
framework v0.0.0-20230425160006-b2d0b0a0b0b0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/gomodule/redigo v1.8.9 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/jinzhu/gorm v1.9.12 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/gorm v1.25.12 // indirect
goutil v0.0.0-20230425160006-b2d0b0a0b0b0 // indirect
)
@@ -0,0 +1,6 @@
package verifyMgr
/*
此包用于对指定url进行访问验证,以便于在程序启动时可以提前知道目标地址的可访问性,
而不用等到实际需要时再验证;从而造成既定的影响。
*/
@@ -0,0 +1,22 @@
package sensitiveWordsMgr
import (
"testing"
)
// type Persion struct {
// name string
// }
// 屏蔽字详细信息
func Test1(t *testing.T) {
//启动获取敏感字
refreshSensitiveWord()
words, pos, exist := SensitiveWords("测试,测试")
if exist {
t.Log(words, pos)
}
t.Log("END")
}
@@ -0,0 +1,377 @@
package utils
import (
"bytes"
"compress/flate"
"encoding/json"
"fmt"
"goutil/logUtilPlus"
"io/ioutil"
"math/rand"
"net/http"
"runtime/debug"
"strconv"
"strings"
"time"
)
// 定义常量
var MinDateTime = time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)
var MaxDateTime = time.Date(3000, 1, 1, 0, 0, 0, 0, time.Local)
var GuidEmpty = "00000000-0000-0000-0000-000000000000"
// IsTrue
// @description: 是否为true
// parameter:
// @data: []byte格式的bool信息
// return:
// @bool: 返回布尔值
func IsTrue(data []byte) bool {
if data == nil {
return false
}
if len(data) == 0 {
return false
}
if data[0] == 0 {
return false
} else {
return true
}
}
// ConvertBooleanToBytes
// @description: bool型转换成byte数组
// parameter:
// @status: 状态
// return:
// @[]byte: 返回字节数组
func ConvertBooleanToBytes(status bool) []byte {
if status == false {
return []byte{0}
} else {
return []byte{1}
}
}
// StrSliceJoinToStr
// @description: 将[]string组装成字符串
// parameter:
// @numArray: 源int32数组
// @sep: 分割字符串
// return:
// @string: 组成的字符串
func StrSliceJoinToStr(numArray []string, sep string) string {
str := ""
if numArray == nil || len(numArray) == 0 {
return str
}
for _, n := range numArray {
str += fmt.Sprintf("%s%s", n, sep)
}
str = strings.TrimSuffix(str, sep)
return str
}
// Int32SliceJoinToStr
// @description: 将[]int32转换成字符串
// parameter:
// @source: 资源
// @sep: 分隔符
// return:
// @result: 字符串
func Int32SliceJoinToStr(source []int32, sep string) (result string) {
if source == nil || len(source) == 0 {
return ""
}
for _, s := range source {
result += fmt.Sprintf("%d%s", s, sep)
}
result = strings.TrimRight(result, sep)
return
}
// SplitToStrSlice
// @description: 将字符串切割为[]string
// parameter:
// @s: 输入字符串
// @sep: 分割字符串
// @removeEmpty: 是否去除空字符串
// return:
// @resultSlice: 字符串列表
func SplitToStrSlice(s, sep string, removeEmpty bool) (resultSlice []string) {
if len(s) == 0 {
return make([]string, 0)
}
// 先按照分隔符进行切割
strSlice := strings.Split(s, sep)
for _, value := range strSlice {
if removeEmpty {
// 去除空格
if value = strings.TrimSpace(value); value == "" {
continue
}
}
resultSlice = append(resultSlice, value)
}
return resultSlice
}
// ParseTimeString
// @description: 解析时间字符串
// parameter:
// @timeStr: 时间字符串,例:12:33:12
// return:
// @hour: 小时
// @minute: 分钟
// @second: 秒数
func ParseTimeString(timeStr string) (hour int, minute int, second int) {
timeSlice := strings.Split(timeStr, ":")
if len(timeSlice) != 3 {
return
}
hour, _ = strconv.Atoi(timeSlice[0])
minute, _ = strconv.Atoi(timeSlice[1])
second, _ = strconv.Atoi(timeSlice[2])
return
}
// Rm_duplicate_string
// @description: 去重
// parameter:
// @list: 列表
// return:
// @[]string: 去重后的数据
func Rm_duplicate_string(list []string) []string {
var x []string = []string{}
for _, i := range list {
if len(x) == 0 {
x = append(x, i)
} else {
for k, v := range x {
if i == v {
break
}
if k == len(x)-1 {
x = append(x, i)
}
}
}
}
return x
}
// Rm_duplicate_int32
// @description: 去重
// parameter:
// @list: 列表数据
// return:
// @[]int32: 去重后的结果
func Rm_duplicate_int32(list []int32) []int32 {
var x []int32 = []int32{}
for _, i := range list {
if len(x) == 0 {
x = append(x, i)
} else {
for k, v := range x {
if i == v {
break
}
if k == len(x)-1 {
x = append(x, i)
}
}
}
}
return x
}
// RandomAarrayOfInt32
// @description: int32数组乱序
// parameter:
// @arr: 数组
// return:
func RandomAarrayOfInt32(arr []int32) {
if arr == nil {
return
}
if len(arr) <= 0 {
return
}
rand.Seed(time.Now().UnixNano())
for i := len(arr) - 1; i > 0; i-- {
num := rand.Intn(i + 1)
arr[i], arr[num] = arr[num], arr[i]
}
}
// RandomAarrayOfString
// @description: string数组乱序
// parameter:
// @arr: 数组
// return:
func RandomAarrayOfString(arr []string) {
if arr == nil {
return
}
if len(arr) <= 0 {
return
}
rand.Seed(time.Now().UnixNano())
for i := len(arr) - 1; i > 0; i-- {
num := rand.Intn(i + 1)
arr[i], arr[num] = arr[num], arr[i]
}
}
// LogErrorRecover
// @description: 记录错误
// parameter:
// return:
func LogErrorRecover() {
if err := recover(); err != nil {
tmsg := fmt.Sprintf("err msg:%s stack:%s", err, debug.Stack())
logUtilPlus.ErrorLog(tmsg)
}
}
// LogReqErrorRecover
// @description: 记录错误
// parameter:
// @r:
// return:
func LogReqErrorRecover(r *http.Request) {
if err := recover(); err != nil {
b, err := json.Marshal(r)
reqStr := ""
if err == nil {
reqStr = string(b)
}
tmsg := fmt.Sprintf("RequestInfo:%s .err msg:%s stack:%s", reqStr, err, debug.Stack())
logUtilPlus.ErrorLog(tmsg)
}
}
// StringSliceIsExists
// @description: StringSliceIsExists
// parameter:
// @strList: strList
// @val: val
// return:
// @bool: 是否存在
func StringSliceIsExists(strList []string, val string) bool {
if strList == nil {
return false
}
for _, v := range strList {
if v == val {
return true
}
}
return false
}
// Int64SliceIsExists
// @description: Int64SliceIsExists
// parameter:
// @strList: strList
// @val: val
// return:
// @bool: 是否存在
func Int64SliceIsExists(strList []int64, val int64) bool {
if strList == nil {
return false
}
for _, v := range strList {
if v == val {
return true
}
}
return false
}
// SliceIsExists
// @description: SliceIsExists
// parameter:
// @n: n
// @f: f
// return:
// @bool: 是否存在
func SliceIsExists(n int, f func(int) bool) bool {
for i := 0; i < n; i++ {
if f(i) {
return true
}
}
return false
}
// FlateEncode
// @description: 压缩字符串
// parameter:
// @input: 输入字符列表
// return:
// @result: 结果字符列表
// @err: 错误信息
func FlateEncode(input []byte) (result []byte, err error) {
var buf bytes.Buffer
w, err := flate.NewWriter(&buf, flate.DefaultCompression)
if err != nil {
return nil, err
}
// 无法使用defer close使用无法拿到结果
_, err = w.Write(input)
if err != nil {
return nil, err
}
w.Close()
result = buf.Bytes()
return
}
// FlateDecode
// @description: FlateDecode
// parameter:
// @input: 输入字符列表
// return:
// @result: 结果字符列表
// @err: 错误信息
func FlateDecode(input []byte) (result []byte, err error) {
result, err = ioutil.ReadAll(flate.NewReader(bytes.NewReader(input)))
return
}
@@ -0,0 +1,34 @@
package admin
import (
"common/connection"
)
func init() {
//注册数据库
connection.RegisterDBModel(&Admin{})
}
type Admin struct {
ID int64 `gorm:"column:id;primary_key;comment:管理员id;autoIncrementIncrement" json:"id"`
//账号
Account string `gorm:"column:account;comment:账号" json:"account"`
Name string `gorm:"column:name;comment:管理员名称" json:"name"`
Password string `gorm:"column:password;comment:管理员密码" json:"password"`
//性别
Sex int32 `gorm:"column:sex;comment:性别" json:"sex"`
//生日
Birthday string `gorm:"column:birthday;comment:生日" json:"birthday"`
//手机
Phone int64 `gorm:"column:phone;comment:手机" json:"phone"`
//邮箱
Email string `gorm:"column:email;comment:邮箱" json:"email"`
//微信群【方便发送通知】
WechatGroup string `gorm:"column:wechat_group;comment:微信群" json:"wechat_group"`
//备注
Describe string `gorm:"column:describe;comment:备注" json:"describe"`
}
func (Admin) TableName() string {
return "admin"
}
@@ -0,0 +1,62 @@
package validationUtil
import (
"testing"
)
// 身份证测试
func TestIdCard(t *testing.T) {
idno := "450325197410077393"
if IsValideIdno(idno) == false {
t.Error("身份证验证出错:", idno)
t.Fail()
}
idno = "36062219701120774X"
if IsValideIdno(idno) == false {
t.Error("身份证验证出错:", idno)
t.Fail()
}
idno = "450325197410071111"
if IsValideIdno(idno) == false {
t.Error("身份证验证出错:", idno)
t.Fail()
}
idno = "3123123123"
if IsValideIdno(idno) == true {
t.Error("身份证验证出错:", idno)
t.Fail()
}
}
// 邮箱测试
func TestMail(t *testing.T) {
mail := "nihao@qq.com"
if IsValideEmail(mail) == false {
t.Error("邮箱验证出错:", mail)
t.Fail()
}
mail = "111@qq.com"
if IsValideEmail(mail) == false {
t.Error("邮箱验证出错:", mail)
t.Fail()
}
mail = "111_@qq.com"
if IsValideEmail(mail) == false {
t.Error("邮箱验证出错:", mail)
t.Fail()
}
}
// 验证中国的手机号
func TestChinesePhone(t *testing.T) {
phoneNum := "15111111111"
if IsValideChinesePhoneNum(phoneNum) == false {
t.Error("手机号验证出错:", phoneNum)
t.Fail()
}
phoneNum = "11111"
if IsValideChinesePhoneNum(phoneNum) == true {
t.Error("手机号验证出错:", phoneNum)
t.Fail()
}
}
@@ -0,0 +1,137 @@
package mysqlUtil
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"goutil/logUtil"
)
// 打开数据库连接
// connectionString:数据库连接字符串,格式:root:moqikaka3306@tcp(10.1.0.10:3306)/gameserver_data?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=10||MaxIdleConns=5
// 返回值:
// 数据库对象
// 错误对象
func OpenMysqlConnection(connectionString string) (dbObj *sql.DB, err error) {
dbConfigObj, err1 := NewDBConfig2(connectionString)
if err1 != nil {
err = err1
return
}
dbObj, err = OpenMysqlConnection3(dbConfigObj)
return
}
// 打开数据库连接
// connectionString:数据库连接字符串
// maxOpenConns:最大打开的连接数
// maxIdleConns:最大处于闲置状态的连接数
// 返回值:
// 数据库对象
// 错误对象
func OpenMysqlConnection2(connectionString string, maxOpenConns, maxIdleConns int) (dbObj *sql.DB, err error) {
dbConfigObj := NewDBConfig(connectionString, maxOpenConns, maxIdleConns)
dbObj, err = OpenMysqlConnection3(dbConfigObj)
return
}
// 建立Mysql数据库连接
// dbConfigObj:数据库配置对象
// 返回值:
// 数据库对象
// 错误对象
func OpenMysqlConnection3(dbConfigObj *DBConfig) (dbObj *sql.DB, err error) {
// 建立数据库连接
logUtil.DebugLog("开始连接Mysql数据库")
dbObj, err = sql.Open("mysql", dbConfigObj.ConnectionString)
if err != nil {
err = fmt.Errorf("打开游戏数据库失败,连接字符串为:%s", dbConfigObj.ConnectionString)
return
}
logUtil.DebugLog("连接Mysql数据库成功")
if dbConfigObj.MaxOpenConns > 0 && dbConfigObj.MaxIdleConns > 0 {
dbObj.SetMaxOpenConns(dbConfigObj.MaxOpenConns)
dbObj.SetMaxIdleConns(dbConfigObj.MaxIdleConns)
}
if err = dbObj.Ping(); err != nil {
err = fmt.Errorf("Ping数据库失败,连接字符串为:%s,错误信息为:%s", dbConfigObj.ConnectionString, err)
return
}
return
}
// 测试数据库连接
// dbObj:数据库连对象
// 返回值:
// 错误对象
func TestConnection(dbObj *sql.DB) error {
command := "SHOW DATABASES;"
rows, err := dbObj.Query(command)
if err != nil {
return err
}
defer rows.Close()
return nil
}
// 开始事务
// db:数据库对象
// 返回值:
// 事务对象
// 错误对象
func BeginTransaction(dbObj *sql.DB) (*sql.Tx, error) {
tx, err := dbObj.Begin()
if err != nil {
logUtil.Log(fmt.Sprintf("开启事务失败,错误信息:%s", err), logUtil.Error, true)
}
return tx, err
}
// 提交事务
// tx:事务对象
// 返回值:
// 错误对象
func CommitTransaction(tx *sql.Tx) error {
err := tx.Commit()
if err != nil {
logUtil.Log(fmt.Sprintf("提交事务失败,错误信息:%s", err), logUtil.Error, true)
}
return err
}
// 记录Prepare错误
// command:执行的SQL语句
// err:错误对象
func WritePrepareError(command string, err error) {
logUtil.Log(fmt.Sprintf("Prepare失败,错误信息:%scommand:%s", err, command), logUtil.Error, true)
}
// 记录Query错误
// command:执行的SQL语句
// err:错误对象
func WriteQueryError(command string, err error) {
logUtil.Log(fmt.Sprintf("Query失败,错误信息:%scommand:%s", err, command), logUtil.Error, true)
}
// 记录Exec错误
// command:执行的SQL语句
// err:错误对象
func WriteExecError(command string, err error) {
logUtil.Log(fmt.Sprintf("Exec失败,错误信息:%scommand:%s", err, command), logUtil.Error, true)
}
// 记录Scan错误
// command:执行的SQL语句
// err:错误对象
func WriteScanError(command string, err error) {
logUtil.Log(fmt.Sprintf("Scan失败,错误信息:%scommand:%s", err, command), logUtil.Error, true)
}
@@ -0,0 +1,20 @@
coroutine-timer支持如下工作:
定时触发设定的回调,最小精度秒级
## 使用方式
### 增加回调
> 1. 导入包
> 2. 调用AddTimerx添加定时回调,传入相关参数
ps:
> 1. AddTimer1AddTimer2AddTimer3是内部自动生成的id,内部保证唯一性。外部如果后续要删除该添加的timer,需要持有返回的id信息
> 2. AddTimer4 需要外部传入id,外部需要保证id的唯一性。并且这个方法会在内部校验id是否已经存在,所以性能上会比其他AddTimer方法慢
### 删除回调
```go
DeleteTimer(id)
```
@@ -0,0 +1,82 @@
package main
import (
"fmt"
"sync"
"time"
"goutil/mathUtil"
"goutil/stringUtil"
)
var (
wg sync.WaitGroup
)
func init() {
wg.Add(1)
}
func main() {
playerMgr := newPlayerMgr()
// insert
go func() {
for {
id := stringUtil.GetNewGUID()
name := fmt.Sprintf("Hero_%s", id)
obj := newPlayer(id, name)
playerMgr.insert(obj)
insert(obj)
time.Sleep(10 * time.Millisecond)
}
}()
// update
go func() {
for {
obj := playerMgr.randomSelect()
if obj == nil {
continue
}
suffix := mathUtil.GetRand().GetRandInt(1000)
newName := fmt.Sprintf("Hero_%d", suffix)
obj.resetName(newName)
update(obj)
time.Sleep(10 * time.Millisecond)
}
}()
// delete
go func() {
for {
obj := playerMgr.randomSelect()
if obj == nil {
continue
}
playerMgr.delete(obj)
clear(obj)
time.Sleep(10 * time.Millisecond)
}
}()
// errorFile
go func() {
for {
time.Sleep(1 * time.Hour)
id := stringUtil.GetNewGUID()
name := fmt.Sprintf("Hero_%s%s", id, id)
obj := newPlayer(id, name)
playerMgr.insert(obj)
print("errorFile")
insert(obj)
}
}()
wg.Wait()
}
@@ -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
}
@@ -0,0 +1,54 @@
# 配置根节点
root:
# 是否是调试模式
debug: true
# Web服务监听地址和端口
web_server_address: "192.168.50.85:10052"
# Elasticsearch 地址
es_urls: "http://10.252.0.70:18099"
# 数据库配置
db_config:
admin_db:
# 最大处于开启状态的连接数
max_open_conns: 0
# 最大处于空闲状态的连接数
max_idle_conns: 0
# 数据库连接字符串
connection_string: "root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true"
user_db:
# 最大处于开启状态的连接数
max_open_conns: 0
# 最大处于空闲状态的连接数
max_idle_conns: 0
# 数据库连接字符串
connection_string: "root:Qq5201530300@tcp(192.168.50.110:3306)/user?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true"
redis_config:
# 数据库连接字符串
connection_string: "192.168.50.110:6379"
# 密码, 如果要设置用户Id,则密码设置为:"UserId:Password"
password: ""
# 数据库序号
database: 5
# 最大活跃连接数
max_active: 500
# 最大空闲的连接数
max_idle: 200
# 连接空闲超时时间,单位:秒
idle_timeout: 300
# 连接超时时间, 单位:秒
dial_connect_timeout: 10
@@ -0,0 +1,123 @@
package redisUtil
import (
"fmt"
"strconv"
"strings"
"time"
)
// Redis配置对象
type RedisConfig struct {
// 连接字符串
ConnectionString string
// 密码
Password string
// 数据库编号
Database int
// 最大活跃连接数
MaxActive int
// 最大空闲连接数
MaxIdle int
// 空闲超时
IdleTimeout time.Duration
// 连接超时
DialConnectTimeout time.Duration
}
// 将redis连接字符串转化为redis config对象
// 格式:ConnectionString=10.1.0.21:6379;Password=redis_pwd;Database=3;MaxActive=50;MaxIdle=20;IdleTimeout=300;DialConnectTimeout=10;
// redisConfigStrredis连接字符串
// 返回值:
// redis config对象
// 错误对象
func NewRedisConfig(redisConfigStr string) (redisConfig *RedisConfig, err error) {
var connectionString string
var password string
var database int
var maxActive int
var maxIdle int
var idleTimeout time.Duration
var dialConectTimeout time.Duration
var count int = 7
var subCount int = 2
itemList := strings.Split(redisConfigStr, ";")
// 去掉最后的空数据
if itemList[len(itemList)-1] == "" {
itemList = itemList[0 : len(itemList)-1]
}
if len(itemList) != count {
err = fmt.Errorf("%s格式不正确,需要包含%d个部分,现在有%d个部分", redisConfigStr, count, len(itemList))
return
}
for _, item := range itemList {
subItemList := strings.Split(item, "=")
if len(subItemList) != subCount {
err = fmt.Errorf("%s格式不正确,需要包含%d个部分", item, subCount)
return
}
// 分别进行判断
switch strings.ToLower(subItemList[0]) {
case strings.ToLower("ConnectionString"):
connectionString = subItemList[1]
case strings.ToLower("Password"):
password = subItemList[1]
case strings.ToLower("Database"):
if database, err = strconv.Atoi(subItemList[1]); err != nil {
err = fmt.Errorf("%s转化为int型失败", subItemList[1])
return
}
case strings.ToLower("MaxActive"):
if maxActive, err = strconv.Atoi(subItemList[1]); err != nil {
err = fmt.Errorf("%s转化为int型失败", subItemList[1])
return
}
case strings.ToLower("MaxIdle"):
if maxIdle, err = strconv.Atoi(subItemList[1]); err != nil {
err = fmt.Errorf("%s转化为int型失败", subItemList[1])
return
}
case strings.ToLower("IdleTimeout"):
if idleTimeout_int, err1 := strconv.Atoi(subItemList[1]); err1 != nil {
err = fmt.Errorf("%s转化为int型失败", subItemList[1])
return
} else {
idleTimeout = time.Duration(idleTimeout_int) * time.Second
}
case strings.ToLower("DialConnectTimeout"):
if dialConectTimeout_int, err1 := strconv.Atoi(subItemList[1]); err1 != nil {
err = fmt.Errorf("%s转化为int型失败", subItemList[1])
return
} else {
dialConectTimeout = time.Duration(dialConectTimeout_int) * time.Second
}
}
}
redisConfig = NewRedisConfig2(connectionString, password, database, maxActive, maxIdle, idleTimeout, dialConectTimeout)
return
}
func NewRedisConfig2(connectionString, password string,
database, maxActive, maxIdle int,
idleTimeout, dialConnectTimeout time.Duration) *RedisConfig {
return &RedisConfig{
ConnectionString: connectionString,
Password: password,
Database: database,
MaxActive: maxActive,
MaxIdle: maxIdle,
IdleTimeout: idleTimeout,
DialConnectTimeout: dialConnectTimeout,
}
}
@@ -0,0 +1,16 @@
package mqMgr
// 消息队列配置对象
type QueueConfig struct {
// 地域
Region string
// 队列名称
QueueName string
// API密钥Id
SecretId string
// API密钥key
SecretKey string
}
@@ -0,0 +1,16 @@
package goroutineMgr
/*
goroutine的管理包,提供了对goroutine的监控机制
对外提供的方法为:
// 监控指定的goroutine
Monitor(goroutineName string)
// 只添加数量,不监控
MonitorZero(goroutineName string)
// 释放监控
ReleaseMonitor(goroutineName string)
*/
@@ -0,0 +1,133 @@
package handleMgr
import (
"fmt"
"reflect"
"testing"
"time"
)
// ---------------申请示例实例--------------
var (
data = make(map[int]int, 0)
)
type testBll struct {
}
// TestAdd
// @description:测试添加
// parameter:
// @receiver t:
// @x:
// @y:
// return:
// @*ResponseObject:
func (t testBll) TestAdd(x, y int) *ResponseObject {
responseObj := GetInitResponseObj()
data[x] = y
z := x + y
print(x)
responseObj.SetData(z)
responseObj.SetResultStatus(-11111, "错误码")
return responseObj
}
// See1
// @description:测试添加
// parameter:
// @receiver t:
// @x:
// return:
// @*ResponseObject:
func (t testBll) See1(x int) *ResponseObject {
responseObj := GetInitResponseObj()
fmt.Print(x)
return responseObj
}
// See2
// @description:测试添加
// parameter:
// @receiver t:
// @x:
// return:
// @*ResponseObject:
func (t testBll) See2(x int) *ResponseObject {
responseObj := GetInitResponseObj()
time.Sleep(2000 * time.Millisecond)
fmt.Print(x)
return responseObj
}
func (t testBll) ReMove(x int) *ResponseObject {
responseObj := GetInitResponseObj()
delete(data, x)
return responseObj
}
// ---------------测试方法--------------
func TestNew(t *testing.T) {
var paramItem interface{}
paramItem = 123
paramFloat64, ok := paramItem.(int)
if ok {
x := reflect.ValueOf(int(paramFloat64))
t.Log(x)
}
t.Log(paramFloat64)
t.Log(ok)
//t.Log("A")
//RegisterNewModule("test", new(testBll), 10)
//parameter := []interface{}{"1", 2}
//x, y, mes := Done("test", "testBll", "TestAdd", parameter, false)
//for key, item := range data {
// t.Log(key, item)
//}
//t.Log("B2")
//parameter1 := []interface{}{2, 2}
//x, y, mes = Done("test", "testBll", "TestAdd", parameter1, true)
//for key, item := range data {
// t.Log(key, item)
//}
//t.Log(x)
//t.Log(y)
//t.Log(mes)
//t.Log("B3")
//
//t.Log("C")
//parameter2 := []interface{}{2}
//Done("test", "testBll", "ReMove", parameter2, true)
//for key, item := range data {
// t.Log(key, item)
//}
//t.Log("D")
//
//t.Log(time.Now().UnixMilli())
//parameter3 := []interface{}{1}
//Done("test", "testBll", "See2", parameter3, false)
//t.Log(time.Now().UnixMilli())
//parameter4 := []interface{}{2}
//Done("test", "testBll", "See1", parameter4, true)
//t.Log(time.Now().UnixMilli())
//parameter5 := []interface{}{1}
//Done("test", "testBll", "See1", parameter5, true)
//t.Log(time.Now().UnixMilli())
//
//t.Log("E")
//
//t.Log(time.Now().UnixMilli())
//for i := 0; i < 1000; i++ {
// Done("test", "testBll", "See1", parameter5, true)
//}
//t.Log(time.Now().UnixMilli())
//t.Log("F")
t.Error("G")
}
@@ -0,0 +1,109 @@
package qcloud
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"time"
"goutil/mathUtil"
"goutil/webUtil"
)
const (
VOICE_CAPTCHA_URL = "https://cloud.tim.qq.com/v5/tlsvoicesvr/sendcvoice"
VOICE_NOTIFICATION_URL = "https://cloud.tim.qq.com/v5/tlsvoicesvr/sendvoiceprompt"
VOICE_TEMPLATE_NOTIFICATION_URL = "https://cloud.tim.qq.com/v5/tlsvoicesvr/sendtvoice"
)
// calculate sign-string for phone numbers
func calcSig(appKey string, rand int, timeStamp int64, mobile string) string {
sum := sha256.Sum256([]byte(fmt.Sprintf("appkey=%s&random=%d&time=%d&mobile=%s", appKey, rand, timeStamp, mobile)))
return hex.EncodeToString(sum[:])
}
// do the http request and parse the response
func request(url string, data map[string]interface{}, appId string, rand int) (success bool, err error) {
url = fmt.Sprintf("%s?sdkappid=%s&random=%d", url, appId, rand)
fmt.Printf("url:%s\n", url)
contentBytes, err := json.Marshal(data)
if err != nil {
return
}
fmt.Printf("data:%v\n", string(contentBytes))
_, retBytes, err := webUtil.PostByteData2(url, contentBytes, nil, nil)
if err != nil {
return
}
var responseObj *response
err = json.Unmarshal(retBytes, &responseObj)
if err != nil {
return
}
if responseObj.Result != 0 {
err = fmt.Errorf(responseObj.ErrMsg)
return
}
success = true
return
}
func SendVoiceCaptcha(appId, appKey, nation, mobile, captcha string, playTimes int) (success bool, err error) {
rand := mathUtil.GetRand().GetRandRangeInt(100000, 999999)
timeStamp := time.Now().Unix()
data := make(map[string]interface{})
data["playtimes"] = playTimes
data["sig"] = calcSig(appKey, rand, timeStamp, mobile)
data["tel"] = newTelField(nation, mobile)
data["time"] = timeStamp
data["ext"] = ""
// dedicated param
data["msg"] = captcha
success, err = request(VOICE_CAPTCHA_URL, data, appId, rand)
return
}
func SendVoiceNotification(appId, appKey, nation, mobile, prompt string, playTimes int) (success bool, err error) {
rand := mathUtil.GetRand().GetRandRangeInt(100000, 999999)
timeStamp := time.Now().Unix()
promptType := 2
data := make(map[string]interface{})
data["playtimes"] = playTimes
data["sig"] = calcSig(appKey, rand, timeStamp, mobile)
data["tel"] = newTelField(nation, mobile)
data["time"] = timeStamp
data["ext"] = ""
// dedicated param
data["promptfile"] = prompt
data["prompttype"] = promptType
success, err = request(VOICE_NOTIFICATION_URL, data, appId, rand)
return
}
func SendVoiceTemplateNotification(appId, appKey, nation, mobile string, templateId int, params []string, playTimes int) (success bool, err error) {
rand := mathUtil.GetRand().GetRandRangeInt(100000, 999999)
timeStamp := time.Now().Unix()
data := make(map[string]interface{})
data["playtimes"] = playTimes
data["sig"] = calcSig(appKey, rand, timeStamp, mobile)
data["tel"] = newTelField(nation, mobile)
data["time"] = timeStamp
data["ext"] = ""
// dedicated param
data["tpl_id"] = templateId
data["params"] = params
success, err = request(VOICE_TEMPLATE_NOTIFICATION_URL, data, appId, rand)
return
}
@@ -0,0 +1,79 @@
package gameServerMgr
import (
"time"
. "Framework/managecenterModel"
"goutil/timeUtil"
"goutil/typeUtil"
)
var (
mServerGroupObj *ServerGroup
)
//解析服务器组信息
func ParseServerGroupInfo(serverGroupObj *ServerGroup) {
mServerGroupObj = serverGroupObj
}
//获取服务器组对象
func GetServerGroup() (serverGroupObj *ServerGroup) {
serverGroupObj = mServerGroupObj
return
}
//检查服务器是否在维护
func CheckMaintainStatus() (maintainMessage string, isMaintaining bool) {
serverGroupObj := GetServerGroup()
nowTick := time.Now().Unix()
if serverGroupObj.GroupState == int32(Con_GroupState_Maintain) || (serverGroupObj.MaintainBeginTimeTick <= nowTick && nowTick <= serverGroupObj.MaintainBeginTimeTick+int64(60*serverGroupObj.MaintainMinutes)) {
maintainMessage = serverGroupObj.MaintainMessage
isMaintaining = true
return
}
return
}
//获取服务器维护开始时间时间戳
func GetMaintainBeginTime() (maintainBeginTimeTick int64) {
serverGroupObj := GetServerGroup()
maintainBeginTimeTick = serverGroupObj.MaintainBeginTimeTick
return
}
//获取服务器维护持续时间 单位分钟
func GetMaintainMinutes() (maintainMinutes int32) {
serverGroupObj := GetServerGroup()
maintainMinutes = serverGroupObj.MaintainMinutes
return
}
//获取服务器开服日期 时间戳
func GetServerOpenDate() (openTimeTick int64) {
serverGroupObj := GetServerGroup()
openTimeTick = serverGroupObj.OpenTimeTick
return
}
//当前服务器已开服天数(开服第几天)
//当前开服天数 计算公式:(当前日期 - 开服日期)的总天数 + 1
func ServerOpenDays() (days int32) {
serverGroupObj := GetServerGroup()
if serverGroupObj.IsOpen() == false {
return 0
}
//(当前日期 - 开服日期)的总天数 + 1
openTimeTick := serverGroupObj.OpenTimeTick
openDate, _ := typeUtil.DateTime(openTimeTick)
days = int32(timeUtil.SubDay(time.Now(), openDate) + 1)
return
}
@@ -0,0 +1,225 @@
package syncUtil
import (
"context"
"fmt"
"sync"
"testing"
"time"
)
func TestMutex(t *testing.T) {
mu := NewMutex()
mu.Lock()
defer mu.UnLock()
if mu.TryLock() {
t.Errorf("cannot fetch mutex !!!")
}
}
func TestMutexTryLockTimeout(t *testing.T) {
fmt.Println("start")
mu := NewMutex()
mu.Lock()
go func() {
time.Sleep(20 * time.Second)
mu.UnLock()
}()
// if !mu.TryLockTimeout(500 * time.Microsecond) {
// t.Errorf("cannot fetch mutex in 500us !!!")
// }
if !mu.TryLockTimeout(15 * time.Second) {
t.Errorf("should fetch mutex in 5ms !!!")
}
mu.UnLock()
}
func TestMutexUnlockTwice(t *testing.T) {
mu := NewMutex()
mu.Lock()
defer func() {
if x := recover(); x != nil {
if x != "unlock of unlocked mutex" {
t.Errorf("unexpect panic")
}
} else {
t.Errorf("should panic after unlock twice")
}
}()
mu.UnLock()
mu.UnLock()
}
func TestMutexTryLockContext(t *testing.T) {
mu := NewMutex()
ctx, cancel := context.WithCancel(context.Background())
mu.Lock()
go func() {
time.Sleep(10 * time.Millisecond)
cancel()
}()
if mu.TryLockContext(ctx) {
t.Errorf("cannot fetch mutex !!!")
}
}
func BenchmarkMutex(b *testing.B) {
mu := NewMutex()
a := 0
c := 0
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
a++
mu.UnLock()
mu.Lock()
c = a
mu.UnLock()
}
})
_ = a
_ = c
}
func TestMutexGroup(t *testing.T) {
mu := NewMutexGroup()
mu.Lock("g")
defer mu.UnLock("g")
if mu.TryLock("g") {
t.Errorf("cannot fetch mutex !!!")
}
}
func TestMutexGroupMutliWaitLock(t *testing.T) {
var (
wg sync.WaitGroup
mu = NewMutexGroup()
cn = 3
)
for i := 0; i < cn; i++ {
wg.Add(1)
go func() {
mu.Lock("h")
time.Sleep(1e7)
mu.UnLock("h")
wg.Done()
}()
}
wg.Wait()
for i := 0; i < cn; i++ {
wg.Add(1)
go func() {
mu.Lock("g")
time.Sleep(1e7)
mu.UnLockAndFree("g")
wg.Done()
}()
}
wg.Wait()
}
func TestMutexGroupUnLockAndFree(t *testing.T) {
var (
wg sync.WaitGroup
mu = NewMutexGroup()
mg = mu.(*mutexGroup)
)
for j := 1; j < 5; j++ {
for i := 0; i < j; i++ {
wg.Add(1)
go func() {
mu.Lock("h")
time.Sleep(1e6)
mu.UnLockAndFree("h")
wg.Done()
}()
}
wg.Wait()
mg.mu.Lock()
if _, ok := mg.group["h"]; ok {
t.Error("h mutex exist after UnLockAndFree")
}
mg.mu.Unlock()
}
}
func TestMutexGroupTryLockFailedAndUnLockAndFree(t *testing.T) {
var (
wg sync.WaitGroup
mu = NewMutexGroup()
mg = mu.(*mutexGroup)
)
for j := 1; j < 5; j++ {
for i := 0; i < j; i++ {
wg.Add(1)
go func() {
if mu.TryLock("h") {
time.Sleep(1e6)
mu.UnLockAndFree("h")
}
wg.Done()
}()
}
wg.Wait()
mg.mu.Lock()
if _, ok := mg.group["h"]; ok {
t.Error("h mutex exist after UnLockAndFree")
}
mg.mu.Unlock()
}
}
func TestMutexGroupTryLockTimeout(t *testing.T) {
mu := NewMutexGroup()
mu.Lock("g")
go func() {
time.Sleep(1 * time.Millisecond)
mu.UnLock("g")
}()
if mu.TryLockTimeout("g", 500*time.Microsecond) {
t.Errorf("cannot fetch mutex in 500us !!!")
}
if !mu.TryLockTimeout("g", 5*time.Millisecond) {
t.Errorf("should fetch mutex in 5ms !!!")
}
mu.UnLock("g")
}
func TestMutexGroupTryLockContext(t *testing.T) {
mu := NewMutexGroup()
ctx, cancel := context.WithCancel(context.Background())
mu.Lock("g")
go func() {
time.Sleep(10 * time.Millisecond)
cancel()
}()
if mu.TryLockContext("g", ctx) {
t.Errorf("cannot fetch mutex !!!")
}
}
func BenchmarkMutexGroup(b *testing.B) {
mu := NewMutexGroup()
a := 0
c := 0
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock("g")
a++
mu.UnLock("g")
mu.Lock("g")
c = a
mu.UnLock("g")
}
})
_ = a
_ = c
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,378 @@
/*
未实现的列表方法:
BLPOP、BRPOP、BRPOPLPUSH
*/
package redisUtil
import (
"github.com/gomodule/redigo/redis"
)
/*
LPUSH key value [value …]
可用版本: >= 1.0.0
时间复杂度: O(1)
将一个或多个值 value 插入到列表 key 的表头
如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头: 比如说,对空列表 mylist 执行命令 LPUSH mylist a b c ,列表的值将是 c b a ,这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。
如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。
当 key 存在但不是列表类型时,返回一个错误。
Note
在Redis 2.4版本以前的 LPUSH 命令,都只接受单个 value 值。
返回值
执行 LPUSH 命令后,列表的长度。
*/
func (this *RedisPool) LPush(key string, values ...interface{}) (length int, err error) {
conn := this.GetConnection()
defer conn.Close()
length, err = redis.Int(conn.Do("LPUSH", redis.Args{}.Add(key).AddFlat(values)...))
return
}
/*
LPUSHX key value
可用版本: >= 2.2.0
时间复杂度: O(1)
将值 value 插入到列表 key 的表头,当且仅当 key 存在并且是一个列表。
和 LPUSH key value [value …] 命令相反,当 key 不存在时, LPUSHX 命令什么也不做。
返回值
LPUSHX 命令执行之后,表的长度。
*/
func (this *RedisPool) LPushX(key string, values ...interface{}) (length int, err error) {
conn := this.GetConnection()
defer conn.Close()
length, err = redis.Int(conn.Do("LPUSHX", redis.Args{}.Add(key).AddFlat(values)...))
return
}
/*
RPUSH key value [value …]
可用版本: >= 1.0.0
时间复杂度: O(1)
将一个或多个值 value 插入到列表 key 的表尾(最右边)。
如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:比如对一个空列表 mylist 执行 RPUSH mylist a b c ,得出的结果列表为 a b c ,等同于执行命令 RPUSH mylist a 、 RPUSH mylist b 、 RPUSH mylist c 。
如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。
当 key 存在但不是列表类型时,返回一个错误。
Note
在 Redis 2.4 版本以前的 RPUSH 命令,都只接受单个 value 值。
返回值
执行 RPUSH 操作后,表的长度。
*/
func (this *RedisPool) RPush(key string, values ...interface{}) (length int, err error) {
conn := this.GetConnection()
defer conn.Close()
length, err = redis.Int(conn.Do("RPUSH", redis.Args{}.Add(key).AddFlat(values)...))
return
}
/*
RPUSHX key value
可用版本: >= 2.2.0
时间复杂度: O(1)
将值 value 插入到列表 key 的表尾,当且仅当 key 存在并且是一个列表。
和 RPUSH key value [value …] 命令相反,当 key 不存在时, RPUSHX 命令什么也不做。
返回值
RPUSHX 命令执行之后,表的长度。
*/
func (this *RedisPool) RPushX(key string, values ...interface{}) (length int, err error) {
conn := this.GetConnection()
defer conn.Close()
length, err = redis.Int(conn.Do("RPUSHX", redis.Args{}.Add(key).AddFlat(values)...))
return
}
/*
LPOP key
可用版本: >= 1.0.0
时间复杂度: O(1)
移除并返回列表 key 的头元素。
返回值
列表的头元素。 当 key 不存在时,返回 nil 。
*/
func (this *RedisPool) LPop(key string) (item interface{}, exist bool, err error) {
conn := this.GetConnection()
defer conn.Close()
item, err = conn.Do("LPOP", key)
if err != nil {
return
}
if item == nil {
return
}
exist = true
return
}
/*
RPOP key
可用版本: >= 1.0.0
时间复杂度: O(1)
移除并返回列表 key 的尾元素。
返回值
列表的尾元素。 当 key 不存在时,返回 nil 。
*/
func (this *RedisPool) RPop(key string) (item interface{}, exist bool, err error) {
conn := this.GetConnection()
defer conn.Close()
item, err = conn.Do("RPOP", key)
if err != nil {
return
}
if item == nil {
return
}
exist = true
return
}
/*
RPOPLPUSH source destination
可用版本: >= 1.2.0
时间复杂度: O(1)
命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:
将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。
将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。
举个例子,你有两个列表 source 和 destination source 列表有元素 a, b, c destination 列表有元素 x, y, z ,执行 RPOPLPUSH source destination 之后, source 列表包含元素 a, b destination 列表包含元素 c, x, y, z ,并且元素 c 会被返回给客户端。
如果 source 不存在,值 nil 被返回,并且不执行其他动作。
如果 source 和 destination 相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。
返回值
被弹出的元素。
*/
func (this *RedisPool) RPopLPush(source, destination string) (item interface{}, err error) {
conn := this.GetConnection()
defer conn.Close()
item, err = conn.Do("RPOPLPUSH", source, destination)
if err != nil {
return
}
return
}
/*
LREM key count value
可用版本: >= 1.0.0
时间复杂度: O(N), N 为列表的长度。
根据参数 count 的值,移除列表中与参数 value 相等的元素。
count 的值可以是以下几种:
count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
count = 0 : 移除表中所有与 value 相等的值。
返回值
被移除元素的数量。 因为不存在的 key 被视作空表(empty list),所以当 key 不存在时, LREM 命令总是返回 0 。
*/
// 错误对象
func (this *RedisPool) LRem(key string, count int, value string) (removeCount int, err error) {
conn := this.GetConnection()
defer conn.Close()
removeCount, err = redis.Int(conn.Do("LREM", key, count, value))
return
}
/*
LLEN key
可用版本: >= 1.0.0
时间复杂度: O(1)
返回列表 key 的长度。
如果 key 不存在,则 key 被解释为一个空列表,返回 0 .
如果 key 不是列表类型,返回一个错误。
返回值
列表 key 的长度。
*/
func (this *RedisPool) LLen(key string) (length int, err error) {
conn := this.GetConnection()
defer conn.Close()
length, err = redis.Int(conn.Do("LLEN", key))
return
}
/*
LINDEX key index
可用版本: >= 1.0.0
时间复杂度:O(N), N 为到达下标 index 过程中经过的元素数量。因此,对列表的头元素和尾元素执行 LINDEX 命令,复杂度为O(1)。
返回列表 key 中,下标为 index 的元素。
下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
如果 key 不是列表类型,返回一个错误。
返回值
列表中下标为 index 的元素。 如果 index 参数的值不在列表的区间范围内(out of range),返回 nil 。
*/
func (this *RedisPool) LIndex(key string, index int) (item interface{}, exist bool, err error) {
conn := this.GetConnection()
defer conn.Close()
item, err = conn.Do("LINDEX", key, index)
if err != nil {
return
}
if item == nil {
return
}
exist = true
return
}
/*
LINSERT key BEFORE|AFTER pivot value
可用版本: >= 2.2.0
时间复杂度: O(N), N 为寻找 pivot 过程中经过的元素数量。
将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
当 pivot 不存在于列表 key 时,不执行任何操作。
当 key 不存在时, key 被视为空列表,不执行任何操作。
如果 key 不是列表类型,返回一个错误。
返回值
如果命令执行成功,返回插入操作完成之后,列表的长度。 如果没有找到 pivot ,返回 -1 。 如果 key 不存在或为空列表,返回 0 。
*/
func (this *RedisPool) LInsert(key, beforeOrAfter string, pivot, value interface{}) (length int, err error) {
conn := this.GetConnection()
defer conn.Close()
length, err = redis.Int(conn.Do("LINSERT", key, beforeOrAfter, pivot, value))
return
}
/*
LSET key index value
可用版本: >= 1.0.0
时间复杂度:对头元素或尾元素进行 LSET 操作,复杂度为 O(1)。其他情况下,为 O(N), N 为列表的长度。
将列表 key 下标为 index 的元素的值设置为 value 。
当 index 参数超出范围,或对一个空列表( key 不存在)进行 LSET 时,返回一个错误。
关于列表下标的更多信息,请参考 LINDEX key index 命令。
返回值
操作成功返回 ok ,否则返回错误信息。
*/
func (this *RedisPool) LSet(key string, index int, value interface{}) (err error) {
conn := this.GetConnection()
defer conn.Close()
_, err = conn.Do("LSet", key, index, value)
return
}
/*
LRANGE key start stop
可用版本: >= 1.0.0
时间复杂度: O(S+N) S 为偏移量 start , N 为指定区间内元素的数量。
返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。
下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
注意LRANGE命令和编程语言区间函数的区别
假如你有一个包含一百个元素的列表,对该列表执行 LRANGE list 0 10 ,结果是一个包含11个元素的列表,这表明 stop 下标也在 LRANGE 命令的取值范围之内(闭区间),这和某些语言的区间函数可能不一致,比如Ruby的 Range.new 、 Array#slice 和Python的 range() 函数。
超出范围的下标
超出范围的下标值不会引起错误。
如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,那么 LRANGE 返回一个空列表。
如果 stop 下标比 end 下标还要大,Redis将 stop 的值设置为 end 。
返回值
一个列表,包含指定区间内的元素。
*/
func (this *RedisPool) LRange(key string, start, stop int) (reply interface{}, err error) {
conn := this.GetConnection()
defer conn.Close()
reply, err = conn.Do("LRANGE", key, start, stop)
return
}
/*
LTRIM key start stop
可用版本: >= 1.0.0
时间复杂度: O(N), N 为被移除的元素的数量。
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余元素全部删除。
下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
当 key 不是列表类型时,返回一个错误。
LTRIM 命令通常和 LPUSH key value [value …] 命令或 RPUSH key value [value …] 命令配合使用,举个例子:
LPUSH log newest_log
LTRIM log 0 99
这个例子模拟了一个日志程序,每次将最新日志 newest_log 放到 log 列表中,并且只保留最新的 100 项。注意当这样使用 LTRIM 命令时,时间复杂度是O(1),因为平均情况下,每次只有一个元素被移除。
注意LTRIM命令和编程语言区间函数的区别
假如你有一个包含一百个元素的列表 list ,对该列表执行 LTRIM list 0 10 ,结果是一个包含11个元素的列表,这表明 stop 下标也在 LTRIM 命令的取值范围之内(闭区间),这和某些语言的区间函数可能不一致,比如Ruby的 Range.new 、 Array#slice 和Python的 range() 函数。
超出范围的下标
超出范围的下标值不会引起错误。
如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大,或者 start > stop LTRIM 返回一个空列表(因为 LTRIM 已经将整个列表清空)。
如果 stop 下标比 end 下标还要大,Redis将 stop 的值设置为 end 。
返回值
命令执行成功时,返回 ok 。
*/
func (this *RedisPool) LTrim(key string, start, stop int) (err error) {
conn := this.GetConnection()
defer conn.Close()
_, err = conn.Do("LTRIM", key, start, stop)
return
}
@@ -0,0 +1,131 @@
package gameServerMgr
import (
"encoding/json"
"fmt"
"strings"
. "Framework/managecenterModel"
"goutil/logUtil"
"goutil/securityUtil"
"goutil/webUtil"
)
// 登陆助手类
type LoginUtil struct{}
// 验证登陆信息
// partnerId:合作商Id
// userId:合作商用户Id
// loginInfo:登陆信息
// isIntranet:是否是内网:true,内网;false:外网
// 返回值:
// 成功与否
// 错误对象
func (this *LoginUtil) CheckLoginInfo(partnerId int32, userId, loginInfo string, isIntranet bool) (success bool, err error) {
// 验证用户合法性
loginItemList := strings.Split(loginInfo, "_")
if len(loginItemList) != 2 {
err = fmt.Errorf("CheckLoginInfo Failed. partnerId:%d, userId:%s, loginInfo:%s", partnerId, userId, loginInfo)
return
}
//将requestUrl地址进行拆分
requestDomainList := strings.Split(loginItemList[1], ";")
//请求的主域名
requestDomain := ""
if isIntranet || len(requestDomainList) == 1 {
requestDomain = requestDomainList[0]
} else {
requestDomain = requestDomainList[1]
}
//构造请求url
requestUrl := fmt.Sprintf("http://%s/API/CheckDynamicLoginKey.ashx", requestDomain)
// 定义请求参数
postDict := make(map[string]string)
postDict["UserId"] = userId
postDict["LoginKey"] = loginItemList[0]
//请求url,请求头
header := webUtil.GetFormHeader()
transport := webUtil.NewTransport()
transport.DisableKeepAlives = true
transport = webUtil.GetTimeoutTransport(transport, 30)
statusCode, returnBytes, err1 := webUtil.PostMapData(requestUrl, postDict, header, transport)
if err1 != nil {
err = fmt.Errorf("CheckLoginInfo Failed. partnerId:%d, userId:%s, loginInfo:%s, err:%s", partnerId, userId, loginInfo, err1)
return
}
if statusCode != 200 {
err = fmt.Errorf("CheckLoginInfo Failed. partnerId:%d, userId:%s, loginInfo:%s, statusCode:%d", partnerId, userId, loginInfo, statusCode)
return
}
// 解析返回值
returnObj := new(ReturnObject)
if err = json.Unmarshal(returnBytes, &returnObj); err != nil {
err = fmt.Errorf("CheckLoginInfo Failed. partnerId:%d, userId:%s, loginInfo:%s, err:%s", partnerId, userId, loginInfo, err)
return
}
// 判断返回状态是否为成功
if returnObj.Code != 0 {
logUtil.ErrorLog(fmt.Sprintf("CheckLoginInfo Failed. partnerId:%d, userId:%s, loginInfo:%s, Code:%d, Message:%s", partnerId, userId, loginInfo, returnObj.Code, returnObj.Message))
return
}
success = true
return
}
// 本地验证登陆信息
// partnerId:合作商Id
// userId:合作商用户Id
// loginInfo:登陆信息
// 返回值:
// 成功与否
// 错误对象
func CheckDynamicTokenLocal(partnerId int32, userId, loginInfo string) (success bool, err error) {
//1001直接返回true
if partnerId == 1001 {
return true, nil
}
//非1001渠道验证
if len(loginInfo) == 0 {
success = false
err = fmt.Errorf("Err:%s", "LoginInfo is null!")
return
}
// 验证用户合法性
loginItemList := strings.Split(loginInfo, "_")
if len(loginItemList) != 2 {
success = false
err = fmt.Errorf("CheckLoginInfo Failed. userId:%s, loginInfo:%s", userId, loginInfo)
return
}
//生成key
localSign := securityUtil.Md5String(userId+GetSysConfig().DynamicLoginKey+loginItemList[1], true)
//判断签名是佛正确
if localSign != loginItemList[0] {
success = false
err = fmt.Errorf("CheckLoginInfo Failed. Sign Check Failed! userId:%s,LocalSign:%s,loginInfo:%s", userId, localSign, loginInfo)
return
}
success = true
return
}
// ------------------类型定义和业务逻辑的分隔符-------------------------
var (
LoginUtilObj = new(LoginUtil)
)
@@ -0,0 +1,112 @@
package webServer
import (
"net/http"
"common/resultStatus"
)
// 处理函数
type HandleFunc func(context *ApiContext) *ResponseObject
// ApiHandler
//
// @description: API处理结构
type ApiHandler struct {
// API完整路径名称
apiFullName string
// 方法定义
handleFun HandleFunc
// 方法参数名称集合
funcParamNames []string
}
// ApiFullName
//
// @description: API完整路径名称
//
// parameter:
//
// @receiver this: this
//
// return:
//
// @string:
func (this *ApiHandler) ApiFullName() string {
return this.apiFullName
}
// HandleFun
//
// @description: 方法定义
//
// parameter:
//
// @receiver this: this
//
// return:
//
// @HandleFunc: 方法
func (this *ApiHandler) HandleFun() HandleFunc {
return this.handleFun
}
// FuncParamNames
//
// @description: 方法参数名称集合
//
// parameter:
//
// @receiver this: this
//
// return:
//
// @[]string: 方法参数名称集合
func (this *ApiHandler) FuncParamNames() []string {
return this.funcParamNames
}
// CheckParam
//
// @description: 检测参数数量
//
// parameter:
//
// @receiver this: this
// @r:
//
// return:
//
// @resultStatus.ResultStatus: 状态码数据
func (this *ApiHandler) CheckParam(r *http.Request) resultStatus.ResultStatus {
for _, name := range this.funcParamNames {
if r.Form[name] == nil || len(r.Form[name]) == 0 {
return resultStatus.APIParamError
}
}
return resultStatus.Success
}
// newApiHandler
//
// @description: 创建新的请求方法对象
//
// parameter:
//
// @_apiFullName: API完整路径名称
// @_funcDefinition: 方法定义
// @_funcParamNames: 方法参数名称集合
//
// return:
//
// @*ApiHandler: 请求方法对象
func newApiHandler(_apiFullName string, _funcDefinition HandleFunc, _funcParamNames ...string) *ApiHandler {
return &ApiHandler{
apiFullName: _apiFullName,
handleFun: _funcDefinition,
funcParamNames: _funcParamNames,
}
}
@@ -0,0 +1,17 @@
module Framework
go 1.22.2
replace goutil => ../goutil
require (
github.com/Shopify/sarama v1.29.1
github.com/go-sql-driver/mysql v1.5.0
github.com/gorilla/websocket v1.4.2
github.com/jinzhu/gorm v1.9.12
github.com/rabbitmq/amqp091-go v1.8.1
github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.230
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vms v1.0.230
goutil v0.0.0-00010101000000-000000000000
)
@@ -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>
@@ -0,0 +1,130 @@
package notify_util
import (
"fmt"
"Framework/goroutineMgr"
"goutil/logUtil"
"goutil/syncUtil"
)
const (
deathLockTime = 0
)
// notifyCenter
// @description: 玩家信息务逻辑类
type notifyCenter struct {
// key:初始化成功的标志名称 val:占位符
registerMap map[string]func()
mutex *syncUtil.RWLocker
}
// newNotifyCenter
// @description: 构造对象
// parameter:
// return:
// @*notifyCenter:
func newNotifyCenter() *notifyCenter {
return &notifyCenter{
registerMap: make(map[string]func()),
mutex: syncUtil.NewRWLocker(),
}
}
// register
// @description: 注册需要被通知的对象
// parameter:
// @receiver nc:
// @chanName:唯一标识
// @cf:回调方法
// return:
func (nc *notifyCenter) register(chanName string, cf func()) {
if isOk, prevStack, currStack := nc.mutex.Lock(deathLockTime); isOk == false {
//记日志
errMsg := fmt.Sprintf("Lock timeout! \n上一个堆栈:\n%s \n当前堆栈:\n%s", prevStack, currStack)
panic(errMsg)
}
defer nc.mutex.Unlock()
if _, exists := nc.registerMap[chanName]; exists {
panic(fmt.Errorf("registerMap.Register-%s已经存在,请检查", chanName))
}
nc.registerMap[chanName] = cf
}
// @description: 取消启动成功通知注册
// parameter:
// @receiver nc:
// @name:唯一标识
// return:
func (nc *notifyCenter) unregister(name string) {
if isOk, prevStack, currStack := nc.mutex.Lock(deathLockTime); isOk == false {
//记日志
errMsg := fmt.Sprintf("Lock timeout! \n上一个堆栈:\n%s \n当前堆栈:\n%s", prevStack, currStack)
panic(errMsg)
}
defer nc.mutex.Unlock()
delete(nc.registerMap, name)
}
// notify
// @description: 通知所有已注册的对象
// parameter:
// @receiver nc:
// return:
func (nc *notifyCenter) notify() {
// 处理goroutine数量
goroutineName := "notifyCenter.Notify"
goroutineMgr.MonitorZero(goroutineName)
defer goroutineMgr.ReleaseMonitor(goroutineName)
if isOk, prevStack, currStack := nc.mutex.RLock(deathLockTime); isOk == false {
//记日志
errMsg := fmt.Sprintf("Lock timeout! \n上一个堆栈:\n%s \n当前堆栈:\n%s", prevStack, currStack)
panic(errMsg)
}
defer nc.mutex.RUnlock()
for name, cf := range nc.registerMap {
cf()
msg := fmt.Sprintf("通知:%s初始化成功", name)
logUtil.DebugLog(msg)
}
}
// notify2
// @description: 通知所有已注册的对象,该方法会在捕获第一个err的时候停止后续的通知。多用于系统启动的判定
// parameter:
// @receiver nc:
// return:
// @error:
func (nc *notifyCenter) notify2() (err error) {
// 处理goroutine数量
goroutineName := "notifyCenter.Notify"
goroutineMgr.MonitorZero(goroutineName)
defer goroutineMgr.ReleaseMonitor(goroutineName)
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("notify2 err:%s", r)
logUtil.ErrorLog(err.Error())
}
}()
if isOk, prevStack, currStack := nc.mutex.RLock(deathLockTime); isOk == false {
//记日志
errMsg := fmt.Sprintf("Lock timeout! \n上一个堆栈:\n%s \n当前堆栈:\n%s", prevStack, currStack)
panic(errMsg)
}
defer nc.mutex.RUnlock()
for name, cf := range nc.registerMap {
cf()
msg := fmt.Sprintf("通知:%s初始化成功", name)
logUtil.DebugLog(msg)
}
return
}
@@ -0,0 +1,57 @@
package timeUtil
import (
"testing"
"time"
)
func TestConverToStandardFormat(t *testing.T) {
str := "2018-10-10T10:10:10"
expected := time.Date(2018, 10, 10, 10, 10, 10, 0, time.Local)
got, err := ConverToStandardFormat(str)
if err != nil {
t.Errorf("发生错误,错误信息为:%s", err)
}
if got != expected {
t.Errorf("转换不正确,期待:%s, 实际:%s", expected, got)
}
}
func TestConvertToInt(t *testing.T) {
date := time.Date(2018, 10, 10, 10, 10, 10, 0, time.Local)
finalInt := ConvertToInt(date)
expecteInt := 20181010
if finalInt != expecteInt {
t.Errorf("转换不正确,期待:%d, 实际:%d", expecteInt, finalInt)
}
}
func TestSubDay(t *testing.T) {
time1 := time.Now().AddDate(0, 0, 5)
time2 := time.Now()
expected := 5
got := SubDay(time1, time2)
if got != expected {
t.Errorf("Expected %d, but now got %d.", expected, got)
}
}
func TestParseTimeString(t *testing.T) {
val := "12:13:14"
expectedHour := 12
expectedMinute := 13
expectedSecond := 14
err, hour, miniute, second := ParseTimeString(val)
if err != nil {
t.Error(err)
}
if expectedHour != hour || expectedMinute != miniute || expectedSecond != second {
t.Fail()
}
}
@@ -0,0 +1,6 @@
package internal
import (
_ "logincenter/internal/game"
_ "logincenter/internal/user"
)

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