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
.idea/goProject.iml Normal file
View File

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

8
.idea/modules.xml Normal file
View File

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

49
.idea/workspace.xml Normal file
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
.svn/entries Normal file
View File

@ -0,0 +1 @@
12

1
.svn/format Normal file
View File

@ -0,0 +1 @@
12

View File

@ -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,
}
}

View File

@ -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.

View File

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

View File

@ -0,0 +1,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
}

View File

@ -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时间不对等")
}
}

View File

@ -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
}

View File

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

View File

@ -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()
}

View File

@ -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读取的值不正确")
}
}

View File

@ -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)
}
}

View File

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

View File

@ -0,0 +1,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)
}

View File

@ -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
}

View File

@ -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
)

View File

@ -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)
}

View File

@ -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"
}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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的方式来报告故障。
*/

View File

@ -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"
)

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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,
}
}

View File

@ -0,0 +1,4 @@
/*
提供时间相关的一些助手方法
*/
package timeUtil

View File

@ -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)
}
}

View File

@ -0,0 +1,7 @@
package mqMgr
// 消息队列类型:消息队列、消息主题
const (
MQ_TYPE_QUEUE = "queue"
MQ_TYPE_TOPIC = "topic"
)

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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,
}
}

View File

@ -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()
}

View File

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

View File

@ -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
}

View File

@ -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),
}
}

View File

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

View File

@ -0,0 +1,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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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
}

View File

@ -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)
}
}

View File

@ -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 浏览器限制为 2KFirefox 限制为 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,
}
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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>

View File

@ -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
}

View File

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

View File

@ -0,0 +1,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----------------------------

View File

@ -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()
}

View File

@ -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>

View File

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

View File

@ -0,0 +1,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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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,
}
}

View File

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

View File

@ -0,0 +1,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
}

View File

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

View File

@ -0,0 +1,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()
}

View File

@ -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
)

View File

@ -0,0 +1,6 @@
package verifyMgr
/*
此包用于对指定url进行访问验证以便于在程序启动时可以提前知道目标地址的可访问性
而不用等到实际需要时再验证;从而造成既定的影响。
*/

View File

@ -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")
}

View File

@ -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
}

View File

@ -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"
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

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

View File

@ -0,0 +1,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()
}

View File

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

View File

@ -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

View File

@ -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,
}
}

View File

@ -0,0 +1,16 @@
package mqMgr
// 消息队列配置对象
type QueueConfig struct {
// 地域
Region string
// 队列名称
QueueName string
// API密钥Id
SecretId string
// API密钥key
SecretKey string
}

View File

@ -0,0 +1,16 @@
package goroutineMgr
/*
goroutine的管理包提供了对goroutine的监控机制
对外提供的方法为:
// 监控指定的goroutine
Monitor(goroutineName string)
// 只添加数量,不监控
MonitorZero(goroutineName string)
// 释放监控
ReleaseMonitor(goroutineName string)
*/

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
)

View File

@ -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,
}
}

View File

@ -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
)

View File

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

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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