commit 1b77f6282051f80dd91987acab252ec7c28d42a0 Author: 皮蛋13361098506 <718964169@qq.com> Date: Mon Jan 6 16:01:02 2025 +0800 初始化项目 diff --git a/trunk/.idea/workspace.xml b/trunk/.idea/workspace.xml new file mode 100644 index 0000000..b43c366 --- /dev/null +++ b/trunk/.idea/workspace.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + { + "associatedIndex": 3 +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + file://$USER_HOME$/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.4.windows-amd64/src/context/context.go + 68 + + + + + \ No newline at end of file diff --git a/trunk/center/.idea/.gitignore b/trunk/center/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/trunk/center/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/trunk/center/.idea/center.iml b/trunk/center/.idea/center.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/trunk/center/.idea/center.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/trunk/center/.idea/modules.xml b/trunk/center/.idea/modules.xml new file mode 100644 index 0000000..12a1f90 --- /dev/null +++ b/trunk/center/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/trunk/center/admincenter/Dockerfile b/trunk/center/admincenter/Dockerfile new file mode 100644 index 0000000..99cbfbf --- /dev/null +++ b/trunk/center/admincenter/Dockerfile @@ -0,0 +1,40 @@ +# 使用官方的 Go 镜像作为构建环境 +FROM golang:1.22.10-alpine AS builder + +# 设置工作目录 +WORKDIR /app + +# 设置 Go 代理 +ENV GOPROXY=https://goproxy.cn,direct + +# 禁用 CGO +ENV CGO_ENABLED=0 + +# 复制所有源代码文件 +COPY . . + +WORKDIR /app/admincenter + +# 构建应用程序,并确认生成的文件 +RUN go build -o adminserver -ldflags="-s -w" + +# 使用官方的 Alpine 镜像作为运行环境 +FROM alpine:latest + +# 设置作者标签 +LABEL authors="tp" + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的可执行文件 +COPY --from=builder /app/admincenter/adminserver . + +# 复制配置文件 +COPY --from=builder /app/admincenter/config.yaml . + +# 暴露端口(假设 adminserver 监听 10051 端口) +EXPOSE 10051 + +# 设置容器启动时运行 adminserver +ENTRYPOINT ["./adminserver"] diff --git a/trunk/center/admincenter/LOG/2025/1/2025-01-02-13.debug.txt b/trunk/center/admincenter/LOG/2025/1/2025-01-02-13.debug.txt new file mode 100644 index 0000000..e4c5b2f --- /dev/null +++ b/trunk/center/admincenter/LOG/2025/1/2025-01-02-13.debug.txt @@ -0,0 +1,6 @@ +2025-01-02 13:41:26----> +开始加载DbConfig +------------------------------------------------------ +2025-01-02 13:41:26----> +开始加载LogMgr +------------------------------------------------------ diff --git a/trunk/center/admincenter/LOG/2025/1/2025-01-02-13.warn.txt b/trunk/center/admincenter/LOG/2025/1/2025-01-02-13.warn.txt new file mode 100644 index 0000000..0f397cc --- /dev/null +++ b/trunk/center/admincenter/LOG/2025/1/2025-01-02-13.warn.txt @@ -0,0 +1,18 @@ +2025-01-02 13:41:26----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-02 13:41:26----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-02 13:41:26----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-02 13:41:26----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-02 13:41:26----> +ping->redis:192.168.50.110:6379成功,信息为:PONG +------------------------------------------------------ +2025-01-02 13:41:26----> +Web服务器开始监听:192.168.50.85:10051 +------------------------------------------------------ diff --git a/trunk/center/admincenter/buildLiunx.sh b/trunk/center/admincenter/buildLiunx.sh new file mode 100644 index 0000000..889c6e6 --- /dev/null +++ b/trunk/center/admincenter/buildLiunx.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# 设置 Go 环境变量,确保使用 Linux 架构 +export GOOS=linux +export GOARCH=amd64 + +echo "开始编译..." + +# 编译 Go 代码 +go build -o adminServer + +# 检查编译是否成功 +if [ $? -eq 0 ]; then + echo "编译成功!" +else + echo "编译失败!" +fi + +# 等待用户输入任意键 +read -p "编译完成,按任意键继续..." +exit 1 diff --git a/trunk/center/admincenter/config.yaml b/trunk/center/admincenter/config.yaml new file mode 100644 index 0000000..100c1b0 --- /dev/null +++ b/trunk/center/admincenter/config.yaml @@ -0,0 +1,54 @@ +# 配置根节点 +root: + # 是否是调试模式 + debug: true + + # Web服务监听地址和端口 + web_server_address: "192.168.50.85:10051" + + # 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 \ No newline at end of file diff --git a/trunk/center/admincenter/docker-compose.yml b/trunk/center/admincenter/docker-compose.yml new file mode 100644 index 0000000..2242ca7 --- /dev/null +++ b/trunk/center/admincenter/docker-compose.yml @@ -0,0 +1,37 @@ +version: '3' +services: + mysql: + image: mysql:8.0 + container_name: mysql-admin + environment: + MYSQL_ROOT_PASSWORD: 123456 + MYSQL_DATABASE: admin,user + volumes: + - /my/own/datadir:/var/lib/mysql + ports: + - "3306:3306" + redis: + image: redis:7.0 + container_name: redis-admin + ports: + - "6379:6379" + volumes: + - redis_data:/data + restart: always + command: ["redis-server", "--appendonly", "yes"] + + admin-center: + build: + context: . + dockerfile: Dockerfile + container_name: admin-center + ports: + - "10051:10051" + volumes: + - ./app:/app + depends_on: + - mysql + - redis + +volumes: + redis_data: \ No newline at end of file diff --git a/trunk/center/admincenter/dockerRun.sh b/trunk/center/admincenter/dockerRun.sh new file mode 100644 index 0000000..c5251f3 --- /dev/null +++ b/trunk/center/admincenter/dockerRun.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# 导航到 Dockerfile 所在目录 +#cd D:\workspace\e2023\goProject\trunk\center\admincenter + +# 构建 Docker 镜像 +echo "开始构建 Docker 镜像..." +docker build -t adminserver-image . + +# 检查构建是否成功 +if [ $? -ne 0 ]; then + echo "构建失败!" + exit 1 +fi + +echo "镜像构建成功!" + +# 运行 Docker 容器 +echo "开始运行 Docker 容器..." +docker run -d -p 10051:10051 --name adminserver-container adminserver-image + +# 检查容器是否成功运行 +if [ $? -ne 0 ]; then + echo "容器启动失败!" + exit 1 +fi + +echo "容器启动成功!" diff --git a/trunk/center/admincenter/go.mod b/trunk/center/admincenter/go.mod new file mode 100644 index 0000000..690030a --- /dev/null +++ b/trunk/center/admincenter/go.mod @@ -0,0 +1,38 @@ +module admincenter + +go 1.22.10 + +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 +) diff --git a/trunk/center/admincenter/go.sum b/trunk/center/admincenter/go.sum new file mode 100644 index 0000000..b77f21d --- /dev/null +++ b/trunk/center/admincenter/go.sum @@ -0,0 +1,89 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 h1:OoL469zqSNrTLSz5zeVF/I6VOO7fiw2bzSzQe4J557c= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4/go.mod h1:xe9a/L2aeOgFKKgrO3ibQTnMdpAeL0GC+5/HpGScSa4= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= +github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= +github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/trunk/center/admincenter/internal/admin.go b/trunk/center/admincenter/internal/admin.go new file mode 100644 index 0000000..5def151 --- /dev/null +++ b/trunk/center/admincenter/internal/admin.go @@ -0,0 +1,5 @@ +package internal + +import ( + _ "admincenter/internal/admin" +) diff --git a/trunk/center/admincenter/internal/admin/admin.go b/trunk/center/admincenter/internal/admin/admin.go new file mode 100644 index 0000000..94cef04 --- /dev/null +++ b/trunk/center/admincenter/internal/admin/admin.go @@ -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" +} diff --git a/trunk/center/admincenter/internal/admin/api.go b/trunk/center/admincenter/internal/admin/api.go new file mode 100644 index 0000000..9f3d91a --- /dev/null +++ b/trunk/center/admincenter/internal/admin/api.go @@ -0,0 +1,198 @@ +package admin + +import ( + "common/remark" + "common/resultStatus" + "common/webServer" + "goutil/intUtil" + "goutil/securityUtil" + "strconv" + "time" +) + +func init() { + + //注册接口 + webServer.RegisterFunction(new(AdminApi)) +} + +func init() { + moduleName := "AdminApi" + desc := "管理员接口" + author := "tangping" + mendor := "" + date := "2024-12-25" + remark.RegisterModuleRemark(moduleName, desc, author, mendor, date) +} + +// AdminApi 管理员接口 +type AdminApi struct { +} + +// ---------------------------------------- 接口 -------------------------------------------------- +func init() { + moduleName := "AdminApi" + methodName := "Add" + methodDesc := "添加管理员用户" + methodAuthor := "tangping" + methodMendor := "" + methodDate := "2024-12-25 15:55:00" + methodInParam := []string{"string 账号,string:管理员名称,string:管理员密码,string:管理员性别,string:管理员生日,int64:管理员手机号,string:管理员邮箱,string:管理员微信群,string:管理员备注"} + methodOutParam := ` + { + "Code '类型:int'": "响应结果的状态值", + "Message '类型:string'": "响应结果的状态值所对应的描述信息", + "Data '类型:interface{}'": "响应结果的数据" + { + "id '类型:int'": "管理员id", + } + }` + remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam) +} +func (a *AdminApi) Add(account string, name string, password string, sex int32, birthday string, phone int64, email string, wechatGroup string, describe string) (responseObj *webServer.ResponseObject) { + responseObj = webServer.GetInitResponseObj() + + //校验参数 + if account == "" || name == "" || password == "" || sex == 0 || birthday == "" || phone == 0 || email == "" || wechatGroup == "" { + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + + //密码md5加密 + password = securityUtil.Md5String(password, true) + + //处理数据 + adminModel := &Admin{ + Account: account, + Name: name, + Password: password, + Sex: sex, + Birthday: birthday, + Phone: phone, + Email: email, + WechatGroup: wechatGroup, + Describe: describe, + } + + //添加到数据库 + AddAdmin(adminModel) + + resultMap := make(map[string]any) + resultMap["id"] = adminModel.ID + responseObj.SetData(resultMap) + return +} + +func init() { + moduleName := "AdminApi" + methodName := "Get" + methodDesc := "获取管理员用户" + methodAuthor := "tangping" + methodMendor := "" + methodDate := "2024-12-25 15:55:00" + methodInParam := []string{"int64:管理员id"} + methodOutParam := ` + { + "Code '类型:int'": "响应结果的状态值", + "Message '类型:string'": "响应结果的状态值所对应的描述信息", + "Data '类型:interface{}'": "响应结果的数据" + { + "account '类型:string'": "管理员账号", + "name '类型:string'": "管理员名称", + "sex '类型:int32'": "管理员性别", + "birthday '类型:string'": "管理员生日", + "phone '类型:int64'": "管理员手机号", + "email '类型:string'": "管理员邮箱", + "wechatGroup '类型:string'": "管理员微信群", + "describe '类型:string'": "管理员备注", + } + }` + + remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam) +} +func (a *AdminApi) Get(adminId int64) (responseObj *webServer.ResponseObject) { + + responseObj = webServer.GetInitResponseObj() + + //验证参数 + if adminId == 0 { + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + + adminData, err := GetAdminByID(adminId) + if err != nil { + responseObj.SetResultStatus(resultStatus.DataError) + return + } + + //adminData映射成map + resultMap := make(map[string]any) + resultMap["account"] = adminData.Account + resultMap["name"] = adminData.Name + resultMap["sex"] = adminData.Sex + resultMap["birthday"] = adminData.Birthday + resultMap["phone"] = adminData.Phone + resultMap["email"] = adminData.Email + resultMap["wechatGroup"] = adminData.WechatGroup + resultMap["describe"] = adminData.Describe + + responseObj.SetData(resultMap) + return +} + +// 管理员登录 +func init() { + moduleName := "AdminApi" + methodName := "Login" + methodDesc := "管理员登录" + methodAuthor := "tangping" + methodMendor := "" + methodDate := "2024-12-26 15:55:00" + methodInParam := []string{"string:账号,string:密码"} + methodOutParam := ` + { + "Code '类型:int'": "响应结果的状态值", + "Message '类型:string'": "响应结果的状态值所对应的描述信息", + "Data '类型:interface{}'": "响应结果的数据" + { + "Token '类型:string'": "登录令牌", + } + }` + + remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam) +} + +func (a *AdminApi) Login(account string, password string) (responseObj *webServer.ResponseObject) { + + responseObj = webServer.GetInitResponseObj() + + //验证参数 + if account == "" || password == "" { + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + + adminData, err := Login(account, securityUtil.Md5String(password, true)) + if err != nil { + responseObj.SetResultStatus(resultStatus.DataError) + return + } + + //生成一个随机token + token, err := intUtil.ShuffleIntDigits(time.Now().UnixNano()) + if err != nil { + responseObj.SetResultStatus(resultStatus.DataError) + return + } + + //添加登录用户 + webServer.AddLoginUserToken(token, &webServer.TokenInfo{Id: adminData.ID, Account: account}) + + //adminData映射成map + resultMap := make(map[string]any) + resultMap["Token"] = strconv.FormatInt(token, 10) + + responseObj.SetData(resultMap) + return +} diff --git a/trunk/center/admincenter/internal/admin/logic.go b/trunk/center/admincenter/internal/admin/logic.go new file mode 100644 index 0000000..b0552a3 --- /dev/null +++ b/trunk/center/admincenter/internal/admin/logic.go @@ -0,0 +1,43 @@ +package admin + +import ( + "common/connection" + "goutil/logUtilPlus" +) + +// AddAdmin 添加管理员 +// AddAdmin 添加新的管理员到数据库中。 +// 参数 admin: 包含管理员信息的对象。 +// 返回值: 插入操作影响的行数和可能发生的错误。 +func AddAdmin(admin *Admin) (int64, error) { + + //处理一些验证 + + // 写入到数据库 + result := connection.GetAdminDB().Create(&admin) // 通过数据的指针来创建 + + if result.Error != nil { + logUtilPlus.ErrorLog("添加管理员失败 错误信息:", result.Error.Error()) + } + return admin.ID, nil +} + +// GetAdminByID 根据管理员ID获取管理员信息 +func GetAdminByID(adminID int64) (*Admin, error) { + var admin Admin + result := connection.GetAdminDB().First(&admin, adminID) + if result.Error != nil { + return nil, result.Error + } + return &admin, nil +} + +// 管理员登录 +func Login(account string, password string) (*Admin, error) { + var admin Admin + result := connection.GetAdminDB().Where("account = ? AND password = ?", account, password).First(&admin) + if result.Error != nil { + return nil, result.Error + } + return &admin, nil +} diff --git a/trunk/center/admincenter/main.go b/trunk/center/admincenter/main.go new file mode 100644 index 0000000..646a1a3 --- /dev/null +++ b/trunk/center/admincenter/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "common/connection" + "sync" + + _ "admincenter/internal/admin" + _ "common/resultStatus" + "common/webServer" +) + +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() +} diff --git a/trunk/center/admincenter/run.sh b/trunk/center/admincenter/run.sh new file mode 100644 index 0000000..65bb18f --- /dev/null +++ b/trunk/center/admincenter/run.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# 赋予可执行权限 +chmod +x adminServer + +echo "启动中..." + +# 接受命令行传入一个参数 +case "$1" in + d) + # 使用 nohup 将进程放到后台运行,并将输出重定向到 nohup.out 文件 + nohup ./adminServer > nohup.out 2>&1 & + echo "启动完成" + ;; + *) + ./adminServer + ;; +esac + +echo "启动完成" diff --git a/trunk/center/admincenter/stop.sh b/trunk/center/admincenter/stop.sh new file mode 100644 index 0000000..73b2ef9 --- /dev/null +++ b/trunk/center/admincenter/stop.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# 查找 adminServer 的 PID +PID=$(pgrep adminServer) + +if [ -z "$PID" ]; then + echo "adminServer 进程未找到" +else + echo "停止中... (PID: $PID)" + kill $PID + if [ $? -eq 0 ]; then + echo "adminServer 进程已终止 (PID: $PID)" + else + echo "无法终止 adminServer 进程 (PID: $PID)" + fi +fi diff --git a/trunk/center/common/cache/cache.go b/trunk/center/common/cache/cache.go new file mode 100644 index 0000000..13d3139 --- /dev/null +++ b/trunk/center/common/cache/cache.go @@ -0,0 +1,52 @@ +package cache + +import ( + "sync" +) + +// Cache 结构体代表缓存 +type Cache struct { + data map[string]any + rwmu sync.RWMutex +} + +// NewCache 创建并返回一个新的缓存实例 +func NewCache() *Cache { + return &Cache{ + data: make(map[string]any), + } +} + +// Set 方法用于向缓存中设置键值对,写操作会加写锁保证并发安全 +func (c *Cache) set(key string, value any) { + c.rwmu.Lock() + defer c.rwmu.Unlock() + c.data[key] = value +} + +// Get 方法用于从缓存中根据键获取对应的值,读操作会加读锁允许并发读 +func (c *Cache) get(key string) any { + c.rwmu.RLock() + defer c.rwmu.RUnlock() + value := c.data[key] + return value +} + +// Delete 方法用于从缓存中删除指定的键值对,写操作会加写锁保证并发安全 +func (c *Cache) Delete(key string) { + c.rwmu.Lock() + defer c.rwmu.Unlock() + delete(c.data, key) +} + +// GetData 方法用于从缓存中根据键获取对应的值,读操作会加读锁允许并发读 +func GetData[V any](c *Cache, key string) (value V, ok bool) { + v := c.get(key) + value, ok = v.(V) + return +} + +// SetData 方法用于向缓存中设置键值对,写操作会加写锁保证并发安全 +func SetData(c *Cache, key string, value any) { + c.set(key, value) +} diff --git a/trunk/center/common/cache/cacheslice.go b/trunk/center/common/cache/cacheslice.go new file mode 100644 index 0000000..47f64b1 --- /dev/null +++ b/trunk/center/common/cache/cacheslice.go @@ -0,0 +1,48 @@ +package cache + +import ( + "sync" +) + +// SliceCache结构体代表基于切片的缓存,内部使用切片存储元素,并通过读写锁保证并发安全 +type SliceCache struct { + data []interface{} + rwmu sync.RWMutex +} + +// NewSliceCache创建并返回一个新的基于切片的缓存实例 +func NewSliceCache() *SliceCache { + return &SliceCache{ + data: make([]interface{}, 0), + } +} + +// Add方法用于向切片缓存中添加元素,写操作加写锁保证并发安全 +func (sc *SliceCache) Add(element interface{}) { + sc.rwmu.Lock() + sc.data = append(sc.data, element) + sc.rwmu.Unlock() +} + +// GetAll方法用于获取切片缓存中的所有元素,读操作加读锁允许并发读 +func (sc *SliceCache) GetAll() []interface{} { + sc.rwmu.RLock() + result := make([]interface{}, len(sc.data)) + copy(result, sc.data) + sc.rwmu.RUnlock() + return result +} + +// Remove方法用于从切片缓存中删除指定元素,写操作加写锁保证并发安全 +func (sc *SliceCache) Remove(element interface{}) bool { + sc.rwmu.Lock() + for i, e := range sc.data { + if e == element { + sc.data = append(sc.data[:i], sc.data[i+1:]...) + sc.rwmu.Unlock() + return true + } + } + sc.rwmu.Unlock() + return false +} diff --git a/trunk/center/common/configs.zip b/trunk/center/common/configs.zip new file mode 100644 index 0000000..910b8dd Binary files /dev/null and b/trunk/center/common/configs.zip differ diff --git a/trunk/center/common/configsYaml/baseConfig.go b/trunk/center/common/configsYaml/baseConfig.go new file mode 100644 index 0000000..5406694 --- /dev/null +++ b/trunk/center/common/configsYaml/baseConfig.go @@ -0,0 +1,97 @@ +/* +配置包,提供项目的配置信息 +*/ +package configYaml + +import ( + "goutil/debugUtil" +) + +var ( + // 是否是DEBUG模式 + DEBUG bool + + // web服务监听端口 + WebServerAddress string + + // gRpc监听地址和端口(内网地址即可) + GrpcServerAddress string + + // 战斗服务器地址 + FightServerAddress string + + // 战区Id + BigGroupId int + + // 页签Id + TagId int + + // 是否是中心服务器地址 + PlayerServerCenter bool + + // 数据中心服务器地址 + DataCenterAddress string + + // es urls + EsUrls string + + BaseDay int +) + +// initBaseConfig +// @description: 初始化基础配置数据 +// parameter: +// @configObj: 基础配置数据 +// return: +// @error: 错误信息 +func initBaseConfig() { + + root := ConfigYaml.Root + + // 为DEBUG模式赋值 + DEBUG = root.Debug + + // 设置debugUtil的状态 + debugUtil.SetDebug(DEBUG) + + // 解析rpcConfig配置 + WebServerAddress = root.WebServerAddress + + // gRpc监听地址和端口(内网地址即可) + GrpcServerAddress = root.GrpcServerAddress + + // 解析BigGroupId配置 + BigGroupId = root.BigGroupId + + // 解析FightServerAddress配置 + FightServerAddress = root.FightServerAddress + + EsUrls = root.EsUrls + + BaseDay = root.BaseDay +} + +// GetDebug +// @description: 获取是否是开发环境 +// parameter: +// return: +// @bool: +func GetDebug() bool { + return ConfigYaml.Root.Debug +} + +// GetWebServerAddress 返回Web服务器的地址。 +// 该函数通过访问ConfigYaml中的配置信息,获取并返回Web服务器的地址。 +// 主要用于需要与Web服务器建立连接的场景。 +func GetWebServerAddress() string { + return ConfigYaml.Root.WebServerAddress +} + +// GetEsUrls 返回配置文件中 Elasticsearch 的 URL 地址。 +// +// 该函数通过访问全局变量 ConfigYaml,获取其 Root 字段下的 EsUrls 属性, +// 并将其作为字符串返回。这提供了一种简单的方法来获取 Elasticsearch 数据库的连接信息, +// 而无需直接访问配置文件。 +func GetEsUrls() string { + return ConfigYaml.Root.EsUrls +} diff --git a/trunk/center/common/configsYaml/configYaml.go b/trunk/center/common/configsYaml/configYaml.go new file mode 100644 index 0000000..f061df7 --- /dev/null +++ b/trunk/center/common/configsYaml/configYaml.go @@ -0,0 +1,154 @@ +package configYaml + +var ConfigYaml = Config{} + +// Config 定义与 YAML 文件结构匹配的结构体 +type Config struct { + Root Root +} + +// Root 是整个配置文件的根结构体 +type Root struct { + // 是否是调试模式 + Debug bool `yaml:"debug"` + + // 是否是 CrossServer 中心节点(标记为 true 时,才会进行排行榜处理) + CrossServerCenter bool `yaml:"cross_server_center"` + + // ManagerCenter 配置 + ManagerCenterConfig ManagerCenterConf `yaml:"manager_center_config"` + + // logmgr 配置 + LogMgr LogMgr `yaml:"log_mgr"` + + // Web 服务监听地址和端口 + WebServerAddress string `yaml:"web_server_address"` + + // gRPC 监听地址和端口 (内网地址即可) + GrpcServerAddress string `yaml:"grpc_server_address"` + + // 战斗服务器地址 + FightServerAddress string `yaml:"fight_server_address"` + + // 数据中心地址 + DataCenterAddress string `yaml:"data_center_address"` + + // OrderId 和 BigGroupId(仅用于非 PlayerServer) + OrderId int `yaml:"order_id"` + BigGroupId int `yaml:"big_group_id"` + + // 重启时载入 Redis 数据日期范围 + BaseDay int `yaml:"base_day"` + + // ES 地址 + EsUrls string `yaml:"es_urls"` + + // 数据库配置 + DbConfig DBConfig `yaml:"db_config"` + + // 监控相关配置 + MonitorConfig MonitorConf `yaml:"monitor_config"` + + // 功能开关配置 + FunctionConfig FunctionConf `yaml:"function_config"` +} + +// ManagerCenterConf 是 ManagerCenter 的配置结构体 +type ManagerCenterConf struct { + // ManagerCenter API 的 URL + ManageCenterAPIUrl string `yaml:"manage_center_api_url"` + + // 服务器组类型 + GroupType string `yaml:"group_type"` + + // 刷新间隔,单位:分钟 + RefreshInterval int `yaml:"refresh_interval"` +} + +// LogMgr 是日志管理器的配置结构体 +type LogMgr struct { + // group boxId + GroupId string `yaml:"group_id"` + + // group secret + GroupSecret string `yaml:"group_secret"` + + // product boxId + ProductId string `yaml:"product_id"` +} + +// DBConfig 包含数据库和 Redis 的配置 +type DBConfig struct { + + //管理员数据库配置 + AdminDB DatabaseConfig `yaml:"admin_db"` + + // 用户数据库配置 + UserDB DatabaseConfig `yaml:"user_db"` + + // 游戏模型数据库配置 + GameModel DatabaseConfig `yaml:"game_model"` + + // 游戏数据库配置 + GameDB DatabaseConfig `yaml:"game_db"` + + // 玩家数据库配置 + PlayerDB DatabaseConfig `yaml:"player_db"` + + // Redis 配置 + RedisConfig RedisConfig `yaml:"redis_config"` +} + +// DatabaseConfig 是数据库连接的配置结构体 +type DatabaseConfig struct { + // 最大处于开启状态的连接数 + MaxOpenConns int `yaml:"max_open_conns"` + + // 最大处于空闲状态的连接数 + MaxIdleConns int `yaml:"max_idle_conns"` + + // 数据库连接字符串 + ConnectionString string `yaml:"connection_string"` +} + +// RedisConfig 是 Redis 连接的配置结构体 +type RedisConfig struct { + // Redis 数据库连接字符串 + ConnectionString string `yaml:"connection_string"` + + // 密码, 如果要设置用户 Id,则密码设置为: "UserId:Password" + Password string `yaml:"password"` + + // 数据库序号 + Database int `yaml:"database"` + + // 最大活跃连接数 + MaxActive int `yaml:"max_active"` + + // 最大空闲的连接数 + MaxIdle int `yaml:"max_idle"` + + // 连接空闲超时时间,单位:秒 + IdleTimeout int `yaml:"idle_timeout"` + + // 连接超时时间, 单位:秒 + DialConnectTimeout int `yaml:"dial_connect_timeout"` +} + +// MonitorConf 是监控配置的结构体 +type MonitorConf struct { + // 监控使用的服务器 IP (本机的外网地址) + ServerIP string `yaml:"server_ip"` + + // 监控使用的服务器名称 + ServerName string `yaml:"server_name"` + + // 监控的时间间隔(单位:分钟) + Interval int `yaml:"interval"` +} + +// FunctionConf 是功能开关配置的结构体 +type FunctionConf struct { + // 游戏代码: qyc, xh, dzz + GameCode string `yaml:"game_code"` +} diff --git a/trunk/center/common/configsYaml/dbConfig.go b/trunk/center/common/configsYaml/dbConfig.go new file mode 100644 index 0000000..d380510 --- /dev/null +++ b/trunk/center/common/configsYaml/dbConfig.go @@ -0,0 +1,188 @@ +package configYaml + +import ( + "goutil/logUtilPlus" + "goutil/mysqlUtil" + "goutil/redisUtil" + "time" +) + +// DbConfig +// +// @description: mysql配置对象 +type DbConfig struct { + + // 管理员数据库链接字符串 + adminConfig *mysqlUtil.DBConfig + + // 用户数据库链接字符串 + userConfig *mysqlUtil.DBConfig + + // 游戏模型数据库链接字符串 + gameModelConfig *mysqlUtil.DBConfig + + // 游戏数据库链接字符串 + gameConfig *mysqlUtil.DBConfig + + // 游戏数据库链接字符串 + playerConfig *mysqlUtil.DBConfig + + // redis配置对象 + redisConfig *redisUtil.RedisConfig +} + +var ( + // 数据库配置对象 + dbConfigObj *DbConfig +) + +// GetAdminConfig +// @description: 获取admin库配置 +// parameter: +// +// @receiver config:config +// +// return: +// +// @*mysqlUtil.DBConfig:admin库配置 +func (config *DbConfig) GetAdminConfig() *mysqlUtil.DBConfig { + return config.adminConfig +} + +// GetUserConfig +// @description: 获取user库配置 +// parameter: +// +// @receiver config:config +// +// return: +// +// @*mysqlUtil.DBConfig:user库配置 +func (config *DbConfig) GetUserConfig() *mysqlUtil.DBConfig { + return config.userConfig +} + +// GetGameModelConfig +// @description: 获取model库配置 +// parameter: +// +// @receiver config:config +// +// return: +// +// @*mysqlUtil.DBConfig:model库配置 +func (config *DbConfig) GetGameModelConfig() *mysqlUtil.DBConfig { + return config.gameModelConfig +} + +// GetGameConfig +// @description: 获取Game库配置 +// parameter: +// +// @receiver config:config +// +// return: +// +// @*mysqlUtil.DBConfig:Game库配置 +func (config *DbConfig) GetGameConfig() *mysqlUtil.DBConfig { + return config.gameConfig +} + +// GetPlayerConfig +// @description: 获取玩家库配置 +// parameter: +// +// @receiver config:config +// +// return: +// +// @*mysqlUtil.DBConfig:玩家库配置 +func (config *DbConfig) GetPlayerConfig() *mysqlUtil.DBConfig { + return config.playerConfig +} + +// GetRedisConfig +// @description: 获取redis配置对象 +// parameter: +// +// @receiver config:config +// +// return: +// +// @*redisUtil.RedisConfig:redis配置 +func (config *DbConfig) GetRedisConfig() *redisUtil.RedisConfig { + return config.redisConfig +} + +// newMysqlConfig +// +// @description: 创建新的Mysql配置对象 +// +// parameter: +// +// @_gameModelConfig: +// @_gameConfig: +// @_playerConfig: +// @_redisConfig: +// +// return: +// +// @*DbConfig: +func newMysqlConfig(_adminConfig *mysqlUtil.DBConfig, _userConfig *mysqlUtil.DBConfig, _gameModelConfig *mysqlUtil.DBConfig, + _gameConfig *mysqlUtil.DBConfig, + _playerConfig *mysqlUtil.DBConfig, + _redisConfig *redisUtil.RedisConfig) *DbConfig { + return &DbConfig{ + adminConfig: _adminConfig, + userConfig: _userConfig, + gameModelConfig: _gameModelConfig, + gameConfig: _gameConfig, + redisConfig: _redisConfig, + playerConfig: _playerConfig, + } +} + +// initDbConfig +// +// @description: 初始化数据库配置 +// +// parameter: +// +// @configObj: 数据库配置 +// +// return: +// +// @error: 错误数据 +func initDbConfig() error { + + logUtilPlus.DebugLog("开始加载DbConfig") + + redisConfig := ConfigYaml.Root.DbConfig.RedisConfig + + //if redisConfig == nil { + // logUtilPlus.DebugLog("redis配置为空") + // return nil + //} + + // 初始化mysql配置对象 + dbConfigObj = newMysqlConfig( + mysqlUtil.NewDBConfig(ConfigYaml.Root.DbConfig.AdminDB.ConnectionString, ConfigYaml.Root.DbConfig.AdminDB.MaxOpenConns, ConfigYaml.Root.DbConfig.AdminDB.MaxIdleConns), + mysqlUtil.NewDBConfig(ConfigYaml.Root.DbConfig.UserDB.ConnectionString, ConfigYaml.Root.DbConfig.UserDB.MaxOpenConns, ConfigYaml.Root.DbConfig.UserDB.MaxIdleConns), + mysqlUtil.NewDBConfig(ConfigYaml.Root.DbConfig.GameModel.ConnectionString, ConfigYaml.Root.DbConfig.GameModel.MaxOpenConns, ConfigYaml.Root.DbConfig.GameModel.MaxIdleConns), + mysqlUtil.NewDBConfig(ConfigYaml.Root.DbConfig.GameDB.ConnectionString, ConfigYaml.Root.DbConfig.GameDB.MaxOpenConns, ConfigYaml.Root.DbConfig.GameDB.MaxIdleConns), + mysqlUtil.NewDBConfig(ConfigYaml.Root.DbConfig.PlayerDB.ConnectionString, ConfigYaml.Root.DbConfig.PlayerDB.MaxOpenConns, ConfigYaml.Root.DbConfig.PlayerDB.MaxIdleConns), + redisUtil.NewRedisConfig2(redisConfig.ConnectionString, redisConfig.Password, redisConfig.Database, redisConfig.MaxActive, redisConfig.MaxIdle, time.Duration(redisConfig.IdleTimeout)*time.Second, time.Duration(redisConfig.DialConnectTimeout)*time.Second)) + return nil +} + +// GetDbConfig +// +// @description: 获取mysql配置 +// +// parameter: +// return: +// +// @*DbConfig: mysql配置对象 +func GetDbConfig() *DbConfig { + return dbConfigObj +} diff --git a/trunk/center/common/configsYaml/functionConfig.go b/trunk/center/common/configsYaml/functionConfig.go new file mode 100644 index 0000000..bf10d85 --- /dev/null +++ b/trunk/center/common/configsYaml/functionConfig.go @@ -0,0 +1,63 @@ +package configYaml + +// FunctionConfig +// +// @description: 功能开关配置 +type FunctionConfig struct { + + // 游戏代码 + GameCode string + + // 仙术副本 清理过期房间信息时间 5 分钟 + MagicCopyTeamOutTime int + + // 请求青瓷的Url + QCSdk_Url string + + // 请求青瓷的GameCode + QCSdk_GameCode string + + // 请求青瓷的GameId + QCSdk_GameId string + + // 请求青瓷的加密signKey + QCSdk_SignKey string +} + +var ( + // 功能开关配置对象 + functionConfigObj *FunctionConfig +) + +// initFunctionConfig +// +// @description: 加载功能开关配置 +// +// parameter: +// +// @configObj: 开关配置 +// +// return: +// +// @error: 错误信息 +func initFunctionConfig() error { + functionConfigObj = &FunctionConfig{} + + functionConfig := ConfigYaml.Root.FunctionConfig + + functionConfigObj.GameCode = functionConfig.GameCode + + return nil +} + +// GetFunctionConfigObj +// +// @description: 获取功能开关配置对象 +// +// parameter: +// return: +// +// @*FunctionConfig: +func GetFunctionConfigObj() *FunctionConfig { + return functionConfigObj +} diff --git a/trunk/center/common/configsYaml/init.go b/trunk/center/common/configsYaml/init.go new file mode 100644 index 0000000..d7b4053 --- /dev/null +++ b/trunk/center/common/configsYaml/init.go @@ -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 +} diff --git a/trunk/center/common/configsYaml/logMgrConfig.go b/trunk/center/common/configsYaml/logMgrConfig.go new file mode 100644 index 0000000..c2ff6fe --- /dev/null +++ b/trunk/center/common/configsYaml/logMgrConfig.go @@ -0,0 +1,62 @@ +package configYaml + +import ( + "goutil/logUtilPlus" +) + +// LogMgrConfig +// +// @description: logMgr配置对象 +type LogMgrConfig struct { + // 组id + GroupId string + + // 组秘钥 + GroupSecret string + + // 项目id + ProductId string +} + +var ( + // logMgr配置对象 + logMgrConfigObj *LogMgrConfig +) + +// initLogMgrConfig +// +// @description: 初始化LogMgr配置 +// +// parameter: +// +// @configObj: +// +// return: +// +// @error: 错误信息 +func initLogMgrConfig() error { + + logUtilPlus.DebugLog("开始加载LogMgr") + + logMgr := ConfigYaml.Root.LogMgr + + logMgrConfigObj = &LogMgrConfig{ + GroupId: logMgr.GroupId, + GroupSecret: logMgr.GroupSecret, + ProductId: logMgr.ProductId, + } + + return nil +} + +// GetLogMgrConfig +// +// @description: 获取游戏模型数据库链接字符串 +// +// parameter: +// return: +// +// @*LogMgrConfig: 游戏模型数据库链接字符串 +func GetLogMgrConfig() *LogMgrConfig { + return logMgrConfigObj +} diff --git a/trunk/center/common/connection/dal.go b/trunk/center/common/connection/dal.go new file mode 100644 index 0000000..0007c44 --- /dev/null +++ b/trunk/center/common/connection/dal.go @@ -0,0 +1,225 @@ +package connection + +import ( + "context" + "fmt" + "framework/sqlAsyncMgr" + goredis "github.com/go-redis/redis/v8" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "goutil/logUtilPlus" + "time" + + // _ "github.com/go-sql-driver/mysql" + config "common/configsYaml" + "goutil/mysqlUtil" + "goutil/redisUtil" +) + +var ( + adminDB *gorm.DB //管理员数据库对象 + userDB *gorm.DB //用户数据库对象 + gameModelDB *gorm.DB // 游戏模型数据库对象 + gameDB *gorm.DB // 游戏数据库 + gamePlayerDB *gorm.DB // 玩家库 + redisDB *goredis.Client // 游戏redis连接客户端 + syncMgr *sqlAsyncMgr.SqlAsyncUtil // 同步管理对象 + syncFileSize = 1024 * 1024 // 同步文件大小 + fileMaxSaveTime = 24 * 7 // 保留7天的sql文件 +) + +var excuteSqlFunc func(*gorm.DB, ITable, string) error +var loghandle func(s string, is ...interface{}) +var defaultType = SyncSql + +type ITable interface { + TableName() string +} + +type SqlExecuteType int32 + +const ( + SyncSql SqlExecuteType = iota + ASyncSql + Name = "BaseDal" +) + +// init +// @description: init +// parameter: +// return: +func init() { + + //管理中心数据库配置 + adminDB = initMysql(config.GetDbConfig().GetAdminConfig()) + userDB = initMysql(config.GetDbConfig().GetUserConfig()) + + // 初始化游戏模型数据库 + gameModelDBConfig := config.GetDbConfig().GetGameModelConfig() + gameModelDB = initMysql(gameModelDBConfig) + + // 初始化crossSever 游戏数据库 + gameDBConfig := config.GetDbConfig().GetGameConfig() + gameDB = initMysql(gameDBConfig) + gamePlayerDB = initMysql(config.GetDbConfig().GetPlayerConfig()) + + // 初始化redis-client + excuteSqlFunc = func(gdb *gorm.DB, table ITable, sql string) error { + db := gdb.Exec(sql) + if db.Error != nil { + loghandle("sql执行错误:%s;错误信息:%v", sql, db.Error.Error()) + return db.Error + } + + return nil + } + + redisDB = initRedis(config.GetDbConfig().GetRedisConfig()) +} + +// 初始化Mysql +// initMysql +// @description: 初始化Mysql +// parameter: +// @dbConfig: dbConfig +// return: +// @*gorm.DB: DB +func initMysql(dbConfig *mysqlUtil.DBConfig) *gorm.DB { + + //不存在配置 + if dbConfig == nil || dbConfig.ConnectionString == "" { + return nil + } + logUtilPlus.WarnLog("开始连接mysql:%s", dbConfig.ConnectionString) + dbObj, err := gorm.Open(mysql.Open(dbConfig.ConnectionString), &gorm.Config{}) + if err != nil { + panic(fmt.Errorf("初始化数据库:%s失败,错误信息为:%s", dbConfig.ConnectionString, err)) + } + logUtilPlus.WarnLog("连接mysql:%s成功", dbConfig.ConnectionString) + + return dbObj +} + +// initRedis +// @description: 初始化redis客户端 +// parameter: +// @redisConfig: redisConfig +// return: +// @*goredis.Client: Client +func initRedis(redisConfig *redisUtil.RedisConfig) *goredis.Client { + + client := goredis.NewClient(&goredis.Options{ + Addr: redisConfig.ConnectionString, + Password: redisConfig.Password, + DB: redisConfig.Database, + IdleTimeout: redisConfig.IdleTimeout, + DialTimeout: redisConfig.DialConnectTimeout, + }) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + ping, err := client.Ping(ctx).Result() + if err != nil { + panic(fmt.Errorf("ping->redis:%s失败,DB:%s,错误信息为:%s", redisConfig.ConnectionString, redisConfig.Database, err)) + } + + logUtilPlus.WarnLog("ping->redis:%s成功,信息为:%s", redisConfig.ConnectionString, ping) + return client +} + +// GetAdminDB +// @description: 获取admin库db实例 +// parameter: +// return: +// @*gorm.DB:admin库db实例 +func GetAdminDB() *gorm.DB { + return adminDB +} + +// GetUserDB +// @description: 获取user库db实例 +// parameter: +// return: +// @*gorm.DB:user库db实例 +func GetUserDB() *gorm.DB { + return userDB +} + +// GetGameDB +// @description: 获取game库db实例 +// parameter: +// return: +// @*gorm.DB:game库db实例 +func GetGameDB() *gorm.DB { + return gameDB +} + +// GetPlayerDB +// @description: 获取玩家库db实例 +// parameter: +// return: +// @*gorm.DB:玩家库db实例 +func GetPlayerDB() *gorm.DB { + return gamePlayerDB +} + +// GetGameModelDB +// @description: 获取model库db实例 +// parameter: +// return: +// @*gorm.DB:model库db实例 +func GetGameModelDB() *gorm.DB { + return gameModelDB +} + +// GetRedisClient +// @description: 获取redisdb实例 +// parameter: +// return: +// @*goredis.Client:redisdb实例 +func GetRedisClient() *goredis.Client { + return redisDB +} + +// GetAll +// @description: 获取model数据 +// parameter: +// @dataList:数据列表 +// return: +// @error:错误信息 +func GetAll(dataList interface{}) error { + mysqlObj := GetGameModelDB() + if mysqlObj = mysqlObj.Find(dataList); mysqlObj.Error != nil { + WriteLog("dal.GetAll", mysqlObj.Error) + return mysqlObj.Error + } + + return nil +} + +// GetGameModelData +// @description: 获取GameModel数据 +// parameter: +// @moduleName:模块名称 +// @dataList:数据列表 +// return: +// @error:错误信息 +func GetGameModelData(moduleName string, dataList interface{}) error { + if result := gameModelDB.Find(dataList); result.Error != nil { + WriteLog(moduleName+"GetGameModelData", result.Error) + return result.Error + } + + return nil +} + +// WriteLog +// @description: 记录错误日志 +// parameter: +// @command:命令 +// @err:错误信息 +// return: +func WriteLog(command string, err error) { + logUtilPlus.ErrorLog(fmt.Sprintf("Scan失败,错误信息:%s,command:%s", err, command)) +} diff --git a/trunk/center/common/connection/dal_test.go b/trunk/center/common/connection/dal_test.go new file mode 100644 index 0000000..2a9aab5 --- /dev/null +++ b/trunk/center/common/connection/dal_test.go @@ -0,0 +1,31 @@ +package connection + +import ( + "testing" + "time" +) + +type testDal struct{} + +func (t testDal) TableName() string { + return "test" +} + +var TestDal = testDal{} + +type User struct { + ID uint `gorm:"primary_key"` + Name string `gorm:"column:name"` + Age int `gorm:"column:age"` + Birthday time.Time `gorm:"column:birthday"` +} + +func TestExecute(t *testing.T) { + user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()} + + result := adminDB.Create(&user) // 通过数据的指针来创建 + + _ = user.ID // 返回插入数据的主键 + _ = result.Error // 返回 error + _ = result.RowsAffected // 返回插入记录的条数 +} diff --git a/trunk/center/common/connection/dbHead.go b/trunk/center/common/connection/dbHead.go new file mode 100644 index 0000000..92b582d --- /dev/null +++ b/trunk/center/common/connection/dbHead.go @@ -0,0 +1,41 @@ +package connection + +import "gorm.io/gorm" + +var ( + // 存放实体结构 + dbModelMap = make([]interface{}, 0) + + //当前管理数据库 + modelDB *gorm.DB +) + +// RegisterDBModel 注册数据库模型到全局变量dbModelMap中。 +// 这个函数接受一个interface{}类型的参数dbModel,表示数据库模型。 +// 函数的目的是将传入的数据库模型添加到全局变量dbModelMap中, +// 以便在其他地方可以访问和使用这些数据库模型。 +func RegisterDBModel(dbModel interface{}) { + // 将dbModel的地址添加到dbModelMap中。 + // 这里使用地址是因为数据库模型可能比较大,通过引用存储可以提高效率。 + dbModelMap = append(dbModelMap, &dbModel) +} + +// 设置modelDB 类型 +func SetModelDB(db *gorm.DB) { + modelDB = db +} + +// BuildDB 用于遍历dbModelMap中的所有数据库模型,并检查每个模型对应的表是否存在于数据库中。 +// 如果表不存在,则自动迁移(创建)该表。这样可以确保数据库模式与程序中的模型保持同步。 +func BuildDB() { + // 遍历dbModelMap中的每个元素 + for _, dbModel := range dbModelMap { + + // 检查数据库中是否存在与dbModel对应的表 + tableExists := modelDB.Migrator().HasTable(dbModel) + if !tableExists { + // 如果表不存在,则进行自动迁移 + modelDB.AutoMigrate(dbModel) + } + } +} diff --git a/trunk/center/common/go.mod b/trunk/center/common/go.mod new file mode 100644 index 0000000..871d1fd --- /dev/null +++ b/trunk/center/common/go.mod @@ -0,0 +1,35 @@ +module common + +go 1.22.10 + +toolchain go1.23.4 + +replace ( + framework => ../../framework + goutil => ../../goutil +) + +require ( + framework v0.0.0-20230425160006-b2d0b0a0b0b0 + github.com/go-redis/redis/v8 v8.11.5 + gorm.io/driver/mysql v1.5.7 + gorm.io/gorm v1.25.12 + goutil v0.0.0-20230425160006-b2d0b0a0b0b0 +) + +require ( + 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-sql-driver/mysql v1.7.0 // indirect + github.com/gomodule/redigo v1.8.9 // 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 + github.com/stretchr/testify v1.8.0 // indirect + golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/trunk/center/common/go.sum b/trunk/center/common/go.sum new file mode 100644 index 0000000..eb50995 --- /dev/null +++ b/trunk/center/common/go.sum @@ -0,0 +1,231 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Shopify/sarama v1.29.1/go.mod h1:mdtqvCSg8JOxk8PmpTNGyo6wzd4BMm4QXSfDnTXmgkE= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 h1:OoL469zqSNrTLSz5zeVF/I6VOO7fiw2bzSzQe4J557c= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4/go.mod h1:xe9a/L2aeOgFKKgrO3ibQTnMdpAeL0GC+5/HpGScSa4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= +github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.230/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vms v1.0.230/go.mod h1:zElyabRGi8DktckzhT3krsYChstFJdSrzDb7pwF2P58= +github.com/xdg/scram v1.0.3/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/trunk/center/common/httpServer/apiContext.go b/trunk/center/common/httpServer/apiContext.go new file mode 100644 index 0000000..e0b7997 --- /dev/null +++ b/trunk/center/common/httpServer/apiContext.go @@ -0,0 +1,191 @@ +package httpServer + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +"goutil/logUtilPlus" +"goutil/typeUtil" +"goutil/zlibUtil" +) + +// ApiContext +// @description: Api请求上下文对象 +type ApiContext struct { + // 请求对象 + request *http.Request + + // 应答写对象 + responseWriter http.ResponseWriter + + // 请求数据 + requestBytes []byte + + // 字典形式的请求数据 + requestDataByMap typeUtil.MapData +} + +// GetRequest +// @description: 获取请求对象 +// parameter: +// @receiver this: this +// return: +// @*http.Request: 请求对象 +func (this *ApiContext) GetRequest() *http.Request { + return this.request +} + +// GetResponseWriter +// @description: 获取应答写对象 +// parameter: +// @receiver this: this +// return: +// @http.ResponseWriter: 应答写对象 +func (this *ApiContext) GetResponseWriter() http.ResponseWriter { + return this.responseWriter +} + +// GetRequestBytes +// @description: 获取请求字节数据 +// parameter: +// @receiver this: this +// return: +// @[]byte: 请求字节数组 +func (this *ApiContext) GetRequestBytes() []byte { + return this.requestBytes +} + +// readContent +// @description: 读取内容 +// parameter: +// @receiver this: this 请求对象 +// @isZlib: 是否数据压缩 +// return: +// @content: 二进制格式的内容 +// @err: 错误对象 +func (this *ApiContext) readContent(isZlib bool) (content []byte, err error) { + var buffer []byte + + defer this.request.Body.Close() + if buffer, err = ioutil.ReadAll(this.request.Body); err != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("url:%s,读取数据出错,错误信息为:%s", this.request.RequestURI, err)) + return + } + + // 不压缩,则直接返回 + if isZlib == false { + this.requestBytes = buffer + + return buffer, err + } + + // 解压数据 + if content, err = zlibUtil.Decompress(buffer); err != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("url:%s,解压缩数据出错,错误信息为:%s", this.request.RequestURI, err)) + return + } + + this.requestBytes = content + + return +} + +// Unmarshal +// @description: 反序列化 +// parameter: +// @receiver this: this +// @obj: 反序列化结果数据 +// return: +// @error: 反序列化错误数据 +func (this *ApiContext) Unmarshal(obj interface{}) error { + contentData := this.GetRequestBytes() + + // 反序列化 + if errMsg := json.Unmarshal(contentData, &obj); errMsg != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("反序列化%s出错,错误信息为:%s", string(contentData), errMsg.Error())) + return errMsg + } + + return nil +} + +// RequestDataByMap +// @description: 获取请求的map格式数据 +// parameter: +// @receiver this: this +// return: +// @typeUtil.MapData: map数据 +// @error: 错误信息 +func (this *ApiContext) RequestDataByMap() (typeUtil.MapData, error) { + if this.requestDataByMap != nil { + return this.requestDataByMap, nil + } + + var data typeUtil.MapData + if err := this.Unmarshal(&data); err != nil { + return nil, err + } + + this.requestDataByMap = data + + return this.requestDataByMap, nil +} + +// RequestDataBySlice +// @description: 获取请求的slice格式数据 +// parameter: +// @receiver this: this +// return: +// @[]interface{}: 返回的数组数据 +func (this *ApiContext) RequestDataBySlice() []interface{} { + result := make([]interface{}, 0, 8) + for _, value := range this.requestDataByMap { + result = append(result, value) + } + return result +} + +// RequestDataBySlice2 +// @description: 获取请求的slice格式数据 +// parameter: +// @receiver this: this +// return: +// @[]interface{}: 返回的数组数据 +// @error: +func (this *ApiContext) RequestDataBySlice2() ([]interface{}, error) { + contentData := this.GetRequestBytes() + + // 反序列化 + result := make([]interface{}, 0, 8) + if errMsg := json.Unmarshal(contentData, &result); errMsg != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("用[]interface{}反序列化%s出错,错误信息为:%s", string(contentData), errMsg.Error())) + return nil, errMsg + } + + return result, nil +} + +// NewApiContext +// @description: 新建API上下文对象 +// parameter: +// @_request: 请求对象 +// @_responseWriter: 应答写对象 +// @isZlib: 数据是否压缩 +// return: +// @*ApiContext: 上下文 +// @error: 错误信息 +func NewApiContext(_request *http.Request, _responseWriter http.ResponseWriter, isZlib bool) (*ApiContext, error) { + context := &ApiContext{ + request: _request, + responseWriter: _responseWriter, + } + + // 读取数据 + _, errMsg := context.readContent(isZlib) + if errMsg != nil { + return nil, errMsg + } + + return context, nil +} diff --git a/trunk/center/common/httpServer/apiHandler.go b/trunk/center/common/httpServer/apiHandler.go new file mode 100644 index 0000000..28587cd --- /dev/null +++ b/trunk/center/common/httpServer/apiHandler.go @@ -0,0 +1,87 @@ +package httpServer + +import ( + "common/webServer" + "net/http" + + "common/resultStatus" +) + +// 处理函数 +type HandleFunc func(context *ApiContext) *webServer.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, + } +} diff --git a/trunk/center/common/httpServer/apiHandlerMgr.go b/trunk/center/common/httpServer/apiHandlerMgr.go new file mode 100644 index 0000000..e0279be --- /dev/null +++ b/trunk/center/common/httpServer/apiHandlerMgr.go @@ -0,0 +1,56 @@ +package httpServer + +import ( + "fmt" + "net/http" +) + +var ( + // 处理方法集合 + // Key:API名 + handlerData = make(map[string]*ApiHandler) + + // 文档方法 + remarkFunc func(w http.ResponseWriter, r *http.Request) +) + +// RegisterRemarkFunc +// @description: 注册文档api方法 +// parameter: +// @_remarkFunc: _remarkFunc +// return: +func RegisterRemarkFunc(_remarkFunc func(w http.ResponseWriter, r *http.Request)) { + remarkFunc = _remarkFunc +} + +// RegisterHandleFunc +// @description: 注册处理方法 +// parameter: +// @apiName: API名称 +// @callback: 回调方法 +// @paramNames: 参数名称集合 +// return: +func RegisterHandleFunc(apiName string, callback HandleFunc, paramNames ...string) { + apiFullName := fmt.Sprintf("/API/%s", apiName) + + if _, exist := handlerData[apiFullName]; exist { + panic(fmt.Errorf("重复注册处理函数:%s", apiFullName)) + } + + handlerData[apiFullName] = newApiHandler(apiFullName, callback, paramNames...) +} + +// GetHandleFunc +// @description: 获取请求方法 +// parameter: +// @apiFullName: 方法名称 +// return: +// @*ApiHandler: 请求方法 +// @bool: 是否存在 +func GetHandleFunc(apiFullName string) (*ApiHandler, bool) { + if requestFuncObj, exists := handlerData[apiFullName]; exists { + return requestFuncObj, exists + } + + return nil, false +} diff --git a/trunk/center/common/httpServer/reflect.go b/trunk/center/common/httpServer/reflect.go new file mode 100644 index 0000000..e01a089 --- /dev/null +++ b/trunk/center/common/httpServer/reflect.go @@ -0,0 +1,400 @@ +package httpServer + +import ( + config "common/configsYaml" + "common/resultStatus" + "common/webServer" + "goutil/logUtilPlus" + "reflect" + "strconv" + "strings" +) + +const ( + // 供客户端访问的模块的后缀 + con_ModuleSuffix = "Module" + + // 定义用于分隔模块名称和方法名称的分隔符 + con_DelimeterOfObjAndMethod = "_" +) + +var ( + // 定义存放所有方法映射的变量 + methodMap = make(map[string]*methodAndInOutTypes) + + // 函数返回值类型 + responseType reflect.Type = reflect.TypeOf(new(webServer.ResponseObject)) +) + +// getStructName +// @description: 获取结构体类型的名称 +// parameter: +// @structType: 结构体类型 +// return: +// @string: 结构体类型的名称 +func getStructName(structType reflect.Type) string { + reflectTypeStr := structType.String() + reflectTypeArr := strings.Split(reflectTypeStr, ".") + + return reflectTypeArr[len(reflectTypeArr)-1] +} + +// getFullModuleName +// @description: 获取完整的模块名称 +// parameter: +// @moduleName: 模块名称 +// return: +// @string: 完整的模块名称 +func getFullModuleName(moduleName string) string { + return moduleName + con_ModuleSuffix +} + +// getFullMethodName +// @description: 获取完整的方法名称 +// parameter: +// @structName: 结构体名称 +// @methodName: 方法名称 +// return: +// @string: 完整的方法名称 +func getFullMethodName(structName, methodName string) string { + return structName + con_DelimeterOfObjAndMethod + methodName +} + +// resolveMethodInOutParams +// @description: 解析方法的输入输出参数 +// parameter: +// @method: 方法对应的反射值 +// return: +// @inTypes: 输入参数类型集合 +// @outTypes: 输出参数类型集合 +func resolveMethodInOutParams(method reflect.Value) (inTypes []reflect.Type, outTypes []reflect.Type) { + methodType := method.Type() + for i := 0; i < methodType.NumIn(); i++ { + inTypes = append(inTypes, methodType.In(i)) + } + + for i := 0; i < methodType.NumOut(); i++ { + outTypes = append(outTypes, methodType.Out(i)) + } + + return +} + +// RegisterFunction +// @description: 将需要对客户端提供方法的对象进行注册 +// parameter: +// @structObject: 对象 +// return: +func RegisterFunction(structObject interface{}) { + // 获取structObject对应的反射 Type 和 Value + reflectValue := reflect.ValueOf(structObject) + reflectType := reflect.TypeOf(structObject) + + // 提取对象类型名称 + structName := getStructName(reflectType) + + // 获取structObject中返回值为responseObject的方法 + for i := 0; i < reflectType.NumMethod(); i++ { + // 获得方法名称 + methodName := reflectType.Method(i).Name + + // 获得方法及其输入参数的类型列表 + method := reflectValue.MethodByName(methodName) + inTypes, outTypes := resolveMethodInOutParams(method) + + // 判断输出参数数量是否正确 + if len(outTypes) != 1 { + continue + } + + // 判断返回值是否为responseObject + if outTypes[0] != responseType { + continue + } + + // 添加到列表中 + methodMap[getFullMethodName(structName, methodName)] = newmethodAndInOutTypes(method, inTypes, outTypes) + } +} + +// CallFunction +// @description: 调用方法 +// parameter: +// @requestObj: 客户端对象 +// return: +// @responseObj: 请求对象 +func CallFunction(requestObj *webServer.RequestObject) (responseObj *webServer.ResponseObject) { + responseObj = webServer.GetInitResponseObj() + + var methodAndInOutTypes *methodAndInOutTypes + var ok bool + + // 根据传入的ModuleName和MethodName找到对应的方法对象 + key := getFullMethodName(requestObj.ModuleName, requestObj.MethodName) + if methodAndInOutTypes, ok = methodMap[key]; !ok { + logUtilPlus.ErrorLog("找不到指定的方法:%s", key) + responseObj.SetResultStatus(resultStatus.NotSpecificMethod) + return + } + + // 判断参数数量是否相同 + inTypesLength := len(methodAndInOutTypes.InTypes) + paramLength := len(requestObj.Parameters) + if paramLength != inTypesLength { + logUtilPlus.ErrorLog("传入的参数数量不符,本地方法%s的参数数量:%d,传入的参数数量为:%d", key, inTypesLength, paramLength) + responseObj.SetResultStatus(resultStatus.ParamNotMatch) + return + } + + // 构造参数 + in := make([]reflect.Value, inTypesLength) + for i := 0; i < inTypesLength; i++ { + inTypeItem := methodAndInOutTypes.InTypes[i] + paramItem := requestObj.Parameters[i] + + // 已支持类型:Client,Player(非基本类型) + // 已支持类型:Bool,Int,Int8,Int16,Int32,Int64,Uint,Uint8,Uint16,Uint32,Uint64,Float32,Float64,String + // 已支持类型:以及上面所列出类型的Slice类型 + // 未支持类型:Uintptr,Complex64,Complex128,Array,Chan,Func,Interface,Map,Ptr,Struct,UnsafePointer + // 由于byte与int8同义,rune与int32同义,所以并不需要单独处理 + // 枚举参数的类型,并进行类型转换 + switch inTypeItem.Kind() { + case reflect.Bool: + if param_bool, ok := paramItem.(bool); ok { + in[i] = reflect.ValueOf(param_bool) + } + case reflect.Int: + if param_float64, ok := paramItem.(int); ok { + in[i] = reflect.ValueOf(int(param_float64)) + } + case reflect.Int8: + if param_float64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(int8(param_float64)) + } + case reflect.Int16: + if param_float64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(int16(param_float64)) + } + case reflect.Int32: + if param_float64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(int32(param_float64)) + } + case reflect.Int64: + if param_float64, ok := paramItem.(int64); ok { + in[i] = reflect.ValueOf(int64(param_float64)) + } else if param_uint64, ok := paramItem.(uint64); ok { + in[i] = reflect.ValueOf(int64(param_uint64)) + } else if param_string, ok := paramItem.(string); ok { + + i64, err := strconv.ParseInt(param_string, 10, 64) + + if err == nil { + in[i] = reflect.ValueOf(i64) + } + } + case reflect.Uint: + if param_float64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(uint(param_float64)) + } + case reflect.Uint8: + if param_float64, ok := paramItem.(uint8); ok { + in[i] = reflect.ValueOf(uint8(param_float64)) + } + case reflect.Uint16: + if param_float64, ok := paramItem.(uint16); ok { + in[i] = reflect.ValueOf(uint16(param_float64)) + } + case reflect.Uint32: + if param_float64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(uint32(param_float64)) + } + case reflect.Uint64: + if param_float64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(uint64(param_float64)) + } + case reflect.Float32: + if param_float64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(float32(param_float64)) + } + case reflect.Float64: + if param_float64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(param_float64) + } + case reflect.String: + if param_string, ok := paramItem.(string); ok { + in[i] = reflect.ValueOf(param_string) + } + case reflect.Slice: + // 如果是Slice类型,则需要对其中的项再次进行类型判断及类型转换 + if param_interface, ok := paramItem.([]interface{}); ok { + switch inTypeItem.String() { + case "[]bool": + params_inner := make([]bool, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_bool, ok := param_interface[i].(bool); ok { + params_inner[i] = param_bool + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int": + params_inner := make([]int, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(int); ok { + params_inner[i] = int(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int8": + params_inner := make([]int8, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(float64); ok { + params_inner[i] = int8(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int16": + params_inner := make([]int16, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(float64); ok { + params_inner[i] = int16(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int32": + params_inner := make([]int32, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + param_float64, ok := param_interface[i].(int32) + if ok { + params_inner[i] = int32(param_float64) + continue + } else { + param_int16, right := param_interface[i].(uint16) + if right == true { + params_inner[i] = int32(param_int16) + } + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int64": + params_inner := make([]int64, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(int64); ok { + params_inner[i] = int64(param_float64) + } else if param_uint64, ok := param_interface[i].(uint64); ok { + params_inner[i] = int64(param_uint64) + } else if param_string, ok := param_interface[i].(string); ok { + + i64, err := strconv.ParseInt(param_string, 10, 64) + + if err == nil { + params_inner[i] = i64 + } + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]uint": + params_inner := make([]uint, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(uint); ok { + params_inner[i] = uint(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + // case "[]uint8": 特殊处理 + case "[]uint16": + params_inner := make([]uint16, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(uint16); ok { + params_inner[i] = uint16(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]uint32": + params_inner := make([]uint32, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(uint32); ok { + params_inner[i] = uint32(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]uint64": + params_inner := make([]uint64, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(uint64); ok { + params_inner[i] = uint64(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]float32": + params_inner := make([]float32, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(float64); ok { + params_inner[i] = float32(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]float64": + params_inner := make([]float64, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(float64); ok { + params_inner[i] = param_float64 + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]string": + params_inner := make([]string, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_string, ok := param_interface[i].(string); ok { + params_inner[i] = param_string + } + } + in[i] = reflect.ValueOf(params_inner) + } + } else if inTypeItem.String() == "[]uint8" { // 由于[]uint8在传输过程中会被转化成字符串,所以单独处理; + if param_string, ok := paramItem.(string); ok { + param_uint8 := ([]uint8)(param_string) + in[i] = reflect.ValueOf(param_uint8) + } + } + } + } + + // 判断是否有无效的参数(传入的参数类型和方法定义的类型不匹配导致没有赋值) + for _, item := range in { + if reflect.Value.IsValid(item) == false { + logUtilPlus.ErrorLog("type:%v,value:%v.方法%s传入的参数%v无效", reflect.TypeOf(item), reflect.ValueOf(item), key, requestObj.Parameters) + responseObj.SetResultStatus(resultStatus.ParamInValid) + return + } + } + + // 传入参数,调用方法 + if config.DEBUG { + if requestObj.MethodName != "GetRefreshData" { + logUtilPlus.DebugLog("Begin Call Func:module:%v, method:%v,inParams:%v\n\n", requestObj.ModuleName, requestObj.MethodName, in) + } + } + + out := methodAndInOutTypes.Method.Call(in) + + if config.DEBUG { + if requestObj.MethodName != "GetRefreshData" { + for i, v := range in { + logUtilPlus.DebugLog("\nparams %v,%v\n", i, v) + } + } + } + + // 并输出结果到客户端(由于只有一个返回值,所以取out[0]) + if responseObj, ok = (&out[0]).Interface().(*webServer.ResponseObject); !ok { + logUtilPlus.ErrorLog("返回值类型推断为ResponseObject 出错, tyep is :%v", reflect.TypeOf(out[0])) + responseObj.SetResultStatus(resultStatus.ParamInValid) + return + } + if config.DEBUG { + if requestObj.MethodName != "GetRefreshData" { + logUtilPlus.DebugLog("返回数据:code:%v, data:%v, mess:%v\n\n", responseObj.Code, responseObj.Value, responseObj.Message) + } + } + return +} diff --git a/trunk/center/common/httpServer/reflectMethod.go b/trunk/center/common/httpServer/reflectMethod.go new file mode 100644 index 0000000..0571879 --- /dev/null +++ b/trunk/center/common/httpServer/reflectMethod.go @@ -0,0 +1,34 @@ +package httpServer + +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, + } +} diff --git a/trunk/center/common/httpServer/result.go b/trunk/center/common/httpServer/result.go new file mode 100644 index 0000000..d42071c --- /dev/null +++ b/trunk/center/common/httpServer/result.go @@ -0,0 +1,27 @@ +package httpServer + +import ( + "common/webServer" + "encoding/json" + "fmt" + "goutil/logUtilPlus" + "net/http" + "strconv" +) + +// responseResult +// @description: responseResult +// parameter: +// @w: w +// @responseObj: responseObj +// return: +func responseResult(w http.ResponseWriter, responseObj *webServer.ResponseObject) { + b, err := json.Marshal(responseObj) + if err != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("序列化输出结果%v出错", responseObj)) + return + } + + w.Header().Add("Content-Length", strconv.Itoa(len(b))) + w.Write(b) +} diff --git a/trunk/center/common/httpServer/serverMux.go b/trunk/center/common/httpServer/serverMux.go new file mode 100644 index 0000000..d188666 --- /dev/null +++ b/trunk/center/common/httpServer/serverMux.go @@ -0,0 +1,80 @@ +package httpServer + +import ( + config "common/configsYaml" + "common/resultStatus" + "common/utils" + "common/webServer" + "encoding/json" + "fmt" + "goutil/logUtilPlus" + "goutil/stringUtil" + "net/http" + "strings" +) + +// selfDefineMux +// @description: 定义自定义的Mux对象 +type selfDefineMux struct { +} + +// ServeHTTP +// @description: ServeHTTP +// parameter: +// @receiver mux: mux +// @w: w +// @r: r +// return: +func (mux *selfDefineMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + responseObj := webServer.GetInitResponseObj() + defer utils.LogReqErrorRecover(r) + + // 判断是否是接口文档 + if strings.Contains(r.RequestURI, "Remarks") && r.Method == "GET" { + remarkFunc(w, r) + return + } + + // 判断是否是POST方法 + if r.Method != "POST" { + responseResult(w, responseObj.SetResultStatus(resultStatus.OnlySupportPOST)) + return + } + + // 因为httpserver——面向外网client故屏蔽了webserver中的ip限制 + // 构造contex + context, errMsg := NewApiContext(r, w, false) + if errMsg != nil { + // 输出结果 + responseResult(w, responseObj.SetResultStatus(resultStatus.APIDataError)) + return + } + + // 根据路径选择不同的处理方法 + if handlerFunObj, exists := GetHandleFunc(r.RequestURI); exists { + defer func() { + if config.DEBUG { + b, _ := json.Marshal(responseObj) + msg := fmt.Sprintf("API:%v 请求数据:%v;返回数据:%s;", + r.RequestURI, string(context.GetRequestBytes()), string(b)) + logUtilPlus.DebugLog(msg) + } + }() + + // 输出结果 + responseObj := handlerFunObj.HandleFun()(context) + responseResult(w, responseObj) + return + } + + // 通过反射选择不同的方法 + strs := stringUtil.Split(r.RequestURI, []string{"/"}) + params, err := context.RequestDataBySlice2() + if err != nil { + responseResult(w, responseObj.SetResultStatus(resultStatus.APIDataError)) + return + } + resquestData := webServer.NewRequestObject(strs[1], strs[2], params) + resp := CallFunction(resquestData) + responseResult(w, resp) +} diff --git a/trunk/center/common/httpServer/start.go b/trunk/center/common/httpServer/start.go new file mode 100644 index 0000000..7f88705 --- /dev/null +++ b/trunk/center/common/httpServer/start.go @@ -0,0 +1,42 @@ +package httpServer + +import ( + "fmt" + "net/http" + "sync" + + "goutil/logUtil" + "goutil/logUtilPlus" + // "log" + "net/http/pprof" + + config "common/configsYaml" +) + +// Start +// @description: 启动服务器 +// parameter: +// @wg: WaitGroup对象 +// return: +func Start(wg *sync.WaitGroup) { + defer func() { + wg.Done() + }() + + // 启动过程中不需要捕获异常 + logUtilPlus.PrintAndWriteLog(logUtil.Info, fmt.Sprintf("Web服务器开始监听:%v", config.WebServerAddress)) + + // 启动Web服务器监听 + mux := http.NewServeMux() + mux.Handle("/", &selfDefineMux{}) + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + err := http.ListenAndServe(config.WebServerAddress, mux) + if err != nil { + panic(fmt.Errorf("ListenAndServe失败,错误信息为:%s", err)) + } +} diff --git a/trunk/center/common/remark/methodRemark.go b/trunk/center/common/remark/methodRemark.go new file mode 100644 index 0000000..d4fe2e5 --- /dev/null +++ b/trunk/center/common/remark/methodRemark.go @@ -0,0 +1,58 @@ +package remark + +// 方法说明对象 +type MethodRemark struct { + // 模块名称 + ModuleName string + + // 方法名称 + Name string + + // 方法描述 + Desc string + + // 接口作者 + Author string + + // 修改者 + Mendor string + + // 接口设计日期 + Date string + + // 输入参数 + InParam []string + + // 输出参数 + OutParam string +} + +// newMethodRemark +// @description: 创建新的方法说明对象 +// parameter: +// @moduleName: 模块名称 +// @name: 方法名称 +// @desc: 方法描述 +// @author: 方法作者 +// @mendor: 方法修改者(多个用,分隔) +// @date: 创建日期 +// @inParam: 输入参数 +// @outParam: 输出参数 +// return: +// @*MethodRemark: 新的方法说明对象 +func newMethodRemark(moduleName, name, desc, author, mendor, date string, inParam []string, outParam string) *MethodRemark { + if mendor == "" { + mendor = "无" + } + + return &MethodRemark{ + ModuleName: moduleName, + Name: name, + Desc: desc, + Author: author, + Mendor: mendor, + Date: date, + InParam: inParam, + OutParam: outParam, + } +} diff --git a/trunk/center/common/remark/moduleRemark.go b/trunk/center/common/remark/moduleRemark.go new file mode 100644 index 0000000..9882279 --- /dev/null +++ b/trunk/center/common/remark/moduleRemark.go @@ -0,0 +1,48 @@ +package remark + +// ModuleRemark +// @description: 模块说明对象 +type ModuleRemark struct { + // 模块名称 + Name string + + // 模块描述 + Desc string + + // 接口作者 + Author string + + // 修改者 + Mendor string + + // 接口设计日期 + Date string + + // 方法对象列表 + MethodRemarkSlice []*MethodRemark +} + +// newModuleRemark +// @description: 创建新的模块说明对象 +// parameter: +// @name: 模块名称 +// @desc: 模块描述 +// @author: 模块作者 +// @mendor: 模块修改者(多个用,分隔) +// @date: 创建日期 +// return: +// @*ModuleRemark: 新的模块说明对象 +func newModuleRemark(name, desc, author, mendor, date string) *ModuleRemark { + if mendor == "" { + mendor = "无" + } + + return &ModuleRemark{ + Name: name, + Desc: desc, + Author: author, + Mendor: mendor, + Date: date, + MethodRemarkSlice: make([]*MethodRemark, 0, 16), + } +} diff --git a/trunk/center/common/remark/remarkMgr.go b/trunk/center/common/remark/remarkMgr.go new file mode 100644 index 0000000..be0fb06 --- /dev/null +++ b/trunk/center/common/remark/remarkMgr.go @@ -0,0 +1,60 @@ +package remark + +import ( + "goutil/logUtilPlus" +) + +var ( + remarksSlice []*ModuleRemark = make([]*ModuleRemark, 0, 32) +) + +// RegisterModuleRemark +// +// @description: 注册模块说明对象 +// +// parameter: +// +// @name: 模块名称 +// @desc: 模块描述 +// @author: 模块作者 +// @mendor: 模块修改者(多个用,分隔) +// @date: 创建日期 +// +// return: +func RegisterModuleRemark(name, desc, author, mendor, date string) { + remarksSlice = append(remarksSlice, newModuleRemark(name, desc, author, mendor, date)) +} + +// RegisterMethodRemark +// +// @description: 注册方法说明对象 +// +// parameter: +// +// @moduleName: 模块名称 +// @name: 方法名称 +// @desc: 方法描述 +// @author: 方法作者 +// @mendor: 方法修改者(多个用,分隔) +// @date: 创建日期 +// @inParam: 输入参数 +// @outParam: 输出参数 +// +// return: +func RegisterMethodRemark(moduleName, name, desc, author, mendor, date string, inParam []string, outParam string) { + var moduleRemark *ModuleRemark + var exists bool + for _, item := range remarksSlice { + if item.Name == moduleName { + moduleRemark = item + exists = true + } + } + + if !exists { + logUtilPlus.ErrorLog("ModuleRemark:%s尚未注册", moduleName) + return + } + + moduleRemark.MethodRemarkSlice = append(moduleRemark.MethodRemarkSlice, newMethodRemark(moduleName, name, desc, author, mendor, date, inParam, outParam)) +} diff --git a/trunk/center/common/remark/webApi.go b/trunk/center/common/remark/webApi.go new file mode 100644 index 0000000..de20829 --- /dev/null +++ b/trunk/center/common/remark/webApi.go @@ -0,0 +1,56 @@ +package remark + +import ( + "common/httpServer" + "fmt" + "net/http" + + "common/webServer" +) + +// init +// @description: init +// parameter: +// return: +func init() { + webServer.RegisterRemarkFunc(remarkdCallback) + httpServer.RegisterRemarkFunc(remarkdCallback) +} + +// remarkdCallback +// @description: remarkdCallback +// parameter: +// @w: w +// @r: r +// return: +func remarkdCallback(w http.ResponseWriter, r *http.Request) { + + for index, moduleItem := range remarksSlice { + // 输出模块信息 + fmt.Fprintf(w, fmt.Sprintf("%d、%s【Name:%s Author:%s Mendor:%s Date:%s】\n", index+1, moduleItem.Desc, moduleItem.Name, moduleItem.Author, moduleItem.Mendor, moduleItem.Date)) + + // 输出方法列表信息 + for subIndex, methodItem := range moduleItem.MethodRemarkSlice { + fmt.Fprintf(w, fmt.Sprintf(" %d.%d、%s【Name:%s Author:%s Mendor:%s Date:%s】\n", index+1, subIndex+1, methodItem.Desc, methodItem.Name, methodItem.Author, methodItem.Mendor, methodItem.Date)) + + fmt.Fprintln(w, " \t输入参数:") + if len(methodItem.InParam) > 0 { + for _, param := range methodItem.InParam { + fmt.Fprintln(w, " ", param) + } + } else { + fmt.Fprintln(w, " ", "无") + } + + fmt.Fprintln(w, " \t输出参数:") + if methodItem.OutParam != "" { + fmt.Fprintln(w, " ", methodItem.OutParam) + } else { + fmt.Fprintln(w, " ", "无") + } + + fmt.Fprintln(w) + } + } + return +} diff --git a/trunk/center/common/resultStatus/resultStatus.go b/trunk/center/common/resultStatus/resultStatus.go new file mode 100644 index 0000000..1ee8723 --- /dev/null +++ b/trunk/center/common/resultStatus/resultStatus.go @@ -0,0 +1,71 @@ +package resultStatus + +import ( + "fmt" +) + +type StatusCode int + +// ResultStatus +// @description: 状态码数据 +type ResultStatus struct { + // 状态码 + code StatusCode + + // 消息 + message string +} + +// Code +// @description: 状态码 +// parameter: +// @receiver this: this +// return: +// @StatusCode: 状态码 +func (this *ResultStatus) Code() StatusCode { + return this.code +} + +// Message +// @description: 错误信息 +// parameter: +// @receiver this: this +// return: +// @string: +func (this *ResultStatus) Message() string { + return this.message +} + +// NewResultStatus +// @description: 创建新的状态码对象 +// parameter: +// @_code: 错误码 +// @_message: 提示消息 +// return: +// @ResultStatus: 状态码对象 +func NewResultStatus(_code StatusCode, _message string) ResultStatus { + return ResultStatus{ + code: _code, + message: _message, + } +} + +// IsSuccess +// @description: 是否是成功 +// parameter: +// @receiver this: this +// return: +// @bool: true:成功 false:失败 +func (this *ResultStatus) IsSuccess() bool { + return this.code == Success.Code() +} + +// String +// @description: 打印状态码信息 +// parameter: +// @receiver this: this +// return: +// @string: 状态码信息 +func (this *ResultStatus) String() string { + return fmt.Sprintf("Code:%d,Message:%s", this.code, this.message) +} diff --git a/trunk/center/common/resultStatus/resultStatusCode.go b/trunk/center/common/resultStatus/resultStatusCode.go new file mode 100644 index 0000000..8aa3b45 --- /dev/null +++ b/trunk/center/common/resultStatus/resultStatusCode.go @@ -0,0 +1,910 @@ +package resultStatus + +// 系统错误码 +var ( + // 成功 + Success = NewResultStatus(0, "Success") + + // 数据库错误 + DBError = NewResultStatus(-2, "DBError") + + // 方法未定义 + MethodNotDefined = NewResultStatus(-3, "MethodNotDefined") + + // 参数无效 + ParamInValid = NewResultStatus(-4, "ParamInValid") + + // 参数不匹配 + ParamNotMatch = NewResultStatus(-5, "ParamNotMatch") + + // 功能未开启 + ModuleNotOpen = NewResultStatus(-6, "ModuleNotOpen") + + // 只支持Post + OnlySupportPOST = NewResultStatus(-7, "OnlySupportPOST") + + // API未定义 + APINotDefined = NewResultStatus(-8, "APINotDefined") + + // API数据错误 + APIDataError = NewResultStatus(-9, "APIDataError") + + // API参数错误 + APIParamError = NewResultStatus(-10, "APIParamError") + + // IP被禁用 + IPForbid = NewResultStatus(-11, "IPForbid") + + // 没有有效的服务器 + NoAvailableServer = NewResultStatus(-12, "NoAvailableServer") + + // 服务器组不存在 + ServerGroupNotExists = NewResultStatus(-13, "ServerGroupNotExists") + + // 测试接口只能debug下调用 + DebugInterface = NewResultStatus(-14, "DebugInterface") + + // 模块不存在 + ModuleNotExists = NewResultStatus(-15, "ModuleNotExists") + + // 未能找到指定方法 + NotSpecificMethod = NewResultStatus(-16, "NotSpecificMethod") + + // 非指定区域 + NotMatchRegion = NewResultStatus(-17, "NotMatchRegion") + + // 发送空数据 + SendNullData = NewResultStatus(-18, "SendNullData") + + // 序列化失败 + MarshalDataError = NewResultStatus(-19, "MarshalDataError") + + //未登录 + NotLogin = NewResultStatus(-20, "NotLogin") + + // 数据错误 + DataError = NewResultStatus(-31, "DataError") + + // 战区合并维护中,敬请期待 + MergeDataRunning = NewResultStatus(-67, "MergeDataRunning") + + // 下载器内容未配置 + QcDownloadConfigNotExists = NewResultStatus(-68, "QcDownloadConfigNotExists") + + // 下载器奖励已领取 + QcDownloadHasReward = NewResultStatus(-69, "QcDownloadHasReward") + + // 下载器奖励没有奖励可领取 + QcDownloadNotReward = NewResultStatus(-70, "QcDownloadNotReward") + + // 下载器奖励积分不足 + QcDownloadNotScore = NewResultStatus(-71, "QcDownloadNotScore") +) + +// 玩家 +var ( + + // 玩家不存在 + PlayerNotExist = NewResultStatus(-1110, "PlayerNotExist") + + // 没有合适的玩家 + NotSuitablePlayer = NewResultStatus(-1155, "NotSuitablePlayer") + + // 玩家阵容不存在 + PlayerSlotFormationNotExist = NewResultStatus(-1156, "PlayerSlotFormationNotExist") +) + +// 仙盟和GS保持同步 +var ( + // 玩家不在仙盟中 + GuildNotIn = NewResultStatus(-9401, "玩家不在仙盟中") + + // 仙盟不存在 + GuildNotExist = NewResultStatus(-9402, "仙盟不存在") + + // 玩家已在仙盟中 + GuildHasIn = NewResultStatus(-9403, "玩家已在仙盟中") + + // 玩家操作目标和所在仙盟不一致 + TargetGuildNotMatch = NewResultStatus(-9404, "玩家操作目标和所在仙盟不一致") + + // 仙盟不存在成员 + GuildMemberNotExist = NewResultStatus(-9405, "仙盟不存在成员") + + // vip等级不足 + GuildNeedVip = NewResultStatus(-9406, "vip等级不足") + + // 仙盟权限不足 + GuildNeedAuth = NewResultStatus(-9407, "仙盟权限不足") + + // boss节点今日未开放 + GuildBossTodayNotOpen = NewResultStatus(-9411, "boss节点今日未开放") + + // 职位人数已满 + GuildPostEnough = NewResultStatus(-9415, "职位人数已满") + + // boss开启错误 + GuildBossOpenError = NewResultStatus(-9416, "boss开启错误") + + // boss开启条件不足 + GuildBossOpenEnough = NewResultStatus(-9417, "boss开启条件不足") + + // 仙盟名称已存在 + GuildNameHasExist = NewResultStatus(-9419, "仙盟名称已存在") + + // 该职位人数达上限 + GuildPostLimit = NewResultStatus(-9420, "该职位人数达上限") + + // 世界喊话CD中 + GuildShareCd = NewResultStatus(-9423, "世界喊话CD中") + + // 弹劾条件不足 + GuildImpeachNotEnough = NewResultStatus(-9424, "弹劾条件不足") + + // 不是成员或者长老 + GuildIsNotMember = NewResultStatus(-9428, "不是成员或者长老") + + // boss已开启 + GuildBossHasOpen = NewResultStatus(-9429, "boss已开启") + + // 盟主不允许退盟 + GuildLeaderNotAllowedExit = NewResultStatus(-9430, "盟主不允许退盟") + + // 盟主公告锁定 + GuildNoticeIsLock = NewResultStatus(-9431, "盟主公告锁定") + + // 仙盟次数不足 + GuildMiniGameNumNotAllow = NewResultStatus(-9439, "仙盟次数不足") + + // 仙盟名称已被占用 + GuildNameIsNotValid = NewResultStatus(-9445, "仙盟名称已存在") + + // 不是盟主 + GuildPlayerNotLeader = NewResultStatus(-9454, "不是盟主") + + // 模式切换未到冷却时间 + GuildSwitchModeNotCool = NewResultStatus(-9455, "模式切换未到冷却时间") + + // 该玩家职位变更还在冷却中 + GuildChangePostNotCool = NewResultStatus(-9460, "该玩家职位变更还在冷却中") + + // 当前仙盟管理模式不可任命 + GuildModeCanNotAppoint = NewResultStatus(-9461, "当前仙盟管理模式不可任命") + + // 当前仙盟管理模式不可挑战 + GuildModeCanNotFight = NewResultStatus(-9463, "当前仙盟管理模式不可挑战") + + // 玩家等级不满足仙盟等级要求 + GuildPlayerLvLessThanNeedLv = NewResultStatus(-9465, "玩家等级不满足仙盟等级要求") + + // 玩家未申请 + GuildPlayerNotApply = NewResultStatus(-9466, "玩家未申请") + + // 仙盟信息未变更 + GuildInfoNotChange = NewResultStatus(-9483, "仙盟信息未变更") + + // 战略编辑次数不足,明日再来 + GuildStrategyEditNum = NewResultStatus(-9486, "战略编辑次数不足,明日再来") + + // 建筑不存在 + GuildBuildNotExist = NewResultStatus(-9489, "建筑不存在") + + // 礼包已采购 + GuildWelfarePurchased = NewResultStatus(-9490, "该礼包已采购") + + // 礼包已采购 + GuildWelfareCaptailNotEnough = NewResultStatus(-9491, "采购礼包所需资金不足") + + // 礼包已采购 + GuildWelfareLvNotEnough = NewResultStatus(-9492, "仙盟等级不足,无法采购") + + // 建筑经验超过最大值 + GuildBuildExpOverMax = NewResultStatus(-9496, "建筑经验超过最大值") + + // 未能设置该类型职位信息 + GuildSetPostTypeNot = NewResultStatus(-9497, "未能设置该类型职位信息") + + // 仙盟成员已达上限 + GuildNumMax = NewResultStatus(-9499, "仙盟成员已达上限") + + // 建筑尚未开启 + GuildBuildIsLock = NewResultStatus(-109013, "建筑尚未开启") + + // 建筑配置未获取 + GuildBuildConfigNotExist = NewResultStatus(-109014, "未获取到建筑配置") + + // 等级不匹配 + GuildWelfareLvNotMatch = NewResultStatus(-109015, "等级不匹配") + + // 建筑等级已达到满级 + GuildBuildHasMaxLv = NewResultStatus(-109017, "建筑等级已达到满级") + + // 采购礼包ID不存在 + GuildGiftIdNotExist = NewResultStatus(-109018, "采购礼包ID不存在") + + // 盟主令次数不足 + GuildLeaderOrderCount = NewResultStatus(-109019, "盟主令次数不足") + + // 仙盟每日可踢出人数已达上限 + GuildTodayKickOutCountIsMax = NewResultStatus(-109020, "仙盟每日可踢出人数已达上限") + + // 已申请该仙盟,请耐心等待审核 + GuildHasApply = NewResultStatus(-109499, "已申请该仙盟,请耐心等待审核") +) + +// 仙盟试炼 +var ( + GuildTrainBoxRewardDrawed = NewResultStatus(-109001, "仙盟试炼宝箱已领取") + + // 仙盟boss战报不存在 + GuildTimeBossReportNotExists = NewResultStatus(-109002, "仙盟boss战报不存在") + + // 仙盟试炼节点不存在 + GuildTrainNodeNotExists = NewResultStatus(-109003, "仙盟试炼节点不存在") + + // 仙盟试炼奖励槽位不存在 + GuildTrainBoxSlotNotExists = NewResultStatus(-109004, "仙盟试炼奖励槽位不存在") + + // 仙盟试炼目标位置奖励信息已变化 + GuildTrainBoxSlotHasRefresh = NewResultStatus(-109005, "仙盟试炼目标位置奖励信息已变化") + + // 仙盟限时boss开启积分不足 + GuildTimedOpenNotEnougth = NewResultStatus(-109006, "仙盟限时boss开启积分不足") + + // 仙盟限时boss开启积分不足 + GuildTrainNotKill = NewResultStatus(-109007, "BOSS未镇压,无法领奖") + + // 试炼章节暂未开启 + GuildTrainNodeNotOpen = NewResultStatus(-109010, "试炼章节暂未开启") + + // 试炼已经镇压 + GuildTrainIsKilled = NewResultStatus(-109011, "BOSS已镇压,不可挑战") +) + +// 仙盟红包 +var ( + // 仙盟红包不存在 + GuildRedPacketNotExist = NewResultStatus(-9950, "仙盟红包不存在") + + // 仙盟玩家红包不存在 + GuildPlayerRedPacketNotExist = NewResultStatus(-9959, "仙盟玩家红包不存在") + + // 仙盟红包已过期 + GuildRedPacketIsExpire = NewResultStatus(-9953, "仙盟红包已过期") + + // 仙盟红包没有奖励可以领取 + GuildRedPacketNotReward = NewResultStatus(-9960, "仙盟红包没有奖励可以领取") + + // 仙盟红包奖励已领取 + GuildRedPacketHasRewarded = NewResultStatus(-9954, "仙盟红包奖励已领取") +) + +// 组队副本 +var ( + // 房间不存在 + TeamCopyRoomNotExists = NewResultStatus(-9612, "TeamCopyRoomNotExists") + + // 成员不存在 + TeamCopyMemberNotExists = NewResultStatus(-9613, "TeamCopyMemberNotExists") + + // 不是房主 + TeamCopyNotIsLeader = NewResultStatus(-9614, "TeamCopyNotIsLeader") + + // 战斗失败 + TeamCopyFigthFail = NewResultStatus(-9615, "TeamCopyFigthFail") + + // 房间玩家未准备 + TeamCopyNotAllReady = NewResultStatus(-9616, "TeamCopyNotAllReady") + + // 玩家人数不足 + TeamCopyNotEnough = NewResultStatus(-9617, "TeamCopyNotEnough") + + // 玩家战力不足 + TeamCopyFapNotEnough = NewResultStatus(-9618, "TeamCopyFapNotEnough") + + // 仍有奖励未领取 + TeamCopyHasReward = NewResultStatus(-9619, "TeamCopyHasReward") + + // 未被邀请 + TeamCopyNotShare = NewResultStatus(-9620, "TeamCopyNotShare") + + // 目标节点尚未开启 + TeamCopyNodeNotOpen = NewResultStatus(-9622, "TeamCopyNodeNotOpen") + + // 人数已满 + TeamCopyIsMax = NewResultStatus(-9624, "TeamCopyIsMax") + + // 没有权限进入房间 + TeamCopyNotAuth = NewResultStatus(-9625, "TeamCopyNotAuth") + + // 组队战斗已开始 + TeamCopyIsFighting = NewResultStatus(-9626, "TeamCopyIsFighting") + + // 邀请已存在 + TeamCopyIsExistShare = NewResultStatus(-9627, "TeamCopyIsExistShare") + + // 队伍不能为全部助战状态 + TeamCopyIsAllHelp = NewResultStatus(-9633, "TeamCopyIsAllHelp") + + // 您已被踢出 + TeamCopyKickOut = NewResultStatus(-9638, "TeamCopyKickOut") + + // 房间已经解散 + TeamCopyRoomRemove = NewResultStatus(-9639, "房间已经解散") + + // 玩家不在房间中 + TeamCopyRoomMemberNotIn = NewResultStatus(-9644, "玩家不在房间中") + + // 玩家战力不足 + TeamCopyPlayerFapLimit = NewResultStatus(-9647, "玩家战力不足") + + // 布阵阵容错误 + TeamCopyFormationError = NewResultStatus(-212603, "布阵阵容错误") + + // 单人模式不允许加入 + TeamCopySingleNotJoin = NewResultStatus(-212604, "单人模式不允许加入") + + // 队员未准备 + PlayerNotReady = NewResultStatus(-212605, "队员未准备") +) + +// 矿战 +var ( + // 矿战玩家信息不存在 + KuangzhanPlayerNotExist = NewResultStatus(-36131, "KuangzhanPlayerNotExist") + + // 占领节点错误 + KuangzhanOccupyNode = NewResultStatus(-36113, "KuangzhanOccupyNode") + + // 矿战节点不存在 + NodeConfigNotExist = NewResultStatus(-36132, "NodeConfigNotExist") + + // 此洞府正在被攻击 + TheOtherPlayerFighting = NewResultStatus(-36114, "TheOtherPlayerFighting") + + // 该节点已经被占领 + KuangzhanoverOccupyNode = NewResultStatus(-36113, "KuangzhanoverOccupyNode") + + // 占领节点玩家无阵容 + KuangzhanPlayerNoFormation = NewResultStatus(-36133, "KuangzhanPlayerNoFormation") + + // 获取阵容失败 + KuangzhanPlayerGetFormationFail = NewResultStatus(-36134, "KuangzhanPlayerGetFormationFail") + + // 已占领洞府节点 + KuangzhanOverLoad = NewResultStatus(-36115, "KuangzhanOverLoad") + + // 今日抢夺次数已经用完 + KuangzhanDailyLootOver = NewResultStatus(-36117, "KuangzhanDailyLootOver") + + // 此洞府今日已无法被抢夺 + KuangzhanDailyRobbedOver = NewResultStatus(-36118, "KuangzhanDailyRobbedOver") + + // 洞天抢夺暂未开启 + KuangzhanRobbedTimeNotOpen = NewResultStatus(-36119, "KuangzhanRobbedTimeNotOpen") + + // 剩余洞天宝石不可抢夺 + KuangzhanGemIsOver = NewResultStatus(-36120, "KuangzhanGemIsOver") + + // 玩家不匹配 + KuangzhanPlayerNotMatching = NewResultStatus(-36122, "KuangzhanPlayerNotMatching") + + // 不是被邀请的玩家 + KuangzhanInviteFailed = NewResultStatus(-36123, "邀请好友失败") + + // 不是被邀请的玩家 + KuangzhanNotInvited = NewResultStatus(-36124, "不是被邀请的玩家") + + // 协助信息已失效 + KuangzhanInviteExpired = NewResultStatus(-36125, "协助信息已失效") + + // 已经帮助过该玩家 + KuangzhanAlreadyHelped = NewResultStatus(-36135, "已经帮助过该玩家") + + // 对方任务已经完成 + KuangzhanTaskFinished = NewResultStatus(-36136, "对方任务已经完成") +) + +// 蜀山论剑 +var ( + // 战斗校验出现异常 + FightException = NewResultStatus(-1354, "FightException") + + // 比武大会玩家信息不存在 + HegeMonyPlayerInfoNotExist = NewResultStatus(-9701, "HegeMonyPlayerInfoNotExist") + + // 比武大会玩家已有匹配 + HegeMonyHasMatch = NewResultStatus(-9702, "HegeMonyHasMatch") + + // 比武大会玩家未找到匹配 + HegeMonyNotMatch = NewResultStatus(-9703, "HegeMonyNotMatch") + + // 比武大会玩家未找到阵容 + HegeMonyFormationNotExist = NewResultStatus(-9704, "HegeMonyFormationNotExist") + + // 比武大会对手玩家信息不存在 + HegeMonyTargetPlayerInfoNotExist = NewResultStatus(-9705, "HegeMonyTargetPlayerInfoNotExist") + + // 比武大会玩家未匹配 + HegeMonyPlayerNotMatch = NewResultStatus(-9706, "HegeMonyPlayerNotMatch") + + // 比武大会赛季不匹配 + SeasonNotMatch = NewResultStatus(-9707, "SeasonNotMatch") + + // 比武大会战报信息不存在 + HegeMonyReportNotExist = NewResultStatus(-9730, "HegeMonyReportNotExist") + + // 比武大会战斗未能找到匹配对手 + HegeMonyFightNotMatch = NewResultStatus(-9731, "HegeMonyFightNotMatch") + + // 比武大会荣耀玩家不存在 + HegeMonyHonorPlayerNotExist = NewResultStatus(-9732, "HegeMonyHonorPlayerNotExist") +) + +// 战报 +var ( + // 战报信息不存在 + FightReportNotExist = NewResultStatus(-9707, "FightReportNotExist") + + // 数据解析错误 + JsonDecodeDataError = NewResultStatus(-9708, "JsonDecodeDataError") +) + +// 喜信 +var ( + // 信息Id错误 + HappyNewsIdError = NewResultStatus(-40103, "HappyNewsIdError") + + // 信息序号错误 + HappyNewsOrderIdError = NewResultStatus(-40104, "HappyNewsOrderIdError") + + // 喜信获取好友失败 + HappyNewsGetFriendsError = NewResultStatus(-40105, "HappyNewsGetFriendsError") + + // 没有好友 + NoFriend = NewResultStatus(-8565, "NoFriend") +) + +// 仙盟远征 +var ( + // 仙盟远征错误 + GuildExpeditionError = NewResultStatus(-29900, "GuildExpeditionError") + + // 仙盟远征活动未开启 + GuildExpeditionActivityNotOpen = NewResultStatus(-29906, "GuildExpeditionActivityNotOpen") + + // 仙盟远征公会未匹配 + GuildExpeditionGuildNotMatch = NewResultStatus(-29908, "GuildExpeditionGuildNotMatch") + + // 仙盟远征阵容无法解析 + GuildExpeditionFormationUnknow = NewResultStatus(-29909, "GuildExpeditionFormationUnknow") + + // 仙盟远征房间信息不存在 + GuildExpeditionRoomInfoError = NewResultStatus(-29912, "GuildExpeditionRoomInfoError") + + // 仙盟远征阶段一挑战次数不足 + GuildExpeditionFirstStepTimesIsNotEnough = NewResultStatus(-29913, "GuildExpeditionfirstStepTimesIsNotEnough") + + // 仙盟远征阶段一挑战次数不足 + GuildExpeditionSecondStepTimesIsNotEnough = NewResultStatus(-29914, "GuildExpeditionSecondStepTimesIsNotEnough") + + // 仙盟远征房间已锁定 + GuildExpeditionRoomIsLock = NewResultStatus(-29915, "GuildExpeditionRoomIsLock") + + // 不是参与远征的仙盟成员 + GuildExpeditionIsNotJoinMember = NewResultStatus(-29916, "GuildExpeditionIsNotJoinMember") + + // 目标没有防御阵容 + GuildExpeditionTargetNoDefenceFormation = NewResultStatus(-29917, "GuildExpeditionTargetNoDefenceFormation") + + // 仙盟远征战斗验证失败 + GuildExpeditionFightValidFail = NewResultStatus(-29918, "GuildExpeditionFightValidFail") +) + +// 义结金兰 +var ( + // 玩家未结交,标记g_yjjl对象 + YjjlPlayerNotExist = NewResultStatus(-33501, "YjjlPlayerNotExist") + + // 玩家未结义,标记g_yjjl_player对象 + YjjlPlayerNotExistForPlayer = NewResultStatus(-33588, "YjjlPlayerNotExistForPlayer") + + // 玩家未结义,标记玩家对象属于第三者 + YjjlPlayerNotMatch = NewResultStatus(-33589, "YjjlPlayerNotMatch") + + // 玩家已结交 + YjjlPlayerHasExist = NewResultStatus(-33502, "YjjlPlayerHasExist") + + // 玩家已设置本特效 + QingyiLvSpecialeffectsIsExists = NewResultStatus(-33515, "QingyiLvSpecialeffectsIsExists") + + // 亲密付已达月上限 + HelpPayIsLimit = NewResultStatus(-33571, "HelpPayIsLimit") + + // 玩家当前未培养花卉 + FlowerNotExist = NewResultStatus(-33523, "FlowerNotExist") + + // 玩家当前花卉经验值已满 + FlowerExpIsMax = NewResultStatus(-33524, "FlowerExpIsMax") + + // 玩家当前花卉经验值未满 + FlowerExpNotEnough = NewResultStatus(-33525, "FlowerExpNotEnough") + + // 玩家已培养花卉 + FlowerIsExists = NewResultStatus(-33530, "FlowerIsExists") + + // 培养的花卉不匹配 + FlowerNotMatch = NewResultStatus(-33531, "FlowerNotMatch") + + // 花卉可收获,不能重置 + FlowerCanGet = NewResultStatus(-33532, "FlowerCanGet") + + // 同心榜CP组唯一标识错误 + RankGroupIdError = NewResultStatus(-33541, "RankGroupIdError") + + // 暂无留言 + RankTipNotExists = NewResultStatus(-33542, "RankTipNotExists") + + // 结交好友匹配错误 + RankCpMatchError = NewResultStatus(-33544, "RankCpMatchError") + + // 留言点赞和玩家结义数据不匹配 + RankCpMessageZanNotMatch = NewResultStatus(-33545, "RankCpMessageZanNotMatch") + + // 该玩家今日已点赞! + PlayerGood = NewResultStatus(-212301, "PlayerGood") + + // 义结金兰没有任务数据 + YjjlTaskNoData = NewResultStatus(-33547, "YjjlTaskNoData") + + // 金兰契奖励节点错误 + YjjlRewardNodeError = NewResultStatus(-33568, "YjjlRewardNodeError") + + // 亲密等级不足 + IntimacyLvNotEnough = NewResultStatus(-33569, "IntimacyLvNotEnough") + + // 活动期间获取的亲密度不足 + AddIntimacyNotEnough = NewResultStatus(-33583, "活动期间获取的亲密度不足") + + // 亲密度奖励已经领取 + HasAddIntimacyReward = NewResultStatus(-33584, "亲密度奖励已经领取") + + // 亲密等级不足,未解锁 + YjjlShowIsLock = NewResultStatus(-33586, "亲密等级不足,未解锁") + + // 活动还没结束 + TimedActivityNotClose = NewResultStatus(-2006, "活动还没结束") +) + +// 蜀山之巅(跨服Pvp冠军赛) +var ( + // 赛季未开启 + WeekChampionSeasonNotOpen = NewResultStatus(-4000, "WeekChampionSeasonNotOpen") + + // 赛季分组数据不存在 + WeekChampionNoExistGroupMatch = NewResultStatus(-4007, "WeekChampionNoExistGroupMatch") + + // 跨服蜀山之巅竞猜玩家错误 + WeekChampionBetPlayerError = NewResultStatus(-4008, "WeekChampionBetPlayerError") + + // 战报信息不存在 + WeekChampionFightReportNotExist = NewResultStatus(-4009, "WeekChampionFightReportNotExist") + + // 战报信息解析错误 + WeekChampionFightReportJsonDecodeDataError = NewResultStatus(-4010, "WeekChampionFightReportJsonDecodeDataError") +) + +var ( + // SocialSquareGiftNotExist 礼物不存在 + SocialSquareGiftNotExist = NewResultStatus(-40400, "SocialSquareGiftNotExist") + + // SocialSquarePlayerNotExist 社交广场玩家不存在 + SocialSquarePlayerNotExist = NewResultStatus(-40401, "SocialSquarePlayerNotExist") + + // SocialSquarePhotoNotExist 社交广场玩家不存在 + SocialSquarePhotoNotExist = NewResultStatus(-40402, "SocialSquarePhotoNotExist") +) + +var ( + // 模块错误 + VideoModuleError = NewResultStatus(-38183, "VideoModuleError") + + // 未检测到该战报 + VideoIdError = NewResultStatus(-38184, "VideoIdError") +) + +var ( + // 金兰宴玩家对象不是举办方 + YjjlFeastIsNotHost = NewResultStatus(-44108, "YjjlFeastIsNotHost") + + // 宴会还在准备中 + YjjlFeastIsPreraring = NewResultStatus(-44111, "YjjlFeastIsPreraring") + + // 金兰宴宴会已开始 + YjjlFeastRandomIsStart = NewResultStatus(-44112, "YjjlFeastRandomIsStart") + + // 金兰宴房间类型错误 + YjjlFeastRoomTypeError = NewResultStatus(-44116, "YjjlFeastRoomTypeError") + + // 金兰宴房间不存在 + YjjlFeastRoomNotExist = NewResultStatus(-44118, "YjjlFeastRoomNotExist") + + // 金兰宴玩家不能加入该房间 + YjjlFeastPlayerCantEnterThisRoom = NewResultStatus(-44119, "YjjlFeastPlayerCantEnterThisRoom") + + // 金兰宴玩家不能同时主办两场及以上的宴会 + YjjlFeastHostBuySamePlayer = NewResultStatus(-44120, "YjjlFeastHostBuySamePlayer") + + // 金兰宴宾客人数达到上限 + YjjlFeastRoomGuestMax = NewResultStatus(-44121, "YjjlFeastRoomGuestMax") + + // 金兰宴玩家不在房间 + YjjlFeastIsNotInScene = NewResultStatus(-44122, "YjjlFeastIsNotInScene") + + // 金兰宴不是来宾 + YjjlFeastIsHost = NewResultStatus(-44123, "YjjlFeastIsHost") + + // 金兰宴红包不存在 + YjjlFeastRedPacketNotExist = NewResultStatus(-44124, "YjjlFeastRedPacketNotExist") + + // 金兰宴红包已领取或已领完 + YjjlFeastRedPacketHadDraw = NewResultStatus(-44125, "YjjlFeastRedPacketHadDraw") +) + +var ( + // 全服红包id错误 + RedPacketIdError = NewResultStatus(-40501, "RedPacketIdError") + + // 全服红包已经领取完 + RedPacketHaveOver = NewResultStatus(-40502, "RedPacketHaveOver") + + // 全服红包不可领取 + RedPacketCanNotDraw = NewResultStatus(-40503, "RedPacketCanNotDraw") + + // 已经领取该红包 + RedPacketHaveDraw = NewResultStatus(-40504, "RedPacketHaveDraw") + + // 红包领取失败 + RedPacketDrawError = NewResultStatus(-40505, "RedPacketDrawError") +) + +// 跨服昆仑神虚 +var ( + // 昆仑之墟尚未开启 + GlobalCitywarMapNotOpen = NewResultStatus(-45003, "昆仑之墟尚未开启") + + // 仙盟未参与 + CitywarCsGuildNotParticipate = NewResultStatus(-50000, "CitywarCsGuildNotParticipate") + + // 跨服昆仑神虚地图不存在 + CitywarCsMapNotExist = NewResultStatus(-50001, "CitywarCsMapNotExist") + + // 跨服昆仑神虚城池不存在 + CitywarCsLandNotExist = NewResultStatus(-50002, "CitywarCsLandNotExist") + + // 跨服昆仑神虚城池未占领 + CitywarCsLandNotOccupy = NewResultStatus(-50003, "CitywarCsLandNotOccupy") + + // 跨服昆仑神虚城池正在战斗中 + CitywarCsLandIsFighting = NewResultStatus(-50004, "CitywarCsLandIsFighting") + + // 昆仑之墟节点坐标不匹配 + CitywarCsAxisNotRight = NewResultStatus(-50011, "CitywarCsAxisNotRight") + + // 昆仑之墟玩家队伍不存在 + CitywarCsPlayerTeamNotExist = NewResultStatus(-50012, "CitywarCsPlayerTeamNotExist") + + // 昆仑之墟玩家队伍复活中 + CitywarCsPlayerTeamReviving = NewResultStatus(-50013, "CitywarCsPlayerTeamReviving") + + // 昆仑之墟玩家已派遣 + CitywarCsPlayerTeamIsMoving = NewResultStatus(-50014, "CitywarCsPlayerTeamIsMoving") + + // 昆仑之墟玩家未派遣 + CitywarCsPlayerTeamNoMoving = NewResultStatus(-50015, "CitywarCsPlayerTeamNoMoving") + + // 昆仑之墟玩家玩家队伍已经死亡 + CitywarCsTeamIdDead = NewResultStatus(-50016, "CitywarCsTeamIdDead") + + // 没有奖励可以领取 + CitywarCsNotDrawReward = NewResultStatus(-50017, "CitywarCsNotDrawReward") + + // 昆仑之墟休战中 + CitywarCsNotFightTime = NewResultStatus(-50018, "CitywarCsNotFightTime") + + // 昆仑之墟已经结束 + CitywarCsIsEnd = NewResultStatus(-50019, "CitywarCsIsEnd") + + // 玩家昆仑之墟出征次数不足 + CitywarCsFightNumNotEnough = NewResultStatus(-50020, "CitywarCsFightNumNotEnough") + + // 昆仑之墟出生地无法操作 + CitywarCsLandIsBirth = NewResultStatus(-50021, "CitywarCsLandIsBirth") + + // 昆仑之墟队伍已放入仙盟大营中 + CitywarCsPlayerTeamInGuildTeam = NewResultStatus(-50022, "CitywarCsPlayerTeamInGuildTeam") + + // 昆仑之墟队伍不在仙盟大营中 + CitywarCsPlayerTeamNotInGuildTeam = NewResultStatus(-50023, "CitywarCsPlayerTeamNotInGuildTeam") + + // 昆仑神墟未选择仙盟大营中队伍 + CitywarCsGuildTeamNotExist = NewResultStatus(-50024, "CitywarCsGuildTeamNotExist") + + // 昆仑神墟地块正在燃烧 + CitywarCsLandIsFire = NewResultStatus(-50025, "CitywarCsLandIsFire") + + // 非相邻地不允许攻击 + CitywarCsLandNotAdjoinNotAtk = NewResultStatus(-50027, "CitywarCsLandNotAdjoinNotAtk") +) + +var ( + // 巅峰对决信息不存在 + PeakInfoNotExist = NewResultStatus(-40804, "PeakInfoNotExist") + + // 该位置不是空位置 + PeakRankNotEmpty = NewResultStatus(-40805, "PeakRankNotEmpty") + + // 不能挑战低于自己的对手 + PeakCantFight = NewResultStatus(-40806, "PeakCantFight") + + // 排位已经变化 + PeakNeedRefresh = NewResultStatus(-40808, "PeakNeedRefresh") + + // 当前排名不可挑战,挑战列表已刷新 + PeakNeedRefreshTwo = NewResultStatus(-40814, "PeakNeedRefreshTwo") + + // 不可挑战前三 + PeakCantFightTopThree = NewResultStatus(-40815, "PeakCantFightTopThree") +) + +// 蜀山论剑PvpTournament +var ( + // 玩家信息不存在 + PvpTournamentInfoNotExist = NewResultStatus(-40901, "PvpTournamentInfoNotExist") + + // 玩家未找到阵容 + PvpTournamentFormationNotExist = NewResultStatus(-40902, "PvpTournamentFormationNotExist") + + // 排位已经变化 + PvpTournamentNeedRefresh = NewResultStatus(-40903, "PvpTournamentNeedRefresh") + + // 不能挑战太靠前的玩家 + PvpTournamentCantFight = NewResultStatus(-40907, "PvpTournamentCantFight") +) + +// 幸运彩卡luckyCard +var ( + // 幸运彩卡奖励配置不存在 + LuckyCardRewardConfigNotExist = NewResultStatus(-42610, "LuckyCardRewardConfigNotExist") + + // 幸运彩卡奖励不足 + LuckyCardRewardNotEnough = NewResultStatus(-42611, "LuckyCardRewardNotEnough") + + // 幸运彩卡青钻奖励不足 + LuckyCardRewardQingzuanNotEnough = NewResultStatus(-42612, "LuckyCardRewardQingzuanNotEnough") + + // 用户青钻额度不足 + LuckyCardQingzuanNotEnough = NewResultStatus(-42613, "LuckyCardQingzuanNotEnough") + + // 交换信息不存在 + LuckyCardExchangeMessageNotExist = NewResultStatus(-42615, "LuckyCardExchangeMessageNotExist") +) + +// 新版金兰宴 +var ( + // NewFeastHaveAppointment 已经预约过 + NewFeastHaveAppointment = NewResultStatus(-41101, "NewFeastHaveAppointment") + + // NewFeastAppointmentTimeOut 时间已过 + NewFeastAppointmentTimeOut = NewResultStatus(-41102, "NewFeastAppointmentTimeOut") + + // NotAppointment 没有预约 + NewFeastNotAppointment = NewResultStatus(-41103, "NewFeastNotAppointment") + + // NewFeastHaveInvite 已经邀请 + NewFeastHaveInvite = NewResultStatus(-41104, "NewFeastHaveInvite") + + // NewFeastAppointmentOverdue 宴会已过期 + NewFeastAppointmentOverdue = NewResultStatus(-41105, "NewFeastAppointmentOverdue") + + // 金兰宴房间不存在 + NewFeastRoomNotExist = NewResultStatus(-41106, "NewFeastRoomNotExist") + + // 金兰宴玩家不存在 + NewFeastPlayerNotInRoom = NewResultStatus(-41107, "NewFeastPlayerNotInRoom") + + // 金兰宴对方玩家不存在 + NewFeastTargetPlayerPlayerNotInRoom = NewResultStatus(-41108, "NewFeastTargetPlayerPlayerNotInRoom") + + // 道具已被抢 + NewFeastPropBeRobbed = NewResultStatus(-41109, "NewFeastPropBeRobbed") + + // 变身球数量不足 + NewFeastBallNotEnough = NewResultStatus(-41110, "NewFeastBallNotEnough") + + // 已拥有烟花 + NewFeastHaveFirework = NewResultStatus(-41111, "NewFeastHaveFirework") + + // 宴会已结束 + NewFeastEnd = NewResultStatus(-41112, "NewFeastEnd") + + // 已经品菜过了 + NewFeastHaveEatFood = NewResultStatus(-41113, "NewFeastHaveEatFood") + + // 不在品菜时间内 + NewFeastNotEatFoodTime = NewResultStatus(-41114, "NewFeastNotEatFoodTime") + + // 房间人气不足 + NewFeastRoomPopularNotEnough = NewResultStatus(-41115, "NewFeastRoomPopularNotEnough") + + // 密码不正确 + NewFeastBoxPasswordError = NewResultStatus(-41116, "NewFeastBoxPasswordError") + + // 不是房主 + NewFeastNotHsot = NewResultStatus(-41117, "NewFeastNotHsot") + + // 已经领取 + NewFeastBoxHaveDraw = NewResultStatus(-41118, "NewFeastBoxHaveDraw") + + // 房间人数已满 + NewFeastRoomPlayerMax = NewResultStatus(-41119, "NewFeastRoomPlayerMax") + + // 不能邀请 + NewFeastCantInvite = NewResultStatus(-41120, "NewFeastCantInvite") + + // 被其他人预约 + NewFeastAppointmentByOthers = NewResultStatus(-41121, "NewFeastAppointmentByOthers") +) + +// NoWorldBossDamageData 世界boss1.0 +var ( + NoWorldBossDamageData = NewResultStatus(-41200, "NoWorldBossDamageData") +) +var ( + // 拍卖行商品不存在 + AuctionGoodsNotExist = NewResultStatus(-41301, "AuctionGoodsNotExist") + + // 拍卖行加价失败 + AuctionAddPriceErr = NewResultStatus(-41302, "AuctionAddPriceErr") + + // 仙盟拍卖暂无商品 + AuctionGuildNoGoods = NewResultStatus(-41303, "AuctionGuildNoGoods") + + // 仙盟拍卖未结束 + AuctionGuildNotEnd = NewResultStatus(-41304, "AuctionGuildNotEnd") + + // 分红已经领取 + AuctionBonusHaveDraw = NewResultStatus(-41305, "AuctionBonusHaveDraw") + + // 暂无分红 + AuctionNoBonus = NewResultStatus(-41306, "AuctionNoBonus") + + // 不能取消关注 + AuctionCantCancelFollow = NewResultStatus(-41307, "AuctionCantCancelFollow") + + // 拍卖未开始 + AuctionNotStart = NewResultStatus(-41308, "AuctionNotStart") + + // 该商品已经售出 + AuctionGoodsHaveAuction = NewResultStatus(-41309, "AuctionGoodsHaveAuction") + + // 该商品已经流拍 + AuctionGoodsHaveNoPrice = NewResultStatus(-41310, "AuctionGoodsHaveNoPrice") + + // 玩家金龙不足 + AuctionPlayerGoldNotEnough = NewResultStatus(-41311, "AuctionPlayerGoldNotEnough") + + //该商品拍卖时间已结束 + AuctionGoodsEnd = NewResultStatus(-41312, "AuctionGoodsEnd") + + //未参与决战神虚,不可分红 + AuctionCantBonus = NewResultStatus(-41313, "AuctionCantBonus") +) + +var ( + // 交易行商品不存在 + TradeProductNotExistInfoNotExist = NewResultStatus(-41500, "TradeProductNotExistInfoNotExist") + + // 交易行审核类型不存在 + TradeAuditTypeNotExist = NewResultStatus(-41501, "TradeAuditTypeNotExist") +) diff --git a/trunk/center/common/utils/commonUtil.go b/trunk/center/common/utils/commonUtil.go new file mode 100644 index 0000000..1da475a --- /dev/null +++ b/trunk/center/common/utils/commonUtil.go @@ -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 +} diff --git a/trunk/center/common/webServer/apiContext.go b/trunk/center/common/webServer/apiContext.go new file mode 100644 index 0000000..dcbbee5 --- /dev/null +++ b/trunk/center/common/webServer/apiContext.go @@ -0,0 +1,218 @@ +package webServer + +import ( + "encoding/json" + "fmt" + "goutil/jsonUtil" + "goutil/logUtilPlus" + "goutil/typeUtil" + "goutil/zlibUtil" + "io/ioutil" + "net/http" +) + +// ApiContext +// @description: Api请求上下文对象 +type ApiContext struct { + // 请求对象 + request *http.Request + + // 应答写对象 + responseWriter http.ResponseWriter + + // 请求数据 + requestBytes []byte + + // 字典形式的请求数据 + requestDataByMap typeUtil.MapData + + //获取头部信息 + header *http.Header +} + +// GetRequest +// @description: 获取请求对象 +// parameter: +// @receiver this: this +// return: +// @*http.Request: 请求对象 +func (this *ApiContext) GetRequest() *http.Request { + return this.request +} + +// GetResponseWriter +// @description: 获取应答写对象 +// parameter: +// @receiver this: this +// return: +// @http.ResponseWriter: 应答写对象 +func (this *ApiContext) GetResponseWriter() http.ResponseWriter { + return this.responseWriter +} + +// GetRequestBytes +// @description: 获取请求字节数据 +// parameter: +// @receiver this: this +// return: +// @[]byte: 请求字节数组 +func (this *ApiContext) GetRequestBytes() []byte { + return this.requestBytes +} + +// readContent +// @description: 读取内容 +// parameter: +// @receiver this: this 请求对象 +// @isZlib: 是否数据压缩 +// return: +// @content: 二进制格式的内容 +// @err: 错误对象 +func (this *ApiContext) readContent(isZlib bool) (content []byte, err error) { + var buffer []byte + + defer this.request.Body.Close() + if buffer, err = ioutil.ReadAll(this.request.Body); err != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("url:%s,读取数据出错,错误信息为:%s", this.request.RequestURI, err)) + return + } + + // 不压缩,则直接返回 + if isZlib == false { + this.requestBytes = buffer + + return buffer, err + } + + // 解压数据 + if content, err = zlibUtil.Decompress(buffer); err != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("url:%s,解压缩数据出错,错误信息为:%s", this.request.RequestURI, err)) + return + } + + this.requestBytes = content + + return +} + +// Unmarshal +// @description: 反序列化 +// parameter: +// @receiver this: this +// @obj: 反序列化结果数据 +// return: +// @error: 反序列化错误数据 +func (this *ApiContext) Unmarshal(obj interface{}) error { + contentData := this.GetRequestBytes() + + var errMsg error + + // 反序列化 + if obj, errMsg = json.Marshal(contentData); errMsg != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("反序列化%s出错,错误信息为:%s", string(contentData), errMsg.Error())) + return errMsg + } + + return nil +} + +// RequestDataByMap +// @description: 获取请求的map格式数据 +// parameter: +// @receiver this: this +// return: +// @typeUtil.MapData: map数据 +// @error: 错误信息 +func (this *ApiContext) RequestDataByMap() (typeUtil.MapData, error) { + if this.requestDataByMap != nil { + return this.requestDataByMap, nil + } + + var data typeUtil.MapData + if err := this.Unmarshal(&data); err != nil { + return nil, err + } + + this.requestDataByMap = data + + return this.requestDataByMap, nil +} + +// RequestDataBySlice +// @description: 获取请求的slice格式数据 +// parameter: +// @receiver this: this +// return: +// @[]interface{}: 返回的数组数据 +func (this *ApiContext) RequestDataBySlice() []interface{} { + result := make([]interface{}, 0, 8) + for _, value := range this.requestDataByMap { + result = append(result, value) + } + return result +} + +// RequestDataBySlice2 +// @description: 获取请求的slice格式数据 +// parameter: +// @receiver this: this +// return: +// @[]interface{}: 返回的数组数据 +// @error: +func (this *ApiContext) RequestDataBySlice2() (interface{}, error) { + contentData := this.GetRequestBytes() + + // 反序列化 + var result interface{} + var errMsg error + if result, errMsg = jsonUtil.UnMarshalWithNumberType(string(contentData)); errMsg != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("用[]interface{}反序列化%s出错,错误信息为:%s", string(contentData), errMsg.Error())) + return nil, errMsg + } + return result, nil +} + +// RequestDataBySlice2ByJson +// @description: 获取请求的slice格式数据 +// parameter: +// @receiver this: this +// return: +// @[]interface{}: 返回的数组数据 +// @error: +func (this *ApiContext) RequestDataBySlice2ByJson() ([]interface{}, error) { + contentData := this.GetRequestBytes() + + // 反序列化 + result := make([]interface{}, 0, 8) + if errMsg := json.Unmarshal(contentData, &result); errMsg != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("用[]interface{}反序列化%s出错,错误信息为:%s", string(contentData), errMsg.Error())) + return nil, errMsg + } + + return result, nil +} + +// NewApiContext +// @description: 新建API上下文对象 +// parameter: +// @_request: 请求对象 +// @_responseWriter: 应答写对象 +// @isZlib: 数据是否压缩 +// return: +// @*ApiContext: 上下文 +// @error: 错误信息 +func NewApiContext(_request *http.Request, _responseWriter http.ResponseWriter, isZlib bool) (*ApiContext, error) { + context := &ApiContext{ + request: _request, + header: &_request.Header, + responseWriter: _responseWriter, + } + + // 读取数据 + _, errMsg := context.readContent(isZlib) + if errMsg != nil { + return nil, errMsg + } + + return context, nil +} diff --git a/trunk/center/common/webServer/apiHandler.go b/trunk/center/common/webServer/apiHandler.go new file mode 100644 index 0000000..d5f7c76 --- /dev/null +++ b/trunk/center/common/webServer/apiHandler.go @@ -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, + } +} diff --git a/trunk/center/common/webServer/apiHandlerMgr.go b/trunk/center/common/webServer/apiHandlerMgr.go new file mode 100644 index 0000000..8d6cebf --- /dev/null +++ b/trunk/center/common/webServer/apiHandlerMgr.go @@ -0,0 +1,56 @@ +package webServer + +import ( + "fmt" + "net/http" +) + +var ( + // 处理方法集合 + // Key:API名 + handlerData = make(map[string]*ApiHandler) + + // 文档方法 + remarkFunc func(w http.ResponseWriter, r *http.Request) +) + +// RegisterRemarkFunc +// @description: 注册文档api方法 +// parameter: +// @_remarkFunc: _remarkFunc +// return: +func RegisterRemarkFunc(_remarkFunc func(w http.ResponseWriter, r *http.Request)) { + remarkFunc = _remarkFunc +} + +// RegisterHandleFunc +// @description: 注册处理方法 +// parameter: +// @apiName: API名称 +// @callback: 回调方法 +// @paramNames: 参数名称集合 +// return: +func RegisterHandleFunc(apiName string, callback HandleFunc, paramNames ...string) { + apiFullName := fmt.Sprintf("/API/%s", apiName) + + if _, exist := handlerData[apiFullName]; exist { + panic(fmt.Errorf("重复注册处理函数:%s", apiFullName)) + } + + handlerData[apiFullName] = newApiHandler(apiFullName, callback, paramNames...) +} + +// GetHandleFunc +// @description: 获取请求方法 +// parameter: +// @apiFullName: 方法名称 +// return: +// @*ApiHandler: 请求方法 +// @bool: 是否存在 +func GetHandleFunc(apiFullName string) (*ApiHandler, bool) { + if requestFuncObj, exists := handlerData[apiFullName]; exists { + return requestFuncObj, exists + } + + return nil, false +} diff --git a/trunk/center/common/webServer/reflect.go b/trunk/center/common/webServer/reflect.go new file mode 100644 index 0000000..79dfc1a --- /dev/null +++ b/trunk/center/common/webServer/reflect.go @@ -0,0 +1,431 @@ +package webServer + +import ( + config "common/configsYaml" + "common/resultStatus" + "goutil/logUtilPlus" + "reflect" + "strconv" + "strings" +) + +const ( + // 供客户端访问的模块的后缀 + con_ModuleSuffix = "Module" + + // 定义用于分隔模块名称和方法名称的分隔符 + con_DelimeterOfObjAndMethod = "_" +) + +var ( + // 定义存放所有方法映射的变量 + methodMap = make(map[string]*methodAndInOutTypes) + + // 函数返回值类型 + responseType reflect.Type = reflect.TypeOf(new(ResponseObject)) +) + +// getStructName +// +// @description: 获取结构体类型的名称 +// +// parameter: +// +// @structType: 结构体类型 +// +// return: +// +// @string: 结构体类型的名称 +func getStructName(structType reflect.Type) string { + reflectTypeStr := structType.String() + reflectTypeArr := strings.Split(reflectTypeStr, ".") + + return reflectTypeArr[len(reflectTypeArr)-1] +} + +// getFullModuleName +// +// @description: 获取完整的模块名称 +// +// parameter: +// +// @moduleName: 模块名称 +// +// return: +// +// @string: 完整的模块名称 +func getFullModuleName(moduleName string) string { + return moduleName + con_ModuleSuffix +} + +// getFullMethodName +// +// @description: 获取完整的方法名称 +// +// parameter: +// +// @structName: 结构体名称 +// @methodName: 方法名称 +// +// return: +// +// @string: 完整的方法名称 +func getFullMethodName(structName, methodName string) string { + return structName + con_DelimeterOfObjAndMethod + methodName +} + +// resolveMethodInOutParams +// +// @description: 解析方法的输入输出参数 +// +// parameter: +// +// @method: 方法对应的反射值 +// +// return: +// +// @inTypes: 输入参数类型集合 +// @outTypes: 输出参数类型集合 +func resolveMethodInOutParams(method reflect.Value) (inTypes []reflect.Type, outTypes []reflect.Type) { + methodType := method.Type() + for i := 0; i < methodType.NumIn(); i++ { + inTypes = append(inTypes, methodType.In(i)) + } + + for i := 0; i < methodType.NumOut(); i++ { + outTypes = append(outTypes, methodType.Out(i)) + } + + return +} + +// RegisterFunction +// +// @description: 将需要对客户端提供方法的对象进行注册 +// +// parameter: +// +// @structObject: 对象 +// +// return: +func RegisterFunction(structObject interface{}) { + // 获取structObject对应的反射 Type 和 Value + reflectValue := reflect.ValueOf(structObject) + reflectType := reflect.TypeOf(structObject) + + // 提取对象类型名称 + structName := getStructName(reflectType) + + // 获取structObject中返回值为responseObject的方法 + for i := 0; i < reflectType.NumMethod(); i++ { + // 获得方法名称 + methodName := reflectType.Method(i).Name + + // 获得方法及其输入参数的类型列表 + method := reflectValue.MethodByName(methodName) + inTypes, outTypes := resolveMethodInOutParams(method) + + // 判断输出参数数量是否正确 + if len(outTypes) != 1 { + continue + } + + // 判断返回值是否为responseObject + if outTypes[0] != responseType { + continue + } + + // 添加到列表中 + methodMap[getFullMethodName(structName, methodName)] = newmethodAndInOutTypes(method, inTypes, outTypes) + } +} + +// CallFunction +// +// @description: 调用方法 +// +// parameter: +// +// @requestObj: 客户端对象 +// +// return: +// +// @responseObj: 请求对象 +func CallFunction(requestObj *RequestObject) (responseObj *ResponseObject) { + responseObj = GetInitResponseObj() + + var methodAndInOutTypes *methodAndInOutTypes + var ok bool + + // 根据传入的ModuleName和MethodName找到对应的方法对象 + key := getFullMethodName(requestObj.ModuleName, requestObj.MethodName) + if methodAndInOutTypes, ok = methodMap[key]; !ok { + logUtilPlus.ErrorLog("找不到指定的方法:%s", key) + responseObj.SetResultStatus(resultStatus.NotSpecificMethod) + return + } + + // 判断参数数量是否相同 + inTypesLength := len(methodAndInOutTypes.InTypes) + paramLength := len(requestObj.Parameters) + if paramLength != inTypesLength { + logUtilPlus.ErrorLog("传入的参数数量不符,本地方法%s的参数数量:%d,传入的参数数量为:%d", key, inTypesLength, paramLength) + responseObj.SetResultStatus(resultStatus.ParamNotMatch) + return + } + + // 构造参数 + in := make([]reflect.Value, inTypesLength) + for i := 0; i < inTypesLength; i++ { + inTypeItem := methodAndInOutTypes.InTypes[i] + paramItem := requestObj.Parameters[i] + + // 已支持类型:Client,Player(非基本类型) + // 已支持类型:Bool,Int,Int8,Int16,Int32,Int64,Uint,Uint8,Uint16,Uint32,Uint64,Float32,Float64,String + // 已支持类型:以及上面所列出类型的Slice类型 + // 未支持类型:Uintptr,Complex64,Complex128,Array,Chan,Func,Interface,Map,Ptr,Struct,UnsafePointer + // 由于byte与int8同义,rune与int32同义,所以并不需要单独处理 + // 枚举参数的类型,并进行类型转换 + switch inTypeItem.Kind() { + case reflect.Bool: + if paramBool, ok := paramItem.(bool); ok { + in[i] = reflect.ValueOf(paramBool) + } + case reflect.Int: + if paramInt, ok := paramItem.(int); ok { + in[i] = reflect.ValueOf(int(paramInt)) + } + case reflect.Int8: + if paramInt8, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(int8(paramInt8)) + } + case reflect.Int16: + if paramInt16, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(int16(paramInt16)) + } + case reflect.Int32: + if paramInt32, ok := paramItem.(int32); ok { + in[i] = reflect.ValueOf(paramInt32) + } else if paramFloat64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(int32(paramFloat64)) + } + case reflect.Int64: + if paramInt64, ok := paramItem.(int64); ok { + in[i] = reflect.ValueOf(paramInt64) + } else if paramUint64, ok := paramItem.(uint64); ok { + in[i] = reflect.ValueOf(int64(paramUint64)) + } else if paramString, ok := paramItem.(string); ok { + + i64, err := strconv.ParseInt(paramString, 10, 64) + if err == nil { + in[i] = reflect.ValueOf(i64) + } + } else if paramFloat64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(int64(paramFloat64)) + } + case reflect.Uint: + if paramUint, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(uint(paramUint)) + } + case reflect.Uint8: + if paramUint8, ok := paramItem.(uint8); ok { + in[i] = reflect.ValueOf(uint8(paramUint8)) + } + case reflect.Uint16: + if paramUint16, ok := paramItem.(uint16); ok { + in[i] = reflect.ValueOf(uint16(paramUint16)) + } + case reflect.Uint32: + if paramUint32, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(uint32(paramUint32)) + } + case reflect.Uint64: + if paramUint64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(uint64(paramUint64)) + } + case reflect.Float32: + if paramFloat32, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(float32(paramFloat32)) + } + case reflect.Float64: + if paramFloat64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.String: + if paramString, ok := paramItem.(string); ok { + in[i] = reflect.ValueOf(paramString) + } + case reflect.Slice: + // 如果是Slice类型,则需要对其中的项再次进行类型判断及类型转换 + if param_interface, ok := paramItem.([]interface{}); ok { + switch inTypeItem.String() { + case "[]bool": + params_inner := make([]bool, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_bool, ok := param_interface[i].(bool); ok { + params_inner[i] = param_bool + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int": + params_inner := make([]int, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(int); ok { + params_inner[i] = int(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int8": + params_inner := make([]int8, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(float64); ok { + params_inner[i] = int8(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int16": + params_inner := make([]int16, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(float64); ok { + params_inner[i] = int16(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int32": + params_inner := make([]int32, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + param_float64, ok := param_interface[i].(int32) + if ok { + params_inner[i] = int32(param_float64) + continue + } else { + param_int16, right := param_interface[i].(uint16) + if right == true { + params_inner[i] = int32(param_int16) + } + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]int64": + params_inner := make([]int64, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(int64); ok { + params_inner[i] = int64(param_float64) + } else if param_uint64, ok := param_interface[i].(uint64); ok { + params_inner[i] = int64(param_uint64) + } else if param_string, ok := param_interface[i].(string); ok { + + i64, err := strconv.ParseInt(param_string, 10, 64) + + if err == nil { + params_inner[i] = i64 + } + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]uint": + params_inner := make([]uint, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(uint); ok { + params_inner[i] = uint(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + // case "[]uint8": 特殊处理 + case "[]uint16": + params_inner := make([]uint16, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(uint16); ok { + params_inner[i] = uint16(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]uint32": + params_inner := make([]uint32, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(uint32); ok { + params_inner[i] = uint32(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]uint64": + params_inner := make([]uint64, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(uint64); ok { + params_inner[i] = uint64(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]float32": + params_inner := make([]float32, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(float64); ok { + params_inner[i] = float32(param_float64) + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]float64": + params_inner := make([]float64, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_float64, ok := param_interface[i].(float64); ok { + params_inner[i] = param_float64 + } + } + in[i] = reflect.ValueOf(params_inner) + case "[]string": + params_inner := make([]string, len(param_interface), len(param_interface)) + for i := 0; i < len(param_interface); i++ { + if param_string, ok := param_interface[i].(string); ok { + params_inner[i] = param_string + } + } + in[i] = reflect.ValueOf(params_inner) + } + } else if inTypeItem.String() == "[]uint8" { // 由于[]uint8在传输过程中会被转化成字符串,所以单独处理; + if param_string, ok := paramItem.(string); ok { + param_uint8 := ([]uint8)(param_string) + in[i] = reflect.ValueOf(param_uint8) + } + } + } + } + + // 判断是否有无效的参数(传入的参数类型和方法定义的类型不匹配导致没有赋值) + for _, item := range in { + if reflect.Value.IsValid(item) == false { + logUtilPlus.ErrorLog("type:%v,value:%v.方法%s传入的参数%v无效", reflect.TypeOf(item), reflect.ValueOf(item), key, requestObj.Parameters) + responseObj.SetResultStatus(resultStatus.ParamInValid) + return + } + } + + // 传入参数,调用方法 + if config.DEBUG { + if requestObj.MethodName != "GetRefreshData" { + logUtilPlus.DebugLog("Begin Call Func:module:%v, method:%v,inParams:%v\n\n", requestObj.ModuleName, requestObj.MethodName, in) + } + } + + out := methodAndInOutTypes.Method.Call(in) + + if config.DEBUG { + if requestObj.MethodName != "GetRefreshData" { + for i, v := range in { + logUtilPlus.DebugLog("\nparams %v,%v\n", i, v) + } + } + } + + // 并输出结果到客户端(由于只有一个返回值,所以取out[0]) + if responseObj, ok = (&out[0]).Interface().(*ResponseObject); !ok { + logUtilPlus.ErrorLog("返回值类型推断为ResponseObject 出错, tyep is :%v", reflect.TypeOf(out[0])) + responseObj.SetResultStatus(resultStatus.ParamInValid) + return + } + if config.DEBUG { + if requestObj.MethodName != "GetRefreshData" { + logUtilPlus.DebugLog("返回数据:code:%v, data:%v, mess:%v\n\n", responseObj.Code, responseObj.Value, responseObj.Message) + } + } + return +} diff --git a/trunk/center/common/webServer/reflectMethod.go b/trunk/center/common/webServer/reflectMethod.go new file mode 100644 index 0000000..0d781a3 --- /dev/null +++ b/trunk/center/common/webServer/reflectMethod.go @@ -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, + } +} diff --git a/trunk/center/common/webServer/reflectRequestObject.go b/trunk/center/common/webServer/reflectRequestObject.go new file mode 100644 index 0000000..89a6088 --- /dev/null +++ b/trunk/center/common/webServer/reflectRequestObject.go @@ -0,0 +1,31 @@ +package webServer + +// RequestObject +// @description: 请求对象 +type RequestObject struct { + // 以下属性是由客户端直接传入的,可以直接反序列化直接得到的 + // 请求的模块名称 + ModuleName string + + // 请求的方法名称 + MethodName string + + // 请求的参数数组 + Parameters []interface{} +} + +// NewRequestObject +// @description: NewRequestObject +// parameter: +// @_ModuleName: _ModuleName +// @_MethodName: _MethodName +// @_Parameters: _Parameters +// return: +// @*RequestObject: RequestObject +func NewRequestObject(_ModuleName string, _MethodName string, _Parameters []interface{}) *RequestObject { + return &RequestObject{ + ModuleName: _ModuleName, + MethodName: _MethodName, + Parameters: _Parameters, + } +} diff --git a/trunk/center/common/webServer/responseObject.go b/trunk/center/common/webServer/responseObject.go new file mode 100644 index 0000000..0b79498 --- /dev/null +++ b/trunk/center/common/webServer/responseObject.go @@ -0,0 +1,83 @@ +package webServer + +import ( + "common/resultStatus" +) + +// ResponseObject +// @description: 响应对象 +type ResponseObject struct { + // 响应结果的状态值 + Code resultStatus.StatusCode + + // Status 状态 + Status int32 + + // 响应结果的状态值所对应的描述信息 + Message string + + // 响应结果的数据 + Value interface{} +} + +// SetResultStatus +// @description: 设置响应结果的状态值 +// parameter: +// @receiver this: this +// @rs: 响应结果的状态值 +// return: +// @*ResponseObject: 响应结果对象 +func (this *ResponseObject) SetResultStatus(rs resultStatus.ResultStatus) *ResponseObject { + this.Code = rs.Code() + this.Message = rs.Message() + + return this +} + +// SetData +// @description: 设置响应对象数据 +// parameter: +// @receiver this: this +// @data: 响应结果的数据 +// return: +// @*ResponseObject: 响应结果对象 +func (this *ResponseObject) SetData(data interface{}) *ResponseObject { + this.Value = data + return this +} + +// IsSuccess +// @description: 是否是请求成功 +// parameter: +// @receiver this:this +// return: +// @bool:是请求成功 +func (this *ResponseObject) IsSuccess() bool { + return this.Code == resultStatus.Success.Code() +} + +// SetCodeStatus +// @description: 同步code和status状态 +// parameter: +// @receiver this:this +// return: +func (this *ResponseObject) SetCodeStatus() { + if this.Code == resultStatus.Success.Code() && resultStatus.StatusCode(this.Status) != resultStatus.Success.Code() { + this.Code = resultStatus.StatusCode(this.Status) + } + + return +} + +// GetInitResponseObj +// @description: 获取初始的响应对象 +// parameter: +// return: +// @*ResponseObject: 响应对象 +func GetInitResponseObj() *ResponseObject { + return &ResponseObject{ + Code: resultStatus.Success.Code(), + Message: "", + Value: nil, + } +} diff --git a/trunk/center/common/webServer/result.go b/trunk/center/common/webServer/result.go new file mode 100644 index 0000000..02af02d --- /dev/null +++ b/trunk/center/common/webServer/result.go @@ -0,0 +1,43 @@ +package webServer + +import ( + "encoding/json" + "fmt" + "goutil/logUtilPlus" + "net/http" + "strconv" +) + +// responseResult +// @description: responseResult +// parameter: +// @w: w +// @responseObj: responseObj +// return: +func responseResult(w http.ResponseWriter, responseObj *ResponseObject) { + b, err := json.Marshal(responseObj) + if err != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("序列化输出结果%v出错", responseObj)) + return + } + + w.Header().Add("Content-Length", strconv.Itoa(len(b))) + w.Write(b) +} + +// responseResultByJson +// @description: responseResult +// parameter: +// @w: w +// @responseObj: responseObj +// return: +func responseResultByJson(w http.ResponseWriter, responseObj *ResponseObject) { + b, err := json.Marshal(responseObj) + if err != nil { + logUtilPlus.ErrorLog(fmt.Sprintf("序列化输出结果%v出错", responseObj)) + return + } + + w.Header().Add("Content-Length", strconv.Itoa(len(b))) + w.Write(b) +} diff --git a/trunk/center/common/webServer/serverMux.go b/trunk/center/common/webServer/serverMux.go new file mode 100644 index 0000000..3aca796 --- /dev/null +++ b/trunk/center/common/webServer/serverMux.go @@ -0,0 +1,122 @@ +package webServer + +import ( + config "common/configsYaml" + "common/resultStatus" + "common/utils" + "encoding/json" + "fmt" + "framework/ipMgr" + "goutil/logUtilPlus" + "goutil/stringUtil" + "goutil/webUtil" + "net/http" + "strings" +) + +// selfDefineMux +// +// @description: 定义自定义的Mux对象 +type selfDefineMux struct { +} + +// ServeHTTP +// +// @description: ServeHTTP +// +// parameter: +// +// @receiver mux: mux +// @w: w +// @r: r +// +// return: +func (mux *selfDefineMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + responseObj := GetInitResponseObj() + defer utils.LogReqErrorRecover(r) + + // 判断是否是接口文档 + if strings.Contains(r.RequestURI, "Remarks") && r.Method == "GET" { + remarkFunc(w, r) + return + } + + // 判断是否是POST方法 + if r.Method != "POST" { + responseResult(w, responseObj.SetResultStatus(resultStatus.OnlySupportPOST)) + return + } + + // 验证IP是否正确 + if config.DEBUG == false && ipMgr.IsIpValid(webUtil.GetRequestIP(r)) == false { + logUtilPlus.ErrorLog(fmt.Sprintf("请求的IP:%s无效", webUtil.GetRequestIP(r))) + responseResult(w, responseObj.SetResultStatus(resultStatus.IPForbid)) + return + } + + // 构造contex + context, errMsg := NewApiContext(r, w, false) + if errMsg != nil { + // 输出结果 + responseResult(w, responseObj.SetResultStatus(resultStatus.APIDataError)) + return + } + + // 根据路径选择不同的处理方法 + if handlerFunObj, exists := GetHandleFunc(r.RequestURI); exists { + defer func() { + if config.DEBUG { + b, _ := json.Marshal(responseObj) + msg := fmt.Sprintf("API:%v 请求数据:%v;返回数据:%s;", + r.RequestURI, string(context.GetRequestBytes()), string(b)) + logUtilPlus.DebugLog(msg) + } + }() + + // 输出结果 + responseObj := handlerFunObj.HandleFun()(context) + responseResult(w, responseObj) + return + } + + // 通过反射选择不同的方法 + strs := stringUtil.Split(r.RequestURI, []string{"/"}) + var params []interface{} + var err error + isJson := false + + // 参数错误 + if len(strs) != 2 { + responseResult(w, responseObj.SetResultStatus(resultStatus.APIDataError)) + return + } + + //验证是否登录 + if strs[0] != "AdminApi" || strs[1] != "Login" { + tokenStr := context.header.Get("token") + + // token 转型成 int64 + token := stringUtil.StringToInt64(tokenStr) + + //是否存在 + if !CheckToken(token) { + responseResult(w, responseObj.SetResultStatus(resultStatus.NotLogin)) + return + } + } + + params, err = context.RequestDataBySlice2ByJson() + if err != nil { + responseResult(w, responseObj.SetResultStatus(resultStatus.APIDataError)) + return + } + + resquestData := NewRequestObject(strs[0], strs[1], params) + resp := CallFunction(resquestData) + if isJson { + responseResultByJson(w, resp) + } else { + responseResult(w, resp) + } + +} diff --git a/trunk/center/common/webServer/start.go b/trunk/center/common/webServer/start.go new file mode 100644 index 0000000..a0569b5 --- /dev/null +++ b/trunk/center/common/webServer/start.go @@ -0,0 +1,41 @@ +package webServer + +import ( + "fmt" + "net/http" + "sync" + + "goutil/logUtil" + "goutil/logUtilPlus" + "net/http/pprof" + + config "common/configsYaml" +) + +// Start +// @description: 启动服务器 +// parameter: +// @wg: WaitGroup对象 +// return: +func Start(wg *sync.WaitGroup) { + defer func() { + wg.Done() + }() + + // 启动过程中不需要捕获异常 + logUtilPlus.PrintAndWriteLog(logUtil.Warn, fmt.Sprintf("Web服务器开始监听:%v", config.ConfigYaml.Root.WebServerAddress)) + + // 启动Web服务器监听 + mux := http.NewServeMux() + mux.Handle("/", &selfDefineMux{}) + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + err := http.ListenAndServe(config.ConfigYaml.Root.WebServerAddress, mux) + if err != nil { + panic(fmt.Errorf("ListenAndServe失败,错误信息为:%s", err)) + } +} diff --git a/trunk/center/common/webServer/tokenHandler.go b/trunk/center/common/webServer/tokenHandler.go new file mode 100644 index 0000000..24048fd --- /dev/null +++ b/trunk/center/common/webServer/tokenHandler.go @@ -0,0 +1,92 @@ +package webServer + +import ( + "sync" + "time" +) + +var ( + //已经登录的用户 + loginUserMap = make(map[int64]*TokenInfo) + + //锁 + lock sync.RWMutex +) + +type TokenInfo struct { + //管理员id + Id int64 + + //管理员账号 + Account string + + //过期时间 + ExpireTime time.Time +} + +// AddLoginUserToken 添加登录用户及其令牌信息到缓存中 +// 参数: +// +// token: 用户的登录令牌 +// tokenInfo: 令牌的详细信息,包括过期时间等 +func AddLoginUserToken(token int64, tokenInfo *TokenInfo) { + + //添加锁防止并发 + lock.Lock() + defer lock.Unlock() + + // 默认24小时过期 + tokenInfo.ExpireTime = time.Now().Add(time.Hour * 24) + + //移除旧的令牌 + for tokenKey, tokenInfoValue := range loginUserMap { + if tokenInfo.Id == tokenInfoValue.Id { + delete(loginUserMap, tokenKey) + } + } + + // 将令牌与用户信息添加到全局的登录用户映射中 + loginUserMap[token] = tokenInfo +} + +// CheckToken 检查令牌是否有效 +// 参数: +// +// token: 需要检查的令牌字符串 +// +// 返回值: +// +// bool: 表示令牌是否有效的布尔值,true表示有效,false表示无效 +func CheckToken(token int64) bool { + + //添加锁防止并发 + lock.Lock() + defer lock.Unlock() + + // 获取当前时间 + now := time.Now() + + // 获取令牌对应的用户信息 + tokenInfo, ok := loginUserMap[token] + if !ok { + // 如果没有找到对应的用户信息,则令牌无效 + return false + } + + // 获取令牌过期时间 + expireTime := tokenInfo.ExpireTime + + // 如果令牌过期时间早于当前时间,则令牌无效 + if expireTime.Before(now) { + + //移除令牌 + delete(loginUserMap, token) + + return false + } + + //如果生效的话,则更新过期时间 + tokenInfo.ExpireTime = now.Add(time.Hour * 24) + + return true +} diff --git a/trunk/center/paycenter/go.mod b/trunk/center/paycenter/go.mod new file mode 100644 index 0000000..a0921d5 --- /dev/null +++ b/trunk/center/paycenter/go.mod @@ -0,0 +1,5 @@ +module main.go + +go 1.22.10 + +require github.com/wechatpay-apiv3/wechatpay-go v0.2.20 diff --git a/trunk/center/paycenter/go.sum b/trunk/center/paycenter/go.sum new file mode 100644 index 0000000..1de042a --- /dev/null +++ b/trunk/center/paycenter/go.sum @@ -0,0 +1,20 @@ +github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw= +github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/wechatpay-apiv3/wechatpay-go v0.2.20 h1:gS8oFn1bHGnyapR2Zb4aqTV6l4kJWgbtqjCq6k1L9DQ= +github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/trunk/center/paycenter/main.go b/trunk/center/paycenter/main.go new file mode 100644 index 0000000..afaa5ed --- /dev/null +++ b/trunk/center/paycenter/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "context" + "github.com/wechatpay-apiv3/wechatpay-go/core" + "github.com/wechatpay-apiv3/wechatpay-go/core/option" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/native" + "github.com/wechatpay-apiv3/wechatpay-go/utils" + "log" +) + +func main() { + var ( + mchID string = "190000****" // 商户号 + mchCertificateSerialNumber string = "3775B6A45ACD588826D15E583A95F5DD********" // 商户证书序列号 + mchAPIv3Key string = "2ab9****************************" // 商户APIv3密钥 + ) + // 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 + mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem") + if err != nil { + log.Fatal("load merchant private key error") + } + ctx := context.Background() + // 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力 + opts := []core.ClientOption{ + option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key), + } + client, err := core.NewClient(ctx, opts...) + if err != nil { + log.Fatalf("new wechat pay client err:%s", err) + } + // 以 Native 支付为例 + svc := native.NativeApiService{Client: client} + // 发送请求 + resp, result, err := svc.Prepay(ctx, + native.PrepayRequest{ + Appid: core.String("wxd678efh567hg6787"), + Mchid: core.String("1900009191"), + Description: core.String("Image形象店-深圳腾大-QQ公仔"), + OutTradeNo: core.String("1217752501201407033233368018"), + Attach: core.String("自定义数据说明"), + NotifyUrl: core.String("https://www.weixin.qq.com/wxpay/pay.php"), + Amount: &native.Amount{ + Total: core.Int64(100), + }, + }, + ) + + // 使用微信扫描 resp.code_url 对应的二维码,即可体验Native支付 + log.Printf("status=%d resp=%s", result.Response.StatusCode, resp) + svc1 := jsapi.JsapiApiService{Client: client} + // 得到prepay_id,以及调起支付所需的参数和签名 + resp1, result, err := svc1.PrepayWithRequestPayment(ctx, + jsapi.PrepayRequest{ + Appid: core.String("wxd678efh567hg6787"), + Mchid: core.String("1900009191"), + Description: core.String("Image形象店-深圳腾大-QQ公仔"), + OutTradeNo: core.String("1217752501201407033233368018"), + Attach: core.String("自定义数据说明"), + NotifyUrl: core.String("https://www.weixin.qq.com/wxpay/pay.php"), + Amount: &jsapi.Amount{ + Total: core.Int64(100), + }, + Payer: &jsapi.Payer{ + Openid: core.String("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"), + }, + }, + ) + + // 使用微信扫描 resp.code_url 对应的二维码,即可体验Native支付 + log.Printf("status=%d resp=%s", result.Response.StatusCode, resp1) +} diff --git a/trunk/center/usercenter/Dockerfile b/trunk/center/usercenter/Dockerfile new file mode 100644 index 0000000..99cbfbf --- /dev/null +++ b/trunk/center/usercenter/Dockerfile @@ -0,0 +1,40 @@ +# 使用官方的 Go 镜像作为构建环境 +FROM golang:1.22.10-alpine AS builder + +# 设置工作目录 +WORKDIR /app + +# 设置 Go 代理 +ENV GOPROXY=https://goproxy.cn,direct + +# 禁用 CGO +ENV CGO_ENABLED=0 + +# 复制所有源代码文件 +COPY . . + +WORKDIR /app/admincenter + +# 构建应用程序,并确认生成的文件 +RUN go build -o adminserver -ldflags="-s -w" + +# 使用官方的 Alpine 镜像作为运行环境 +FROM alpine:latest + +# 设置作者标签 +LABEL authors="tp" + +# 设置工作目录 +WORKDIR /app + +# 从构建阶段复制编译好的可执行文件 +COPY --from=builder /app/admincenter/adminserver . + +# 复制配置文件 +COPY --from=builder /app/admincenter/config.yaml . + +# 暴露端口(假设 adminserver 监听 10051 端口) +EXPOSE 10051 + +# 设置容器启动时运行 adminserver +ENTRYPOINT ["./adminserver"] diff --git a/trunk/center/usercenter/LOG/2025/1/2025-01-02.tar.gz b/trunk/center/usercenter/LOG/2025/1/2025-01-02.tar.gz new file mode 100644 index 0000000..430b0a9 Binary files /dev/null and b/trunk/center/usercenter/LOG/2025/1/2025-01-02.tar.gz differ diff --git a/trunk/center/usercenter/LOG/2025/1/2025-01-03-10.debug.txt b/trunk/center/usercenter/LOG/2025/1/2025-01-03-10.debug.txt new file mode 100644 index 0000000..8b9e46c --- /dev/null +++ b/trunk/center/usercenter/LOG/2025/1/2025-01-03-10.debug.txt @@ -0,0 +1,48 @@ +2025-01-03 10:17:22----> +开始加载DbConfig +------------------------------------------------------ +2025-01-03 10:17:22----> +开始加载LogMgr +------------------------------------------------------ +2025-01-03 10:21:53----> +开始加载DbConfig +------------------------------------------------------ +2025-01-03 10:21:53----> +开始加载LogMgr +------------------------------------------------------ +2025-01-03 10:26:46----> +开始加载DbConfig +------------------------------------------------------ +2025-01-03 10:26:46----> +开始加载LogMgr +------------------------------------------------------ +2025-01-03 10:27:18----> +开始加载DbConfig +------------------------------------------------------ +2025-01-03 10:27:18----> +开始加载LogMgr +------------------------------------------------------ +2025-01-03 10:27:57----> +开始加载DbConfig +------------------------------------------------------ +2025-01-03 10:27:57----> +开始加载LogMgr +------------------------------------------------------ +2025-01-03 10:28:06----> +开始加载DbConfig +------------------------------------------------------ +2025-01-03 10:28:06----> +开始加载LogMgr +------------------------------------------------------ +2025-01-03 10:30:37----> +开始加载DbConfig +------------------------------------------------------ +2025-01-03 10:30:37----> +开始加载LogMgr +------------------------------------------------------ +2025-01-03 10:31:16----> +开始加载DbConfig +------------------------------------------------------ +2025-01-03 10:31:16----> +开始加载LogMgr +------------------------------------------------------ diff --git a/trunk/center/usercenter/LOG/2025/1/2025-01-03-10.warn.txt b/trunk/center/usercenter/LOG/2025/1/2025-01-03-10.warn.txt new file mode 100644 index 0000000..65e9ef7 --- /dev/null +++ b/trunk/center/usercenter/LOG/2025/1/2025-01-03-10.warn.txt @@ -0,0 +1,165 @@ +2025-01-03 10:17:22----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:17:22----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:17:22----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:17:22----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:17:22----> +ping->redis:192.168.50.110:6379成功,信息为:PONG +------------------------------------------------------ +2025-01-03 10:17:22----> +Web服务器开始监听:192.168.50.85:10052 +------------------------------------------------------ +2025-01-03 10:17:32----> +monitorNewMgr没有初始化参数,请调用SetParam进行初始化 +------------------------------------------------------ +2025-01-03 10:21:53----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:21:53----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:21:53----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:21:53----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:21:53----> +ping->redis:192.168.50.110:6379成功,信息为:PONG +------------------------------------------------------ +2025-01-03 10:21:53----> +Web服务器开始监听:192.168.50.85:10052 +------------------------------------------------------ +2025-01-03 10:22:03----> +monitorNewMgr没有初始化参数,请调用SetParam进行初始化 +------------------------------------------------------ +2025-01-03 10:26:46----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:26:46----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:26:46----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:26:46----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:26:46----> +ping->redis:192.168.50.110:6379成功,信息为:PONG +------------------------------------------------------ +2025-01-03 10:26:46----> +Web服务器开始监听:192.168.50.85:10052 +------------------------------------------------------ +2025-01-03 10:26:56----> +monitorNewMgr没有初始化参数,请调用SetParam进行初始化 +------------------------------------------------------ +2025-01-03 10:27:18----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:27:18----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:27:18----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:27:18----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:27:18----> +ping->redis:192.168.50.110:6379成功,信息为:PONG +------------------------------------------------------ +2025-01-03 10:27:18----> +Web服务器开始监听:192.168.50.85:10052 +------------------------------------------------------ +2025-01-03 10:27:28----> +monitorNewMgr没有初始化参数,请调用SetParam进行初始化 +------------------------------------------------------ +2025-01-03 10:27:57----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:27:57----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:27:57----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:27:57----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:27:57----> +ping->redis:192.168.50.110:6379成功,信息为:PONG +------------------------------------------------------ +2025-01-03 10:27:57----> +Web服务器开始监听:192.168.50.85:10052 +------------------------------------------------------ +2025-01-03 10:28:06----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:28:06----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:28:06----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:28:06----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:28:06----> +ping->redis:192.168.50.110:6379成功,信息为:PONG +------------------------------------------------------ +2025-01-03 10:28:56----> +monitorNewMgr没有初始化参数,请调用SetParam进行初始化 +------------------------------------------------------ +2025-01-03 10:30:02----> +Web服务器开始监听:192.168.50.85:10052 +------------------------------------------------------ +2025-01-03 10:30:37----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:30:37----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:30:42----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:30:42----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:31:04----> +monitorNewMgr没有初始化参数,请调用SetParam进行初始化 +------------------------------------------------------ +2025-01-03 10:31:04----> +ping->redis:192.168.50.110:6379成功,信息为:PONG +------------------------------------------------------ +2025-01-03 10:31:04----> +Web服务器开始监听:192.168.50.85:10052 +------------------------------------------------------ +2025-01-03 10:31:16----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:31:16----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/admin?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:31:19----> +开始连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/user?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true +------------------------------------------------------ +2025-01-03 10:31:19----> +连接mysql:root:Qq5201530300@tcp(192.168.50.110:3306)/user?charset=utf8&parseTime=true&loc=Local&timeout=30s&multiStatements=true成功 +------------------------------------------------------ +2025-01-03 10:31:19----> +ping->redis:192.168.50.110:6379成功,信息为:PONG +------------------------------------------------------ +2025-01-03 10:31:21----> +Web服务器开始监听:192.168.50.85:10052 +------------------------------------------------------ +2025-01-03 10:31:26----> +monitorNewMgr没有初始化参数,请调用SetParam进行初始化 +------------------------------------------------------ diff --git a/trunk/center/usercenter/buildLiunx.sh b/trunk/center/usercenter/buildLiunx.sh new file mode 100644 index 0000000..889c6e6 --- /dev/null +++ b/trunk/center/usercenter/buildLiunx.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# 设置 Go 环境变量,确保使用 Linux 架构 +export GOOS=linux +export GOARCH=amd64 + +echo "开始编译..." + +# 编译 Go 代码 +go build -o adminServer + +# 检查编译是否成功 +if [ $? -eq 0 ]; then + echo "编译成功!" +else + echo "编译失败!" +fi + +# 等待用户输入任意键 +read -p "编译完成,按任意键继续..." +exit 1 diff --git a/trunk/center/usercenter/config.yaml b/trunk/center/usercenter/config.yaml new file mode 100644 index 0000000..8a386c2 --- /dev/null +++ b/trunk/center/usercenter/config.yaml @@ -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 \ No newline at end of file diff --git a/trunk/center/usercenter/docker-compose.yml b/trunk/center/usercenter/docker-compose.yml new file mode 100644 index 0000000..2242ca7 --- /dev/null +++ b/trunk/center/usercenter/docker-compose.yml @@ -0,0 +1,37 @@ +version: '3' +services: + mysql: + image: mysql:8.0 + container_name: mysql-admin + environment: + MYSQL_ROOT_PASSWORD: 123456 + MYSQL_DATABASE: admin,user + volumes: + - /my/own/datadir:/var/lib/mysql + ports: + - "3306:3306" + redis: + image: redis:7.0 + container_name: redis-admin + ports: + - "6379:6379" + volumes: + - redis_data:/data + restart: always + command: ["redis-server", "--appendonly", "yes"] + + admin-center: + build: + context: . + dockerfile: Dockerfile + container_name: admin-center + ports: + - "10051:10051" + volumes: + - ./app:/app + depends_on: + - mysql + - redis + +volumes: + redis_data: \ No newline at end of file diff --git a/trunk/center/usercenter/dockerRun.sh b/trunk/center/usercenter/dockerRun.sh new file mode 100644 index 0000000..c5251f3 --- /dev/null +++ b/trunk/center/usercenter/dockerRun.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# 导航到 Dockerfile 所在目录 +#cd D:\workspace\e2023\goProject\trunk\center\admincenter + +# 构建 Docker 镜像 +echo "开始构建 Docker 镜像..." +docker build -t adminserver-image . + +# 检查构建是否成功 +if [ $? -ne 0 ]; then + echo "构建失败!" + exit 1 +fi + +echo "镜像构建成功!" + +# 运行 Docker 容器 +echo "开始运行 Docker 容器..." +docker run -d -p 10051:10051 --name adminserver-container adminserver-image + +# 检查容器是否成功运行 +if [ $? -ne 0 ]; then + echo "容器启动失败!" + exit 1 +fi + +echo "容器启动成功!" diff --git a/trunk/center/usercenter/go.mod b/trunk/center/usercenter/go.mod new file mode 100644 index 0000000..8cf794c --- /dev/null +++ b/trunk/center/usercenter/go.mod @@ -0,0 +1,38 @@ +module logincenter + +go 1.22.10 + +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.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 +) diff --git a/trunk/center/usercenter/go.sum b/trunk/center/usercenter/go.sum new file mode 100644 index 0000000..c61518a --- /dev/null +++ b/trunk/center/usercenter/go.sum @@ -0,0 +1,89 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 h1:OoL469zqSNrTLSz5zeVF/I6VOO7fiw2bzSzQe4J557c= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4/go.mod h1:xe9a/L2aeOgFKKgrO3ibQTnMdpAeL0GC+5/HpGScSa4= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= +github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= +github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/trunk/center/usercenter/internal/game/game.go b/trunk/center/usercenter/internal/game/game.go new file mode 100644 index 0000000..67d35e3 --- /dev/null +++ b/trunk/center/usercenter/internal/game/game.go @@ -0,0 +1,21 @@ +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" +} diff --git a/trunk/center/usercenter/internal/game/logic.go b/trunk/center/usercenter/internal/game/logic.go new file mode 100644 index 0000000..e96dc74 --- /dev/null +++ b/trunk/center/usercenter/internal/game/logic.go @@ -0,0 +1,77 @@ +package game + +import ( + "common/cache" + "common/connection" + "goutil/logUtilPlus" + . "logincenter/internal/user" + "sync" +) + +// 包名称(功能名称) +var packageName = "game" + +// 读写锁 +var lock sync.RWMutex + +func AddGame(user *User, game *Game) (int64, error) { + + //处理一些验证 + + //判断缓存是否存在 + gameMap, _ := cache.GetData[map[int64]*Game](user.Cache, packageName) + if gameMap == nil { + gameMap = make(map[int64]*Game) + } + + //添加新的缓存 + func() { + lock.Lock() + defer lock.Unlock() + gameMap[game.GameID] = game + }() + + //写入缓存 + cache.SetData[*Game](user.Cache, packageName, gameMap) + + // 写入到数据库 + result := connection.GetUserDB().Create(&game) // 通过数据的指针来创建 + + if result.Error != nil { + logUtilPlus.ErrorLog("添加用户失败 错误信息:", result.Error.Error()) + } + return game.GameID, nil +} + +// 获取有些列表 +func GetGameList(user *User) (map[int64]*Game, error) { + + //处理一些验证 + + //验证缓存 + gameMap, _ := cache.GetData[map[int64]*Game](user.Cache, packageName) + if gameMap != nil { + return gameMap, nil + } + + // 获取全部列表 + var gameList []*Game + result := connection.GetUserDB().Find(&gameList) // 通过数据的指针来创建 + + if result.Error != nil { + logUtilPlus.ErrorLog("添加用户失败 错误信息:", result.Error.Error()) + } + + for _, game := range gameList { + gameMap[game.GameID] = game + } + + //添加缓存 + func() { + lock.Lock() + defer lock.Unlock() + cache.SetData(user.Cache, packageName, gameMap) + }() + + return gameMap, nil +} diff --git a/trunk/center/usercenter/internal/user.go b/trunk/center/usercenter/internal/user.go new file mode 100644 index 0000000..b835d9e --- /dev/null +++ b/trunk/center/usercenter/internal/user.go @@ -0,0 +1,6 @@ +package internal + +import ( + _ "logincenter/internal/game" + _ "logincenter/internal/user" +) diff --git a/trunk/center/usercenter/internal/user/api.go b/trunk/center/usercenter/internal/user/api.go new file mode 100644 index 0000000..530ea3c --- /dev/null +++ b/trunk/center/usercenter/internal/user/api.go @@ -0,0 +1,139 @@ +package user + +import ( + "common/remark" + "common/resultStatus" + "common/webServer" + "goutil/intUtil" + "goutil/securityUtil" + "strconv" + "time" +) + +func init() { + + //注册接口 + webServer.RegisterFunction(new(UserApi)) +} + +func init() { + moduleName := "UserApi" + desc := "用户接口" + author := "tangping" + mendor := "" + date := "2024-12-25" + remark.RegisterModuleRemark(moduleName, desc, author, mendor, date) +} + +// UserApi 用户接口 +type UserApi struct { +} + +// ---------------------------------------- 接口 -------------------------------------------------- +func init() { + moduleName := "UserApi" + methodName := "Register" + methodDesc := "注册用户" + methodAuthor := "tangping" + methodMendor := "" + methodDate := "2024-12-25 15:55:00" + methodInParam := []string{"string 账号,string:用户名称,string:用户密码,string:用户性别,string:用户生日,int64:用户手机号,string:用户邮箱,string:用户微信群,string:用户备注"} + methodOutParam := ` + { + "Code '类型:int'": "响应结果的状态值", + "Message '类型:string'": "响应结果的状态值所对应的描述信息", + "Data '类型:interface{}'": "响应结果的数据" + { + "id '类型:int'": "用户id", + } + }` + remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam) +} +func (a *UserApi) Register(account string, name string, password string, sex int32, birthday string, phone int64, email string, wechatGroup string, describe string) (responseObj *webServer.ResponseObject) { + responseObj = webServer.GetInitResponseObj() + + //校验参数 + if account == "" || name == "" || password == "" || sex == 0 || birthday == "" || phone == 0 || email == "" || wechatGroup == "" { + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + + //密码md5加密 + password = securityUtil.Md5String(password, true) + + //处理数据 + UserModel := &User{ + Account: account, + Name: name, + Password: password, + Sex: sex, + Birthday: birthday, + Phone: phone, + Email: email, + Describe: describe, + } + + //添加到数据库 + AddUser(UserModel) + + resultMap := make(map[string]any) + resultMap["id"] = UserModel.ID + responseObj.SetData(resultMap) + return +} + +// 用户登录 +func init() { + moduleName := "UserApi" + methodName := "Login" + methodDesc := "用户登录" + methodAuthor := "tangping" + methodMendor := "" + methodDate := "2024-12-26 15:55:00" + methodInParam := []string{"string:账号,string:密码"} + methodOutParam := ` + { + "Code '类型:int'": "响应结果的状态值", + "Message '类型:string'": "响应结果的状态值所对应的描述信息", + "Data '类型:interface{}'": "响应结果的数据" + { + "Token '类型:string'": "登录令牌", + } + }` + + remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam) +} + +func (a *UserApi) Login(account string, password string) (responseObj *webServer.ResponseObject) { + + responseObj = webServer.GetInitResponseObj() + + //验证参数 + if account == "" || password == "" { + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + + userData, err := Login(account, securityUtil.Md5String(password, true)) + if err != nil { + responseObj.SetResultStatus(resultStatus.DataError) + return + } + + //生成一个随机token + token, err := intUtil.ShuffleIntDigits(time.Now().UnixNano()) + if err != nil { + responseObj.SetResultStatus(resultStatus.DataError) + return + } + + //添加登录用户 + webServer.AddLoginUserToken(token, &webServer.TokenInfo{Id: userData.ID, Account: account}) + + //UserData映射成map + resultMap := make(map[string]any) + resultMap["Token"] = strconv.FormatInt(token, 10) + + responseObj.SetData(resultMap) + return +} diff --git a/trunk/center/usercenter/internal/user/logic.go b/trunk/center/usercenter/internal/user/logic.go new file mode 100644 index 0000000..0e03193 --- /dev/null +++ b/trunk/center/usercenter/internal/user/logic.go @@ -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 +} diff --git a/trunk/center/usercenter/internal/user/user.go b/trunk/center/usercenter/internal/user/user.go new file mode 100644 index 0000000..25d24fd --- /dev/null +++ b/trunk/center/usercenter/internal/user/user.go @@ -0,0 +1,36 @@ +package user + +import ( + "common/cache" + "common/connection" +) + +func init() { + //注册数据库 + connection.RegisterDBModel(&User{}) +} + +type User 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"` + //备注 + Describe string `gorm:"column:describe;comment:备注" json:"describe"` + + //玩家的缓存对象 + Cache *cache.Cache `gorm:"-"` +} + +func (User) TableName() string { + return "user" +} diff --git a/trunk/center/usercenter/main.go b/trunk/center/usercenter/main.go new file mode 100644 index 0000000..be896d2 --- /dev/null +++ b/trunk/center/usercenter/main.go @@ -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() +} diff --git a/trunk/center/usercenter/run.sh b/trunk/center/usercenter/run.sh new file mode 100644 index 0000000..65bb18f --- /dev/null +++ b/trunk/center/usercenter/run.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# 赋予可执行权限 +chmod +x adminServer + +echo "启动中..." + +# 接受命令行传入一个参数 +case "$1" in + d) + # 使用 nohup 将进程放到后台运行,并将输出重定向到 nohup.out 文件 + nohup ./adminServer > nohup.out 2>&1 & + echo "启动完成" + ;; + *) + ./adminServer + ;; +esac + +echo "启动完成" diff --git a/trunk/center/usercenter/stop.sh b/trunk/center/usercenter/stop.sh new file mode 100644 index 0000000..73b2ef9 --- /dev/null +++ b/trunk/center/usercenter/stop.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# 查找 adminServer 的 PID +PID=$(pgrep adminServer) + +if [ -z "$PID" ]; then + echo "adminServer 进程未找到" +else + echo "停止中... (PID: $PID)" + kill $PID + if [ $? -eq 0 ]; then + echo "adminServer 进程已终止 (PID: $PID)" + else + echo "无法终止 adminServer 进程 (PID: $PID)" + fi +fi diff --git a/trunk/framework/.idea/.gitignore b/trunk/framework/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/trunk/framework/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/trunk/framework/.idea/Framework.iml b/trunk/framework/.idea/Framework.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/trunk/framework/.idea/Framework.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/trunk/framework/.idea/inspectionProfiles/profiles_settings.xml b/trunk/framework/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..0eefe32 --- /dev/null +++ b/trunk/framework/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/trunk/framework/.idea/misc.xml b/trunk/framework/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/trunk/framework/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/trunk/framework/.idea/modules.xml b/trunk/framework/.idea/modules.xml new file mode 100644 index 0000000..62e68f6 --- /dev/null +++ b/trunk/framework/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/trunk/framework/.idea/vcs.xml b/trunk/framework/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/trunk/framework/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/trunk/framework/README b/trunk/framework/README new file mode 100644 index 0000000..8036f5b --- /dev/null +++ b/trunk/framework/README @@ -0,0 +1,17 @@ +v1.0版本,支持以下功能: +1、项目中的各种基础功能; +2、其中的managecenterMgr兼容旧版本的ManageCenter(2019-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 diff --git a/trunk/framework/bash.exe.stackdump b/trunk/framework/bash.exe.stackdump new file mode 100644 index 0000000..2f36e71 --- /dev/null +++ b/trunk/framework/bash.exe.stackdump @@ -0,0 +1,9 @@ +Stack trace: +Frame Function Args +000FFFFA328 0018006021E (00180252DED, 001802340A6, 000FFFFA328, 000FFFF9220) +000FFFFA328 00180048859 (00000000000, 00000000000, 00000000000, 00000000000) +000FFFFA328 00180048892 (00180252EA9, 000FFFFA1D8, 000FFFFA328, 00000000000) +000FFFFA328 001800AF0D8 (00000000000, 00000000000, 00000000000, 00000000000) +000FFFFA328 001800AF25D (000FFFFA340, 00000000000, 00000000000, 00000000000) +000FFFFA5B0 001800B0673 (000FFFFA340, 00000000000, 00000000000, 00000000000) +End of stack trace diff --git a/trunk/framework/configMgr/configMgr.go b/trunk/framework/configMgr/configMgr.go new file mode 100644 index 0000000..2d1454a --- /dev/null +++ b/trunk/framework/configMgr/configMgr.go @@ -0,0 +1,34 @@ +package configMgr + +import ( + "goutil/configUtil" +) + +// 配置管理对象 +type ConfigManager struct { + // 初始化方法列表 + initFuncList []func(*configUtil.XmlConfig) error +} + +// 注册初始化方法 +func (this *ConfigManager) RegisterInitFunc(initFunc func(*configUtil.XmlConfig) error) { + this.initFuncList = append(this.initFuncList, initFunc) +} + +// 初始化 +func (this *ConfigManager) Init(configObj *configUtil.XmlConfig) error { + for _, initFunc := range this.initFuncList { + if err := initFunc(configObj); err != nil { + return err + } + } + + return nil +} + +// 创建配置管理对象 +func NewConfigManager() *ConfigManager { + return &ConfigManager{ + initFuncList: make([]func(*configUtil.XmlConfig) error, 0, 8), + } +} diff --git a/trunk/framework/contextcheckMgr/resultmodel.go b/trunk/framework/contextcheckMgr/resultmodel.go new file mode 100644 index 0000000..4634347 --- /dev/null +++ b/trunk/framework/contextcheckMgr/resultmodel.go @@ -0,0 +1,13 @@ +package contextcheckMgr + +type ResultModel struct { + Code int `json:"code"` + Msg string `json:"msg"` + Result ResultDetail `json:"result"` +} + +type ResultDetail struct { + TaskId string `json:"taskId"` + Action int `json:"action"` + CensorType int `json:"censorType"` +} diff --git a/trunk/framework/contextcheckMgr/textcheck.go b/trunk/framework/contextcheckMgr/textcheck.go new file mode 100644 index 0000000..ec7d8cd --- /dev/null +++ b/trunk/framework/contextcheckMgr/textcheck.go @@ -0,0 +1,126 @@ +package contextcheckMgr + +import ( + "encoding/json" + "fmt" + "math/rand" + "sort" + "strconv" + "time" + + "goutil/logUtil" + "goutil/securityUtil" + "goutil/webUtil" +) + +var ( + //接口密钥Id + msecretId string = "c98276c73c122eaa65163fe431cbf7d4" + //接口密钥key + msecretKey string = "3d2b39f824fa2f992494e17be500e303" + //业务ID + mbusinessId string = "9559ce74b5c5d24f6b124799a53c7431" + //接口请求地址 + mapiurl string = "http://as.dun.163.com/v3/text/check" + //版本号 + mversion string = "v3.1" +) + +//参数设置 +func SetPara(secretId, secretKey, businessId, apiurl, version string) { + msecretId = secretId + msecretKey = secretKey + mbusinessId = businessId + mapiurl = apiurl + mversion = version +} + +//content:用户发表内容 +//account:玩家账号(用户唯一标识) +//nickname:角色名称 +//extStr1:角色区服名称 +//extStr2:UserId +//ip:用户IP地址,建议抄送,辅助机审策略精准调优 +//extLon1:区服ID +//返回值 code: 0:通过, 1:嫌疑,2:不通过 +//文本内容检测 +func TextCheck(content, account, nickname, extStr1, extStr2, ip string, extLon1 int64) (code int, err error) { + //构造请求参数 + postDataDict := make(map[string]string) + postDataDict["secretId"] = msecretId + postDataDict["businessId"] = mbusinessId + postDataDict["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10) + postDataDict["nonce"] = strconv.FormatInt(rand.New(rand.NewSource(time.Now().UnixNano())).Int63n(10000000000), 10) + rawString := fmt.Sprintf("%v%v%v", account, postDataDict["timestamp"], content) + postDataDict["dataId"] = securityUtil.Md5String(rawString, false) + postDataDict["content"] = content + postDataDict["version"] = mversion + postDataDict["account"] = account + postDataDict["nickname"] = nickname + postDataDict["extLon1"] = strconv.FormatInt(extLon1, 10) + // postDataDict["extLon2"] = extLon2 + postDataDict["extStr1"] = extStr1 + postDataDict["extStr2"] = extStr2 + postDataDict["ip"] = ip + postDataDict["signature"] = getSignature(postDataDict) + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + //请求接口 + statusCode, result, err := webUtil.PostMapData(mapiurl, postDataDict, header, transport) + //定义错误信息 + var logMessage string + + //post请求错误 + if err != nil { + logMessage = fmt.Sprintf("TextCheck:,错误信息为:%s", err.Error()) + logUtil.ErrorLog(logMessage) + return + } + if statusCode != 200 { + logMessage = fmt.Sprintf("TextCheck:%d is wrong", statusCode) + logUtil.ErrorLog(logMessage) + err = fmt.Errorf("TextCheck:%d is wrong", statusCode) + return + } + + //反序列化结果 + var checkResponseObj *ResultModel + err = json.Unmarshal(result, &checkResponseObj) + if err != nil { + logMessage = fmt.Sprintf("json.Unmarshal(checkResponseObj),err:%s", err.Error()) + logUtil.ErrorLog(logMessage) + return + } + + //判断接口是否调用成功 + if checkResponseObj.Code != 200 { + logMessage = fmt.Sprintf("TextCheck接口调用失败,ResultStatus %d", checkResponseObj.Code) + err = fmt.Errorf("TextCheck接口调用失败,code:%d", checkResponseObj.Code) + return + } + + //返回检测结果 + code = checkResponseObj.Result.Action + return +} + +//生成签名字符串 +func getSignature(postDataDict map[string]string) string { + var paramStr string + keys := make([]string, 0, len(postDataDict)) + for k := range postDataDict { + keys = append(keys, k) + } + sort.Strings(keys) + for _, key := range keys { + paramStr += key + postDataDict[key] + } + paramStr += msecretKey + + return securityUtil.Md5String(paramStr, false) +} diff --git a/trunk/framework/contextcheckMgr/textcheck_test.go b/trunk/framework/contextcheckMgr/textcheck_test.go new file mode 100644 index 0000000..99f1231 --- /dev/null +++ b/trunk/framework/contextcheckMgr/textcheck_test.go @@ -0,0 +1,49 @@ +package contextcheckMgr + +import ( + "fmt" + "testing" +) + +func TestCheck(t *testing.T) { + //content, account, nickname, extLon1, extLon2, extStr1, extStr2, ip + var content = "文本检查测试" + var account = "000e6a6e-7cfc-4da7-81a2-ef3a5d24c586" + var nickname = "不仅仅是喜欢" + var extLon1 int64 = 10001 + //var extLon2 = "游戏内聊天" + var extStr1 = "10001铁血丹心" + var extStr2 = "5DF67DD225640567D4D0B6FE273262B5" + var ip = "113.116.66.45" + //设置完全正确的参数 + code, err := TextCheck(content, account, nickname, extStr1, extStr2, ip, extLon1) + + if err != nil { + t.Errorf("There should be no error, but now there is:%v", err) + return + } + + fmt.Printf("检测完成,状态码为:%d\n", code) + + content = "修炼发轮功" + + code, err = TextCheck(content, account, nickname, extStr1, extStr2, ip, extLon1) + + if err != nil { + t.Errorf("There should be no error, but now there is:%v", err) + return + } + + fmt.Printf("检测完成,状态码为:%d\n", code) + + content = "" + + code, err = TextCheck(content, account, nickname, extStr1, extStr2, ip, extLon1) + + if err != nil { + t.Errorf("There should be no error, but now there is:%v", err) + return + } + + fmt.Printf("检测完成,状态码为:%d\n", code) +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/doc.go b/trunk/framework/dataSyncMgr/mysqlSync/doc.go new file mode 100644 index 0000000..af97244 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/doc.go @@ -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的方式来报告故障。 +*/ diff --git a/trunk/framework/dataSyncMgr/mysqlSync/errorHandle.go b/trunk/framework/dataSyncMgr/mysqlSync/errorHandle.go new file mode 100644 index 0000000..b347b84 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/errorHandle.go @@ -0,0 +1,100 @@ +package mysqlSync + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "goutil/fileUtil" + "goutil/logUtil" +) + +var ( + // 记录错误sql命令的文件名 + con_Error_FileName = "errorFile.txt" +) + +// 定义处理错误命令的文件对象 +type errorFile struct { + // 错误文件 + file *os.File + + // 文件路径 + filePath string + + // 同步数据对象的唯一标识,用于进行重复判断 + identifier string +} + +// 保存命令到错误文件 +// command: sql命令 +func (this *errorFile) SaveCommand(command string) { + this.open() + defer this.close() + + // 覆盖写入 + this.file.Seek(0, 0) + + // 写入命令 + _, err := this.file.WriteString(command) + if err != nil { + prefix := fmt.Sprintf("%s-%s", this.identifier, "errorFile.SaveCommand") + err = fmt.Errorf("%s-Write %s to file failed:%s", prefix, command, err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + + // 清理残留数据 + this.file.Truncate(int64(len(command))) +} + +// 读取文件中命令 +func (this *errorFile) ReadCommand() string { + this.open() + defer this.close() + + this.file.Seek(0, 0) + content, err := ioutil.ReadAll(this.file) + if err != nil { + prefix := fmt.Sprintf("%s-%s", this.identifier, "errorFile.ReadCommand") + err = fmt.Errorf("%s-Read command failed:%s", prefix, err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + return string(content) +} + +// 打开文件 +func (this *errorFile) open() { + // 打开errorFile文件, 如果没有就创建 + var err error + this.file, err = os.OpenFile(this.filePath, os.O_CREATE|os.O_RDWR, os.ModePerm|os.ModeTemporary) + if err != nil { + prefix := fmt.Sprintf("%s-%s", this.identifier, "errorFile.newErrorFile.os.OpenFile") + err = fmt.Errorf("%s-Open File failed:%s", prefix, err) + logUtil.ErrorLog(err.Error()) + panic(err) + } +} + +// 关闭文件 +func (this *errorFile) close() { + this.file.Close() +} + +// 删除文件 +func (this *errorFile) Delete() { + fileUtil.DeleteFile(this.filePath) +} + +// 构造错误文件对象 +// _dirPath:文件路径 +// _identifier:唯一标识 +func newErrorFile(_dirPath string, _identifier string) *errorFile { + _filePath := filepath.Join(_dirPath, con_Error_FileName) + return &errorFile{ + filePath: _filePath, + identifier: _identifier, + } +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/logSqlSync/syncErrorInfo.go b/trunk/framework/dataSyncMgr/mysqlSync/logSqlSync/syncErrorInfo.go new file mode 100644 index 0000000..df565f6 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/logSqlSync/syncErrorInfo.go @@ -0,0 +1,88 @@ +package logSqlSync + +import ( + "database/sql" + "fmt" + "time" + + "goutil/logUtil" +) + +// 错误信息记录表是否已经初始化 +var ifSyncErrorInfoTableInited bool = false + +// 同步的错误信息处理对象 +type syncErrorInfo struct { + // 数据库连接对象 + db *sql.DB +} + +// 初始化表信息 +func (this *syncErrorInfo) init() error { + // 初始化表结构 + if ifSyncErrorInfoTableInited == false { + err := this.initTable(this.db) + if err == nil { + ifSyncErrorInfoTableInited = true + } + + return err + } + + return nil +} + +// 把同步信息更新到数据库 +// data:待更新的数据 +// 返回值: +// error:错误信息 +func (this *syncErrorInfo) AddErrorSql(tran *sql.Tx, data string, errMsg string) error { + updateSql := "INSERT INTO `sync_error_info` (`SqlString`,`ExecuteTime`,`RetryCount`,`ErrMessage`) VALUES(?,?,?,?);" + + var err error + if tran != nil { + _, err = tran.Exec(updateSql, data, time.Now(), 0, errMsg) + } else { + _, err = this.db.Exec(updateSql, data, time.Now(), 0, errMsg) + } + + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncErrorInfo.AddErrorSql Error:%s", err.Error())) + } + + return err +} + +// 初始化同步信息表结构 +// db:数据库连接对象 +func (this *syncErrorInfo) initTable(db *sql.DB) error { + // 创建同步信息表 + createTableSql := `CREATE TABLE IF NOT EXISTS sync_error_info ( + Id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增Id', + SqlString varchar(1024) NOT NULL COMMENT '执行的sql', + ExecuteTime datetime NOT NULL COMMENT '最近一次执行时间', + RetryCount int NOT NULL COMMENT '重试次数', + ErrMessage text NULL COMMENT '执行错误的信息', + PRIMARY KEY (Id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='未执行成功的sql数据';` + if _, err := db.Exec(createTableSql); err != nil { + logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncErrorInfo.initTable Error:%s", err.Error())) + return err + } + + return nil +} + +// 创建同步信息对象 +// _db:数据库连接对象 +// 返回值: +// 同步信息对象 +func newSyncErrorInfoObject(_db *sql.DB) (result *syncErrorInfo, err error) { + result = &syncErrorInfo{ + db: _db, + } + + err = result.init() + + return result, err +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/logSqlSync/syncObject.go b/trunk/framework/dataSyncMgr/mysqlSync/logSqlSync/syncObject.go new file mode 100644 index 0000000..832f309 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/logSqlSync/syncObject.go @@ -0,0 +1,204 @@ +package logSqlSync + +import ( + "database/sql" + "fmt" + "os" + "path/filepath" + "time" + + "Framework/dataSyncMgr/mysqlSync/sqlSync" + "goutil/logUtil" +) + +// 同步对象定义 +type SyncObject struct { + // 服务器组Id + serverGroupId int32 + + // 同步数据的存储路径 + dirPath string + + // 同步数据对象的唯一标识,用于进行重复判断 + identifier string + + // 数据库对象 + dbObj *sql.DB + + // 同步信息对象 + syncingInfoObj *syncingInfo + + // 错误处理对象 + errorHandleObj *syncErrorInfo + + // 同步对象 + syncObj *sqlSync.SyncObject +} + +// 初始化 +// baseObj:基础同步对象 +func (this *SyncObject) Init(baseObj *sqlSync.SyncObject) { + this.syncObj = baseObj + + // 初始化同步信息对象 + syncingInfoObj, err := newSyncingInfoObject(this.serverGroupId, this.dbObj) + if err != nil { + panic(err) + } + //// 初始化错误处理对象 + errorHandleObj, err := newSyncErrorInfoObject(this.dbObj) + if err != nil { + panic(err) + } + this.syncingInfoObj = syncingInfoObj + this.errorHandleObj = errorHandleObj + + // 初始化当前处理的文件 + fileList := sqlSync.GetDataFileList(this.dirPath) + filePath, _ := this.syncingInfoObj.GetSyncingInfo() + if len(filePath) < 0 && len(fileList) > 0 { + this.syncingInfoObj.Update(fileList[0], 0, nil) + } +} + +// 获取正在同步的信息 +// filePath:文件路径 +// offset:文件偏移量 +func (this *SyncObject) GetSyncingInfo() (filePath string, offset int64) { + return this.syncingInfoObj.GetSyncingInfo() +} + +// 更新 +// filePath:文件路径 +// offset:文件偏移量 +// tran:事务对象 +// 返回值: +// error:错误对象 +func (this *SyncObject) Update(filePath string, offset int64, tx *sql.Tx) error { + return this.syncingInfoObj.Update(filePath, offset, tx) +} + +// 同步一条sql语句 +// command:待执行的命令 +// filePath:保存路径 +// offset:文件偏移量 +// 返回值: +// error:错误信息 +func (this *SyncObject) SyncOneSql(command string, filePath string, offset int64) { + var err error + for { + err = sqlSync.ExecuteByTran(this.dbObj, func(tran *sql.Tx) (isCommit bool, err error) { + // 保存sql到数据库 + err = this.syncToMysql(command, tran) + if err != nil { + return + } + + // 保存进度信息到数据库 + err = this.syncingInfoObj.Update(filePath, offset, tran) + if err != nil { + return + } + + isCommit = true + return + }) + + // 如果是连接出错,则仍然循环执行 + if err != nil { + if sqlSync.CheckIfConnectionError(err.Error()) { + time.Sleep(5 * time.Second) + continue + } + } + + // 如果不是数据库连接出错,则算是执行完成 + break + } + + // 如果存在错误,则循环尝试执行 + if err != nil { + this.recordSqlError(command, filePath, offset, err.Error()) + } + + return +} + +// 同步数据到mysql中 +// command:sql语句 +// tx:事务处理对象 +// 返回值: +// error:错误信息 +func (this *SyncObject) syncToMysql(command string, tx *sql.Tx) error { + _, err := tx.Exec(command) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("mysqlSync/logSqlSync/syncObject.syncToMysql error:%s", err.Error())) + return err + } + + return nil +} + +// 错误处理 +// cmd:待执行的命令 +// filePath:保存路径 +// offset:文件偏移量 +// errMsg:错误信息 +func (this *SyncObject) recordSqlError(command string, filePath string, offset int64, errMsg string) { + errMsg = sqlSync.GetSimpleErrorMessage(errMsg) + + for { + err := sqlSync.ExecuteByTran(this.dbObj, func(tran *sql.Tx) (isCommit bool, err error) { + // 保存sql到数据库 + err = this.errorHandleObj.AddErrorSql(tran, command, errMsg) + if err != nil { + return + } + + // 保存进度信息到数据库 + err = this.syncingInfoObj.Update(filePath, offset, tran) + if err != nil { + return + } + + isCommit = true + return + }) + + if err == nil { + return + } + + time.Sleep(5 * time.Second) + } +} + +// 创新新的mysql同步对象 +// dirPath:存放数据的目录 +// identifier:当前数据的唯一标识(可以使用数据库表名) +// dbObj:数据库对象 +// syncingInfoObj:同步信息记录对象 +// errorHandleObj:错误处理对象 +// 返回值: +// mysql同步对象 +func NewSyncObject(serverGroupId int32, dirPath, identifier string, dbObj *sql.DB) *SyncObject { + dirPath = filepath.Join(dirPath, identifier) + + // 创建更新目录 + err := os.MkdirAll(dirPath, os.ModePerm|os.ModeTemporary) + if err != nil { + err = fmt.Errorf("%s-%s-make dir failed:%s", identifier, "SyncObject.newSyncObject.os.MkdirAll", err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + + // 构造同步信息对象 + result := &SyncObject{ + serverGroupId: serverGroupId, + dirPath: dirPath, + identifier: identifier, + dbObj: dbObj, + } + + return result +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/logSqlSync/syncingInfo.go b/trunk/framework/dataSyncMgr/mysqlSync/logSqlSync/syncingInfo.go new file mode 100644 index 0000000..0314033 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/logSqlSync/syncingInfo.go @@ -0,0 +1,186 @@ +package logSqlSync + +import ( + "database/sql" + "fmt" + "time" + + "goutil/logUtil" +) + +// 同步信息表是否已经被初始化 +var ifSyncingTableInited bool = false + +// 同步信息项,保存已经处理过的文件的信息 +type syncingModel struct { + // 服务器组Id + ServerGroupId int32 + + // 待处理文件的绝对路径 + FilePath string + + // 待处理文件的偏移量 + FileOffset int64 + + // 更新时间 + UpdateTime time.Time +} + +// 同步信息对象 +type syncingInfo struct { + // 服务器组Id + ServerGroupId int32 + + // 同步信息项 + item *syncingModel + + // 数据库连接对象 + db *sql.DB +} + +// 获取同步信息 +// filePath:正在同步的文件 +// fileOffset:同步到的位置 +func (this *syncingInfo) GetSyncingInfo() (filePath string, fileOffset int64) { + return this.item.FilePath, this.item.FileOffset +} + +// 更新正在同步的位置和文件信息 +// filePath:文件路径 +// offset:当前同步到的位置 +// tran:事务对象,可以为nil +// 返回值: +// error:处理的错误信息 +func (this *syncingInfo) Update(filePath string, offset int64, tran *sql.Tx) error { + this.item.FilePath = filePath + this.item.FileOffset = offset + this.item.UpdateTime = time.Now() + + // 更新到数据库 + return this.update(this.item, tran) +} + +// 初始化同步信息 +// 返回值: +// error:错误信息 +func (this *syncingInfo) init() error { + // 数据表初始化 + if ifSyncingTableInited == false { + if err := this.initSyncingInfoTable(this.db); err == nil { + ifSyncingTableInited = true + } else { + return err + } + } + + // 获取此表的同步信息 + data, exist, err := this.get() + if err != nil { + return err + } + + // 2. 如果同步信息不存在,则初始化一条到此表 + if exist == false { + data = &syncingModel{ + ServerGroupId: this.ServerGroupId, + FilePath: "", + FileOffset: 0, + UpdateTime: time.Now(), + } + } + + this.item = data + return nil +} + +// 初始化同步信息表结构 +// db:数据库连接对象 +func (this *syncingInfo) initSyncingInfoTable(db *sql.DB) error { + // 创建同步信息表 + createTableSql := `CREATE TABLE IF NOT EXISTS syncing_info ( + ServerGroupId int NOT NULL COMMENT '服务器组Id', + FilePath varchar(500) NOT NULL COMMENT '正在同步的文件路径', + FileOffset bigint(20) NOT NULL COMMENT '偏移量', + UpdateTime datetime NOT NULL COMMENT '最后一次更新时间', + PRIMARY KEY (ServerGroupId) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='正在同步的文件信息';` + if _, err := db.Exec(createTableSql); err != nil { + logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncingInfo.initSyncingInfoTable Error:%s", err.Error())) + return err + } + + return nil +} + +// 从数据库获取数据 +// 返回值: +// data:获取到的数据 +// exist:是否存在此数据 +// err:错误信息 +func (this *syncingInfo) get() (data *syncingModel, exist bool, err error) { + //// 从数据库查询 + querySql := fmt.Sprintf("SELECT FilePath,FileOffset,UpdateTime FROM syncing_info WHERE ServerGroupId ='%v'", this.ServerGroupId) + var rows *sql.Rows + rows, err = this.db.Query(querySql) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncingInfo.get.Query ServerGroupId:%v error:%s", this.ServerGroupId, err.Error())) + return + } + defer rows.Close() + + if rows.Next() == false { + exist = false + return + } + exist = true + + // 读取数据 + data = &syncingModel{ + ServerGroupId: this.ServerGroupId, + } + err = rows.Scan(&data.FilePath, &data.FileOffset, &data.UpdateTime) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncingInfo.get.Query ServerGroupId:%v error:%s", this.ServerGroupId, err.Error())) + return + } + + return +} + +// 把同步信息更新到数据库 +// data:待更新的数据 +// tran:事务处理对象 +// 返回值: +// error:错误信息 +func (this *syncingInfo) update(data *syncingModel, tran *sql.Tx) error { + updateSql := "REPLACE INTO `syncing_info` SET `ServerGroupId` = ?, `FilePath` = ?,`FileOffset` = ?, `UpdateTime` = ?;" + var err error + if tran != nil { + _, err = tran.Exec(updateSql, data.ServerGroupId, data.FilePath, data.FileOffset, data.UpdateTime) + } else { + _, err = this.db.Exec(updateSql, data.ServerGroupId, data.FilePath, data.FileOffset, data.UpdateTime) + } + + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncingInfo.update ServerGroupId:%v error:%s", this.ServerGroupId, err.Error())) + } + + return err +} + +// 创建同步信息对象 +// _dirPath:目录的路径 +// _identifier:当前数据的唯一标识(可以使用数据库表名) +// _db:数据库连接对象 +// 返回值: +// 同步信息对象 +func newSyncingInfoObject(serverGroupId int32, _db *sql.DB) (result *syncingInfo, err error) { + result = &syncingInfo{ + ServerGroupId: serverGroupId, + db: _db, + } + + err = result.init() + + return result, err +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/dal.go b/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/dal.go new file mode 100644 index 0000000..3c3d110 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/dal.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + + "github.com/go-sql-driver/mysql" + "github.com/jinzhu/gorm" + "Framework/dataSyncMgr/mysqlSync" + "goutil/logUtil" +) + +var _ = mysql.DeregisterLocalFile + +var ( + connectionString = "root:moqikaka3309@tcp(10.1.0.10:3309)/develop_liujun?charset=utf8&parseTime=true&loc=Local&timeout=60s" + maxOpenConns = 10 + maxIdleConns = 10 + + syncFileSize = 1024 * 1024 +) + +var ( + // 数据库对象 + dbObj *gorm.DB + + // 同步管理对象 + syncMgr *mysqlSync.SyncMgr +) + +func init() { + // 初始化数据库连接 + dbObj = initMysql() + + // 构造同步管理对象 + syncMgr = mysqlSync.NewLogSyncMgr(1, "Sync", syncFileSize, dbObj.DB()) +} + +// 初始化Mysql +func initMysql() *gorm.DB { + dbObj, err := gorm.Open("mysql", connectionString) + if err != nil { + panic(fmt.Errorf("初始化数据库:%s失败,错误信息为:%s", connectionString, err)) + } + logUtil.DebugLog(fmt.Sprintf("连接mysql:%s成功", connectionString)) + + if maxOpenConns > 0 && maxIdleConns > 0 { + dbObj.DB().SetMaxOpenConns(maxOpenConns) + dbObj.DB().SetMaxIdleConns(maxIdleConns) + } + + return dbObj +} + +// 注册同步对象 +func registerSyncObj(identifier string) { + syncMgr.RegisterSyncObj(identifier) +} + +// 保存sql数据 +func save(identifier string, command string) { + syncMgr.Save(identifier, command) +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/main.go b/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/main.go new file mode 100644 index 0000000..87927dd --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "fmt" + "sync" + "time" + + "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.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() +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/player.go b/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/player.go new file mode 100644 index 0000000..66a6829 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/player.go @@ -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), + } +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/player_dal.go b/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/player_dal.go new file mode 100644 index 0000000..04b2869 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/logSyncTest/player_dal.go @@ -0,0 +1,29 @@ +//package test +package main + +import ( + "fmt" +) + +var ( + con_player_tableName = "player" +) + +func init() { + registerSyncObj(con_player_tableName) +} + +func insert(obj *player) { + command := fmt.Sprintf("INSERT INTO `%s` (`Id`,`Name`) VALUES ('%v','%v') ", con_player_tableName, obj.Id, obj.Name) + save(con_player_tableName, command) +} + +func update(obj *player) { + command := fmt.Sprintf("UPDATE `%s` SET `Name` = '%v' WHERE `Id` = '%v';", con_player_tableName, obj.Name, obj.Id) + save(con_player_tableName, command) +} + +func clear(obj *player) { + command := fmt.Sprintf("DELETE FROM %s where Id = '%v';", con_player_tableName, obj.Id) + save(con_player_tableName, command) +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/dataProtocol.go b/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/dataProtocol.go new file mode 100644 index 0000000..4bd1dbc --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/dataProtocol.go @@ -0,0 +1,73 @@ +package sqlSync + +import ( + "encoding/binary" + "errors" + "os" + + "goutil/fileUtil" + "goutil/intAndBytesUtil" +) + +const ( + // 头部字节长度 + con_Header_Length = 4 +) + +var ( + // 字节的大小端顺序 + byteOrder = binary.LittleEndian +) + +// 按照指定方式读取文本内容 +// fileObj:大文件对象 +// data:待写入的数据 +// 返回值: +// error:写入是否存在异常 +func Write(fileObj *fileUtil.BigFile, data string) error { + // 获得数据内容的长度 + dataLength := len(data) + + // 将长度转化为字节数组 + header := intAndBytesUtil.Int32ToBytes(int32(dataLength), byteOrder) + + // 将头部与内容组合在一起 + message := append(header, data...) + + // 写入数据 + return fileObj.WriteMessage(message) +} + +// 从文件读取一条数据 +// fileObj:文件对象 +// 返回值: +// result:读取到的字符串 +// err:错误信息 +func Read(fileObj *os.File) (result string, readLen int64, err error) { + // 1. 读取头部内容 + header := make([]byte, 4) + var n int + n, err = fileObj.Read(header) + if err != nil { + return + } + + if n < con_Header_Length { + err = errors.New("can not read 4 byte for read len") + readLen = int64(n) + return + } + + dataLength := intAndBytesUtil.BytesToInt32(header, byteOrder) + + // 2. 读取指定长度的内容 + data := make([]byte, dataLength) + n, err = fileObj.Read(data) + if err != nil { + return + } + + readLen = int64(len(header) + int(dataLength)) + result = string(data) + return +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/sqlFile.go b/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/sqlFile.go new file mode 100644 index 0000000..fe2541d --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/sqlFile.go @@ -0,0 +1,139 @@ +package sqlSync + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + + "goutil/fileUtil" + "goutil/logUtil" +) + +const ( + // 第一个文件名 + con_Default_FileName = "00000000" + + // 文件名后缀 + con_FileName_Suffix = "data" +) + +// 同步数据对象(用于往文件中写入sql语句) +type SqlFile struct { + // 存放同步数据的文件夹路径 + dirPath string + + // 同步数据对象的唯一标识,用于进行重复判断 + identifier string + + // 保存数据的大文件对象 + bigFileObj *fileUtil.BigFile + + // 数据同步对象 + mutex sync.Mutex +} + +// 将数据写入同步数据对象 +// data:待写入的数据 +func (this *SqlFile) Write(data string) { + this.mutex.Lock() + defer this.mutex.Unlock() + + // 写入数据 + err := Write(this.bigFileObj, data) + if err != nil { + prefix := fmt.Sprintf("%s-%s", this.identifier, "SqlFile.write.bigFileObj.WriteMessage") + err = fmt.Errorf("%s-Write message to big file object failed:%s", prefix, err) + logUtil.ErrorLog(err.Error()) + panic(err) + } +} + +// 获取大文件对象的文件绝对路径 +func (this *SqlFile) FileFullName() string { + return filepath.Join(this.dirPath, this.bigFileObj.FileName()) +} + +// 当前读写的文件名 +func (this *SqlFile) FileName() string { + return this.bigFileObj.FileName() +} + +// 创建同步数据对象 +// _dirPath:目录的路径 +// _identifier:当前数据的唯一标识(可以使用数据库表名) +// _maxFileSize:每个大文件的最大写入值(单位:Byte) +// 返回值: +// 同步数据对象 +func NewSqlFile(dirPath, identifier, fileName string, maxFileSize int) *SqlFile { + result := &SqlFile{ + dirPath: dirPath, + identifier: identifier, + } + + // 初始化大文件对象 + if fileName == "" { + fileName = con_Default_FileName + } + bigFileObj, err := fileUtil.NewBigFileWithNewFileNameFunc2(dirPath, "", fileName, maxFileSize, NewFileName) + if err != nil { + prefix := fmt.Sprintf("%s-%s", result.identifier, "SqlFile.newSqlFile.fileUtil.NewBigFileWithNewFileNameFunc") + err = fmt.Errorf("%s-Create big file object failed:%s", prefix, err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + result.bigFileObj = bigFileObj + + return result +} + +// 根据当前文件名生成下一个sql文件名 +// prefix:文件名前缀 +// path:当前文件的路径 +// 返回值: +// string:下一个文件的完整路径 +func NewFileName(prefix, path string) string { + fullName := filepath.Base(path) + curFileName := strings.Split(fullName, ".")[0] + curFileId, err := strconv.Atoi(curFileName) + if err != nil { + err = fmt.Errorf("%s-Convert newFileName:%s to int failed:%s", prefix, curFileName, err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + + newFileId := curFileId + 1 + newFileName := fmt.Sprintf("%08d", newFileId) + + // 加上文件后缀 + newFileName = fmt.Sprintf("%s.%s", newFileName, con_FileName_Suffix) + + return newFileName +} + +// 获取文件夹下所有的sql文件 +// dirPath:指定要获取的文件夹路径 +// 返回值: +// []string:sql文件列表 +func GetDataFileList(dirPath string) []string { + // 获取当前目录中所有的数据文件列表 + fileList, err := fileUtil.GetFileList2(dirPath, "", con_FileName_Suffix) + if err != nil { + if os.IsNotExist(err) { + } else { + err = fmt.Errorf("%s/*.%s-Get file list failed:%s", dirPath, con_FileName_Suffix, err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + } + + // 如果文件数量大于1,则进行排序,以便于后续处理 + if len(fileList) > 1 { + sort.Strings(fileList) + } + + return fileList +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/syncObject.go b/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/syncObject.go new file mode 100644 index 0000000..1a902c9 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/syncObject.go @@ -0,0 +1,271 @@ +package sqlSync + +import ( + "database/sql" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" + + "goutil/debugUtil" + "goutil/fileUtil" + "goutil/logUtil" +) + +// 同步对象定义 +type SyncObject struct { + // 同步数据的存储路径 + dirPath string + + // 同步数据对象的唯一标识,用于进行重复判断 + identifier string + + // 数据库对象 + dbObj *sql.DB + + // 处理数据写入的文件 + sqlFileObj *SqlFile + + // 同步处理对象 + syncHandleObj SyncHandler +} + +// 进行同步对象初始化 +// maxFileSize:每个大文件的最大写入值(单位:Byte) +func (this *SyncObject) Init(maxFileSize int) { + // 启动时同步所有数据(然后才能从数据库中查询数据,以免数据丢失) + this.syncHandleObj.Init(this) + + // 构造同步数据对象 + fileName, _ := this.syncHandleObj.GetSyncingInfo() + this.sqlFileObj = NewSqlFile(this.dirPath, this.identifier, fileName, maxFileSize) + + // 当前没有正在同步的文件,则指向当前正在写的文件 + if len(fileName) <= 0 { + this.syncHandleObj.Update(this.sqlFileObj.FileFullName(), 0, nil) + } + + // 启动一个新goroutine来负责同步数据 + go func() { + /* 此处不使用goroutineMgr.Monitor/ReleaseMonitor,因为此处不能捕获panic,需要让外部进程终止执行, + 因为此模块的文件读写为核心逻辑,一旦出现问题必须停止进程,否则会造成脏数据 + */ + this.Sync() + }() +} + +// 保存数据到本地文件 +// command:待保存的指令 +func (this *SyncObject) Save(command string) { + this.sqlFileObj.Write(command) +} + +// 循环同步多个文件 +func (this *SyncObject) Sync() { + // 开始循环同步 + for { + // 同步当前文件 + this.syncOneFile() + + // 当前文件同步完成,记录同步日志 + nowFilePath, _ := this.syncHandleObj.GetSyncingInfo() + + // 删除已同步完成的文件 + WaitForOk(func() bool { + fileExist, err := fileUtil.IsFileExists(nowFilePath) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncObject IsFileExists error:%s", err.Error())) + return false + } + if fileExist == false { + return true + } + + err = fileUtil.DeleteFile(nowFilePath) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncObject delete file error:%s", err.Error())) + return false + } + + return true + }, 10*time.Second) + + // 当前文件同步完成,获取下个文件 + nextFileName := NewFileName("", nowFilePath) + filePath := filepath.Join(this.dirPath, nextFileName) + exist, err := fileUtil.IsFileExists(filePath) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncObject IsFileExists error:%s", err.Error())) + panic(err) + } + + // 如果文件不存在,退出 + if !exist { + // fmt.Println("协程退出了") + return + } + + // 更新同步的位置信息 此处忽略错误是因为,哪怕是出错了,也不会影响整体逻辑 + this.syncHandleObj.Update(filePath, 0, nil) + } +} + +// 同步单个文件 +func (this *SyncObject) syncOneFile() { + // 获取信息同步项对象 + filePath, offset := this.syncHandleObj.GetSyncingInfo() + + // 打开待读取的文件 + f, exist := this.openFile(filePath) + if exist == false { + // logUtil.WarnLog(fmt.Sprintf("待同步的文件不存在,跳过此文件:%s", filePath),"") + return + } + defer f.Close() + + for { + // 移动到需要读取的位置 + if _, err := f.Seek(offset, io.SeekStart); err != nil { + prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.Seek") + err = fmt.Errorf("%s-Seek offset for header failed:%s", prefix, err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + + command, readLen, err := Read(f) + if err != nil { + // 如果读取到文件末尾,判断是否等待 + if err == io.EOF { + if this.sqlFileObj != nil && strings.Contains(filePath, this.sqlFileObj.FileName()) { + time.Sleep(20 * time.Millisecond) + continue + } + + // 如果该文件是空文件,同步更新信息 + return + } + + prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncOneFile.f.Read") + err = fmt.Errorf("%s-Read header failed:%s", prefix, err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + + // 3. 同步到mysql中,并更新同步位置 + this.syncHandleObj.SyncOneSql(command, filePath, offset+readLen) + + // 4. 更新内存中的同步位置 + offset += readLen + } +} + +// 打开待读取的文件 +// filePath:待打开的文件 +// 返回值: +// *os.File:文件句柄, +func (this *SyncObject) openFile(filePath string) (f *os.File, exist bool) { + var err error + for { + exist, err = fileUtil.IsFileExists(filePath) + if err != nil { + err = fmt.Errorf("check file error,filePath:%v error:%v", filePath, err.Error()) + logUtil.ErrorLog(err.Error()) + time.Sleep(time.Second * 5) + continue + } + if exist == false { + // 如果文件不存在,则跳过此文件 + logUtil.WarnLog(fmt.Sprintf("file no exist, skip file:%v", filePath)) + exist = false + return + } + exist = true + + // 打开当前处理文件 + f, err = os.OpenFile(filePath, os.O_RDONLY, os.ModePerm|os.ModeTemporary) + if err != nil { + prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncOneFile.os.OpenFile") + err = fmt.Errorf("%s-Open file:%s failed:%s", prefix, filePath, err) + logUtil.ErrorLog(err.Error()) + + time.Sleep(time.Second * 5) + continue + } + + return + } +} + +// 同步数据到mysql中 +// command:sql语句 +// tx:事务处理对象 +// 返回值: +// error:错误信息 +func (this *SyncObject) syncToMysql(command string, tx *sql.Tx) error { + _, err := tx.Exec(command) + if err != nil { + prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncToMysql") + err = fmt.Errorf("%s-%s Update to mysql failed:%s", prefix, command, err) + logUtil.ErrorLog(err.Error()) + debugUtil.Printf("fatal Error:%v", err.Error()) + return err + } + + return nil +} + +// 创新新的mysql同步对象 +// dirPath:存放数据的目录 +// identifier:当前数据的唯一标识(可以使用数据库表名) +// dbObj:数据库对象 +// _syncHandleObj:同步处理对象 +// 返回值: +// mysql同步对象 +func NewSyncObject(dirPath, identifier string, dbObj *sql.DB, _syncHandleObj SyncHandler) *SyncObject { + dirPath = filepath.Join(dirPath, identifier) + + // 创建更新目录 + err := os.MkdirAll(dirPath, os.ModePerm|os.ModeTemporary) + if err != nil { + err = fmt.Errorf("%s-%s-make dir failed:%s", identifier, "SyncObject.newSyncObject.os.MkdirAll", err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + + // 构造同步信息对象 + result := &SyncObject{ + dirPath: dirPath, + identifier: identifier, + dbObj: dbObj, + syncHandleObj: _syncHandleObj, + } + + return result +} + +// 同步处理接口 +type SyncHandler interface { + // 初始化 + Init(baseObj *SyncObject) + + // 获取正在同步的信息 + // filePath:文件路径 + // offset:文件偏移量 + GetSyncingInfo() (filePath string, offset int64) + + // 更新 + // filePath:文件路径 + // offset:文件偏移量 + // tran:事务对象 + // 返回值: + // error:错误对象 + Update(filePath string, offset int64, tran *sql.Tx) error + + // 同步一条sql + // command:指令数据 + // filePath:文件路径 + // offset:文件偏移量 + SyncOneSql(command string, filePath string, offset int64) +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/utility.go b/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/utility.go new file mode 100644 index 0000000..b64025d --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/sqlSync/utility.go @@ -0,0 +1,99 @@ +package sqlSync + +import ( + "database/sql" + "fmt" + "strings" + "time" + + "goutil/logUtil" +) + +// 以事务的方式执行 +// db:数据库对象 +// funcObj:对应的具体处理函数 +// 返回值: +// error:处理是否存在错误 +func ExecuteByTran(db *sql.DB, funcObj func(tran *sql.Tx) (isCommit bool, err error)) error { + tran, err := db.Begin() + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("start transaction error:%v", err.Error())) + return err + } + + // 事务处理 + isCommit := false + defer func() { + if isCommit { + err = tran.Commit() + } else { + err = tran.Rollback() + } + + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("transaction end error:%v", err.Error())) + } + }() + + isCommit, err = funcObj(tran) + + return err +} + +// 循环执行知道返回成功为止 +// funcObj:待执行的函数 +// interval:执行间隔时间 +func WaitForOk(funcObj func() bool, interval time.Duration) { + for { + if funcObj() == false { + time.Sleep(interval) + } + + break + } +} + +// 检查是否是连接错误 +// errMsg:错误信息 +// 返回值: +// bool:true:连接错误 false:其他异常 +func CheckIfConnectionError(errMsg string) bool { + //// 连接被关闭 + ifConnectionClose := strings.Contains(errMsg, "A connection attempt failed because the connected party did not properly respond") + if ifConnectionClose { + return true + } + + // 使用过程中连接断开 + ifConnectionClose = strings.Contains(errMsg, "No connection could be made") + if ifConnectionClose { + return true + } + + // 事务处理过程中连接断开的提示 + ifConnectionClose = strings.Contains(errMsg, "bad connection") + if ifConnectionClose { + return true + } + + // socket压根儿连不上的处理 + ifConnectionClose = strings.Contains(errMsg, "A socket operation was attempted to an unreachable network") + if ifConnectionClose { + return true + } + + // 用户无法访问 + return strings.Contains(errMsg, "Access denied for user") +} + +// 获取比较简洁的错误信息 +// errMsg:错误信息 +// 返回值: +// string:比较简洁的错误信息 +func GetSimpleErrorMessage(errMsg string) string { + if strings.Contains(errMsg, "Error 1064: You have an error in your SQL syntax") { + return "SqlError" + } + + return errMsg +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/syncMgr.go b/trunk/framework/dataSyncMgr/mysqlSync/syncMgr.go new file mode 100644 index 0000000..48d8834 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/syncMgr.go @@ -0,0 +1,119 @@ +package mysqlSync + +import ( + "database/sql" + "fmt" + "sync" + + "Framework/dataSyncMgr/mysqlSync/logSqlSync" + "Framework/dataSyncMgr/mysqlSync/sqlSync" + "goutil/debugUtil" + "goutil/logUtil" +) + +// 数据同步管理 +type SyncMgr struct { + // 服务器组Id + serverGroupId int32 + + // 同步数据的存储路径 + dirPath string + + // 大文件对象size + maxFileSize int + + // 数据库对象 + dbObj *sql.DB + + // 同步对象集合 + syncObjMap map[string]*sqlSync.SyncObject + + // 同步对象锁 + mutex sync.RWMutex + + // 新建实例对象的函数 + newInstanceFunc func(mgr *SyncMgr, identifier string) *sqlSync.SyncObject +} + +// 注册同步对象 +// identifier:当前数据的唯一标识(可以使用数据库表名) +func (this *SyncMgr) RegisterSyncObj(identifier string) { + this.mutex.Lock() + defer this.mutex.Unlock() + + // 判断是否设置了相同的唯一标识,以免弄混淆 + if _, exists := this.syncObjMap[identifier]; exists { + prefix := fmt.Sprintf("%s-%s", identifier, "SyncMgr.RegisterSyncObj") + err := fmt.Errorf("%s has already existed, please change another identifier", prefix) + logUtil.ErrorLog(err.Error()) + panic(err) + } + + syncObj := this.newInstanceFunc(this, identifier) + syncObj.Init(this.maxFileSize) + this.syncObjMap[identifier] = syncObj + + if debugUtil.IsDebug() { + fmt.Printf("%s同步对象成功注册进SyncMgr, 当前有%d个同步对象\n", identifier, len(this.syncObjMap)) + } +} + +// 保存数据 +// identifier:当前数据的唯一标识(可以使用数据库表名) +// command:sql命令 +func (this *SyncMgr) Save(identifier string, command string) { + this.mutex.RLock() + defer this.mutex.RUnlock() + + syncObj, exists := this.syncObjMap[identifier] + if !exists { + err := fmt.Errorf("syncObj:%s does not existed, please register first", identifier) + logUtil.ErrorLog(err.Error()) + panic(err) + } + + syncObj.Save(command) +} + +// 构造同步管理对象 +// serverGroupId:服务器组Id +// dirPath: 文件目录 +// maxFileSize: 大文件对象大小 +// survivalTime: 同步数据存活时间 (单位:hour) +// dbObj: 数据库对象 +func NewSyncMgr(serverGroupId int32, dirPath string, maxFileSize int, survivalTime int, dbObj *sql.DB) *SyncMgr { + result := &SyncMgr{ + serverGroupId: serverGroupId, + dirPath: dirPath, + maxFileSize: maxFileSize, + dbObj: dbObj, + syncObjMap: make(map[string]*sqlSync.SyncObject), + newInstanceFunc: func(mgr *SyncMgr, identifier string) *sqlSync.SyncObject { + handler := newSyncObject(mgr.dirPath, identifier, mgr.dbObj) + return sqlSync.NewSyncObject(mgr.dirPath, identifier, mgr.dbObj, handler) + }, + } + + return result +} + +// 新建日志同步管理对象 +// serverGroupId:服务器组Id +// dirPath: 文件目录 +// maxFileSize: 大文件对象大小 +// dbObj: 数据库对象 +func NewLogSyncMgr(serverGroupId int32, dirPath string, maxFileSize int, dbObj *sql.DB) *SyncMgr { + result := &SyncMgr{ + serverGroupId: serverGroupId, + dirPath: dirPath, + maxFileSize: maxFileSize, + dbObj: dbObj, + syncObjMap: make(map[string]*sqlSync.SyncObject), + newInstanceFunc: func(mgr *SyncMgr, identifier string) *sqlSync.SyncObject { + handler := logSqlSync.NewSyncObject(mgr.serverGroupId, mgr.dirPath, identifier, mgr.dbObj) + return sqlSync.NewSyncObject(mgr.dirPath, identifier, mgr.dbObj, handler) + }, + } + + return result +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/syncObject.go b/trunk/framework/dataSyncMgr/mysqlSync/syncObject.go new file mode 100644 index 0000000..e24f082 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/syncObject.go @@ -0,0 +1,210 @@ +package mysqlSync + +import ( + "database/sql" + "fmt" + "os" + "path/filepath" + "time" + + "Framework/dataSyncMgr/mysqlSync/sqlSync" + "goutil/debugUtil" + "goutil/logUtil" +) + +// 同步对象定义 +type SyncObject struct { + // 同步数据的存储路径 + dirPath string + + // 同步数据对象的唯一标识,用于进行重复判断 + identifier string + + // 数据库对象 + dbObj *sql.DB + + // 同步信息对象 + syncingInfoObj *syncingInfo + + // 错误处理对象 + errorHandleObj *errorFile + + // 同步对象 + syncObj *sqlSync.SyncObject +} + +// 进行同步对象初始化 +// maxFileSize:每个大文件的最大写入值(单位:Byte) +func (this *SyncObject) Init(baseObj *sqlSync.SyncObject) { + this.syncObj = baseObj + + // 创建同步信息记录对象 + syncingInfoObj, err := newSyncingInfoObject(this.identifier, this.dbObj) + if err != nil { + panic(err) + } + this.syncingInfoObj = syncingInfoObj + + // 启动时同步所有数据(然后才能从数据库中查询数据,以免数据丢失) + this.syncOldData() +} + +// 同步完成之前未同步完的数据 +func (this *SyncObject) syncOldData() { + // 获取文件列表(有序的列表) + fileList := sqlSync.GetDataFileList(this.dirPath) + filePath, _ := this.syncingInfoObj.GetSyncingInfo() + + // 判断是否有文件 + if len(fileList) == 0 { + return + } + + // 判断当前文件是否为空,如果为空则将第一个文件赋给它 + if filePath == "" { + this.syncingInfoObj.Update(fileList[0], 0, nil) + } + + // 开始同步数据 + this.syncObj.Sync() + + return +} + +// 获取正在同步的信息 +// filePath:文件路径 +// offset:文件偏移量 +func (this *SyncObject) GetSyncingInfo() (filePath string, offset int64) { + return this.syncingInfoObj.GetSyncingInfo() +} + +// 更新 +// filePath:文件路径 +// offset:文件偏移量 +// tran:事务对象 +// 返回值: +// error:错误对象 +func (this *SyncObject) Update(filePath string, offset int64, tran *sql.Tx) error { + return this.syncingInfoObj.Update(filePath, offset, tran) +} + +// 同步一条sql语句 +// command:待执行的命令 +// filePath:保存路径 +// offset:文件偏移量 +// 返回值: +// error:错误信息 +func (this *SyncObject) SyncOneSql(command string, filePath string, offset int64) { + err := this.syncOneSqlDetail(command, filePath, offset) + if err == nil { + return + } + + // 发送监控报警 + this.handleError(command, filePath, offset, err) + + return +} + +// 同步一条sql语句的具体逻辑 +// command:待执行的命令 +// filePath:保存路径 +// offset:文件偏移量 +// 返回值: +// error:错误信息 +func (this *SyncObject) syncOneSqlDetail(command string, filePath string, offset int64) error { + return sqlSync.ExecuteByTran(this.dbObj, func(tx *sql.Tx) (isCommit bool, err error) { + // 保存sql到数据库 + err = this.syncToMysql(command, tx) + if err != nil { + return false, err + } + + // 保存进度信息到数据库 + err = this.syncingInfoObj.Update(filePath, offset, tx) + if err != nil { + return false, err + } + + return true, nil + }) +} + +// 同步数据到mysql中 +// command:待执行的命令 +// tx:事务对象 +// 返回值: +// error:错误信息 +func (this *SyncObject) syncToMysql(command string, tx *sql.Tx) error { + _, err := tx.Exec(command) + if err != nil { + prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncToMysql") + err = fmt.Errorf("%s-%s Update to mysql failed:%s", prefix, command, err) + logUtil.ErrorLog(err.Error()) + debugUtil.Printf("fatal Error:%v", err.Error()) + return err + } + + return nil +} + +// 进行错误处理 +// command:存在异常的数据 +// filePath:文件路径 +// offset:文件偏移量 +// err:错误信息 +func (this *SyncObject) handleError(command string, filePath string, offset int64, err error) { + defer this.errorHandleObj.Delete() + + // 保存当前sql命令 + this.errorHandleObj.SaveCommand(command) + + // 循环处理当前命令,直到没有错误 + beginTime := time.Now().Unix() + for { + // 每隔5分钟,发送警报 + if time.Now().Unix()-beginTime > 5*60 { + beginTime = time.Now().Unix() + } + + // 每次循环休眠20秒 + time.Sleep(5 * time.Second) + command = this.errorHandleObj.ReadCommand() + err = this.syncOneSqlDetail(command, filePath, offset) + if err != nil { + continue + } + + break + } +} + +// 创新新的mysql同步对象 +// dirPath:存放数据的目录 +// identifier:当前数据的唯一标识(可以使用数据库表名) +// dbObj:数据库对象 +// syncingInfoObj:同步信息记录对象 +// errorHandleObj:错误处理对象 +// 返回值: +// mysql同步对象 +func newSyncObject(dirPath, identifier string, dbObj *sql.DB) *SyncObject { + dirPath = filepath.Join(dirPath, identifier) + + // 创建更新目录 + err := os.MkdirAll(dirPath, os.ModePerm|os.ModeTemporary) + if err != nil { + err = fmt.Errorf("%s-%s-make dir failed:%s", identifier, "SyncObject.newSyncObject.os.MkdirAll", err) + logUtil.ErrorLog(err.Error()) + panic(err) + } + + // 构造同步信息对象 + result := &SyncObject{ + dirPath: dirPath, + identifier: identifier, + dbObj: dbObj, + errorHandleObj: newErrorFile(dirPath, identifier), + } + + return result +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/syncTest/dal.go b/trunk/framework/dataSyncMgr/mysqlSync/syncTest/dal.go new file mode 100644 index 0000000..60a5abf --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/syncTest/dal.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + + "github.com/go-sql-driver/mysql" + "github.com/jinzhu/gorm" + "Framework/dataSyncMgr/mysqlSync" + "goutil/logUtil" +) + +var _ = mysql.DeregisterLocalFile + +var ( + connectionString = "root:moqikaka3309@tcp(10.1.0.10:3309)/develop_liujun?charset=utf8&parseTime=true&loc=Local&timeout=60s" + maxOpenConns = 10 + maxIdleConns = 10 + + syncFileSize = 1024 * 1024 +) + +var ( + // 数据库对象 + dbObj *gorm.DB + + // 同步管理对象 + syncMgr *mysqlSync.SyncMgr +) + +func init() { + // 初始化数据库连接 + dbObj = initMysql() + + // 构造同步管理对象 + syncMgr = mysqlSync.NewSyncMgr(1, "Sync", syncFileSize, 1, dbObj.DB()) +} + +// 初始化Mysql +func initMysql() *gorm.DB { + dbObj, err := gorm.Open("mysql", connectionString) + if err != nil { + panic(fmt.Errorf("初始化数据库:%s失败,错误信息为:%s", connectionString, err)) + } + logUtil.DebugLog(fmt.Sprintf("连接mysql:%s成功", connectionString)) + + if maxOpenConns > 0 && maxIdleConns > 0 { + dbObj.DB().SetMaxOpenConns(maxOpenConns) + dbObj.DB().SetMaxIdleConns(maxIdleConns) + } + + return dbObj +} + +// 注册同步对象 +func registerSyncObj(identifier string) { + syncMgr.RegisterSyncObj(identifier) +} + +// 保存sql数据 +func save(identifier string, command string) { + syncMgr.Save(identifier, command) +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/syncTest/main.go b/trunk/framework/dataSyncMgr/mysqlSync/syncTest/main.go new file mode 100644 index 0000000..e209ebf --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/syncTest/main.go @@ -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() +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/syncTest/player.go b/trunk/framework/dataSyncMgr/mysqlSync/syncTest/player.go new file mode 100644 index 0000000..66a6829 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/syncTest/player.go @@ -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), + } +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/syncTest/player_dal.go b/trunk/framework/dataSyncMgr/mysqlSync/syncTest/player_dal.go new file mode 100644 index 0000000..04b2869 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/syncTest/player_dal.go @@ -0,0 +1,29 @@ +//package test +package main + +import ( + "fmt" +) + +var ( + con_player_tableName = "player" +) + +func init() { + registerSyncObj(con_player_tableName) +} + +func insert(obj *player) { + command := fmt.Sprintf("INSERT INTO `%s` (`Id`,`Name`) VALUES ('%v','%v') ", con_player_tableName, obj.Id, obj.Name) + save(con_player_tableName, command) +} + +func update(obj *player) { + command := fmt.Sprintf("UPDATE `%s` SET `Name` = '%v' WHERE `Id` = '%v';", con_player_tableName, obj.Name, obj.Id) + save(con_player_tableName, command) +} + +func clear(obj *player) { + command := fmt.Sprintf("DELETE FROM %s where Id = '%v';", con_player_tableName, obj.Id) + save(con_player_tableName, command) +} diff --git a/trunk/framework/dataSyncMgr/mysqlSync/syncingInfo.go b/trunk/framework/dataSyncMgr/mysqlSync/syncingInfo.go new file mode 100644 index 0000000..809c755 --- /dev/null +++ b/trunk/framework/dataSyncMgr/mysqlSync/syncingInfo.go @@ -0,0 +1,222 @@ +package mysqlSync + +import ( + "database/sql" + "fmt" + "time" + + "goutil/logUtil" +) + +var ( + // 是否已经初始化了正在同步的表信息 + ifSyncingInfoTableInited = false + + // 表初始化错误信息 + initTableError error = nil + + // 表是否已经初始化 + isTableInited bool = false +) + +// 同步信息项,保存已经处理过的文件的信息 +type syncingModel struct { + // 唯一标识 + Identifier string + + // 待处理文件的绝对路径 + FilePath string + + // 待处理文件的偏移量 + FileOffset int64 + + // 更新时间 + UpdateTime time.Time +} + +// 同步信息对象 +type syncingInfo struct { + // 同步数据对象的唯一标识,用于进行重复判断 + identifier string + + // 同步信息项 + item *syncingModel + + // 数据库连接对象 + db *sql.DB +} + +// 获取同步信息 +// filePath:正在同步的文件 +// fileOffset:同步到的位置 +func (this *syncingInfo) GetSyncingInfo() (filePath string, fileOffset int64) { + return this.item.FilePath, this.item.FileOffset +} + +// 更新正在同步的位置和文件信息 +// filePath:文件路径 +// offset:当前同步到的位置 +// tran:事务对象,可以为nil +// 返回值: +// error:处理的错误信息 +func (this *syncingInfo) Update(filePath string, offset int64, tran *sql.Tx) error { + this.item.FilePath = filePath + this.item.FileOffset = offset + this.item.UpdateTime = time.Now() + + // 更新到数据库 + return this.update(this.item, tran) +} + +// 初始化同步信息 +// 返回值: +// error:错误信息 +func (this *syncingInfo) init() error { + if ifSyncingInfoTableInited == false { + err := initSyncingInfoTable(this.db) + if err != nil { + return err + } + + ifSyncingInfoTableInited = true + } + + // 获取此表的同步信息 + data, exist, err := this.get() + if err != nil { + return err + } + + // 2. 如果同步信息不存在,则初始化一条到此表 + if exist == false { + data = &syncingModel{ + Identifier: this.identifier, + FilePath: "", + FileOffset: 0, + UpdateTime: time.Now(), + } + + err = this.insert(data) + if err != nil { + return err + } + } + + this.item = data + return nil +} + +// 从数据库获取数据 +// 返回值: +// data:获取到的数据 +// exist:是否存在此数据 +// err:错误信息 +func (this *syncingInfo) get() (data *syncingModel, exist bool, err error) { + //// 从数据库查询 + querySql := fmt.Sprintf("SELECT FilePath,FileOffset,UpdateTime FROM syncing_info WHERE Identifier ='%v';", this.identifier) + var rows *sql.Rows + rows, err = this.db.Query(querySql) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo get.query error:%v", err.Error())) + return + } + defer rows.Close() + + if rows.Next() == false { + exist = false + return + } + exist = true + + // 读取数据 + data = &syncingModel{ + Identifier: this.identifier, + } + err = rows.Scan(&data.FilePath, &data.FileOffset, &data.UpdateTime) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo get.scan error:%v", err.Error())) + return + } + + return +} + +// 把同步信息写入到数据库 +// data:待插入的数据 +// 返回值: +// error:错误信息 +func (this *syncingInfo) insert(data *syncingModel) error { + insertSql := "INSERT INTO syncing_info(Identifier,FilePath,FileOffset,UpdateTime) VALUES(?,?,?,?);" + _, err := this.db.Exec(insertSql, data.Identifier, data.FilePath, data.FileOffset, data.UpdateTime) + + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo insert error:%v", err.Error())) + } + + return err +} + +// 把同步信息更新到数据库 +// data:待更新的数据 +// tran:事务对象 +// 返回值: +// error:错误信息 +func (this *syncingInfo) update(data *syncingModel, tran *sql.Tx) error { + updateSql := "UPDATE syncing_info SET FilePath=?, FileOffset=?, UpdateTime=? WHERE Identifier=?;" + var err error + if tran != nil { + _, err = tran.Exec(updateSql, data.FilePath, data.FileOffset, data.UpdateTime, data.Identifier) + } else { + _, err = this.db.Exec(updateSql, data.FilePath, data.FileOffset, data.UpdateTime, data.Identifier) + } + + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo update error:%v", err.Error())) + } + + return err +} + +// 创建同步信息对象 +// _dirPath:目录的路径 +// _identifier:当前数据的唯一标识(可以使用数据库表名) +// _db:数据库连接对象 +// 返回值: +// 同步信息对象 +func newSyncingInfoObject(identifier string, _db *sql.DB) (result *syncingInfo, err error) { + result = &syncingInfo{ + identifier: identifier, + db: _db, + } + + err = result.init() + + return result, err +} + +// 初始化同步信息表结构 +// db:数据库连接对象 +func initSyncingInfoTable(db *sql.DB) error { + if isTableInited { + return initTableError + } + + defer func() { + isTableInited = true + }() + + // 创建同步信息表 + createTableSql := `CREATE TABLE IF NOT EXISTS syncing_info ( + Identifier varchar(30) NOT NULL COMMENT '同步唯一标识(数据库表名)', + FilePath varchar(500) NOT NULL COMMENT '正在同步的文件路径', + FileOffset bigint NOT NULL COMMENT '文件偏移量', + UpdateTime datetime NOT NULL COMMENT '最后一次更新时间', + PRIMARY KEY (Identifier) + ) COMMENT 'P表同步信息';` + if _, initTableError = db.Exec(createTableSql); initTableError != nil { + logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo initSyncingInfoTable error:%v", initTableError.Error())) + return initTableError + } + + return nil +} diff --git a/trunk/framework/exitMgr/doc.go b/trunk/framework/exitMgr/doc.go new file mode 100644 index 0000000..b47db9a --- /dev/null +++ b/trunk/framework/exitMgr/doc.go @@ -0,0 +1,6 @@ +package exitMgr + +// 程序退出包,提供程序退出时的功能 +// 使用方法: +// 1、先调用RegisterExitFunc方法,将系统退出时需要调用的方法进行注册。 +// 2、在程序退出时调用Exit()方法 diff --git a/trunk/framework/exitMgr/exit.go b/trunk/framework/exitMgr/exit.go new file mode 100644 index 0000000..f5bcaeb --- /dev/null +++ b/trunk/framework/exitMgr/exit.go @@ -0,0 +1,33 @@ +package exitMgr + +import ( + "fmt" + + "goutil/logUtil" +) + +var ( + exitFuncMap = make(map[string]func()) +) + +// RegisterExitFunc ...注册Exit方法 +// funcName:方法名称 +// exitFunc:exit方法 +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) + } +} diff --git a/trunk/framework/forbidWordsMgr/forbidWordsUtil.go b/trunk/framework/forbidWordsMgr/forbidWordsUtil.go new file mode 100644 index 0000000..8c13573 --- /dev/null +++ b/trunk/framework/forbidWordsMgr/forbidWordsUtil.go @@ -0,0 +1,256 @@ +package forbidWordsMgr + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "Framework/goroutineMgr" + . "Framework/managecenterModel" + "goutil/dfaUtil" + "goutil/logUtil" + "goutil/mathUtil" + "goutil/webUtil" + "goutil/zlibUtil" +) + +type ForbidWords struct { + //游戏ID + GameId int + //屏蔽字 + Words string +} + +// 请求屏蔽字库地址 +const GetForbidWordURL string = "http://forbidword.7qule.com/Query" + +//const GetForbidWordURL string = "http://10.253.0.186:10090/Query" + +var ( + mHashValue string + mDFAUtil *dfaUtil.DFAUtil + mGameId int + rand *mathUtil.Rand + mGameOnly bool = false +) + +// 获取屏蔽字 +func refreshForbidWord() error { + + //定义参数 + requestParamMap := make(map[string]string, 0) + requestParamMap["GameId"] = strconv.Itoa(mGameId) + requestParamMap["HashValue"] = mHashValue + requestParamMap["DataType"] = "0" + requestParamMap["IsResultCompressed"] = "true" + if mGameOnly { + requestParamMap["GameOnly"] = "true" + } + //data, _ := json.Marshal(requestParamMap) + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 1000) + + statusCode, returnBytesTemp, err := webUtil.PostMapData(GetForbidWordURL, requestParamMap, header, transport) + //statusCode, returnBytes, err := webUtil.PostMapData(GetForbidWordURL, requestParamMap, header, nil) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取屏蔽字出错,url:%s,错误信息为:%s", GetForbidWordURL, err)) + return err + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("获取屏蔽字出错,url:%s,错误码为:%d", GetForbidWordURL, statusCode)) + return err + } + + //解压 + returnBytes, err := zlibUtil.Decompress(returnBytesTemp) + if err != nil { + logUtil.ErrorLog("返回结果解压失败") + return err + } + + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取屏蔽字反序列化返回值出错,错误信息为:%s, str:%s", err, string(returnBytes))) + return err + } + + // 判断返回状态是否为成功 + if returnObj.Code != 0 { + msg := fmt.Sprintf("获取屏蔽字出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(msg) + return errors.New(msg) + } else if returnObj.Code == 0 && len(returnObj.HashValue) == 0 { //表示没有更新 + return nil + } + + // 解析Data + var tmpForbidWordList []*ForbidWords + if data, ok := returnObj.Data.(string); !ok { + msg := "获取屏蔽字出错,返回的数据不是string类型" + logUtil.ErrorLog(msg) + return errors.New(msg) + } else { + if err = json.Unmarshal([]byte(data), &tmpForbidWordList); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取屏蔽字反序列化数据出错,错误信息为:%s", err)) + return err + } + } + + //缓存hasvalue + mHashValue = returnObj.HashValue + + //获取屏蔽字数组 + var temWordArray []string + for _, item := range tmpForbidWordList { + temWordArray = append(temWordArray, strings.ToUpper(item.Words)) + } + //dfa + mDFAUtil = dfaUtil.NewDFAUtil(temWordArray) + + return nil +} + +// 是否包含屏蔽字 +func IsSensitiveWords(input string) (exist bool) { + input = strings.ToUpper(input) + exist = mDFAUtil.IsMatch(input) + + return +} + +// 取出屏蔽字及开始位置 +func SensitiveWords(input string) (words []string, pos []int, exist bool) { + input2 := strings.ToUpper(input) + inputRune := []rune(input) + startIndexList, endIndexList := mDFAUtil.SearchSentence(input2) + if len(startIndexList) > 0 { + exist = true + words = make([]string, 0, len(startIndexList)) + pos = make([]int, 0, len(startIndexList)) + for i := 0; i < len(startIndexList); i++ { + start := startIndexList[i] + end := endIndexList[i] + words = append(words, string(inputRune[start:end+1])) + pos = append(pos, start) + } + } + + return +} + +// 取出屏蔽字及开始及结束位置 +func SensitiveWordsEndStartPos(input string) (words []string, starts, ends []int, exist bool) { + input2 := strings.ToUpper(input) + inputRune := []rune(input) + startIndexList, endIndexList := mDFAUtil.SearchSentence(input2) + if len(startIndexList) > 0 { + exist = true + words = make([]string, 0, len(startIndexList)) + starts = startIndexList + ends = endIndexList + for i := 0; i < len(startIndexList); i++ { + start := startIndexList[i] + end := endIndexList[i] + words = append(words, string(inputRune[start:end+1])) + //pos = append(pos, start) + } + } + + return +} + +// 根据用户输入的替换词替换敏感词 +func ReplaceSendsitiveWords(input, replaceStr string) (newStr string) { + words, _, _, exist := SensitiveWordsEndStartPos(input) + newStr = input + //如果不存在敏感词,则直接返回 + if !exist { + return + } + //循环替换 + for _, sendsitiveWord := range words { + newStr = strings.Replace(newStr, sendsitiveWord, replaceStr, -1) + } + + return +} + +// 判断服务器是否存在 +func IfServerExists() (exist bool) { + + return +} + +// 处理屏蔽字 +func HandleSendsitiveWords(input string) (newStr string) { + newStr = mDFAUtil.HandleWord(input, rune('*')) + + return +} + +// 处理敏感字-xjChat +func HandleSendsitiveWordsUseStr(input string, replaceCh string) (newStr string) { + newStr = mDFAUtil.HandleWordUseStr(input, replaceCh) + + return +} + +// 定时刷新屏蔽字库 +func StartRefreshForbidWordListTread(gameId int) { + rand = mathUtil.GetRand() + mGameId = gameId + // 定时刷新数据 + go func() { + goroutineName := "forbidWordsMgr.StartRefreshForbidWordListTread" + goroutineMgr.Monitor(goroutineName) + defer goroutineMgr.ReleaseMonitor(goroutineName) + + for { + + func() { + // 防止panic + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + }() + + // 刷新屏蔽字 + refreshForbidWord() + + }() + + // 每5分钟刷新一次 + time.Sleep(time.Duration(rand.GetRandRangeInt64(120, 300)) * time.Second) + } + }() +} + +// 定时刷新屏蔽字库(排除公共字库,只刷新游戏内的) +func StartRefreshForbidWordListTreadExcludeComm(gameId int) { + rand = mathUtil.GetRand() + mGameId = gameId + mGameOnly = true + // 定时刷新数据 + go func() { + goroutineName := "forbidWordsMgr.StartRefreshForbidWordListTread" + goroutineMgr.Monitor(goroutineName) + defer goroutineMgr.ReleaseMonitor(goroutineName) + + for { + // 刷新屏蔽字 + refreshForbidWord() + + // 每5分钟刷新一次 + time.Sleep(time.Duration(rand.GetRandRangeInt64(120, 300)) * time.Second) + } + }() +} diff --git a/trunk/framework/forbidWordsMgr/forbidWords_test.go b/trunk/framework/forbidWordsMgr/forbidWords_test.go new file mode 100644 index 0000000..2b89205 --- /dev/null +++ b/trunk/framework/forbidWordsMgr/forbidWords_test.go @@ -0,0 +1,61 @@ +package forbidWordsMgr + +import ( + "fmt" + "testing" + "time" +) + +// type Persion struct { +// name string +// } + +// 屏蔽字详细信息 +func Test1(t *testing.T) { + //启动获取屏蔽字 + refreshForbidWord() + + words, pos, exist := SensitiveWords("测试,测试") + if exist { + t.Log(words, pos) + } + + t.Log("END") +} + +func TestForbidWord(t *testing.T) { + // //启动获取屏蔽字 + // refreshForbidWord() + + // //判断是否有屏蔽字 + // str := "好多花姑凉" + + // isExist := IsSensitiveWords(str) + + // fmt.Println("是否有敏感字:", isExist) + + // //处理屏蔽字 + // if isExist { + // newstr := HandleSendsitiveWords(str) + + // fmt.Println("newStr:", newstr) + // } + + // persionSlice := make([]*Persion, 0, 5) + // //var persionSlice []*Persion + // for i := 0; i < 10; i++ { + // p := &Persion{ + // name: "append", + // } + // persionSlice = append(persionSlice, p) + // fmt.Println(&persionSlice) + // } + + ///fmt.Println(IfServerExists()) + StartRefreshForbidWordListTread(27) + time.Sleep(10 * time.Second) + fmt.Println(IsSensitiveWords("g点双享器")) + fmt.Println(IsSensitiveWords("G点双享器")) + time.Sleep(5 * time.Hour) + +} diff --git a/trunk/framework/gameLogMgr/gameLog.go b/trunk/framework/gameLogMgr/gameLog.go new file mode 100644 index 0000000..1c8739a --- /dev/null +++ b/trunk/framework/gameLogMgr/gameLog.go @@ -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 +} diff --git a/trunk/framework/gameLogMgr/gameLog_test.go b/trunk/framework/gameLogMgr/gameLog_test.go new file mode 100644 index 0000000..8e52a2b --- /dev/null +++ b/trunk/framework/gameLogMgr/gameLog_test.go @@ -0,0 +1,67 @@ +package gameLogMgr + +import ( + "bytes" + "fmt" + "testing" + "time" + + "goutil/debugUtil" + "goutil/stringUtil" + "goutil/timeUtil" +) + +func TestWrite(t *testing.T) { + debugUtil.SetDebug(true) + + brokerList := []string{"10.1.0.202:9092", "10.1.0.204:9092", "10.1.0.205:9092"} + Start(brokerList, "", "") + + topic := "test2" + serverGroupId := int32(20011) + for i := 0; i < 5; i++ { + Write(topic, serverGroupId, getGameLog(i)) + } + + time.Sleep(5 * time.Second) + Stop() +} + +func BenchmarkWrite(b *testing.B) { + debugUtil.SetDebug(true) + topic := "test2" + serverGroupId := int32(20011) + + brokerList := []string{"10.1.0.202:9092", "10.1.0.204:9092", "10.1.0.205:9092"} + Start(brokerList, "", "") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + Write(topic, serverGroupId, getGameLog(i)) + } + b.StopTimer() + + Stop() +} + +func getGameLog(int2 int) string { + //kafkaLog组装 + var buffer bytes.Buffer + buffer.WriteString("{") + buffer.WriteString(fmt.Sprintf("\"#account_id\":\"%s\"", "123456789123456789")) + buffer.WriteString(",") + buffer.WriteString(fmt.Sprintf("\"#time\":\"%s\"", time.Now().Format("2006-01-02 15:04:05"))) + buffer.WriteString(",") + buffer.WriteString(fmt.Sprintf("\"#uuid\":\"%s\"", stringUtil.GetNewGUID())) + buffer.WriteString(",") + buffer.WriteString(fmt.Sprintf("\"#event_id\":\"\"")) + buffer.WriteString(",") + buffer.WriteString(fmt.Sprintf("\"#type\":\"track\"")) + buffer.WriteString(",") + buffer.WriteString(fmt.Sprintf("\"#event_name\":\"achievement_change_log\"")) + buffer.WriteString(",") + buffer.WriteString(fmt.Sprintf("\"properties\":{\"PartnerId\":%d,\"ServerId\":%d,\"Crtime\":\"%s\",\"Crdate\":\"%s\"}", 600021, int2, timeUtil.ToDateTimeString2(time.Now()), timeUtil.ToDateString2(time.Now()))) + buffer.WriteString("}") + + return buffer.String() +} diff --git a/trunk/framework/gameLogMgr/model.go b/trunk/framework/gameLogMgr/model.go new file mode 100644 index 0000000..cbbbf18 --- /dev/null +++ b/trunk/framework/gameLogMgr/model.go @@ -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, + } +} diff --git a/trunk/framework/gameServerMgr/area.go b/trunk/framework/gameServerMgr/area.go new file mode 100644 index 0000000..764d1d6 --- /dev/null +++ b/trunk/framework/gameServerMgr/area.go @@ -0,0 +1,76 @@ +package gameServerMgr + +import ( + . "Framework/managecenterModel" +) + +var ( + mAreaList = make([]*Area, 0) +) + +// 解析大区信息 +func ParseAreaInfo(areaList []*Area) { + mAreaList = areaList +} + +// 根据服务器组id获取大区Id +func GetAreaIdByGroupId(groupId int32) (areaId int32) { + areaId = 0 + + //如果没有大区数据,返回0 + if mAreaList == nil || len(mAreaList) < 1 { + return + } + + for _, area := range mAreaList { + if area.CheckServerIdIsInRange(groupId) { + areaId = area.AreaId + + break + } + } + + return +} + +// 根据服务器组id获取大区对象数据 +func GetAreaDBByGroupId(groupId int32) (areaDB *Area, exist bool) { + //如果没有大区数据,返回空 + exist = false + if mAreaList == nil || len(mAreaList) < 1 { + return + } + + for _, area := range mAreaList { + if area.CheckServerIdIsInRange(groupId) { + areaDB = area + exist = true + break + } + } + return +} + +// 根据大区ID获取大区信息 +func GetAreaDBbyAreaID(areaId int32) (areaDB *Area, exist bool) { + //如果没有大区数据,返回空 + exist = false + if mAreaList == nil || len(mAreaList) < 1 { + return + } + + for _, area := range mAreaList { + if area.AreaId == areaId { + areaDB = area + exist = true + break + } + } + return +} + +// 获取所有大区信息 +func GetAllAreaDB() []*Area { + tempList := mAreaList + return tempList +} diff --git a/trunk/framework/gameServerMgr/chargeConfig.go b/trunk/framework/gameServerMgr/chargeConfig.go new file mode 100644 index 0000000..47fa1a1 --- /dev/null +++ b/trunk/framework/gameServerMgr/chargeConfig.go @@ -0,0 +1,33 @@ +package gameServerMgr + +import ( + "encoding/json" + + . "Framework/managecenterModel" +) + +var ( + mChargeConfigMap = make(map[int32][]*ChargeConfig, 0) +) + +//解析充值配置信息 +func ParseChargeConfigInfo(partnerList []*Partner) { + tmpChargeConfigMap := make(map[int32][]*ChargeConfig, 0) + + //循环解析所有合作商里面的充值配置信息 + for _, partner := range partnerList { + var chargeConfigList []*ChargeConfig + if err := json.Unmarshal([]byte(partner.ChargeConfig), &chargeConfigList); err == nil { + tmpChargeConfigMap[partner.Id] = chargeConfigList + } + } + + mChargeConfigMap = tmpChargeConfigMap +} + +// 根据合作商Id获取合作商充值配置对象 +func GetChargeConfigList(partnerId int32) (chargeConfigList []*ChargeConfig, exist bool) { + chargeConfigList, exist = mChargeConfigMap[partnerId] + + return +} diff --git a/trunk/framework/gameServerMgr/chargeUtil.go b/trunk/framework/gameServerMgr/chargeUtil.go new file mode 100644 index 0000000..825d3d1 --- /dev/null +++ b/trunk/framework/gameServerMgr/chargeUtil.go @@ -0,0 +1,433 @@ +package gameServerMgr + +import ( + "encoding/json" + "fmt" + "math" + "sort" + "strconv" + + . "Framework/managecenterModel" + "goutil/logUtil" + "goutil/securityUtil" + "goutil/webUtil" +) + +type ChargeUtil struct{} + +//获取Int32型的充值金额(充值金额必须大于1,否则可能就会无效) +func (this *ChargeUtil) GetInt32ChargeMoney(money float32) int32 { + return int32(money) +} + +//获取排好序的充值配置列表 +func (this *ChargeUtil) GetOrderedChargeConfigList(partnerId int32, isMonthCard bool) (list []*ChargeConfig, exist bool) { + var tempList []*ChargeConfig + tempList, exist = GetChargeConfigList(partnerId) + + //如果不存在,则返回空集合 + if !exist { + return + } + + //循环遍历集合,找出是月卡的数据 + for _, item := range tempList { + if item.IsMonthCard == isMonthCard { + list = append(list, item) + } + } + + return +} + +// 生成充值订单号 +// url:生成订单号的服务器地址 +// productId:产品Id +// partnerId:合作商Id +// serverId:服务器Id +// userId:平台用户Id +// playerId:玩家Id +// mac:mac +// idfa:idfa +// ip:ip +// imei:imei +// extra:extra +// isMonthCard:是否月卡 +// 返回值: +// 订单号 +// 错误对象 +func (this *ChargeUtil) GenerateOrderId(url, productId string, partnerId, serverId int32, + userId, playerId, mac, idfa, ip, imei, extra string, + isMonthCard bool) (orderId string, err error) { + + if extra == "" { + extra = "FromGameServer" + } + + // 定义请求参数 + postDict := make(map[string]string) + postDict["ProductId"] = productId + postDict["PartnerId"] = fmt.Sprintf("%d", partnerId) + postDict["ServerId"] = fmt.Sprintf("%d", serverId) + postDict["UserId"] = userId + postDict["PlayerId"] = playerId + postDict["MAC"] = mac + postDict["IDFA"] = idfa + postDict["IP"] = ip + postDict["IMEI"] = imei + postDict["Extra"] = extra + postDict["IsMonthCard"] = strconv.FormatBool(isMonthCard) + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, returnBytes, err1 := webUtil.PostMapData(url, postDict, header, transport) + if err1 != nil { + logUtil.ErrorLog(fmt.Sprintf("生成充值订单号出错,url:%s,错误信息为:%s", url, err1)) + err = err1 + return + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("生成充值订单号出错,url:%s,错误码为:%d", url, statusCode)) + err = fmt.Errorf("生成充值订单号出错,url:%s,错误码为:%d", url, statusCode) + return + } + + orderId = string(returnBytes) + if orderId == "" { + err = fmt.Errorf("Order Is Empty") + return + } + + return +} + +//生成支付订单信息 +func (this *ChargeUtil) GeneratePayOrderInfo(url string, partnerId int32, jsonBase64Data string) (returnObject ReturnObject, err error) { + //String requestParam = String.Format(@"PartnerId={0}&JsonBase64Data={1}", partnerId, jsonBase64Data); + postDict := make(map[string]string) + postDict["PartnerId"] = fmt.Sprintf("%d", partnerId) + postDict["JsonBase64Data"] = jsonBase64Data + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + //通过POST方式请求数据 + statusCode, returnBytes, err1 := webUtil.PostMapData(url, postDict, header, transport) + if err1 != nil { + logUtil.ErrorLog(fmt.Sprintf("生成支付订单信息错误:%s,错误信息为:%s", url, err1)) + err = err1 + return + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("生成支付订单信息出错,url:%s,错误码为:%d", url, statusCode)) + err = fmt.Errorf("生成支付订单信息出错,url:%s,错误码为:%d", url, statusCode) + return + } + + err = json.Unmarshal(returnBytes, &returnObject) + + return + //return JsonUtil.Deserialize(WebUtil.PostWebData(generateOrderIdUrl, requestParam, DataCompress.NotCompress)); +} + +// 获取充值配置列表 +// partnerId:合作商Id +// vipLv:玩家VIP等级 +// isMonthCard:是否月卡 +// filter:过滤器 +// 返回值: +// 充值配置列表 +// 是否存在 +// 错误对象 +func (this *ChargeUtil) GetChargeConfigList(partnerId int32, vipLv byte, isMonthCard bool, + filter func(*ChargeConfig) bool) (chargeConfigList []*ChargeConfig, exists bool, err error) { + + // 获取排好序的充值配置列表 + var tmpList []*ChargeConfig + tmpList, exists, err = this.getSortedChargeConfigList(partnerId, isMonthCard) + if err != nil || !exists { + return + } + + // 根据vip和filter进行筛选 + for _, item := range tmpList { + if filter != nil { + if vipLv >= item.VipLv && filter(item) { + chargeConfigList = append(chargeConfigList, item) + } + } else { + if vipLv >= item.VipLv { + chargeConfigList = append(chargeConfigList, item) + } + } + } + + // 判断是否有符合条件的数据 + if len(chargeConfigList) == 0 { + exists = false + return + } + + return +} + +// 获取有序的充值配置列表 +// partnerId:合作商Id +// isMonthCard:是否月卡 +// 返回值: +// 充值配置列表 +// 是否存在 +// 错误对象 +func (this *ChargeUtil) getSortedChargeConfigList(partnerId int32, isMonthCard bool) (chargeConfigList []*ChargeConfig, exists bool, err error) { + // 判断合作商是否存在 + //var partnerObj *Partner + //partnerObj, exists = managecenterMgr.GetPartner(partnerId) + //if !exists { + //return + //} + + // 反序列化充值配置 + var tmpChargeConfigList []*ChargeConfig + tmpChargeConfigList, exists = GetChargeConfigList(partnerId) + + if !exists { + return + } + //if err = json.Unmarshal([]byte(partnerObj.ChargeConfig), &tmpChargeConfigList); err != nil { + //return + //} + + // 根据isMonthCard进行筛选 + for _, item := range tmpChargeConfigList { + if item.IsMonthCard == isMonthCard { + chargeConfigList = append(chargeConfigList, item) + } + } + + // 判断是否有符合条件的数据 + if len(chargeConfigList) == 0 { + exists = false + return + } + + // 按默认规则进行排序 + sort.Slice(chargeConfigList, func(i, j int) bool { + return chargeConfigList[i].SortByChargePointAsc(chargeConfigList[j]) + }) + + return +} + +// 获取充值配置项 +// partnerId:合作商Id +// vipLv:玩家VIP等级 +// isMonthCard:是否月卡 +// money:充值金额 +// 返回值: +// 充值配置项 +// 是否存在 +// 错误对象 +func (this *ChargeUtil) GetChargeConfigItem(partnerId int32, vipLv byte, isMonthCard bool, money float64) (chargeConfigItem *ChargeConfig, exists bool, err error) { + // 获取排好序的充值配置列表 + var tmpList []*ChargeConfig + tmpList, exists, err = this.getSortedChargeConfigList(partnerId, isMonthCard) + if err != nil || !exists { + return + } + + // 获取满足充值金额和VIP条件的最后一条数据 + for _, item := range tmpList { + if vipLv >= item.VipLv && money >= item.ChargePoint { + chargeConfigItem = item + } + } + + // 如果没有符合条件的,则选择第一条配置 + if chargeConfigItem == nil { + chargeConfigItem = tmpList[0] + } + + return +} + +// 计算充值获得的游戏点数 +// partnerId:合作商Id +// vipLv:玩家VIP等级 +// isMonthCard:是否月卡 +// money:充值金额 +// 返回值: +// 充值获得的游戏点数 +// 是否存在 +// 错误对象 +func (this *ChargeUtil) CalcChargeGamePoint(partnerId int32, vipLv byte, isMonthCard bool, money float64, productId string) (chargeGamePoint int, exists bool, err error) { + // 获取排好序的充值配置列表 + chargeGamePoint = 0 + + tmpList := make([]*ChargeConfig, 0, 8) + tmpList, exists, err = this.getSortedChargeConfigList(partnerId, isMonthCard) + if err != nil || !exists { + return + } + + var chargeConfigItem *ChargeConfig + + if money > 0 { + // 获取满足充值金额和VIP条件的最后一条数据 + for _, item := range tmpList { + if vipLv >= item.VipLv && money >= item.ChargePoint { + chargeConfigItem = item + } + } + + // 如果找不到对应的档位,则选择最低金额档位 + if chargeConfigItem == nil { + chargeConfigItem = tmpList[0] + } + + // 计算充值对应的ProductId,以及获得的游戏货币 + if money == chargeConfigItem.ChargePoint { + chargeGamePoint = chargeConfigItem.GamePoint + } else { + chargeGamePoint = int(math.Ceil(money * chargeConfigItem.Ratio)) + } + return + } else { + for _, item := range tmpList { + if item.ProductId == productId && item.VipLv <= vipLv { + chargeConfigItem = item + break + } else { + continue + } + } + + if chargeConfigItem != nil { + chargeGamePoint = chargeConfigItem.GamePoint + return + } + } + + return +} + +// 计算充值获得的所有游戏点数 +// partnerId:合作商Id +// vipLv:玩家VIP等级 +// isMonthCard:是否月卡 +// money:充值金额 +// activityMoney:活动金额 +// isFirstCharge:是否首充 +// 返回值: +// 充值获得的游戏点数 +// 充值赠送获得的游戏内货币数量 +// 充值活动获得的游戏内货币数量 +// 总的元宝数量 +// 是否存在 +// 错误对象 +func (this *ChargeUtil) CalcChargeAllGamePoint(partnerId int32, vipLv byte, isMonthCard bool, + money, activityMoney float64, productId string, isFirstCharge bool) ( + chargeGamePoint int, giveGamePoint int, activityGamePoint int, totalGamePoint int, + exists bool, err error) { + + // 获取排好序的充值配置列表 + tmpList := make([]*ChargeConfig, 0, 8) + tmpList, exists, err = this.getSortedChargeConfigList(partnerId, isMonthCard) + if err != nil || !exists { + return + } + + var chargeConfigItem *ChargeConfig + + if money > 0 { + // 获取满足充值金额和VIP条件的最后一条数据 + for _, item := range tmpList { + if vipLv >= item.VipLv && money >= item.ChargePoint { + chargeConfigItem = item + } + } + + // 如果找不到对应的档位,则选择最低金额档位 + if chargeConfigItem == nil { + chargeConfigItem = tmpList[0] + } + + // 计算充值对应的ProductId,以及获得的游戏货币 + if money == chargeConfigItem.ChargePoint { + chargeGamePoint = chargeConfigItem.GamePoint + if isFirstCharge { + giveGamePoint = chargeConfigItem.FirstGiveGamePoint + } else { + giveGamePoint = chargeConfigItem.GiveGamePoint + } + } else { + chargeGamePoint = int(math.Ceil(money * chargeConfigItem.Ratio)) + if isFirstCharge { + giveGamePoint = chargeConfigItem.FirstGiveGamePoint + } else { + giveGamePoint = int(math.Ceil(money * chargeConfigItem.Ratio * chargeConfigItem.GiveRatio)) + } + } + + activityGamePoint = int(math.Ceil(activityMoney * chargeConfigItem.Ratio)) + } else { + // 获取满足充值金额和VIP条件的最后一条数据 + for _, item := range tmpList { + if vipLv >= item.VipLv && item.ProductId == productId { + chargeConfigItem = item + } + } + + if chargeConfigItem != nil { + chargeGamePoint = chargeConfigItem.GamePoint + if isFirstCharge { + giveGamePoint = chargeConfigItem.FirstGiveGamePoint + } else { + giveGamePoint = chargeConfigItem.GiveGamePoint + } + + activityGamePoint = int(math.Ceil(activityMoney * chargeConfigItem.Ratio)) + } + } + + // 计算总和 + totalGamePoint = chargeGamePoint + giveGamePoint + activityGamePoint + + return +} + +// 验证充值签名 +// partnerId:合作商Id +// serverId:区服Id +// playerId:玩家Id +// orderId:订单Id +// productId:产品Id +// deviceIdentifier:设备唯一标识 +// sign:签名 +// 返回值: +// bool:签名验证情况 +func (this *ChargeUtil) CheckChargeSign(partnerId int32, serverId int32, playerId string, orderId string, productId string, deviceIdentifier string, sign string) bool { + partnerObj, exist := GetPartnerItem(partnerId) + if exist == false { + return false + } + + rawString := fmt.Sprintf("%v%v%v%v%v%v%v", partnerId, serverId, playerId, orderId, productId, deviceIdentifier, partnerObj.LoginKey) + if securityUtil.Md5String(rawString, false) != sign { + return false + } + + return true +} + +// ------------------类型定义和业务逻辑的分隔符------------------------- + +var ( + ChargeUtilObj = new(ChargeUtil) +) diff --git a/trunk/framework/gameServerMgr/chargeUtil_test.go b/trunk/framework/gameServerMgr/chargeUtil_test.go new file mode 100644 index 0000000..2162edb --- /dev/null +++ b/trunk/framework/gameServerMgr/chargeUtil_test.go @@ -0,0 +1,410 @@ +package gameServerMgr + +import ( + "Framework/managecenterModel" + "goutil/stringUtil" + "testing" +) + +func TestChargeUtil_GetChargeConfigList(t *testing.T) { + /* + 渠道充值配置:[{"ProductId":"fzxh_00060","ChargePoint":6,"GamePoint":60,"Ratio":10,"GiveGamePoint":6,"FirstGiveGamePoint":60,"GiveRatio":0.1,"VipLv":0,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_00300","ChargePoint":30,"GamePoint":300,"Ratio":10,"GiveGamePoint":30,"FirstGiveGamePoint":300,"GiveRatio":0.1,"VipLv":0,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_00500","ChargePoint":50,"GamePoint":550,"Ratio":11,"GiveGamePoint":110,"FirstGiveGamePoint":550,"GiveRatio":0.2,"VipLv":1,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_00980","ChargePoint":98,"GamePoint":1470,"Ratio":15,"GiveGamePoint":441,"FirstGiveGamePoint":1470,"GiveRatio":0.3,"VipLv":5,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_restriction_18","ChargePoint":18,"GamePoint":90,"Ratio":5,"GiveGamePoint":0,"FirstGiveGamePoint":0,"GiveRatio":0,"VipLv":3,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":true},{"ProductId":"fzxh_restriction_50","ChargePoint":50,"GamePoint":300,"Ratio":6,"GiveGamePoint":0,"FirstGiveGamePoint":0,"GiveRatio":0,"VipLv":1,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":true}] + */ + + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + var partnerId int32 = 1001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + type VipRange struct { + MinVip byte + MaxVip byte + } + + var isMonthCard = false + var maxVip byte = 20 + + vipMap := make(map[VipRange][]float64) + vipMap[VipRange{MinVip: 0, MaxVip: 0}] = []float64{6, 30} + vipMap[VipRange{MinVip: 1, MaxVip: 4}] = []float64{6, 30, 50} + vipMap[VipRange{MinVip: 5, MaxVip: maxVip}] = []float64{6, 30, 50, 98} + + isExistsChargeConfig := func(configs []*managecenterModel.ChargeConfig, point float64) bool { + for _, item := range configs { + if item.ChargePoint == point { + return true + } + } + + return false + } + + //每个VIP等级都获取一遍充值档位 + for vipRange, points := range vipMap { + for vip := vipRange.MinVip; vip <= vipRange.MaxVip; vip++ { + showChargeList, exists, error := ChargeUtilObj.GetChargeConfigList(partnerId, vip, isMonthCard, func(config *managecenterModel.ChargeConfig) bool { + if config.IfFirstShow == 1 && config.IfSecondShow == 1 { + return true + } + + return false + }) + + if error != nil { + t.Fatalf("获取充值配置列表出现错误:%v", error) + } + + if !exists { + t.Fatalf("PartnerId[%d],Vip[%d],IsMonthCard[%v],未找到充值配置列表", partnerId, vip, isMonthCard) + } + + for _, point := range points { + if !isExistsChargeConfig(showChargeList, point) { + t.Fatalf("PartnerId[%d],Vip[%d],IsMonthCard[%v],金额[%f]未找到充值配置", partnerId, vip, isMonthCard, point) + } + } + } + } +} + +// 测试生成订单号 +func TestChargeUtil_GenerateOrderId(t *testing.T) { + var chargeServerUrl = "https://chargetest-xht.moqikaka.com/API/GenerateOrderId.ashx" + var serverGroupId int32 = 20001 + var partnerId int32 = 1001 + var productId = "fzxh_00060" + var userId = stringUtil.GetNewGUID() + var playerId = stringUtil.GetNewGUID() + var isMonthCard = true + + //生成订单号 + orderId, error := ChargeUtilObj.GenerateOrderId( + chargeServerUrl, productId, partnerId, serverGroupId, userId, playerId, "macmacmacmac", "idfa", "ip", "imei", "extra", isMonthCard) + if error != nil { + t.Fatalf("生成订单号[GenerateOrderId]出现错误:%v", error) + } + + t.Logf("生成订单号[GenerateOrderId]:%s", orderId) +} + +// 测试验证充值回调签名 +func TestChargeUtil_CheckChargeSign(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + var partnerId int32 = 1001 + var playerId = "04bade21-d7f7-4188-bc7c-8914c5330a23" + var orderId = "1001_20001_1590394423_2" + var productId = "fzxh_00060" + var deviceIdentifier = "deviceIdentifier" + //var loginKey = "a0482eaf-14e8-4a65-950e-864214f62da5" + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //充值服务器生成的签名 + var targetSign = "02bd33319fc0f520a3e801f725973017" + + //验证充值回调签名是否正确 + if !ChargeUtilObj.CheckChargeSign(partnerId, serverGroupId, playerId, orderId, productId, deviceIdentifier, targetSign) { + t.Fatalf("验证充值回调签名失败!目标签名:%s", targetSign) + } +} + +// 测试获取充值配置 +func TestChargeUtil_GetChargeConfigItem(t *testing.T) { + /* + 渠道充值配置:[{"ProductId":"fzxh_00060","ChargePoint":6,"GamePoint":60,"Ratio":10,"GiveGamePoint":6,"FirstGiveGamePoint":60,"GiveRatio":0.1,"VipLv":0,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_00300","ChargePoint":30,"GamePoint":300,"Ratio":10,"GiveGamePoint":30,"FirstGiveGamePoint":300,"GiveRatio":0.1,"VipLv":0,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_00500","ChargePoint":50,"GamePoint":550,"Ratio":11,"GiveGamePoint":110,"FirstGiveGamePoint":550,"GiveRatio":0.2,"VipLv":1,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_00980","ChargePoint":98,"GamePoint":1470,"Ratio":15,"GiveGamePoint":441,"FirstGiveGamePoint":1470,"GiveRatio":0.3,"VipLv":5,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_restriction_18","ChargePoint":18,"GamePoint":90,"Ratio":5,"GiveGamePoint":0,"FirstGiveGamePoint":0,"GiveRatio":0,"VipLv":3,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":true},{"ProductId":"fzxh_restriction_50","ChargePoint":50,"GamePoint":300,"Ratio":6,"GiveGamePoint":0,"FirstGiveGamePoint":0,"GiveRatio":0,"VipLv":1,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":true}] + */ + + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + var partnerId int32 = 1001 + var maxVip byte = 20 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + type VipRange struct { + MinVip byte + MaxVip byte + } + + //#region 测试获取普通充值配置 + + isMonthCard := false + vipMap := make(map[VipRange][]float64) + vipMap[VipRange{MinVip: 0, MaxVip: 0}] = []float64{6, 30} + vipMap[VipRange{MinVip: 1, MaxVip: 4}] = []float64{6, 30, 50} + vipMap[VipRange{MinVip: 5, MaxVip: maxVip}] = []float64{6, 30, 50, 98} + + //每个VIP等级都获取一遍充值档位 + for vipRange, points := range vipMap { + for vip := vipRange.MinVip; vip <= vipRange.MaxVip; vip++ { + for _, point := range points { + //获取充值配置 + chargeConfig, exists, error := ChargeUtilObj.GetChargeConfigItem(partnerId, vip, isMonthCard, point) + if error != nil { + t.Fatalf("普通充值,获取充值配置[GetChargeConfigItem],Vip[%d],Point[%f]获取充值档位出现错误:%v", vip, point, error) + } + + if !exists { + t.Fatalf("普通充值,获取充值配置[GetChargeConfigItem],Vip[%d],Point[%f]未找到充值档位配置", vip, point) + } + + if chargeConfig == nil { + t.Fatalf("普通充值,获取充值配置[GetChargeConfigItem],Vip[%d],Point[%f]找到的充值档位配置为nil", vip, point) + } + + if chargeConfig.ChargePoint != point { + t.Fatalf("普通充值,获取充值配置[GetChargeConfigItem],Vip[%d],Point[%f]找到的充值档位ChargePoint[%f]错误", vip, point, chargeConfig.ChargePoint) + } + } + } + } + + //#endregion + + //#region 测试获取月卡充值配置 + + isMonthCard = true + vipMap = make(map[VipRange][]float64) + vipMap[VipRange{MinVip: 1, MaxVip: 1}] = []float64{50} + vipMap[VipRange{MinVip: 3, MaxVip: maxVip}] = []float64{18, 50} + + //每个VIP等级都获取一遍充值档位 + for vipRange, points := range vipMap { + for vip := vipRange.MinVip; vip <= vipRange.MaxVip; vip++ { + for _, point := range points { + //获取充值配置 + chargeConfig, exists, error := ChargeUtilObj.GetChargeConfigItem(partnerId, vip, isMonthCard, point) + if error != nil { + t.Fatalf("月卡充值,获取充值配置[GetChargeConfigItem],Vip[%d],Point[%f]获取充值档位出现错误:%v", vip, point, error) + } + + if !exists { + t.Fatalf("月卡充值,获取充值配置[GetChargeConfigItem],Vip[%d],Point[%f]未找到充值档位配置", vip, point) + } + + if chargeConfig == nil { + t.Fatalf("月卡充值,Vip[%d],Point[%f]找到的充值档位配置为nil", vip, point) + } + + if chargeConfig.ChargePoint != point { + t.Fatalf("月卡充值,获取充值配置[GetChargeConfigItem],Vip[%d],Point[%f]找到的充值档位ChargePoint[%f]错误", vip, point, chargeConfig.ChargePoint) + } + } + } + } + + //#endregion +} + +// 测试计算充值获得的游戏货币 +func TestChargeUtil_CalcChargeAllGamePoint(t *testing.T) { + /* + 渠道充值配置:[{"ProductId":"fzxh_00060","ChargePoint":6,"GamePoint":60,"Ratio":10,"GiveGamePoint":6,"FirstGiveGamePoint":60,"GiveRatio":0.1,"VipLv":0,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_00300","ChargePoint":30,"GamePoint":300,"Ratio":10,"GiveGamePoint":30,"FirstGiveGamePoint":300,"GiveRatio":0.1,"VipLv":0,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_00500","ChargePoint":50,"GamePoint":550,"Ratio":11,"GiveGamePoint":110,"FirstGiveGamePoint":550,"GiveRatio":0.2,"VipLv":1,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_00980","ChargePoint":98,"GamePoint":1470,"Ratio":15,"GiveGamePoint":441,"FirstGiveGamePoint":1470,"GiveRatio":0.3,"VipLv":5,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":false},{"ProductId":"fzxh_restriction_18","ChargePoint":18,"GamePoint":90,"Ratio":5,"GiveGamePoint":0,"FirstGiveGamePoint":0,"GiveRatio":0,"VipLv":3,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":true},{"ProductId":"fzxh_restriction_50","ChargePoint":50,"GamePoint":300,"Ratio":6,"GiveGamePoint":0,"FirstGiveGamePoint":0,"GiveRatio":0,"VipLv":1,"IfFirstShow":1,"IfSecondShow":1,"IsMonthCard":true}] + */ + + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + var partnerId int32 = 1001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //输入订单信息 + type OrderItem struct { + Vip byte + Money float64 + ActivityMoney float64 + ProductId string + IsFirstCharge bool + IsMonthCard bool + } + + //计算结果 + type GamePointResult struct { + ChargeGamePoint int + GiveGamePoint int + ActivityGamePoint int + TotalGamePoint int + } + + testOrderMap := make(map[OrderItem]GamePointResult) + + //#region 普通充值 + + testOrderMap[OrderItem{ + Vip: 0, + Money: 6, + ActivityMoney: 1, + ProductId: "fzxh_00060", + IsFirstCharge: true, + IsMonthCard: false, + }] = GamePointResult{ + ChargeGamePoint: 60, //GamePoint + GiveGamePoint: 60, //FirstGiveGamePoint + ActivityGamePoint: 10, //ActivityMoney*Ratio = 1*10 + TotalGamePoint: 130, + } //首充6元,活动金额1元 + + testOrderMap[OrderItem{ + Vip: 0, + Money: 6, + ActivityMoney: 1, + ProductId: "fzxh_00060", + IsFirstCharge: false, + IsMonthCard: false, + }] = GamePointResult{ + ChargeGamePoint: 60, //GamePoint + GiveGamePoint: 6, //GiveGamePoint 6 + ActivityGamePoint: 10, //ActivityMoney*Ratio = 1*10 + TotalGamePoint: 76, + } //非首充6元,活动金额1元 + + testOrderMap[OrderItem{ + Vip: 0, + Money: 0, + ActivityMoney: 1, + ProductId: "fzxh_00060", + IsFirstCharge: false, + IsMonthCard: false, + }] = GamePointResult{ + ChargeGamePoint: 60, //Money*Ratio = 6*10 + GiveGamePoint: 6, //GiveGamePoint 6 + ActivityGamePoint: 10, //ActivityMoney*Ratio = 1*10 + TotalGamePoint: 76, + } //无金额匹配6元,活动金额1元 + + testOrderMap[OrderItem{ + Vip: 0, + Money: 10, + ActivityMoney: 3, + ProductId: "", + IsFirstCharge: false, + IsMonthCard: false, + }] = GamePointResult{ + ChargeGamePoint: 100, //Money*Ratio = 10*10 + GiveGamePoint: 10, //Money*Ratio*GiveRatio 10 * 10 * 0.1 + ActivityGamePoint: 30, //ActivityMoney*Ratio = 3*10 + TotalGamePoint: 140, + } //无对应档位配置,模糊匹配10元,活动金额1元,实际匹配到6元档位 + + testOrderMap[OrderItem{ + Vip: 3, + Money: 98, + ActivityMoney: 3, + ProductId: "", + IsFirstCharge: false, + IsMonthCard: false, + }] = GamePointResult{ + ChargeGamePoint: 1078, //Money*Ratio = 98*11 + GiveGamePoint: 216, //Money*Ratio*GiveRatio 98 * 11 * 0.2 = 215.6 + ActivityGamePoint: 33, //ActivityMoney*Ratio = 3*11 + TotalGamePoint: 1327, + } //Vip限制,匹配98元,实际匹配到50档位,活动金额3元 + + //#endregion + + for order, result := range testOrderMap { + //计算充值获得的游戏币 + chargeGamePoint, giveGamePoint, activityGamePoint, totalGamePoint, exists, err := ChargeUtilObj.CalcChargeAllGamePoint( + partnerId, order.Vip, order.IsMonthCard, order.Money, order.ActivityMoney, order.ProductId, order.IsFirstCharge) + + if err != nil { + t.Fatalf("普通充值,计算充值获得游戏币[CalcChargeAllGamePoint],金额[%f] error:%v", order.Money, err) + } + + if !exists { + t.Fatalf("普通充值,计算充值获得游戏币[CalcChargeAllGamePoint],金额[%f]未找到充值配置:%v", order.Money, exists) + } + + if chargeGamePoint != result.ChargeGamePoint { + t.Fatalf("普通充值,计算充值获得游戏币[CalcChargeAllGamePoint],ChargeGamePoint计算结果不正确,金额[%f],期望[%d],结果[%d]", order.Money, result.ChargeGamePoint, chargeGamePoint) + } + + if giveGamePoint != result.GiveGamePoint { + t.Fatalf("普通充值,计算充值获得游戏币[CalcChargeAllGamePoint],GiveGamePoint计算结果不正确,金额[%f],期望[%d],结果[%d]", order.Money, result.GiveGamePoint, giveGamePoint) + } + + if activityGamePoint != result.ActivityGamePoint { + t.Fatalf("普通充值,计算充值获得游戏币[CalcChargeAllGamePoint],ActivityGamePoint计算结果不正确,金额[%f],期望[%d],结果[%d]", order.Money, result.ActivityGamePoint, activityGamePoint) + } + + if totalGamePoint != result.TotalGamePoint { + t.Fatalf("普通充值,计算充值获得游戏币[CalcChargeAllGamePoint],TotalGamePoint计算结果不正确,金额[%f],期望[%d],结果[%d]", order.Money, result.TotalGamePoint, totalGamePoint) + } + } + + //#region 月卡充值 + + testOrderMap[OrderItem{ + Vip: 3, + Money: 18, + ProductId: "fzxh_restriction_18", + IsMonthCard: true, + }] = GamePointResult{ + ChargeGamePoint: 90, //GamePoint + } //18元月卡 + + testOrderMap[OrderItem{ + Vip: 0, + Money: 50, + ProductId: "fzxh_restriction_50", + IsMonthCard: true, + }] = GamePointResult{ + ChargeGamePoint: 250, //Money * Ratio = 50 * 5 + } //VIP限制,匹配50元月卡,实际匹配到18元月卡 + + testOrderMap[OrderItem{ + Vip: 1, + Money: 0, + ProductId: "fzxh_restriction_50", + IsMonthCard: true, + }] = GamePointResult{ + ChargeGamePoint: 300, //GamePoint + } //无金额匹配50元月卡 + + testOrderMap[OrderItem{ + Vip: 0, + Money: 0, + ProductId: "fzxh_restriction_50", + IsMonthCard: true, + }] = GamePointResult{ + ChargeGamePoint: 0, //匹配不成功 + } //VIP限制,无金额匹配50元月卡 + + for order, result := range testOrderMap { + //计算充值获得的游戏币 + chargeGamePoint, exists, err := ChargeUtilObj.CalcChargeGamePoint( + partnerId, order.Vip, order.IsMonthCard, order.Money, order.ProductId) + + if err != nil { + t.Fatalf("月卡充值,计算充值获得游戏币[CalcChargeGamePoint],金额[%f] error:%v", order.Money, err) + } + + if !exists { + t.Fatalf("月卡充值,计算充值获得游戏币[CalcChargeGamePoint],金额[%f]未找到充值配置:%v", order.Money, exists) + } + + if chargeGamePoint != result.ChargeGamePoint { + t.Fatalf("月卡充值,计算充值获得游戏币[CalcChargeGamePoint],ChargeGamePoint计算结果不正确,金额[%f],期望[%d],结果[%d]", order.Money, result.ChargeGamePoint, chargeGamePoint) + } + } + + //#endregion +} diff --git a/trunk/framework/gameServerMgr/gameserver_test.go b/trunk/framework/gameServerMgr/gameserver_test.go new file mode 100644 index 0000000..bba1bc3 --- /dev/null +++ b/trunk/framework/gameServerMgr/gameserver_test.go @@ -0,0 +1,15 @@ +package gameServerMgr + +import ( + "fmt" + "testing" +) + +func TestActive(t *testing.T) { + err := ActiveServer("https://managecenterapitest-xxx.79yougame.com/API/ServerActivate.ashx", 20002) + if err != nil { + fmt.Println("xxx") + } + + CheckNewResourceVersion(1001, 20002, 100, "1584085505_769926880ac0ae89a31dcdfef5b94b1e") +} diff --git a/trunk/framework/gameServerMgr/loginUtil.go b/trunk/framework/gameServerMgr/loginUtil.go new file mode 100644 index 0000000..c469ac4 --- /dev/null +++ b/trunk/framework/gameServerMgr/loginUtil.go @@ -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) +) diff --git a/trunk/framework/gameServerMgr/manageUtil.go b/trunk/framework/gameServerMgr/manageUtil.go new file mode 100644 index 0000000..3796ce7 --- /dev/null +++ b/trunk/framework/gameServerMgr/manageUtil.go @@ -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 +} diff --git a/trunk/framework/gameServerMgr/manageUtil_test.go b/trunk/framework/gameServerMgr/manageUtil_test.go new file mode 100644 index 0000000..3b5f244 --- /dev/null +++ b/trunk/framework/gameServerMgr/manageUtil_test.go @@ -0,0 +1,374 @@ +package gameServerMgr + +import ( + "testing" + "time" + + "goutil/timeUtil" + "goutil/typeUtil" +) + +func TestActiveServer(t *testing.T) { + var managecenterUrl = "https://managecenterapitest-ds3.7qule.com/" + var serverGroupId int32 = 1000 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } +} + +func TestGetServerGroup(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //#region测试获取服务器组 + serverGroup := GetServerGroup() + if serverGroup == nil { + t.Fatalf("获取服务器组[GetServerGroup]失败:serverGroup = nil") + } + + if serverGroup.Id != serverGroupId { + t.Fatalf("获取服务器组[GetServerGroup]失败,期望[%d],结果[%d]", serverGroupId, serverGroup.Id) + } + //#endregion +} + +func TestGetDbConfig(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //#region测试获取服务器组 + serverGroup := GetServerGroup() + if serverGroup == nil { + t.Fatalf("获取服务器组[GetServerGroup]失败:serverGroup = nil") + } + + if serverGroup.Id != serverGroupId { + t.Fatalf("获取服务器组[GetServerGroup]失败,期望[%d],结果[%d]", serverGroupId, serverGroup.Id) + } + //#endregion + + //#region 测试获取数据库配置 + /* + { + "GameDB": "DataSource=10.1.0.4;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=h5xh_player_master;Allow Zero Datetime=true;charset=utf8;pooling=true;MinimumPoolSize=20;maximumpoolsize=200;command timeout=60;", + "LogDB": "DataSource=10.1.0.4;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=h5xh_log_master;Allow Zero Datetime=true;charset=utf8;pooling=true;MinimumPoolSize=20;maximumpoolsize=200;command timeout=60;", + "GameModelDB": "DataSource=10.1.0.4;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=h5xh_model_master;Allow Zero Datetime=true;charset=utf8;pooling=true;MinimumPoolSize=10;maximumpoolsize=10;command timeout=60;" + } + */ + gameDb := "DataSource=10.1.0.4;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=h5xh_player_master;Allow Zero Datetime=true;charset=utf8;pooling=true;MinimumPoolSize=20;maximumpoolsize=200;command timeout=60;" + logDb := "DataSource=10.1.0.4;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=h5xh_log_master;Allow Zero Datetime=true;charset=utf8;pooling=true;MinimumPoolSize=20;maximumpoolsize=200;command timeout=60;" + modelDb := "DataSource=10.1.0.4;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=h5xh_model_master;Allow Zero Datetime=true;charset=utf8;pooling=true;MinimumPoolSize=10;maximumpoolsize=10;command timeout=60;" + + dbConfig, err := serverGroup.GetDBConfig() + if err != nil { + t.Fatalf("获取服务器数据库配置[GetDBConfig]失败:%v", error) + } + + if dbConfig.GameDB != gameDb { + t.Fatalf("获取服务器数据库配置[GetDBConfig]失败,GameDB,期望[%s],结果[%s]", gameDb, dbConfig.GameDB) + } + + if dbConfig.LogDB != logDb { + t.Fatalf("获取服务器数据库配置[GetDBConfig]失败,LogDB,期望[%s],结果[%s]", logDb, dbConfig.LogDB) + } + + if dbConfig.GameModelDB != modelDb { + t.Fatalf("获取服务器数据库配置[GetDBConfig]失败,GameModelDB,期望[%s],结果[%s]", modelDb, dbConfig.GameModelDB) + } + //#endregion +} + +func TestGetServerItem(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var partnerId int32 = 1001 + var serverGroupId int32 = 20001 + var serverId = serverGroupId + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //#region测试获取服务器 + serverItem, exists := GetServerItem(partnerId, serverId) + if !exists || serverItem == nil { + t.Fatalf("获取服务器[GetServerItem][%d]失败,PartnerId[%d],服务器不存在!", partnerId, serverId) + } + + if serverItem.Id != serverId { + t.Fatalf("获取服务器[GetServerItem]失败,期望[%d],结果[%d]", serverId, serverItem.Id) + } + //#endregion +} + +func TestGetServerName(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var partnerId int32 = 1001 + var serverGroupId int32 = 20001 + var serverId = serverGroupId + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //测试获取服务器名称 + var expectServerName = "开发测试服" + serverName := GetServerName(partnerId, serverId) + if serverName != expectServerName { + t.Fatalf("获取服务器名称[GetServerName]失败,期望[%s],结果[%s]", expectServerName, serverName) + } +} + +func TestIfServerExists(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var partnerId int32 = 1001 + var serverGroupId int32 = 20001 + var serverId = serverGroupId + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //测试服务器是否存在 + if !IfServerExists(partnerId, serverId) { + t.Fatalf("判断服务器是否存在[IfServerExists]失败,PartnerId[%d],服务器[%d]不存在", partnerId, serverId) + } +} + +func TestGetPartnerServerPairString(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //获取合作商、服务器的组合字符串 + expectStr := "1001_20001|6666001_20001|" + partnerServerPairStr := GetPartnerServerPairString() + if expectStr != partnerServerPairStr { + t.Fatalf("获取合作商、服务器的组合字符串[GetPartnerServerPairString]失败,期望[%s],结果[%s]", expectStr, partnerServerPairStr) + } +} + +func TestGetLoginKey(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var partnerId int32 = 1001 + var serverGroupId int32 = 20001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //#region测试获取登录密钥 + var expectStr = "a0482eaf-14e8-4a65-950e-864214f62da5" + loginKey, exists := GetLoginKey(partnerId) + if !exists { + t.Fatalf("获取登录密钥[GetLoginKey]失败,未找到渠道[%d]配置", partnerId) + } + + if expectStr != loginKey { + t.Fatalf("获取登录密钥[GetLoginKey]失败,期望[%s],结果[%s]", expectStr, loginKey) + } + //#endregion +} + +func TestGetServerOpenDate(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //测试获取开服日期 2020/4/21 20:19:56 + expectOpenDate := time.Date(2020, 4, 21, 20, 19, 56, 0, time.Local) + openTimeTick := GetServerOpenDate() + openDate, _ := typeUtil.DateTime(openTimeTick) + if openDate != expectOpenDate { + t.Fatalf("获取服务器开服日期[GetServerOpenDate]失败,期望[%s],结果[%s]", timeUtil.ToDateTimeString(expectOpenDate), timeUtil.ToDateTimeString(openDate)) + } +} + +func TestServerOpenDays(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //测试获取服务器开服天数 + now := time.Now() + nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) + expectOpenDate := time.Date(2020, 4, 21, 0, 0, 0, 0, time.Local) + expectOpenDays := int32(nowDate.Sub(expectOpenDate).Hours()/24) + 1 + openDays := ServerOpenDays() + if expectOpenDays != openDays { + t.Fatalf("获取服务器开服天数[ServerOpenDays]失败,期望[%d],结果[%d]", expectOpenDays, openDays) + } +} + +func TestCheckMaintainStatus(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var serverGroupId int32 = 20001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //测试服务器维护检查 + expectMsg := "维护中" + maintainMessage, isMaintaining := CheckMaintainStatus() + if isMaintaining && expectMsg != maintainMessage { + t.Fatalf("服务器维护检查[CheckMaintainStatus]失败,期望维护消息[%s],结果维护消息[%s]", expectMsg, maintainMessage) + } +} + +func TestCheckNewGameVersion(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var partnerId int32 = 1001 + var serverGroupId int32 = 20001 + var serverId = serverGroupId + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //#region 测试检查是否有新版本 + + var gameVersionId int32 = 55 //老版本 + var expectGameVersionUrl = "testUrl" //已配置的游戏版本地址(IOS用) + gameVersionUrl, exist := CheckNewGameVersion(partnerId, serverId, gameVersionId) + if !exist { + t.Fatalf("检查是否有新版本[CheckNewGameVersion]失败,未找到渠道[%d]配置或检查版本[%d]失败", partnerId, gameVersionId) + } else if gameVersionUrl != expectGameVersionUrl { + t.Fatalf("检查是否有新版本[CheckNewGameVersion]失败,期望版本地址[%s],结果版本地址[%s]", expectGameVersionUrl, gameVersionUrl) + } + + gameVersionId = 100 //当前版本 + _, exist = CheckNewGameVersion(partnerId, serverId, gameVersionId) + if exist { + t.Fatalf("检查是否有新版本[CheckNewGameVersion]失败,渠道[%d],版本[%d]不应有更新", partnerId, gameVersionId) + } + + //#endregion +} + +func TestCheckNewResourceVersion(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var partnerId int32 = 1001 + var serverGroupId int32 = 20001 + var serverId = serverGroupId + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //#region 测试检查新资源版本 + partnerId = 6666001 + var gameVersionId int32 = 100 + resourceVersionName := "1587372891_9eab40313feec913cace0adf0fe05341" //非最新资源版本号 + _, exist := CheckNewResourceVersion(partnerId, serverId, gameVersionId, resourceVersionName) + if !exist { + t.Fatalf("检查新资源版本[CheckNewResourceVersion]失败,渠道[%d],服务器[%d],游戏版本Id[%d],资源版本号[%s],应有更新!", partnerId, serverId, gameVersionId, resourceVersionName) + } + + gameVersionId = 100 + resourceVersionName = "1587374043_cee48a8611276d3e3450782a1585c1a3" //最新资源版本号 + _, exist = CheckNewResourceVersion(partnerId, serverId, gameVersionId, resourceVersionName) + if exist { + t.Fatalf("检查新资源版本[CheckNewResourceVersion]失败,渠道[%d],服务器[%d],游戏版本Id[%d],资源版本号[%s],不应有更新!", partnerId, serverId, gameVersionId, resourceVersionName) + } + + gameVersionId = 123456 //不存在的游戏版本 + resourceVersionName = "1587374043_cee48a8611276d3e3450782a1585c1a3" + _, exist = CheckNewResourceVersion(partnerId, serverId, gameVersionId, resourceVersionName) + if exist { + t.Fatalf("检查新资源版本[CheckNewResourceVersion]失败,渠道[%d],服务器[%d],游戏版本Id[%d],资源版本号[%s],不应有更新!", partnerId, serverId, gameVersionId, resourceVersionName) + } + + //#endregion +} + +func TestIsInWhiteList(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var partnerId int32 = 1001 + var serverGroupId int32 = 20001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //测试白名单 + partnerId = 1001 + var userId = "446bfd6ccccd4229aa295686f9e5855b" //已配置的白名单用户Id + if !IsInWhiteList(partnerId, userId) { + t.Fatalf("检查是否用户在白名单中[IsInWhiteList]失败,渠道[%d],UserId[%s]", partnerId, userId) + } +} + +// 测试获取服务器组 +func TestGetOtherConfigInfo(t *testing.T) { + var managecenterUrl = "http://managecenterapitest-xxx.79yougame.com/" + var partnerId int32 = 1001 + var serverGroupId int32 = 20001 + + //激活器服务器,获取mc配置 + error := ActiveServer(managecenterUrl, serverGroupId) + if error != nil { + t.Fatalf("激活服务器出现错误:%v", error) + } + + //#region测试获取其他配置 + var configKey = "AppKey" + var expectConfigValue = "a0482eaf-14e8-4a65-950e-864214f62da5" //已配置的配置值 + configValue, exist, error := GetOtherConfigInfo(partnerId, configKey) + if error != nil { + t.Fatalf("获取渠道其他配置[GetOtherConfigInfo]失败,渠道[%d],ConfigKey[%s]", partnerId, configKey) + } + if !exist { + t.Fatalf("获取渠道其他配置[GetOtherConfigInfo]失败,渠道[%d],ConfigKey[%s],渠道或配置不存在!", partnerId, configKey) + } + + if configValue != expectConfigValue { + t.Fatalf("获取渠道其他配置[GetOtherConfigInfo]失败,期望[%s],结果[%s]", expectConfigValue, configValue) + } + //#endregion +} diff --git a/trunk/framework/gameServerMgr/partner.go b/trunk/framework/gameServerMgr/partner.go new file mode 100644 index 0000000..00e4ca8 --- /dev/null +++ b/trunk/framework/gameServerMgr/partner.go @@ -0,0 +1,60 @@ +package gameServerMgr + +import ( + . "Framework/managecenterModel" +) + +var ( + mPartnerMap = make(map[int32]*Partner, 0) +) + +//解析合作商信息 +func ParsePartnerInfo(partnerList []*Partner) { + tmpPartnerMap := make(map[int32]*Partner, 0) + + //循环解析所有合作商信息 + for _, partner := range partnerList { + tmpPartnerMap[partner.Id] = partner + } + + mPartnerMap = tmpPartnerMap +} + +//根据渠道id获取渠道对象 +func GetPartnerItem(partnerId int32) (partner *Partner, exist bool) { + + //判断渠道是否存在 + partner, exist = mPartnerMap[partnerId] + + return +} + +//获取渠道的登录加密key +func GetLoginKey(partnerId int32) (loginKey string, exist bool) { + partnerObj, exist := mPartnerMap[partnerId] + if !exist { + return + } + + loginKey = partnerObj.LoginKey + + return +} + +//根据渠道和key获取其他配置 +func GetOtherConfigInfo(partnerId int32, configKey string) (configValue string, exist bool, err error) { + partnerObj, exist := GetPartnerItem(partnerId) + if !exist { + return + } + + var otherConfigMap map[string]string + otherConfigMap, err = partnerObj.ResolveOtherConfig() + if err != nil { + return + } + + configValue, exist = otherConfigMap[configKey] + + return +} diff --git a/trunk/framework/gameServerMgr/resourceversion.go b/trunk/framework/gameServerMgr/resourceversion.go new file mode 100644 index 0000000..e7b5c46 --- /dev/null +++ b/trunk/framework/gameServerMgr/resourceversion.go @@ -0,0 +1,57 @@ +package gameServerMgr + +import ( + . "Framework/managecenterModel" +) + +var ( + mResourceVersionLit = make([]*ResourceVersion, 0) +) + +// 解析资源版本信息 +func ParseResourceVersionInfo(resourceVersionList []*ResourceVersion) { + mResourceVersionLit = resourceVersionList +} + +//返回所有的资源包列表 +func GetResourceVersionList() (resourceVersionList []*ResourceVersion) { + resourceVersionList = mResourceVersionLit + + return +} + +//检测资源 +func CheckNewResourceVersion(partnerId, serverId, gameVersionId int32, resourceVersionName string) (availableResourceVersionMap map[string]interface{}, exist bool) { + _, exist = GetServerItem(partnerId, serverId) + if !exist { + return + } + + //获取服务所在大区Id + areaId := GetAreaIdByGroupId(serverId) + + //获取大区的资源列表 + var tempResourceVersionList []*ResourceVersion + for _, resourceVerion := range mResourceVersionLit { + if resourceVerion.AreaID == areaId { + tempResourceVersionList = append(tempResourceVersionList, resourceVerion) + } + } + + //获取服务器 + serverGroup := GetServerGroup() + + //获取资源版本列表 + availableResourceVersionMap = GetAvailableResource(tempResourceVersionList, partnerId, gameVersionId, resourceVersionName, OfficialOrTest(serverGroup.OfficialOrTest)) + + //检测是否有新资源 + if len(availableResourceVersionMap) != 0 && availableResourceVersionMap["IsNewResource"] == true { + exist = true + + return + } + + exist = false + + return +} diff --git a/trunk/framework/gameServerMgr/server.go b/trunk/framework/gameServerMgr/server.go new file mode 100644 index 0000000..8bbc616 --- /dev/null +++ b/trunk/framework/gameServerMgr/server.go @@ -0,0 +1,166 @@ +package gameServerMgr + +import ( + "fmt" + + . "Framework/managecenterModel" +) + +var ( + mServerMap = make(map[int32][]*Server, 0) +) + +//合作商、服务器组合列表 +type PartnerServer struct { + //合作商Id + mPartnerId int32 + + //服务器Id + mServerId int32 +} + +//解析服务器信息 +func ParseServerInfo(serverList []*Server) { + tempServerMap := make(map[int32][]*Server, 0) + for _, server := range serverList { + _, exist := tempServerMap[server.PartnerId] + if !exist { + tempServerMap[server.PartnerId] = make([]*Server, 0) + } + tempServerMap[server.PartnerId] = append(tempServerMap[server.PartnerId], server) + } + mServerMap = tempServerMap +} + +//获取服务器对象 +func GetServerItem(partnerId, serverId int32) (server *Server, exist bool) { + serverList, existServer := mServerMap[partnerId] + if !existServer { + return + } + for _, serverItem := range serverList { + if serverItem.Id == serverId { + server = serverItem + exist = true + break + } + } + return +} + +//判断服务器是否存在 +func IfServerExists(partnerId, serverId int32) (exists bool) { + _, exists = GetPartnerItem(partnerId) + if !exists { + return + } + _, exists = GetServerItem(partnerId, serverId) + if !exists { + return + } + + return +} + +//获取服务器名称 +func GetServerName(partnerId, serverId int32) (serverName string) { + _, existPartner := GetPartnerItem(partnerId) + if !existPartner { + return + } + server, existServer := GetServerItem(partnerId, serverId) + if !existServer { + return + } + serverName = server.Name + + return +} + +//检测是否有游戏版本更新 +func CheckNewGameVersion(partnerId, serverId, gameVersionId int32) (gameVersionUrl string, exist bool) { + server, existServer := GetServerItem(partnerId, serverId) + if !existServer { + exist = existServer + return + } + + if gameVersionId < server.MinGameVersionId { + partner, existPartner := GetPartnerItem(partnerId) + if !existPartner { + exist = existPartner + return + } + exist = true + gameVersionUrl = partner.GameVersionUrl + } + + return +} + +//获取合作商、服务器的组合字符串 +//字符串格式:PartnerId_ServerId|PartnerId_ServerId| +func GetPartnerServerPairString() (partnerServerPair string) { + for _, ps := range GetPartnerServerPairList() { + partnerServerPair += fmt.Sprintf("%d_%d|", ps.mPartnerId, ps.mServerId) + } + return +} + +//获取合作商、服务器的组合列表 +func GetPartnerServerPairList() (partnerServerList []*PartnerServer) { + for key := range mServerMap { + for _, server := range mServerMap[key] { + if server.GroupId == GetServerGroup().Id { + var ps *PartnerServer = &PartnerServer{server.PartnerId, server.Id} + partnerServerList = append(partnerServerList, ps) + } + } + } + + return +} + +// // 获取合作商、服务器的组合字符串 +// // serverGroupId:服务器组Id +// // 返回值 +// // 合作商、服务器的组合字符串 +// func (this *ManageCenterUtil) GetPartnerServerPairString(serverGroupId int32) string { +// var buf bytes.Buffer + +// serverList := managecenterMgr.GetServerList(serverGroupId) +// for _, item := range serverList { +// buf.WriteString(fmt.Sprintf("%d_%d|", item.PartnerId, item.Id)) +// } + +// return buf.String() +// } + +// // 是否是有效的合作商、服务器组合 +// // partnerId:合作商Id +// // serverId:服务器Id +// // parnterServerPairString:合作商、服务器的组合字符串,格式为:PartnerId_ServerId|PartnerId_ServerId| +// // 返回值 +// // 是否有效 +// func (this *ManageCenterUtil) IfValidPartnerServerPair(partnerId, serverId int32, parnterServerPairString string) bool { +// if parnterServerPairString == "" { +// return false +// } + +// partnerServerPairStringList := strings.Split(parnterServerPairString, "|") +// if len(partnerServerPairStringList) == 0 { +// return false +// } + +// // 获得玩家的合作商、服务器组合字符串 +// partnerServerPair := fmt.Sprintf("%d_%d", partnerId, serverId) + +// // 遍历寻找 +// for _, item := range partnerServerPairStringList { +// if item == partnerServerPair { +// return true +// } +// } + +// return false +// } diff --git a/trunk/framework/gameServerMgr/serverGroup.go b/trunk/framework/gameServerMgr/serverGroup.go new file mode 100644 index 0000000..1f8c312 --- /dev/null +++ b/trunk/framework/gameServerMgr/serverGroup.go @@ -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 +} diff --git a/trunk/framework/gameServerMgr/serverGroupRegistration.go b/trunk/framework/gameServerMgr/serverGroupRegistration.go new file mode 100644 index 0000000..2cbcc12 --- /dev/null +++ b/trunk/framework/gameServerMgr/serverGroupRegistration.go @@ -0,0 +1,57 @@ +package gameServerMgr + +import ( + "encoding/json" + "fmt" + "strconv" + + . "Framework/managecenterModel" + "goutil/logUtil" + "goutil/webUtil" +) + +//修改区服注册人数 +func UpdateRegisterCount(serverGroupId, registerCount int32) (err error, success bool) { + success = false + + //定义参数 + requestParamMap := make(map[string]string, 0) + requestParamMap["ServerGroupId"] = strconv.Itoa(int(serverGroupId)) + requestParamMap["Registrations"] = strconv.Itoa(int(registerCount)) + + //构造请求url + url := fmt.Sprintf("%s/%s", mManageCenterServerAPIUrl, "/API/RegistrationUpdate.ashx") + + //请求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 { + return + } + if statusCode != 200 { + err = fmt.Errorf("StatusCode:%d", statusCode) + return + } + + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("更新区服注册人数出错,反序列化返回值出错,错误信息为:%s, str:%s", err, string(returnBytes))) + return + } + + // 判断返回状态是否为成功 + if returnObj.Code != 0 { + err = fmt.Errorf("更新区服注册人数出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(fmt.Sprintf("更新区服注册人数出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message)) + return + } + + success = true + return +} diff --git a/trunk/framework/gameServerMgr/sysconfig.go b/trunk/framework/gameServerMgr/sysconfig.go new file mode 100644 index 0000000..d7d8f9b --- /dev/null +++ b/trunk/framework/gameServerMgr/sysconfig.go @@ -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 +} diff --git a/trunk/framework/gameServerMgr/userWhiteList.go b/trunk/framework/gameServerMgr/userWhiteList.go new file mode 100644 index 0000000..e4aa34d --- /dev/null +++ b/trunk/framework/gameServerMgr/userWhiteList.go @@ -0,0 +1,124 @@ +package gameServerMgr + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + "Framework/goroutineMgr" + . "Framework/managecenterModel" + "goutil/logUtil" + "goutil/webUtil" +) + +const URL_SUFFIX string = "/API/UserWhiteList.ashx" + +var ( + mUserWhiteListMap = make(map[int32]map[string]*WhiteList, 0) + mHashValue string +) + +// 获取白名单 +func GetWhiteListFromManageCenterServer() error { + //定义参数 + requestParamMap := make(map[string]string, 0) + requestParamMap["HashValue"] = mHashValue + + //构造url + url := fmt.Sprintf("%s/%s", mManageCenterServerAPIUrl, 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("获取白名单列表出错,url:%s,错误信息为:%s", url, err)) + return err + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("获取白名单列表出错,url:%s,错误码为:%d", url, statusCode)) + return err + } + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取白名单列表出错,反序列化返回值出错,错误信息为:%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("获取白名单列表出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(msg) + return errors.New(msg) + } + } + + // 解析Data + var tmpWhiteList []*WhiteList + if data, ok := returnObj.Data.(string); !ok { + msg := "获取白名单列表出错,返回的数据不是string类型" + logUtil.ErrorLog(msg) + return errors.New(msg) + } else { + if err = json.Unmarshal([]byte(data), &tmpWhiteList); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取白名单列表出错,反序列化数据出错,错误信息为:%s", err)) + return err + } + } + + tmpWhiteListMap := make(map[int32]map[string]*WhiteList, 64) + for _, item := range tmpWhiteList { + if _, exist := tmpWhiteListMap[item.PartnerId]; !exist { + tmpWhiteListMap[item.PartnerId] = make(map[string]*WhiteList, 8) + } + + tmpWhiteListMap[item.PartnerId][item.UserId] = item + } + + // 赋值给最终的whiteListMap + mUserWhiteListMap = tmpWhiteListMap + mHashValue = returnObj.HashValue + + return nil +} + +// 判断用户是否在白名单里面 +// partnerId: 合作商ID +// userId: userId +func IsInWhiteList(partnerId int32, userId string) bool { + subWhiteListMap, exist := mUserWhiteListMap[partnerId] + if !exist { + return false + } + + _, exist = subWhiteListMap[userId] + return exist +} + +// 定时刷新白名单 +func StartRefreshWhiteListTread() { + // 定时刷新数据 + go func() { + goroutineName := "gameServerMgr.StartRefreshWhiteListTread" + goroutineMgr.Monitor(goroutineName) + defer goroutineMgr.ReleaseMonitor(goroutineName) + + for { + // 每30秒刷新一次 + time.Sleep(30 * time.Second) + + // 刷新白名单 + GetWhiteListFromManageCenterServer() + } + }() +} diff --git a/trunk/framework/go.mod b/trunk/framework/go.mod new file mode 100644 index 0000000..5fc9ec1 --- /dev/null +++ b/trunk/framework/go.mod @@ -0,0 +1,17 @@ +module Framework + +go 1.22.10 + +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 +) diff --git a/trunk/framework/go.sum b/trunk/framework/go.sum new file mode 100644 index 0000000..3139e01 --- /dev/null +++ b/trunk/framework/go.sum @@ -0,0 +1,242 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Shopify/sarama v1.29.1 h1:wBAacXbYVLmWieEA/0X/JagDdCZ8NVFOfS6l6+2u5S0= +github.com/Shopify/sarama v1.29.1/go.mod h1:mdtqvCSg8JOxk8PmpTNGyo6wzd4BMm4QXSfDnTXmgkE= +github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 h1:OoL469zqSNrTLSz5zeVF/I6VOO7fiw2bzSzQe4J557c= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4/go.mod h1:xe9a/L2aeOgFKKgrO3ibQTnMdpAeL0GC+5/HpGScSa4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= +github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA= +github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= +github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= +github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A= +github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= +github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414 h1:AJNDS0kP60X8wwWFvbLPwDuojxubj9pbfK7pjHw0vKg= +github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.230 h1:4n29EOxPk3fi0epVT+cY7Iwygep7vxn/LCq6RCBAwaM= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.230/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vms v1.0.230 h1:jIEph3MVl/7ex+4gK6LtBCpmeGwq+OUSRDmoPshWxpc= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vms v1.0.230/go.mod h1:zElyabRGi8DktckzhT3krsYChstFJdSrzDb7pwF2P58= +github.com/xdg/scram v1.0.3/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/trunk/framework/goroutineMgr/doc.go b/trunk/framework/goroutineMgr/doc.go new file mode 100644 index 0000000..b39c205 --- /dev/null +++ b/trunk/framework/goroutineMgr/doc.go @@ -0,0 +1,16 @@ +package goroutineMgr + +/* +goroutine的管理包,提供了对goroutine的监控机制 +对外提供的方法为: + +// 监控指定的goroutine +Monitor(goroutineName string) + +// 只添加数量,不监控 +MonitorZero(goroutineName string) + +// 释放监控 +ReleaseMonitor(goroutineName string) + +*/ diff --git a/trunk/framework/goroutineMgr/goroutineCount.go b/trunk/framework/goroutineMgr/goroutineCount.go new file mode 100644 index 0000000..d9f6b04 --- /dev/null +++ b/trunk/framework/goroutineMgr/goroutineCount.go @@ -0,0 +1,93 @@ +package goroutineMgr + +import ( + "fmt" + "runtime" + "sort" + "sync" + + "goutil/logUtil" +) + +var ( + goroutineCountMap = make(map[string]int) + goroutineCountMutex sync.RWMutex +) + +// 增加指定名称的goroutine的数量 +// goroutineName:goroutine名称 +func increaseCount(goroutineName string) { + goroutineCountMutex.Lock() + defer goroutineCountMutex.Unlock() + + newCount := 1 + if currCount, exist := goroutineCountMap[goroutineName]; exist { + newCount = currCount + 1 + } + + goroutineCountMap[goroutineName] = newCount +} + +// 减少指定名称的goroutine的数量 +// goroutineName:goroutine名称 +func decreaseCount(goroutineName string) { + goroutineCountMutex.Lock() + defer goroutineCountMutex.Unlock() + + newCount := -1 + if currCount, exist := goroutineCountMap[goroutineName]; exist { + newCount = currCount - 1 + } + + if newCount <= 0 { + delete(goroutineCountMap, goroutineName) + } else { + goroutineCountMap[goroutineName] = newCount + } +} + +// 获取指定名称的goroutine的数量 +// goroutineName:goroutine名称 +// 返回值: +// 对应数量 +func getGoroutineCount(goroutineName string) int { + goroutineCountMutex.RLock() + defer goroutineCountMutex.RUnlock() + + if currCount, exist := goroutineCountMap[goroutineName]; exist { + return currCount + } + + return 0 +} + +// 转化成字符串 +func toString() string { + goroutineCountMutex.RLock() + defer goroutineCountMutex.RUnlock() + + keys := make([]string, 0, 16) + for key := range goroutineCountMap { + keys = append(keys, key) + } + + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + str := fmt.Sprintf("Goroutine Info:(%s,%d)", "NumGoroutine", runtime.NumGoroutine()) + for _, key := range keys { + str += fmt.Sprintf("(%s,%d)", key, goroutineCountMap[key]) + } + + return str +} + +// 记录goroutine数量信息 +func logGoroutineCountInfo() { + logUtil.DebugLog(toString()) +} + +func Test() { + logGoroutineCountInfo() +} diff --git a/trunk/framework/goroutineMgr/goroutineInfo.go b/trunk/framework/goroutineMgr/goroutineInfo.go new file mode 100644 index 0000000..d3ae5d2 --- /dev/null +++ b/trunk/framework/goroutineMgr/goroutineInfo.go @@ -0,0 +1,75 @@ +package goroutineMgr + +import ( + "fmt" + "runtime" + "sync" + + "framework/monitorNewMgr" + "goutil/logUtil" +) + +var ( + goroutineInfoMap map[string]int = make(map[string]int) + goroutineInfoMutex sync.RWMutex + goroutineWarnCount int = 50 +) + +func init() { + monitorNewMgr.RegisterMonitorFunc(monitor) +} + +// 设置系统协程上报阈值 +func SetGoroutineWarnCount(warnCount int) { + goroutineWarnCount = warnCount +} + +func registerGoroutineInfo(goroutineName string, count int) { + goroutineInfoMutex.Lock() + defer goroutineInfoMutex.Unlock() + + goroutineInfoMap[goroutineName] = count +} + +// 监控指定的goroutine +func Monitor(goroutineName string) { + increaseCount(goroutineName) + registerGoroutineInfo(goroutineName, 1) +} + +// 只添加数量,不监控 +func MonitorZero(goroutineName string) { + increaseCount(goroutineName) +} + +// 释放监控 +func ReleaseMonitor(goroutineName string) { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + + decreaseCount(goroutineName) +} + +func monitor() error { + // 判断当前goroutine数量是否达到打印条件(数量查过设置的值就打印) + if goroutineWarnCount >= runtime.NumGoroutine() { + return nil + } + /* + 先记录活跃的goroutine的数量信息 + 然后再判断数量是否匹配 + */ + logGoroutineCountInfo() + + goroutineInfoMutex.RLock() + defer goroutineInfoMutex.RUnlock() + + for goroutineName, expectedCount := range goroutineInfoMap { + if currCount := getGoroutineCount(goroutineName); currCount != expectedCount { + return fmt.Errorf("%s需要%d个goroutine,现在有%d个", goroutineName, expectedCount, currCount) + } + } + + return nil +} diff --git a/trunk/framework/handleMgr/doc.go b/trunk/framework/handleMgr/doc.go new file mode 100644 index 0000000..964cfe6 --- /dev/null +++ b/trunk/framework/handleMgr/doc.go @@ -0,0 +1,81 @@ +// ************************************ +// @package: handleMgr +// @description: 全局操作接口管理API类 +// @author: +// @revision history: +// @create date: 2022-02-23 16:24:59 +// ************************************ + +package handleMgr + +/* +用于提供全局操作接口管理API类 +*/ + +/* +接口说明 + +接口1: +// RegisterNewModule +// @description: 注册方法 +// parameter: +// @moduleName:模块名 +// @structObject:模块对象 +// @monitorTime:监控日志超时时间,传入0是用默认值100 +// return: + +示例:RegisterNewModule("test", new(testBll), 0) + +接口2: +// Done +// @description: 执行访问 +// parameter: +// @logicalId:逻辑实例Id-同一串行错误类的唯一标识 +// @moduleName:模块名 +// @funcName:执行方法名称 +// @parameter:方法参数 +// @isHaveResult:是否处理返回值 +// return: +// @data:返还对象 +// @code:错误码-0未无错误 +// @message:错误说明-空字符串为无错误 + +示例:data,code,mes := Done("test", "testBll", "Add", parameter, false) + +注意: +注册的类实现的方法返回值必须是*ResponseObject为返回对象 +示例: + +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 + + responseObj.SetData(z) + responseObj.SetResultStatus("错误码") + return responseObj +} + +func TestNew(t *testing.T) { + // 注册类 + RegisterNewModule("test", new(testBll), 10) + + // 调用方法 + data, code, mes := Done("test", "testBll", "TestAdd", parameter, false) + + // 可以重复注册多个类,逻辑实例Id保持一致即可 + RegisterNewModule("test", new(test2Bll), 10) + data, code, mes := Done("test", "test2Bll", "TestAdd", parameter, false) +} +*/ diff --git a/trunk/framework/handleMgr/handleMgr.go b/trunk/framework/handleMgr/handleMgr.go new file mode 100644 index 0000000..4d6e5a3 --- /dev/null +++ b/trunk/framework/handleMgr/handleMgr.go @@ -0,0 +1,131 @@ +// ************************************ +// @package: handleMgr +// @description: 全局操作接口管理 +// @author: +// @revision history: +// @create date: 2022-02-21 15:53:59 +// ************************************ + +package handleMgr + +import ( + "fmt" + "Framework/goroutineMgr" + "goutil/logUtil" + "runtime/debug" + "time" +) + +var ( + handleData = make(map[string]*HandleObj) +) + +// RegisterNewModule +// @description: 注册调用方法 +// parameter: +// @logicalId:逻辑实例Id-同一串行错误类的唯一标识 +// @structObject:模块对象 +// @monitorTime:监控日志超时时间,传入0是用默认值100 +// return: +func RegisterNewModule(logicalId string, structObject interface{}, monitorTime int64) { + // 注册模块,开启线程 + _, isExists := handleData[logicalId] + if isExists == false { + newHandleObj := NewHandleObj(logicalId) + handleData[logicalId] = newHandleObj + + // 设置监控默认值 + if monitorTime == 0 { + monitorTime = 100 + } + + // 开启监控-channel + go func() { + registerName := fmt.Sprintf("handleMgr.RegisterNewModule.logicalId=%s", logicalId) + goroutineMgr.MonitorZero(registerName) + defer goroutineMgr.ReleaseMonitor(registerName) + defer logErrorRecover() + + ret := 0 + for { + select { + case c := <-newHandleObj.HandleChannel: + start := time.Now().UnixMilli() + responseObject := CallFunction(c) + end := time.Now().UnixMilli() + + if c.IsHaveResult == true { + c.ResultChan <- responseObject + } + if end-start > monitorTime { + message := fmt.Sprintf("方法执行时间超长,logicalId:%s,moduleName:%s,MethodName:%s,消耗时间过程,总耗时:%d,需要检查代码!!!!!", logicalId, c.ModuleName, c.MethodName, end-start) + logUtil.ErrorLog(message) + } + + ret++ + if ret == 100 { + num := len(newHandleObj.HandleChannel) + if num > 500 { + message := fmt.Sprintf("时间戳:%d,channel监控,内部待执行操作过多,logicalId:%s,当前待执行数量:%d,需要检查代码!!!!!", time.Now().UnixMilli(), logicalId, num) + logUtil.ErrorLog(message) + } + } + default: + // 如果找不到数据则休眠5ms + time.Sleep(5 * time.Millisecond) + } + } + }() + } + + // 注册对象 + RegisterFunction(structObject) +} + +// Done +// @description: 执行访问 +// parameter: +// @logicalId:逻辑实例Id-同一串行错误类的唯一标识 +// @moduleName:模块名 +// @funcName:执行方法名称 +// @parameter:方法参数 +// @isHaveResult:是否处理返回值 +// return: +// @data:返还对象 +// @code:错误码-0未无错误 +// @message:错误说明-空字符串为无错误 +func Done(logicalId string, moduleName string, funcName string, parameter []interface{}, isHaveResult bool) (data interface{}, code int, message string) { + responseObj := NewRequestObject(moduleName, funcName, parameter, isHaveResult) + handleObj, isExists := handleData[logicalId] + if isExists == false { + message := fmt.Sprintf("未注册模块,moduleName:%s", moduleName) + logUtil.ErrorLog(message) + return nil, -1, message + } + handleObj.HandleChannel <- responseObj + if responseObj.IsHaveResult == false { + return nil, 0, "" + } + + for { + select { + case responseObject := <-responseObj.ResultChan: + // 处理返回值 + return responseObject.Data, 0, responseObject.Message + default: + // 如果找不到数据则休眠5ms + time.Sleep(5 * time.Millisecond) + } + } +} + +// logErrorRecover +// @description: 打印日志 +// parameter: +// return: +func logErrorRecover() { + if err := recover(); err != nil { + msg := fmt.Sprintf("err msg:%s stack:%s", err, debug.Stack()) + logUtil.ErrorLog(msg) + } +} diff --git a/trunk/framework/handleMgr/handleObj.go b/trunk/framework/handleMgr/handleObj.go new file mode 100644 index 0000000..fe55c18 --- /dev/null +++ b/trunk/framework/handleMgr/handleObj.go @@ -0,0 +1,26 @@ +// ************************************ +// @package: handleMgr +// @description: HandleObj 操作对象 +// @author: zhaoxin +// @revision history: +// @create date: 2022-02-21 16:52:44 +// ************************************ + +package handleMgr + +// HandleObj 操作对象 +type HandleObj struct { + // 模块命名 + ModuleName string + + // 操作channel + HandleChannel chan *RequestObject +} + +// NewHandleObj 初始化 +func NewHandleObj(moduleName string) *HandleObj { + return &HandleObj{ + ModuleName: moduleName, + HandleChannel: make(chan *RequestObject, 1000), + } +} diff --git a/trunk/framework/handleMgr/reflect.go b/trunk/framework/handleMgr/reflect.go new file mode 100644 index 0000000..5597929 --- /dev/null +++ b/trunk/framework/handleMgr/reflect.go @@ -0,0 +1,351 @@ +// ************************************ +// @package: handleMgr +// @description: 反射类 +// @author: +// @revision history: +// @create date: 2022-02-23 16:33:27 +// ************************************ + +package handleMgr + +import ( + "fmt" + "goutil/logUtil" + "reflect" + "strings" +) + +const ( + // 定义用于分隔模块名称和方法名称的分隔符 + con_DelimeterOfObjAndMethod = "_" +) + +var ( + // 定义存放所有方法映射的变量 + methodMap = make(map[string]*ReflectMethod, 4) + + // 函数返回值类型 + responseType reflect.Type = reflect.TypeOf(new(ResponseObject)) +) + +// getStructName +// @description: 获取结构体类型的名称 +// parameter: +// @structType:结构体类型 +// return: +// @string:结构体类型的名称 +func getStructName(structType reflect.Type) string { + reflectTypeStr := structType.String() + reflectTypeArr := strings.Split(reflectTypeStr, ".") + + return reflectTypeArr[len(reflectTypeArr)-1] +} + +// getFullMethodName +// @description: 获取完整的方法名称 +// parameter: +// @structName:结构体名称 +// @methodName:方法名称 +// return: +// @string:完整的方法名称 +func getFullMethodName(structName, methodName string) string { + return structName + con_DelimeterOfObjAndMethod + methodName +} + +// resolveMethodInOutParams +// @description: 解析方法的输入输出参数 +// parameter: +// @method:方法对应的反射值 +// return: +// @inTypes:输入参数类型集合 +// @outTypes:输出参数类型集合 +func resolveMethodInOutParams(method reflect.Value) (inTypes []reflect.Type, outTypes []reflect.Type) { + methodType := method.Type() + for i := 0; i < methodType.NumIn(); i++ { + inTypes = append(inTypes, methodType.In(i)) + } + + for i := 0; i < methodType.NumOut(); i++ { + outTypes = append(outTypes, methodType.Out(i)) + } + + return +} + +// RegisterFunction +// @description: 对象注册 +// parameter: +// @structObject:对象 +// return: +func RegisterFunction(structObject interface{}) { + // 获取structObject对应的反射 Type 和 Value + reflectValue := reflect.ValueOf(structObject) + reflectType := reflect.TypeOf(structObject) + + // 提取对象类型名称 + structName := getStructName(reflectType) + + // 获取structObject中返回值为responseObject的方法 + for i := 0; i < reflectType.NumMethod(); i++ { + // 获得方法名称 + methodName := reflectType.Method(i).Name + + // 获得方法及其输入参数的类型列表 + method := reflectValue.MethodByName(methodName) + inTypes, outTypes := resolveMethodInOutParams(method) + + // 判断输出参数数量是否正确 + if len(outTypes) != 1 { + continue + } + + // 判断返回值是否为responseObject + if outTypes[0] != responseType { + continue + } + + // 添加到列表中 + methodMap[getFullMethodName(structName, methodName)] = NewReflectMethod(method, inTypes, outTypes) + } +} + +// CallFunction +// @description: 调用方法 +// parameter: +// @requestObject:请求对象 +// return: +// @responseObj:返还对象 +func CallFunction(requestObject *RequestObject) (responseObj *ResponseObject) { + responseObj = GetInitResponseObj() + + var reflectMethod *ReflectMethod + var ok bool + + // 根据传入的ModuleName和MethodName找到对应的方法对象 + key := getFullMethodName(requestObject.ModuleName, requestObject.MethodName) + if reflectMethod, ok = methodMap[key]; !ok { + message := fmt.Sprintf("找不到指定的方法:%s", key) + logUtil.ErrorLog(message) + responseObj.SetResultStatus(-2, message) + return + } + + // 判断参数数量是否相同 + inTypesLength := len(reflectMethod.InTypes) + paramLength := len(requestObject.Parameters) + if paramLength != inTypesLength { + message := fmt.Sprintf("传入的参数数量不符,本地方法%s的参数数量:%d,传入的参数数量为:%d", key, inTypesLength, paramLength) + logUtil.ErrorLog(message) + responseObj.SetResultStatus(-3, message) + return + } + + // 构造参数 + in := make([]reflect.Value, inTypesLength) + for i := 0; i < inTypesLength; i++ { + inTypeItem := reflectMethod.InTypes[i] + paramItem := requestObject.Parameters[i] + + // 已支持类型:Client,Player(非基本类型) + // 已支持类型:Bool,Int,Int8,Int16,Int32,Int64,Uint,Uint8,Uint16,Uint32,Uint64,Float32,Float64,String + // 已支持类型:以及上面所列出类型的Slice类型 + // 未支持类型:Uintptr,Complex64,Complex128,Array,Chan,Func,Interface,Map,Ptr,Struct,UnsafePointer + // 由于byte与int8同义,rune与int32同义,所以并不需要单独处理 + // 枚举参数的类型,并进行类型转换 + switch inTypeItem.Kind() { + case reflect.Bool: + if paramBool, ok := paramItem.(bool); ok { + in[i] = reflect.ValueOf(paramBool) + } + case reflect.Int: + if paramFloat64, ok := paramItem.(int); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Int8: + if paramFloat64, ok := paramItem.(int8); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Int16: + if paramFloat64, ok := paramItem.(int16); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Int32: + if paramFloat64, ok := paramItem.(int32); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Int64: + if paramFloat64, ok := paramItem.(int64); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Uint: + if paramFloat64, ok := paramItem.(uint); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Uint8: + if paramFloat64, ok := paramItem.(uint8); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Uint16: + if paramFloat64, ok := paramItem.(uint16); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Uint32: + if paramFloat64, ok := paramItem.(uint32); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Uint64: + if paramFloat64, ok := paramItem.(uint64); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Float32: + if paramFloat64, ok := paramItem.(float32); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.Float64: + if paramFloat64, ok := paramItem.(float64); ok { + in[i] = reflect.ValueOf(paramFloat64) + } + case reflect.String: + if paramString, ok := paramItem.(string); ok { + in[i] = reflect.ValueOf(paramString) + } + case reflect.Slice: + // 如果是Slice类型,则需要对其中的项再次进行类型判断及类型转换 + if paramInterface, ok := paramItem.([]interface{}); ok { + switch inTypeItem.String() { + case "[]bool": + paramsInner := make([]bool, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramBool, ok := paramInterface[i].(bool); ok { + paramsInner[i] = paramBool + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]int": + paramsInner := make([]int, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(int); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]int8": + paramsInner := make([]int8, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(int8); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]int16": + paramsInner := make([]int16, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(int16); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]int32": + paramsInner := make([]int32, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(int32); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]int64": + paramsInner := make([]int64, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(int64); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]uint": + paramsInner := make([]uint, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(uint); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + // case "[]uint8": 特殊处理 + case "[]uint16": + paramsInner := make([]uint16, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(uint16); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]uint32": + paramsInner := make([]uint32, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(uint32); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]uint64": + paramsInner := make([]uint64, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(uint64); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]float32": + paramsInner := make([]float32, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(float32); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]float64": + paramsInner := make([]float64, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramFloat64, ok := paramInterface[i].(float64); ok { + paramsInner[i] = paramFloat64 + } + } + in[i] = reflect.ValueOf(paramsInner) + case "[]string": + paramsInner := make([]string, len(paramInterface)) + for i := 0; i < len(paramInterface); i++ { + if paramString, ok := paramInterface[i].(string); ok { + paramsInner[i] = paramString + } + } + in[i] = reflect.ValueOf(paramsInner) + } + } else if inTypeItem.String() == "[]uint8" { // 由于[]uint8在传输过程中会被转化成字符串,所以单独处理; + if paramString, ok := paramItem.(string); ok { + paramUint8 := ([]uint8)(paramString) + in[i] = reflect.ValueOf(paramUint8) + } + } + } + } + + // 判断是否有无效的参数(传入的参数类型和方法定义的类型不匹配导致没有赋值) + for _, item := range in { + if reflect.Value.IsValid(item) == false { + message := fmt.Sprintf("type:%v,value:%v.方法%s传入的参数%v无效", reflect.TypeOf(item), reflect.ValueOf(item), key, requestObject.Parameters) + logUtil.ErrorLog(message) + responseObj.SetResultStatus(-1, message) + return + } + } + + out := reflectMethod.Method.Call(in) + + // 并输出结果到客户端(由于只有一个返回值,所以取out[0]) + if responseObj, ok = (&out[0]).Interface().(*ResponseObject); !ok { + message := fmt.Sprintf("返回值类型推断为ResponseObject 出错, tyep is :%v", reflect.TypeOf(out[0])) + logUtil.ErrorLog(message) + responseObj.SetResultStatus(-1, message) + return + } + return +} diff --git a/trunk/framework/handleMgr/reflectMethod.go b/trunk/framework/handleMgr/reflectMethod.go new file mode 100644 index 0000000..1644bfe --- /dev/null +++ b/trunk/framework/handleMgr/reflectMethod.go @@ -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, + } +} diff --git a/trunk/framework/handleMgr/requestObject.go b/trunk/framework/handleMgr/requestObject.go new file mode 100644 index 0000000..3a149be --- /dev/null +++ b/trunk/framework/handleMgr/requestObject.go @@ -0,0 +1,46 @@ +// ************************************ +// @package: handleMgr +// @description: 反射类-请求对象 +// @author: +// @revision history: +// @create date: 2022-02-23 16:33:53 +// ************************************ + +package handleMgr + +// RequestObject 请求对象 +type RequestObject struct { + // 请求的模块名称 + ModuleName string + + // 请求的方法名称 + MethodName string + + // 请求的参数数组 + Parameters []interface{} + + // 是否处理返回值 + IsHaveResult bool + + // 执行完成,返回chan监控 + ResultChan chan *ResponseObject +} + +// NewRequestObject +// @description: +// parameter: +// @moduleName:模块名名称 +// @methodName:执行方法名称 +// @parameters:方法参数 +// @isHaveResult:是否处理返回值 +// return: +// @*RequestObject: +func NewRequestObject(moduleName string, methodName string, parameters []interface{}, isHaveResult bool) *RequestObject { + return &RequestObject{ + ModuleName: moduleName, + MethodName: methodName, + Parameters: parameters, + IsHaveResult: isHaveResult, + ResultChan: make(chan *ResponseObject, 1), + } +} diff --git a/trunk/framework/handleMgr/responseObject.go b/trunk/framework/handleMgr/responseObject.go new file mode 100644 index 0000000..06bca16 --- /dev/null +++ b/trunk/framework/handleMgr/responseObject.go @@ -0,0 +1,63 @@ +// ************************************ +// @package: handleMgr +// @description: 反射类-响应对象 +// @author: +// @revision history: +// @create date: 2022-02-23 16:34:08 +// ************************************ + +package handleMgr + +// ResponseObject 响应对象 +type ResponseObject struct { + // 错误码 + Code int + + // 响应结果的状态值所对应的描述信息 + Message string + + // 响应结果的数据 + Data interface{} +} + +// SetResultStatus +// @description: 设置响应结果的状态值 +// parameter: +// @receiver r: +// @code:响应结果的错误码 +// @message:响应结果的字符串 +// return:响应结果对象 +// @*ResponseObject: +func (r *ResponseObject) SetResultStatus(code int, message string) *ResponseObject { + r.Code = code + r.Message = message + return r +} + +// SetData +// @description: 设置响应结果的状态值 +// parameter: +// @receiver r: +// @message:响应结果的字符串 +// return:响应结果对象 +// @*ResponseObject: +func (r *ResponseObject) SetData(data interface{}) *ResponseObject { + r.Data = data + return r +} + +// GetInitResponseObj +// @description: 初始化 +// parameter: +// @moduleName:模块名名称 +// @methodName:执行方法名称 +// @parameters:方法参数 +// @isHaveResult:是否处理返回值 +// return: +// @*ResponseObject:GetInitResponseObj +func GetInitResponseObj() *ResponseObject { + return &ResponseObject{ + Message: "", + Data: nil, + } +} diff --git a/trunk/framework/handleMgr/test_test.go b/trunk/framework/handleMgr/test_test.go new file mode 100644 index 0000000..83a6660 --- /dev/null +++ b/trunk/framework/handleMgr/test_test.go @@ -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") +} diff --git a/trunk/framework/initMgr/initSuccess.go b/trunk/framework/initMgr/initSuccess.go new file mode 100644 index 0000000..7dfd226 --- /dev/null +++ b/trunk/framework/initMgr/initSuccess.go @@ -0,0 +1,62 @@ +package initMgr + +import ( + "fmt" + "sync" + + "goutil/debugUtil" + "goutil/logUtil" +) + +// 初始化成功对象 +type InitSuccess struct { + name string + registerMap map[string]chan bool + mutex sync.RWMutex +} + +// 注册需要被通知的对象 +// name:唯一标识 +// ch:用于通知的通道 +func (this *InitSuccess) Register(name string, ch chan bool) { + this.mutex.Lock() + defer this.mutex.Unlock() + + if _, exists := this.registerMap[name]; exists { + panic(fmt.Errorf("InitSuccess.Register-%s-%s已经存在,请检查", this.name, name)) + } + + this.registerMap[name] = ch + + logUtil.DebugLog(fmt.Sprintf("InitSuccess.Register-%s,当前共有%d个注册", this.name, len(this.registerMap))) +} + +// 取消启动成功通知注册 +// name:唯一标识 +func (this *InitSuccess) Unregister(name string) { + this.mutex.Lock() + defer this.mutex.Unlock() + + delete(this.registerMap, name) +} + +// 通知所有已注册的对象 +func (this *InitSuccess) Notify() { + this.mutex.RLock() + defer this.mutex.RUnlock() + + for name, ch := range this.registerMap { + ch <- true + msg := fmt.Sprintf("通知:%s-%s初始化成功", this.name, name) + debugUtil.Println(msg) + logUtil.DebugLog(msg) + } +} + +// 创建初始化成功对象 +func NewInitSuccess(name string) *InitSuccess { + return &InitSuccess{ + name: name, + registerMap: make(map[string]chan bool), + } +} diff --git a/trunk/framework/ipMgr/ipCheck.go b/trunk/framework/ipMgr/ipCheck.go new file mode 100644 index 0000000..c8ecf86 --- /dev/null +++ b/trunk/framework/ipMgr/ipCheck.go @@ -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) +} diff --git a/trunk/framework/ipMgr/ipCheck_test.go b/trunk/framework/ipMgr/ipCheck_test.go new file mode 100644 index 0000000..77652f6 --- /dev/null +++ b/trunk/framework/ipMgr/ipCheck_test.go @@ -0,0 +1,44 @@ +package ipMgr + +import ( + "testing" +) + +func TestIsIpValid(t *testing.T) { + ip := "10.255.0.7" + if IsIpValid(ip) { + t.Errorf("%s应该无效,但是现在却有效", ip) + } + + ipList := []string{"10.255.0.7", "10.1.0.21"} + Init(ipList) + + if IsIpValid(ip) == false { + t.Errorf("%s应该有效,但是现在却无效", ip) + } + + ipStr := "10.255.0.7,10.1.0.21;10.255.0.6|10.1.0.30||" + InitString(ipStr) + if IsIpValid(ip) == false { + t.Errorf("%s应该有效,但是现在却无效", ip) + } + + ip = "192.168.1.1" + RegisterIpCheckFunc("", alwaysFalse) + if IsIpValid(ip) { + t.Errorf("%s应该无效,但是现在却有效", ip) + } + + RegisterIpCheckFunc("", alwaysTrue) + if IsIpValid(ip) == false { + t.Errorf("%s应该有效,但是现在却无效", ip) + } +} + +func alwaysTrue(ip string) bool { + return true +} + +func alwaysFalse(ip string) bool { + return false +} diff --git a/trunk/framework/ipMgr/ipQuery.go b/trunk/framework/ipMgr/ipQuery.go new file mode 100644 index 0000000..dc864a9 --- /dev/null +++ b/trunk/framework/ipMgr/ipQuery.go @@ -0,0 +1,93 @@ +package ipMgr + +import ( + "encoding/json" + "fmt" + "time" + + "goutil/securityUtil" + "goutil/webUtil" +) + +var ( + IP_SERVICE_URL = "http://ipip.7qule.com/query" +) + +// 服务器的响应对象 +type QueryResponse struct { + // 响应结果的状态值 + ResultStatus string + + // 响应结果的数据 + Data *IPInfo +} + +// IP信息对象 +type IPInfo struct { + // 所属大陆块 + Continent string + + // 国家 + Country string + + // 省份 + Region string + + // 城市 + City string + + // 网络服务提供商 + Isp string +} + +// 查询ip地址的详细信息 +// appId: 为应用分配的唯一标识 +// appSecret: 为应用分配的密钥 +// ip: 待查询的ip地址 +// isDomestic: 是否为国内的IP +// timeout:超时时间(单位:秒) +// 返回值: +// ipInfoObj: IP地址信息对象 +// err: 错误对象 +func Query(appId, appSecret, ip string, isDomestic bool, timeout int) (ipInfoObj *IPInfo, err error) { + timeStamp := fmt.Sprintf("%d", time.Now().Unix()) + rawString := fmt.Sprintf("AppId=%s&IP=%s&Timestamp=%s&AppSecret=%s", appId, ip, timeStamp, appSecret) + sign := securityUtil.Md5String(rawString, true) + + postData := make(map[string]string, 5) + postData["AppId"] = appId + postData["IP"] = ip + postData["IsDomestic"] = fmt.Sprintf("%t", isDomestic) + 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(IP_SERVICE_URL, postData, header, transport) + //statusCode, result, err := webUtil.PostMapData(IP_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 + } + + ipInfoObj = queryResponseObj.Data + + return +} diff --git a/trunk/framework/ipMgr/ipQuery_test.go b/trunk/framework/ipMgr/ipQuery_test.go new file mode 100644 index 0000000..3ec77dc --- /dev/null +++ b/trunk/framework/ipMgr/ipQuery_test.go @@ -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) + } +} diff --git a/trunk/framework/kafkaMgr/kafkaConfig.go b/trunk/framework/kafkaMgr/kafkaConfig.go new file mode 100644 index 0000000..c85fd5a --- /dev/null +++ b/trunk/framework/kafkaMgr/kafkaConfig.go @@ -0,0 +1,51 @@ +package kafkaMgr + +import "strings" + +// Kafka配置对象 +type KafkaConfig struct { + // Brokers的地址 + Brokers string + + // 主题 + Topics string + + // 分区 + Partitions string + + // 分组Id + GroupId string + + // 用户名 + UserName string + + // 密码 + Passward string + + // 需要的证书文件 + CertFile string +} + +func (this *KafkaConfig) GetBrokerList() []string { + if len(this.Brokers) <= 0 { + return make([]string, 0) + } + + return strings.Split(this.Brokers, ",") +} + +func (this *KafkaConfig) GetTopicList() []string { + if len(this.Topics) <= 0 { + return make([]string, 0) + } + + return strings.Split(this.Topics, ",") +} + +func (this *KafkaConfig) GetPartitionList() []string { + if len(this.Partitions) <= 0 { + return make([]string, 0) + } + + return strings.Split(this.Partitions, ",") +} diff --git a/trunk/framework/linuxMgr/doc.go b/trunk/framework/linuxMgr/doc.go new file mode 100644 index 0000000..3460c63 --- /dev/null +++ b/trunk/framework/linuxMgr/doc.go @@ -0,0 +1,5 @@ +package linuxMgr + +// Linux平台的管理器,主要提供在Linux平台上才能运行的一些功能 +// 如:syscall.Dup2 +// 使用方式,在游戏中引用此包即可 diff --git a/trunk/framework/linuxMgr/dup.go b/trunk/framework/linuxMgr/dup.go new file mode 100644 index 0000000..468be51 --- /dev/null +++ b/trunk/framework/linuxMgr/dup.go @@ -0,0 +1,29 @@ +// +build !windows + +package linuxMgr + +import ( + "os" + "path/filepath" + "syscall" + + "goutil/fileUtil" +) + +func init() { + //验证文件夹是否存在 + fileAbsoluteDirectory := filepath.Join("Log") + if !fileUtil.IsDirExists(fileAbsoluteDirectory) { + if err := os.MkdirAll(fileAbsoluteDirectory, os.ModePerm|os.ModeTemporary); err != nil { + return + } + } + + // 标准输出 + stdoutFile, _ := os.OpenFile("Log/Stdout.txt", os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644) + syscall.Dup2(int(stdoutFile.Fd()), 1) + + // 标准错误输出 + stderrFile, _ := os.OpenFile("Log/Stderr.txt", os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0644) + syscall.Dup2(int(stderrFile.Fd()), 2) +} diff --git a/trunk/framework/managecenterMgr/area.go b/trunk/framework/managecenterMgr/area.go new file mode 100644 index 0000000..5845955 --- /dev/null +++ b/trunk/framework/managecenterMgr/area.go @@ -0,0 +1,174 @@ +package managecenterMgr + +import ( + "encoding/json" + "errors" + "fmt" + + "Framework/managecenterModel" + . "Framework/managecenterModel" + "goutil/logUtil" + "goutil/webUtil" +) + +var ( + areaMap = make(map[int32]*Area, 128) + areaHash string +) + +// 重新加载大区数据 +func reloadArea(isInit bool) error { + //logUtil.DebugLog("开始刷新大区列表") + + // 连接服务器,以获取数据 + url := getManageCenterUrl("/API/AreaList.ashx") + + // 定义请求参数 + postDict := make(map[string]string) + postDict["HashValue"] = areaHash + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, transport) + //statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, nil) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取大区列表出错,url:%s,错误信息为:%s", url, err)) + return err + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("获取大区列表出错,url:%s,错误码为:%d", url, statusCode)) + return err + } + + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取大区列表出错,反序列化返回值出错,错误信息为:%s, str:%s", err, string(returnBytes))) + return err + } + + // 判断返回状态是否为成功 + if returnObj.Code != 0 { + // 数据没有变化,所以没有获取到新的数据,不能算错误。 + if returnObj.Code == 47 || returnObj.Message == "DataNotChanged" { + //如果本地集合为空,且数据又没变化时,重新初始化一下本地hash值 + if len(areaMap) == 0 { + areaHash = "" + } + return nil + } else { + msg := fmt.Sprintf("获取大区列表出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(msg) + return errors.New(msg) + } + } + + // 解析Data + tmpAreaList := make([]*Area, 0, 128) + if data, ok := returnObj.Data.(string); !ok { + msg := "获取大区列表出错,返回的数据不是string类型" + logUtil.ErrorLog(msg) + return errors.New(msg) + } else { + if err = json.Unmarshal([]byte(data), &tmpAreaList); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取大区列表出错,反序列化数据出错,错误信息为:%s", err)) + return err + } + } + + //logUtil.DebugLog(fmt.Sprintf("刷新大区信息结束,大区数量:%d", len(tmpAreaList))) + + tmpAreaMap := make(map[int32]*Area) + for _, item := range tmpAreaList { + tmpAreaMap[item.AreaId] = item + } + + // 赋值给最终的areaMap + areaMap = tmpAreaMap + areaHash = returnObj.HashValue + + // 通知变更 + mcDataChangeCallBack(managecenterModel.AreaData, isInit) + + return nil +} + +// 根据区服ID获取所属大区ID +func GetAreaIdByServerId(serverId int32) (areaId int32, exist bool) { + for _, item := range areaMap { + if item.CheckServerIdIsInRange(serverId) { + areaId = item.AreaId + exist = true + return + } + } + + return +} + +// 根据服务器组id获取大区对象数据 +func GetAreaDBByGroupId(groupId int32) (areaDB *Area, exist bool) { + //如果没有大区数据,返回空 + exist = false + if areaMap == nil || len(areaMap) < 1 { + return + } + + for _, item := range areaMap { + if item.CheckServerIdIsInRange(groupId) { + areaDB = item + exist = true + break + } + } + return +} + +// 获取所有大区信息 +func GetAllAreaDB() []*Area { + tempList := make([]*Area, 8) + for _, item := range areaMap { + tempList = append(tempList, item) + } + return tempList +} + +// 根据大区ID获取大区信息 +func GetAreaDBbyAreaID(areaId int32) (areaDB *Area, exist bool) { + //如果没有大区数据,返回空 + exist = false + if areaMap == nil || len(areaMap) < 1 { + return + } + + if item, ok := areaMap[areaId]; ok { + areaDB = item + exist = true + } + return +} + +// 根据服务器ID获取大区页签信息 +func GetAreaLabelDBByGroupId(groupId int32) (areaLabelDB *AreaLabel, exist bool) { + areaDB, exist := GetAreaDBByGroupId(groupId) + if !exist { + return nil, false + } + if areaDB.AreaLabelList == nil || len(areaDB.AreaLabelList) < 1 { + return nil, false + + } + //校服务器ID是否在大区页签服务器范围内 + for _, item := range areaDB.AreaLabelList { + if exist := item.CheckServerIdIsInLabelRange(groupId); exist { + return item, true + } + } + + //如果没找到,则返回空 + return nil, false +} diff --git a/trunk/framework/managecenterMgr/doc.go b/trunk/framework/managecenterMgr/doc.go new file mode 100644 index 0000000..68a437e --- /dev/null +++ b/trunk/framework/managecenterMgr/doc.go @@ -0,0 +1,7 @@ +package managecenterMgr + +// ManageCenter的管理包,提供管理合作商、服务器、服务器组、游戏资源、白名单等功能 + +// 使用方法: +// 首先调用Start方法,这样会进行初始化,后续就可以使用提供的其它方法 +// 内部会定时去ManageCenter刷新最新的数据 diff --git a/trunk/framework/managecenterMgr/managecenter.go b/trunk/framework/managecenterMgr/managecenter.go new file mode 100644 index 0000000..7557a5b --- /dev/null +++ b/trunk/framework/managecenterMgr/managecenter.go @@ -0,0 +1,137 @@ +package managecenterMgr + +import ( + "fmt" + "strings" + "time" + + "Framework/goroutineMgr" + "Framework/initMgr" + "Framework/managecenterModel" + "goutil/logUtil" +) + +var ( + mcAPIUrl string + mcDataSwitchObj *ManageCenterDataSwitch + initSuccessObj = initMgr.NewInitSuccess("managecenterMgr") + + //数据变更通知回调 + mDataChangeCallBackFunc func(managecenterModel.MCDataType) error +) + +// 注册初始化成功的通道 +// name:模块名称 +// ch:通道对象 +func RegisterInitSuccess(name string, ch chan bool) { + initSuccessObj.Register(name, ch) +} + +// 注册MC数据变更通知回调函数 +// handler:回调方法 +func RegisterDataChangeCallBackFunc(handler func(managecenterModel.MCDataType) error) { + mDataChangeCallBackFunc = handler +} + +// Start ...启动ManageCenter管理器 +// manageCenterAPIUrl:ManageCenter对外提供的API +// manageCenterDataSwitchObj:ManageCenter数据获取开关对象 +func Start(manageCenterAPIUrl string, manageCenterDataSwitchObj *ManageCenterDataSwitch) { + mcAPIUrl = manageCenterAPIUrl + mcDataSwitchObj = manageCenterDataSwitchObj + + // 先初始化一次数据 + if err := reload(true); err != nil { + panic(err) + } + + // 通知初始化成功 + initSuccessObj.Notify() + + // 定时刷新数据 + go func() { + goroutineName := "managecenterMgr.timelyRefresh" + goroutineMgr.Monitor(goroutineName) + defer goroutineMgr.ReleaseMonitor(goroutineName) + + for { + // 每5秒刷新一次 + time.Sleep(5 * time.Second) + + // 刷新服务器组 + reload(false) + } + }() +} + +// 重新加载/初始化 +func reload(isInit bool) error { + var err error + + // 加载合作商数据 + if mcDataSwitchObj.AllDataSwitch || mcDataSwitchObj.PartnerDataSwitch { + if err = reloadPartner(isInit); err != nil && isInit { + return err + } + } + + // 加载服务器数据 + if mcDataSwitchObj.AllDataSwitch || mcDataSwitchObj.ServerDataSwitch { + if err = reloadServer(isInit); err != nil && isInit { + return err + } + } + + // 加载服务器组数据 + if mcDataSwitchObj.AllDataSwitch || mcDataSwitchObj.ServerGroupDataSwitch { + if err = reloadServerGroup(isInit); err != nil && isInit { + return err + } + } + + // 加载资源数据 + if mcDataSwitchObj.AllDataSwitch || mcDataSwitchObj.ResourceVersionDataSwitch { + if err = reloadResourceVersion(isInit); err != nil && isInit { + return err + } + } + + // 加载玩家白名单数据 + if mcDataSwitchObj.AllDataSwitch || mcDataSwitchObj.WhiteListDataSwitch { + if err = reloadWhiteList(isInit); err != nil && isInit { + return err + } + } + + // 加载大区数据 + if mcDataSwitchObj.AllDataSwitch || mcDataSwitchObj.AreaDataSwitch { + if err = reloadArea(isInit); err != nil && isInit { + return err + } + } + + return nil +} + +// 获取可访问的ManageCenter地址 +// suffix:Url后缀 +// 返回值: +// 可访问的ManageCenter地址 +func getManageCenterUrl(suffix string) string { + return fmt.Sprintf("%s/%s", strings.TrimRight(mcAPIUrl, "/"), strings.TrimLeft(suffix, "/")) +} + +// 回调 +// dataType:数据类型 +// isInit:是否初始化 +func mcDataChangeCallBack(dataType managecenterModel.MCDataType, isInit bool) { + //如果没注册回调函数,或者是数据初始化,则不回调 + if mDataChangeCallBackFunc == nil || isInit { + return + } + + //回调 + err := mDataChangeCallBackFunc(dataType) + + logUtil.ErrorLog(fmt.Sprintf("通知回调出错,DataType:%d,错误信息为:%s", dataType, err)) +} diff --git a/trunk/framework/managecenterMgr/managecenterDataSwitch.go b/trunk/framework/managecenterMgr/managecenterDataSwitch.go new file mode 100644 index 0000000..0e8bf38 --- /dev/null +++ b/trunk/framework/managecenterMgr/managecenterDataSwitch.go @@ -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 +} diff --git a/trunk/framework/managecenterMgr/managecenter_test.go b/trunk/framework/managecenterMgr/managecenter_test.go new file mode 100644 index 0000000..6eb647c --- /dev/null +++ b/trunk/framework/managecenterMgr/managecenter_test.go @@ -0,0 +1,132 @@ +package managecenterMgr + +import ( + "fmt" + "testing" +) + +func init() { + manageCenterDataSwitchObj := &ManageCenterDataSwitch{ + AllDataSwitch: true, + } + Start("https://managecenterapi-sd3.7qule.com/", manageCenterDataSwitchObj) + + item, exist := GetAreaLabelDBByGroupId(82) + if exist { + fmt.Printf("%v", item) + } +} + +func TestPartner(t *testing.T) { + partnerList := GetPartnerList() + fmt.Printf("PartnerList count:%v\n", len(partnerList)) + if len(partnerList) == 0 { + t.Errorf("There is no partner.") + return + } + + firstPartner := partnerList[0] + _, exist := GetPartner(firstPartner.Id) + if !exist { + t.Errorf("Partner with Id:%d should exist, but now it doesn't.", firstPartner.Id) + return + } + + _, exist = GetPartnerByAlias(firstPartner.Alias) + if !exist { + t.Errorf("Partner with Alias:%s should exist, but now it doesn't.", firstPartner.Alias) + return + } + + _, exist, err := GetOtherConfigInfo(firstPartner.Id, "LoginHandler") + if err != nil { + t.Errorf("There should no error for Partner:%d, but now there is one:%v", err, firstPartner.Id) + return + } + if !exist { + t.Errorf("Partner with Id:%d should have an other config named LoginHandler, but now there isn't.", firstPartner.Id) + return + } +} + +func TestServer(t *testing.T) { + serverGroupList := GetServerGroupList() + if len(serverGroupList) == 0 { + t.Errorf("There is no ServerGroup.") + return + } + firstServerGroup := serverGroupList[0] + + serverList := GetServerListByGroupId(firstServerGroup.Id) + fmt.Printf("There are %d servers of GroupId:%d\n", len(serverList), firstServerGroup.Id) + + firstServer := serverList[0] + serverList = GetServerListByPartnerId(firstServer.PartnerId) + fmt.Printf("There are %d servers of PartnerId:%d\n", len(serverList), firstServer.PartnerId) + + _, exist := GetServerItem(firstServer.PartnerId, firstServer.Id) + if !exist { + t.Errorf("There is no server with PartnerId:%d, ServerId:%d.", firstServer.PartnerId, firstServer.Id) + return + } + + distinctServerIdList := GetDistinctServerIdList() + fmt.Printf("There are %d distinct ServerId\n", len(distinctServerIdList)) +} + +func TestServerGroup(t *testing.T) { + serverGroupList := GetServerGroupList() + if len(serverGroupList) == 0 { + t.Errorf("There is no ServerGroup.") + return + } + + firstServerGroup := serverGroupList[0] + _, exist := GetServerGroupItem(firstServerGroup.Id) + if !exist { + t.Errorf("The ServerGroup with Id:%d should exist, but now it doesn't.", firstServerGroup.Id) + return + } + + serverList := GetServerListByGroupId(firstServerGroup.Id) + if len(serverList) == 0 { + t.Errorf("There is no server of ServerGroupId:%d", firstServerGroup.Id) + return + } + firstServer := serverList[0] + _, _, exist = GetServerGroup(firstServer.PartnerId, firstServer.Id) + if !exist { + t.Errorf("The ServerGroup with PartnerId:%d, ServerId:%d should exist, but now it doesn't.", firstServer.PartnerId, firstServer.Id) + return + } +} + +func TestResourceVersion(t *testing.T) { + list := GetResourceVersionList() + fmt.Printf("There are %d resource versions.\n", len(list)) +} + +func TestUserWhiteList(t *testing.T) { + fmt.Printf("There are %d whiteLists.\n", len(whiteListMap)) + if len(whiteListMap) == 0 { + return + } + + var partnerId int32 + var userId string + for _, v := range whiteListMap { + for _, _v := range v { + partnerId = _v.PartnerId + userId = _v.UserId + break + } + break + } + + expected := true + got := IsInWhiteList(partnerId, userId) + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } +} diff --git a/trunk/framework/managecenterMgr/partner.go b/trunk/framework/managecenterMgr/partner.go new file mode 100644 index 0000000..d593d4a --- /dev/null +++ b/trunk/framework/managecenterMgr/partner.go @@ -0,0 +1,158 @@ +package managecenterMgr + +import ( + "encoding/json" + "errors" + "fmt" + + "Framework/managecenterModel" + . "Framework/managecenterModel" + "goutil/logUtil" + "goutil/webUtil" +) + +var ( + partnerMap = make(map[int32]*Partner, 128) + partnerHash string +) + +// 重新加载合作商 +func reloadPartner(isInit bool) error { + //logUtil.DebugLog("开始刷新合作商列表") + + // 连接服务器,以获取数据 + url := getManageCenterUrl("/API/PartnerList.ashx") + + // 定义请求参数 + postDict := make(map[string]string) + postDict["HashValue"] = partnerHash + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, transport) + //statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, nil) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取合作商列表出错,url:%s,错误信息为:%s", url, err)) + return err + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("获取合作商列表出错,url:%s,错误码为:%d", url, statusCode)) + return err + } + + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取合作商列表出错,反序列化返回值出错,错误信息为:%s, str:%s", err, string(returnBytes))) + return err + } + + // 判断返回状态是否为成功 + if returnObj.Code != 0 { + // 数据没有变化,所以没有获取到新的数据,不能算错误。 + if returnObj.Code == 47 || returnObj.Message == "DataNotChanged" { + //如果本地集合为空,且数据又没变化时,重新初始化一下本地hash值 + if len(partnerMap) == 0 { + partnerHash = "" + } + return nil + } else { + msg := fmt.Sprintf("获取合作商列表出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(msg) + return errors.New(msg) + } + } + + // 解析Data + tmpPartnerList := make([]*Partner, 0, 128) + if data, ok := returnObj.Data.(string); !ok { + msg := "获取合作商列表出错,返回的数据不是string类型" + logUtil.ErrorLog(msg) + return errors.New(msg) + } else { + if err = json.Unmarshal([]byte(data), &tmpPartnerList); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取合作商列表出错,反序列化数据出错,错误信息为:%s", err)) + return err + } + } + + //logUtil.DebugLog(fmt.Sprintf("刷新合作商信息结束,合作商数量:%d", len(tmpPartnerList))) + + tmpPartnerMap := make(map[int32]*Partner) + for _, item := range tmpPartnerList { + tmpPartnerMap[item.Id] = item + } + + // 赋值给最终的partnerMap + partnerMap = tmpPartnerMap + partnerHash = returnObj.HashValue + + //通知变更 + mcDataChangeCallBack(managecenterModel.PartnerData, isInit) + return nil +} + +// 获取合作商对象列表 +// 返回值: +// 合作商对象列表 +func GetPartnerList() (partnerList []*Partner) { + for _, item := range partnerMap { + partnerList = append(partnerList, item) + } + + return +} + +// 根据合作商Id获取合作商对象 +// id:合作商Id +// 返回值: +// 合作商对象 +// 是否存在 +func GetPartner(id int32) (partnerObj *Partner, exist bool) { + partnerObj, exist = partnerMap[id] + return +} + +// 根据合作商别名获取合作商对象 +// alias:合作商别名 +// 返回值: +// 合作商对象 +// 是否存在 +func GetPartnerByAlias(alias string) (partnerObj *Partner, exist bool) { + for _, item := range partnerMap { + if item.Alias == alias { + partnerObj = item + exist = true + return + } + } + + return +} + +// 获取合作商的其它配置信息 +// id:合作商Id +// configKey:其它配置Key +// 返回值 +// 配置内容 +// 是否存在 +// 错误对象 +func GetOtherConfigInfo(id int32, configKey string) (configValue string, exist bool, err error) { + partnerObj, exist := GetPartner(id) + if !exist { + return + } + + var otherConfigMap map[string]string + otherConfigMap, err = partnerObj.ResolveOtherConfig() + if err != nil { + return + } + + configValue, exist = otherConfigMap[configKey] + return +} diff --git a/trunk/framework/managecenterMgr/resourceVersion.go b/trunk/framework/managecenterMgr/resourceVersion.go new file mode 100644 index 0000000..9a966e7 --- /dev/null +++ b/trunk/framework/managecenterMgr/resourceVersion.go @@ -0,0 +1,100 @@ +package managecenterMgr + +import ( + "encoding/json" + "errors" + "fmt" + + "Framework/managecenterModel" + . "Framework/managecenterModel" + "goutil/logUtil" + "goutil/webUtil" +) + +var ( + resourceVersionList = make([]*ResourceVersion, 128) + resourceVersionHash string +) + +// 重新加载资源列表 +func reloadResourceVersion(isInit bool) error { + //logUtil.DebugLog("开始刷新资源列表") + + // 连接服务器,以获取数据 + url := getManageCenterUrl("/API/ResourceVersionList.ashx") + + // 定义请求参数 + postDict := make(map[string]string) + postDict["HashValue"] = resourceVersionHash + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, transport) + //statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, nil) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取资源列表出错,url:%s,错误信息为:%s", url, err)) + return err + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("获取资源列表出错,url:%s,错误码为:%d", url, statusCode)) + return err + } + + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取资源列表出错,反序列化返回值出错,错误信息为:%s, str:%s", err, string(returnBytes))) + return err + } + + // 判断返回状态是否为成功 + if returnObj.Code != 0 { + // 数据没有变化,所以没有获取到新的数据,不能算错误。 + if returnObj.Code == 47 || returnObj.Message == "DataNotChanged" { + //如果本地集合为空,且数据又没变化时,重新初始化一下本地hash值 + if len(resourceVersionList) == 0 { + resourceVersionHash = "" + } + return nil + } else { + msg := fmt.Sprintf("获取资源列表出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(msg) + return errors.New(msg) + } + } + + // 解析Data + tmpResourceVersionList := make([]*ResourceVersion, 0, 128) + if data, ok := returnObj.Data.(string); !ok { + msg := "获取资源列表出错,返回的数据不是string类型" + logUtil.ErrorLog(msg) + return errors.New(msg) + } else { + if err = json.Unmarshal([]byte(data), &tmpResourceVersionList); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取资源列表出错,反序列化数据出错,错误信息为:%s", err)) + return err + } + } + + //logUtil.DebugLog(fmt.Sprintf("刷新资源信息结束,资源数量:%d", len(tmpResourceVersionList))) + + // 赋值给最终的partnerMap + resourceVersionList = tmpResourceVersionList + resourceVersionHash = returnObj.HashValue + + //通知变更 + mcDataChangeCallBack(managecenterModel.ResourceVersionData, isInit) + + return nil +} + +// 获取资源包列表 +// 返回值: +// 获取资源包列表 +func GetResourceVersionList() []*ResourceVersion { + return resourceVersionList +} diff --git a/trunk/framework/managecenterMgr/server.go b/trunk/framework/managecenterMgr/server.go new file mode 100644 index 0000000..a953b31 --- /dev/null +++ b/trunk/framework/managecenterMgr/server.go @@ -0,0 +1,175 @@ +package managecenterMgr + +import ( + "encoding/json" + "errors" + "fmt" + + "Framework/managecenterModel" + . "Framework/managecenterModel" + "goutil/logUtil" + "goutil/webUtil" +) + +var ( + serverMap = make(map[int32]map[int32]*Server, 128) + serverDistinctMap = make(map[int32]*Server, 1024) + serverHash string +) + +// 重新加载服务器 +func reloadServer(isInit bool) error { + //logUtil.DebugLog("开始刷新服务器列表") + + url := getManageCenterUrl("/API/ServerList.ashx") + + // 定义请求参数 + postDict := make(map[string]string) + postDict["GroupType"] = "Mix" + postDict["HashValue"] = serverHash + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, transport) + //statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, nil) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取服务器列表出错,url:%s,错误信息为:%s", url, err)) + return err + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("获取服务器列表出错,url:%s,错误码为:%d", url, statusCode)) + return err + } + + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取服务器列表出错,反序列化返回值出错,错误信息为:%s, str:%s", err, string(returnBytes))) + return err + } + + // 判断返回状态是否成功 + if returnObj.Code != 0 { + // 数据没有变化,所以没有获取到新的数据,不能算错误。 + if returnObj.Code == 47 || returnObj.Message == "DataNotChanged" { + //如果本地集合为空,且数据又没变化时,重新初始化一下本地hash值 + if len(serverMap) == 0 { + serverHash = "" + } + return nil + } else { + msg := fmt.Sprintf("获取服务器列表出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(msg) + return errors.New(msg) + } + } + + // 解析Data + tmpServerList := make([]*Server, 0, 1024) + if data, ok := returnObj.Data.(string); !ok { + msg := "获取服务器列表出错,返回的数据不是string类型" + logUtil.ErrorLog(msg) + return errors.New(msg) + } else { + if err = json.Unmarshal([]byte(data), &tmpServerList); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取服务器列表出错,反序列化数据出错,错误信息为:%s", err)) + return err + } + } + + //logUtil.DebugLog(fmt.Sprintf("刷新服务器信息结束,服务器数量:%d", len(tmpServerList))) + + tmpServerMap := make(map[int32]map[int32]*Server, 128) + tmpServerDistinctMap := make(map[int32]*Server, 1024) + for _, item := range tmpServerList { + // 构造tmpServerMap数据 + if _, ok := tmpServerMap[item.PartnerId]; !ok { + tmpServerMap[item.PartnerId] = make(map[int32]*Server, 1024) + } + tmpServerMap[item.PartnerId][item.Id] = item + + // 构造tmpServerDistinctMap数据 + tmpServerDistinctMap[item.Id] = item + } + + // 赋值给最终的serverMap、serverDistinctMap + serverMap = tmpServerMap + serverDistinctMap = tmpServerDistinctMap + serverHash = returnObj.HashValue + + //通知变更 + mcDataChangeCallBack(managecenterModel.ServerData, isInit) + + return nil +} + +// 根据服务器组Id获取对应的服务器列表 +// serverGroupId:服务器组Id +// 返回值: +// 服务器列表 +func GetServerListByGroupId(serverGroupId int32) (serverList []*Server) { + for _, subServerMap := range serverMap { + for _, item := range subServerMap { + if item.GroupId == serverGroupId { + serverList = append(serverList, item) + } + } + } + + return +} + +// 根据合作商Id获取对应的服务器列表 +// partnerId:合作商Id +// 返回值: +// 服务器列表 +func GetServerListByPartnerId(partnerId int32) (serverList []*Server) { + for _, item := range serverMap[partnerId] { + serverList = append(serverList, item) + } + + return +} + +// 根据合作商对象、服务器Id获取服务器对象 +// partnerObj:合作商对象 +// serverId:服务器Id +// 返回值: +// 服务器对象 +// 是否存在 +func GetServer(partnerObj *Partner, serverId int32) (serverObj *Server, exist bool) { + if subServerMap, exist1 := serverMap[partnerObj.Id]; exist1 { + serverObj, exist = subServerMap[serverId] + } + + return +} + +// 根据合作商Id、服务器Id获取服务器对象 +// partnerId:合作商Id +// serverId:服务器Id +// 返回值: +// 服务器对象 +// 是否存在 +func GetServerItem(partnerId, serverId int32) (serverObj *Server, exist bool) { + if subServerMap, exist1 := serverMap[partnerId]; exist1 { + serverObj, exist = subServerMap[serverId] + } + + return +} + +// 获取不重复的服务器Id列表 +// 返回值: +// 不重复的服务器Id列表 +func GetDistinctServerIdList() (distinctServerIdList []int32) { + for _, item := range serverDistinctMap { + distinctServerIdList = append(distinctServerIdList, item.Id) + } + + return +} diff --git a/trunk/framework/managecenterMgr/servergroup.go b/trunk/framework/managecenterMgr/servergroup.go new file mode 100644 index 0000000..fc2e539 --- /dev/null +++ b/trunk/framework/managecenterMgr/servergroup.go @@ -0,0 +1,247 @@ +package managecenterMgr + +import ( + "encoding/json" + "errors" + "fmt" + "sort" + + "Framework/ipMgr" + "Framework/managecenterModel" + . "Framework/managecenterModel" + "goutil/logUtil" + "goutil/webUtil" +) + +var ( + // 服务器组集合 + serverGroupMap = make(map[int32]*ServerGroup, 512) + serverGroupHash string + + // 服务器组变化方法集合 (完整列表,新增列表,删除列表,更新列表) + serverGroupChangeFuncMap = make(map[string]func([]*ServerGroup, []*ServerGroup, []*ServerGroup, []*ServerGroup)) +) + +func init() { + ipMgr.RegisterIpCheckFunc("ServerGroupIpCheck", isIpValid) +} + +// 重新加载服务器组 +func reloadServerGroup(isInit bool) error { + //logUtil.DebugLog("开始刷新服务器组信息") + + url := getManageCenterUrl("/API/ServerGroupList.ashx") + + // 定义请求参数 + postDict := make(map[string]string) + postDict["GroupType"] = "Mix" + postDict["HashValue"] = serverGroupHash + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, transport) + //statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, nil) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取服务器组列表出错,url:%s,错误信息为:%s", url, err)) + return err + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("获取服务器组列表出错,url:%s,错误码为:%d", url, statusCode)) + return err + } + + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取服务器组列表出错,反序列化返回值出错,错误信息为:%s", err)) + return err + } + + // 判断返回状态是否为成功 + if returnObj.Code != 0 { + // 数据没有变化,所以没有获取到新的数据,不能算错误。 + if returnObj.Code == 47 || returnObj.Message == "DataNotChanged" { + //如果本地集合为空,且数据又没变化时,重新初始化一下本地hash值 + if len(serverGroupMap) == 0 { + serverGroupHash = "" + } + return nil + } else { + msg := fmt.Sprintf("获取服务器组列表出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(msg) + return errors.New(msg) + } + } + + // 解析Data + tmpServerGroupList := make([]*ServerGroup, 0, 512) + if data, ok := returnObj.Data.(string); !ok { + msg := "获取服务器组列表出错,返回的数据不是string类型" + logUtil.ErrorLog(msg) + return errors.New(msg) + } else { + if err = json.Unmarshal([]byte(data), &tmpServerGroupList); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取服务器组列表出错,反序列化数据出错,错误信息为:%s", err)) + return err + } + } + + //logUtil.DebugLog(fmt.Sprintf("刷新服务器组信息结束,服务器组数量:%d", len(tmpServerGroupList))) + + tmpServerGroupMap := make(map[int32]*ServerGroup, 512) + for _, item := range tmpServerGroupList { + tmpServerGroupMap[item.Id] = item + } + + // 处理服务器组是否变化情况 + addList, deleteList, updateList := handleServerGroupChange(GetServerGroupMap(), tmpServerGroupMap) + triggerServerGroupChangeFunc(tmpServerGroupList, addList, deleteList, updateList) + + // 赋值给最终的ServerGroupMap + serverGroupMap = tmpServerGroupMap + serverGroupHash = returnObj.HashValue + + //通知变更 + mcDataChangeCallBack(managecenterModel.ServerGroupData, isInit) + + return nil +} + +// 处理服务器组是否变化情况 +// originalServerGroupMap: 当前服务器组集合 +// newServerGroupMap: 新的服务器组集合 +func handleServerGroupChange(currServerGroupMap, newServerGroupMap map[int32]*ServerGroup) ( + addList []*ServerGroup, deleteList []*ServerGroup, updateList []*ServerGroup) { + + // 判断是否有新增的数据 + for k, v := range newServerGroupMap { + if _, exist := currServerGroupMap[k]; !exist { + addList = append(addList, v) + } + } + + // 判断是否有删除的数据 + for k, v := range currServerGroupMap { + if _, exist := newServerGroupMap[k]; !exist { + deleteList = append(deleteList, v) + } + } + + // 判断是否有更新的数据 + for k, currItem := range currServerGroupMap { + if newItem, exist := newServerGroupMap[k]; exist { + if currItem.IsEqual(newItem) == false { + updateList = append(updateList, newItem) + } + } + } + + return +} + +// 触发服务器组变化的方法 +func triggerServerGroupChangeFunc(allList []*ServerGroup, addList []*ServerGroup, deleteList []*ServerGroup, updateList []*ServerGroup) { + // 如果有注册服务器组变化的方法 + if len(serverGroupChangeFuncMap) > 0 { + for _, serverGroupChangeFunc := range serverGroupChangeFuncMap { + serverGroupChangeFunc(allList, addList, deleteList, updateList) + } + } +} + +// 注册服务器组变化方法 +// funcName:方法名称 +// serverGroupChangeFunc:服务器组变化方法 +func RegisterServerGroupChangeFunc(funcName string, serverGroupChangeFunc func([]*ServerGroup, []*ServerGroup, []*ServerGroup, []*ServerGroup)) { + if _, exists := serverGroupChangeFuncMap[funcName]; exists { + panic(fmt.Errorf("RegisterServerGroupChange:%s已经存在,请检查", funcName)) + } + serverGroupChangeFuncMap[funcName] = serverGroupChangeFunc + + logUtil.DebugLog(fmt.Sprintf("注册服务器组变化方法 funcName:%s,当前共有%d个注册", funcName, len(serverGroupChangeFuncMap))) +} + +// 获取服务器组集合 +// 返回值: +// 服务器组集合 +func GetServerGroupMap() (retServerGroupMap map[int32]*ServerGroup) { + retServerGroupMap = make(map[int32]*ServerGroup, 128) + for k, v := range serverGroupMap { + retServerGroupMap[k] = v + } + + return +} + +// 获取服务器组列表 +// 返回值: +// 服务器组列表 +func GetServerGroupList() (serverGroupList []*ServerGroup) { + for _, item := range serverGroupMap { + serverGroupList = append(serverGroupList, item) + } + + sort.Slice(serverGroupList, func(i, j int) bool { + return serverGroupList[i].SortByIdAsc(serverGroupList[j]) + }) + + return +} + +// 获取服务器组项 +// id +// 返回值: +// 服务器组对象 +// 是否存在 +func GetServerGroupItem(id int32) (serverGroupObj *ServerGroup, exist bool) { + serverGroupObj, exist = serverGroupMap[id] + return +} + +// 根据合作商Id、服务器Id获取服务器组对象 +// partnerId:合作商Id +// serverId:服务器Id +// 返回值: +// 服务器组对象 +// 服务器对象 +// 是否存在 +func GetServerGroup(partnerId, serverId int32) (serverGroupObj *ServerGroup, serverObj *Server, exist bool) { + var partnerObj *Partner + + // 获取合作商对象 + partnerObj, exist = GetPartner(partnerId) + if !exist { + return + } + + // 获取服务器对象 + serverObj, exist = GetServer(partnerObj, serverId) + if !exist { + return + } + + // 获取服务器组对象 + serverGroupObj, exist = GetServerGroupItem(serverObj.GroupId) + return +} + +// 判断IP是否有效 +// ip:指定IP地址 +// 返回值: +// IP是否有效 +func isIpValid(ip string) (exist bool) { + for _, v := range serverGroupMap { + for _, item := range v.GetIPList() { + if ip == item { + exist = true + return + } + } + } + + return +} diff --git a/trunk/framework/managecenterMgr/userWhiteList.go b/trunk/framework/managecenterMgr/userWhiteList.go new file mode 100644 index 0000000..fab6e08 --- /dev/null +++ b/trunk/framework/managecenterMgr/userWhiteList.go @@ -0,0 +1,114 @@ +package managecenterMgr + +import ( + "encoding/json" + "errors" + "fmt" + + "Framework/managecenterModel" + . "Framework/managecenterModel" + "goutil/logUtil" + "goutil/webUtil" +) + +var ( + whiteListMap = make(map[int32]map[string]*WhiteList, 64) + whiteListHash string +) + +// 重新加载白名单 +func reloadWhiteList(isInit bool) error { + //logUtil.DebugLog("开始刷新白名单列表") + + url := getManageCenterUrl("/API/UserWhiteList.ashx") + + // 定义请求参数 + postDict := make(map[string]string) + postDict["HashValue"] = whiteListHash + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, transport) + //statusCode, returnBytes, err := webUtil.PostMapData(url, postDict, header, nil) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取白名单列表出错,url:%s,错误信息为:%s", url, err)) + return err + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("获取白名单列表出错,url:%s,错误码为:%d", url, statusCode)) + return err + } + + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取白名单列表出错,反序列化返回值出错,错误信息为:%s, str:%s", err, string(returnBytes))) + return err + } + + // 判断返回状态是否为成功 + if returnObj.Code != 0 { + // 数据没有变化,所以没有获取到新的数据,不能算错误。 + if returnObj.Code == 47 || returnObj.Message == "DataNotChanged" { + //如果本地集合为空,且数据又没变化时,重新初始化一下本地hash值 + if len(whiteListMap) == 0 { + whiteListHash = "" + } + return nil + } else { + msg := fmt.Sprintf("获取白名单列表出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(msg) + return errors.New(msg) + } + } + + // 解析Data + tmpWhiteList := make([]*WhiteList, 0, 128) + if data, ok := returnObj.Data.(string); !ok { + msg := "获取白名单列表出错,返回的数据不是string类型" + logUtil.ErrorLog(msg) + return errors.New(msg) + } else { + if err = json.Unmarshal([]byte(data), &tmpWhiteList); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取白名单列表出错,反序列化数据出错,错误信息为:%s", err)) + return err + } + } + + //logUtil.DebugLog(fmt.Sprintf("刷新白名单信息结束,白名单数量:%d", len(tmpWhiteList))) + + tmpWhiteListMap := make(map[int32]map[string]*WhiteList, 64) + for _, item := range tmpWhiteList { + if _, exist := tmpWhiteListMap[item.PartnerId]; !exist { + tmpWhiteListMap[item.PartnerId] = make(map[string]*WhiteList, 8) + } + + tmpWhiteListMap[item.PartnerId][item.UserId] = item + } + + // 赋值给最终的whiteListMap + whiteListMap = tmpWhiteListMap + whiteListHash = returnObj.HashValue + + //通知变更 + mcDataChangeCallBack(managecenterModel.UserWhiteListData, isInit) + + return nil +} + +// 判断用户是否在白名单里面 +// partnerId: 合作商ID +// userId: userId +func IsInWhiteList(partnerId int32, userId string) bool { + subWhiteListMap, exist := whiteListMap[partnerId] + if !exist { + return false + } + + _, exist = subWhiteListMap[userId] + return exist +} diff --git a/trunk/framework/managecenterModel/area.go b/trunk/framework/managecenterModel/area.go new file mode 100644 index 0000000..36317a3 --- /dev/null +++ b/trunk/framework/managecenterModel/area.go @@ -0,0 +1,69 @@ +package managecenterModel + +import "goutil/stringUtil" + +// 大区对象 +type Area struct { + // 大区Id + AreaId int32 `json:"AreaId"` + + //大区名字 + AreaName string `json:"AreaName"` + + //客户端显示名字 + ClientName string `json:"ClientName"` + + //区服范围 + ServerRange string `json:"ServerRange"` + + //资源包下载地址 + ResourceURL string `json:"ResourceURL"` + + //推荐服开关:0关闭,1:打开 + RecommendStitch int32 `json:"RecommendStitch"` + + //推荐最大注册人数 + MaxRegistration int32 `json:"MaxRegistration"` + + //限制注册的开关 + RegRestrictionSwitch int32 `json:"RegRestrictionSwitch"` + + //玩家数量大于该数量,自动开服 + AutoOpenServerPlayers int32 `json:"AutoOpenServerPlayers"` + + //大区维护状态(1:正常;2:维护) + AreaMaintainStatus int32 `json:"AreaMaintainStatus"` + + //维护消息 + AreaMaintainMsg string `json:"AreaMaintainMsg"` + + //大区页签集合 + AreaLabelList []*AreaLabel `json:"AreaLabelList"` +} + +// +//检测服务器是否在大区的区间范围 +func (this *Area) CheckServerIdIsInRange(serverId int32) (isVaild bool) { + isVaild = false + for _, serverRangeItem := range stringUtil.Split(this.ServerRange, []string{","}) { + serverRange, _ := stringUtil.SplitToInt32Slice(serverRangeItem, "-") + lower := serverRange[0] + upper := serverRange[1] + + //如果范围大小顺序不对,则换位 + if lower > upper { + temp := lower + lower = upper + upper = temp + } + + //如果服务器在该大区的任意区服区间,则返回true + if serverId >= lower && serverId <= upper { + isVaild = true + + return + } + } + + return +} diff --git a/trunk/framework/managecenterModel/areaLabel.go b/trunk/framework/managecenterModel/areaLabel.go new file mode 100644 index 0000000..e8e8e7b --- /dev/null +++ b/trunk/framework/managecenterModel/areaLabel.go @@ -0,0 +1,45 @@ +package managecenterModel + +import "goutil/stringUtil" + +//大区页签对象 +type AreaLabel struct { + // 大区Id + AreaId int32 `json:"AreaID"` + //标签ID + LabelID int32 `json:"LabelID"` + //标签名字 + LabelName string `json:"LabelName"` + //客户端显示名字 + ClientName string `json:"ClientName"` + //标签区间字符串1-100,200-300 + LabelServerRange string `json:"LabelServerRange"` + //渠道列表 + PartnerIdList []int64 `json:"PartnerIdList"` +} + +//检测服务器是否在大区页签的区间范围 +func (this *AreaLabel) CheckServerIdIsInLabelRange(serverId int32) (isVaild bool) { + isVaild = false + for _, serverRangeItem := range stringUtil.Split(this.LabelServerRange, []string{","}) { + serverRange, _ := stringUtil.SplitToInt32Slice(serverRangeItem, "-") + lower := serverRange[0] + upper := serverRange[1] + + //如果范围大小顺序不对,则换位 + if lower > upper { + temp := lower + lower = upper + upper = temp + } + + //如果服务器在该大区的任意区服区间,则返回true + if serverId >= lower && serverId <= upper { + isVaild = true + + return + } + } + + return +} diff --git a/trunk/framework/managecenterModel/chargeConfig.go b/trunk/framework/managecenterModel/chargeConfig.go new file mode 100644 index 0000000..3c3eff3 --- /dev/null +++ b/trunk/framework/managecenterModel/chargeConfig.go @@ -0,0 +1,44 @@ +package managecenterModel + +// 充值配置 +type ChargeConfig struct { + // 产品Id + ProductId string + + // 充值点数(以元为单位;如果是人民币是整数,但如果是美元,或者其它货币可能为小数) + ChargePoint float64 + + // 游戏内货币点数(元宝/钻石等,必定是整数) + GamePoint int + + // 充值金额与游戏内货币的兑换比率 + Ratio float64 + + // 赠送的游戏内货币点数(必定是整数) + GiveGamePoint int + + // 赠送的比率 + GiveRatio float64 + + // 首充时赠送的游戏内货币点数 + FirstGiveGamePoint int + + // 所需的vip等级 + VipLv byte + + // 首充时是否显示 + IfFirstShow byte + + // 第二次(及以后)充值时是否显示 + IfSecondShow byte + + // 是否为月卡 + IsMonthCard bool +} + +// 按照充值金额进行升序排序 +// target:另一个充值配置对象 +// 是否是小于 +func (this *ChargeConfig) SortByChargePointAsc(target *ChargeConfig) bool { + return this.ChargePoint < target.ChargePoint +} diff --git a/trunk/framework/managecenterModel/dbConnectionConfig.go b/trunk/framework/managecenterModel/dbConnectionConfig.go new file mode 100644 index 0000000..9363eea --- /dev/null +++ b/trunk/framework/managecenterModel/dbConnectionConfig.go @@ -0,0 +1,57 @@ +package managecenterModel + +import ( + . "goutil/mysqlUtil" + . "goutil/redisUtil" +) + +// 数据库连接字符串配置 +type DBConnectionConfig struct { + // 模型数据库内网连接字符串 + GameModelDB string + + // 游戏数据库内网连接字符串 + GameDB string + + // 日志数据库内网连接字符串 + LogDB string + + // Redis连接字符串 + RedisConfig string +} + +// 获取游戏模型数据库连接 +// 返回值: +// 数据库连接配置对象 +// 错误对象 +func (this *DBConnectionConfig) GetGameModelDBConn() (dbConfig *DBConfig, err error) { + dbConfig, err = NewDBConfig2(this.GameModelDB) + return +} + +// 获取游戏数据库连接 +// 返回值: +// 数据库连接配置对象 +// 错误对象 +func (this *DBConnectionConfig) GetGameDBConn() (dbConfig *DBConfig, err error) { + dbConfig, err = NewDBConfig2(this.GameDB) + return +} + +// 获取游戏日志数据库连接 +// 返回值: +// 数据库连接配置对象 +// 错误对象 +func (this *DBConnectionConfig) GetLogDBConn() (dbConfig *DBConfig, err error) { + dbConfig, err = NewDBConfig2(this.LogDB) + return +} + +// 获取Redis配置 +// 返回值: +// redis配置对象 +// 错误对象 +func (this *DBConnectionConfig) GetRedisConfig() (redisConfig *RedisConfig, err error) { + redisConfig, err = NewRedisConfig(this.RedisConfig) + return +} diff --git a/trunk/framework/managecenterModel/doc.go b/trunk/framework/managecenterModel/doc.go new file mode 100644 index 0000000..a7eeb3a --- /dev/null +++ b/trunk/framework/managecenterModel/doc.go @@ -0,0 +1,3 @@ +package managecenterModel + +// 与ManageCenter共享的模型对象定义 diff --git a/trunk/framework/managecenterModel/gameVersion.go b/trunk/framework/managecenterModel/gameVersion.go new file mode 100644 index 0000000..dd6be49 --- /dev/null +++ b/trunk/framework/managecenterModel/gameVersion.go @@ -0,0 +1,10 @@ +package managecenterModel + +// 游戏版本 +type GameVersion struct { + // Id + Id int32 `json:"GameVersionID"` + + // 名称 + Name string `json:"GameVersionName"` +} diff --git a/trunk/framework/managecenterModel/maintainType.go b/trunk/framework/managecenterModel/maintainType.go new file mode 100644 index 0000000..2e03a2d --- /dev/null +++ b/trunk/framework/managecenterModel/maintainType.go @@ -0,0 +1,15 @@ +package managecenterModel + +// 服务器状态 +type MaintainType int32 + +const ( + // 计划维护 + Con_MaintainType_PlanMaintain MaintainType = 1 + + // 开始维护 + Con_MaintainType_BeginMaintain MaintainType = 2 + + // 结束维护 + Con_MaintainType_EndMaintain MaintainType = 3 +) diff --git a/trunk/framework/managecenterModel/mcDataType.go b/trunk/framework/managecenterModel/mcDataType.go new file mode 100644 index 0000000..6e09d53 --- /dev/null +++ b/trunk/framework/managecenterModel/mcDataType.go @@ -0,0 +1,26 @@ +package managecenterModel + +type MCDataType int + +const ( + // 服务器 + ServerData MCDataType = 1 + + // 服务器组 + ServerGroupData MCDataType = 2 + + // 合作商 + PartnerData MCDataType = 3 + + // 资源 + ResourceVersionData MCDataType = 4 + + // 白名单 + UserWhiteListData MCDataType = 5 + + // 公告 + GameNoticeData MCDataType = 6 + + // 大区 + AreaData MCDataType = 7 +) diff --git a/trunk/framework/managecenterModel/officialOrTest.go b/trunk/framework/managecenterModel/officialOrTest.go new file mode 100644 index 0000000..ba84112 --- /dev/null +++ b/trunk/framework/managecenterModel/officialOrTest.go @@ -0,0 +1,11 @@ +package managecenterModel + +type OfficialOrTest int32 + +const ( + // 正式服 + Con_Official OfficialOrTest = 1 + + // 测试服 + Con_Test OfficialOrTest = 2 +) diff --git a/trunk/framework/managecenterModel/partner.go b/trunk/framework/managecenterModel/partner.go new file mode 100644 index 0000000..8b08222 --- /dev/null +++ b/trunk/framework/managecenterModel/partner.go @@ -0,0 +1,55 @@ +package managecenterModel + +import ( + "encoding/json" +) + +// 合作商 +type Partner struct { + // 合作商Id + Id int32 `json:"PartnerID"` + + // 合作商名称 + Name string `json:"PartnerName"` + + // 合作商别名 + Alias string `json:"PartnerAlias"` + + // 应用Id + AppId string `json:"AppID"` + + // 登陆加密Key + LoginKey string `json:"LoginKey"` + + // 充值配置 + ChargeConfig string `json:"ChargeConfig"` + + // 其它配置 + OtherConfigInfo string `json:"OtherConfigInfo"` + + // 游戏版本下载Url + GameVersionUrl string `json:"GameVersionUrl"` + + // 充值服务器Url + ChargeServerUrl string `json:"ChargeServerUrl"` + + // 合作商类型 + PartnerType int32 `json:"PartnerType"` + + // 权重 + Weight int32 `json:"Weight"` + + // 资源包所属类型 + ResourceType int32 `json:"ResourceType"` +} + +// 解析其它配置信息 +func (this *Partner) ResolveOtherConfig() (otherConfigMap map[string]string, err error) { + otherConfigMap = make(map[string]string, 8) + if this.OtherConfigInfo == "" { + return + } + + err = json.Unmarshal([]byte(this.OtherConfigInfo), &otherConfigMap) + return +} diff --git a/trunk/framework/managecenterModel/partnerType.go b/trunk/framework/managecenterModel/partnerType.go new file mode 100644 index 0000000..1b90968 --- /dev/null +++ b/trunk/framework/managecenterModel/partnerType.go @@ -0,0 +1,15 @@ +package managecenterModel + +// 合作商类型 +type PartnerType int32 + +const ( + // IOS + Con_IOS PartnerType = 0 + + // Android + Con_Android PartnerType = 1 + + // 越狱 + Con_JailBreak PartnerType = 2 +) diff --git a/trunk/framework/managecenterModel/resourceVersion.go b/trunk/framework/managecenterModel/resourceVersion.go new file mode 100644 index 0000000..444c3b4 --- /dev/null +++ b/trunk/framework/managecenterModel/resourceVersion.go @@ -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 +} diff --git a/trunk/framework/managecenterModel/returnObject.go b/trunk/framework/managecenterModel/returnObject.go new file mode 100644 index 0000000..84586a5 --- /dev/null +++ b/trunk/framework/managecenterModel/returnObject.go @@ -0,0 +1,16 @@ +package managecenterModel + +// 返回结果对象 +type ReturnObject struct { + // 返回的状态值;0:成功;非0:失败(根据实际情况进行定义) + Code int32 + + // 返回的失败描述信息 + Message string + + // 返回的数据 + Data interface{} + + // 返回的数据hash值 + HashValue string +} diff --git a/trunk/framework/managecenterModel/server.go b/trunk/framework/managecenterModel/server.go new file mode 100644 index 0000000..1984b25 --- /dev/null +++ b/trunk/framework/managecenterModel/server.go @@ -0,0 +1,22 @@ +package managecenterModel + +// 服务器 +type Server struct { + // 服务器Id + Id int32 `json:"ServerID"` + + // 服务器名称 + Name string `json:"ServerName"` + + // 合作商Id + PartnerId int32 `json:"PartnerID"` + + // 服务器组Id + GroupId int32 `json:"GroupID"` + + // 对应的游戏版本号 + GameVersionId int32 `json:"GameVersionID"` + + // 需要的最低游戏版本号 + MinGameVersionId int32 `json:"MinGameVersionID"` +} diff --git a/trunk/framework/managecenterModel/serverGroup.go b/trunk/framework/managecenterModel/serverGroup.go new file mode 100644 index 0000000..fdd8647 --- /dev/null +++ b/trunk/framework/managecenterModel/serverGroup.go @@ -0,0 +1,184 @@ +package managecenterModel + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "goutil/stringUtil" +) + +// 服务器组 +type ServerGroup struct { + // 服务器组Id + Id int32 `json:"GroupID"` + + // 服务器组名称 + Name string `json:"GroupName"` + + // 服务器组Url + Url string `json:"GroupUrl"` + + // 聊天服务器Url + ChatServerUrl string `json:"ChatServerUrl"` + + // 数据库连接配置 + DBConnectionConfig string `json:"DBConnectionConfig"` + + // 服务器组状态(1:正常;2:维护) + GroupState int32 `json:"GroupState"` + + // 服务器组热度(1:正常;2:新服;3:推荐) + GroupHeat int32 `json:"GroupHeat"` + + // 服务器组负载(1:正常;2:火爆) + GroupLoad int32 `json:"GroupLoad"` + + // 服务器开服时间对应的Unix时间戳 + OpenTimeTick int64 `json:OpenTimeTick` + + // 服务器组Ip(外网IP;内网IP;回调GS内网端口) + Ip string `json:"GroupIp"` + + // 正式服或测试服;1:正式服;2:测试服 + OfficialOrTest int32 `json:"OfficialOrTest"` + + // 服务器组类型 + Type int32 `json:"GroupType"` + + // 服务器组排序 + Order int32 `json:"GroupOrder"` + + // 服务器组维护开始时间对应的时间戳 + MaintainBeginTimeTick int64 `json:MaintainBeginTimeTick` + + // 维护持续分钟数 + MaintainMinutes int32 `json:"MaintainMinutes"` + + // 维护信息 + MaintainMessage string `json:"MaintainMessage"` + + // 游戏监听地址 + GameListenAddr string `json:"GameListenAddr"` + + // 回调监听地址 + CallbackListenAddr string `json:"CallbackListenAddr"` + + // 外网回调地址 + ExternalCallbackUrl string `json:"ExternalCallbackUrl"` + + // 内网回调地址 + InternalCallbackUrl string `json:"InternalCallbackUrl"` + + // 是否在主群组(机房)内 + IsInMainGroup bool `json:"IsInMainGroup"` + + // 监控端口 + GopsPort string `json:"GopsPort"` +} + +// 排序方法(默认按照Id进行升序排序) +// target:另一个服务器组对象 +// 是否是小于 +func (this *ServerGroup) SortByIdAsc(target *ServerGroup) bool { + return this.Id < target.Id +} + +// 按照开服时间进行升序排序 +// target:另一个服务器组对象 +// 是否是小于 +func (this *ServerGroup) SortByOpenTimeAsc(target *ServerGroup) bool { + return this.OpenTimeTick < target.OpenTimeTick +} + +// 获取数据库配置对象 +// 返回值: +// 数据库配置对象 +// 错误对象 +func (this *ServerGroup) GetDBConfig() (*DBConnectionConfig, error) { + var dbConfig *DBConnectionConfig + if err := json.Unmarshal([]byte(this.DBConnectionConfig), &dbConfig); err != nil { + return nil, err + } + + return dbConfig, nil +} + +// 获取ip列表 +// 返回值: +// ip列表 +func (this *ServerGroup) GetIPList() []string { + return stringUtil.Split(this.Ip, nil) +} + +// 服务器组是否开启 +// 返回值: +// 是否开启 +func (this *ServerGroup) IsOpen() bool { + return this.OpenTimeTick < time.Now().Unix() +} + +// 获取游戏服务器的回调地址 +// suffix:地址后缀 +// 返回值 +// 游戏服务器的回调地址 +func (this *ServerGroup) GetGSCallbackUrl(suffix string) string { + // 如果是在主群组(机房)内,则使用内网地址,否则使用外网地址 + url := "" + if this.IsInMainGroup { + url = this.InternalCallbackUrl + } else { + url = this.ExternalCallbackUrl + } + + if url != "" { + if strings.HasSuffix(url, "/") { + return fmt.Sprintf("%s%s", url, suffix) + } else { + return fmt.Sprintf("%s/%s", url, suffix) + } + } + + // 兼容旧的ManageCenter版本 + ipList := this.GetIPList() + + // 外网IP;内网IP;回调GS内网端口;如果数量小于3,则直接使用配置的GroupUrl;否则使用第3个值 + if len(ipList) < 3 { + if strings.HasSuffix(this.Url, "/") { + return fmt.Sprintf("%s%s", this.Url, suffix) + } else { + return fmt.Sprintf("%s/%s", this.Url, suffix) + } + } else { + return fmt.Sprintf("http://%s:%s/%s", ipList[1], ipList[2], suffix) + } +} + +// 判断服务器组是否相同 +// target:目标服务器组 +// 是否相同 +func (this *ServerGroup) IsEqual(target *ServerGroup) bool { + return this.Id == target.Id && + this.Name == target.Name && + this.Url == target.Url && + this.ChatServerUrl == target.ChatServerUrl && + this.DBConnectionConfig == target.DBConnectionConfig && + this.GroupState == target.GroupState && + this.GroupHeat == target.GroupHeat && + this.GroupLoad == target.GroupLoad && + this.OpenTimeTick == target.OpenTimeTick && + this.Ip == target.Ip && + this.OfficialOrTest == target.OfficialOrTest && + this.Type == target.Type && + this.Order == target.Order && + this.MaintainBeginTimeTick == target.MaintainBeginTimeTick && + this.MaintainMinutes == target.MaintainMinutes && + this.MaintainMessage == target.MaintainMessage && + this.GameListenAddr == target.GameListenAddr && + this.CallbackListenAddr == target.CallbackListenAddr && + this.ExternalCallbackUrl == target.ExternalCallbackUrl && + this.InternalCallbackUrl == target.InternalCallbackUrl && + this.IsInMainGroup == target.IsInMainGroup && + this.GopsPort == target.GopsPort +} diff --git a/trunk/framework/managecenterModel/serverGroupHeat.go b/trunk/framework/managecenterModel/serverGroupHeat.go new file mode 100644 index 0000000..4f400e2 --- /dev/null +++ b/trunk/framework/managecenterModel/serverGroupHeat.go @@ -0,0 +1,18 @@ +package managecenterModel + +// 服务器组热度 +type GroupHeat int32 + +const ( + // 正常 + Con_GroupHeat_Normal GroupHeat = 1 + + // 新服 + Con_GroupHeat_New GroupHeat = 2 + + // 推荐 + Con_GroupHeat_Recommend GroupHeat = 3 + + // 热门 + Con_GroupHeat_Heat GroupHeat = 4 +) diff --git a/trunk/framework/managecenterModel/serverGroupLoad.go b/trunk/framework/managecenterModel/serverGroupLoad.go new file mode 100644 index 0000000..a0802a0 --- /dev/null +++ b/trunk/framework/managecenterModel/serverGroupLoad.go @@ -0,0 +1,12 @@ +package managecenterModel + +// 服务器状态 +type GroupLoad int32 + +const ( + // 正常 + Con_GroupLoad_Normal GroupLoad = 1 + + // 火爆 + Con_GroupLoad_Hot GroupLoad = 2 +) diff --git a/trunk/framework/managecenterModel/serverGroupState.go b/trunk/framework/managecenterModel/serverGroupState.go new file mode 100644 index 0000000..8a7c114 --- /dev/null +++ b/trunk/framework/managecenterModel/serverGroupState.go @@ -0,0 +1,12 @@ +package managecenterModel + +// 服务器状态 +type GroupState int32 + +const ( + // 正常 + Con_GroupState_Normal GroupState = 1 + + // 维护 + Con_GroupState_Maintain GroupState = 2 +) diff --git a/trunk/framework/managecenterModel/serverIdRange.go b/trunk/framework/managecenterModel/serverIdRange.go new file mode 100644 index 0000000..9950e86 --- /dev/null +++ b/trunk/framework/managecenterModel/serverIdRange.go @@ -0,0 +1,22 @@ +package managecenterModel + +// 服务器(组)Id区间类型 +type ServerIdRange struct { + Min int32 // 最小值 + Max int32 // 最大值 +} + +// 是否包含指定的值 +// value:指定值 +// 返回值: +// 是否包含指定的值 +func (this *ServerIdRange) Contains(value int32) bool { + return this.Min <= value && value <= this.Max +} + +func NewServerIdRange(min, max int32) *ServerIdRange { + return &ServerIdRange{ + Min: min, + Max: max, + } +} diff --git a/trunk/framework/managecenterModel/serverIdRule.go b/trunk/framework/managecenterModel/serverIdRule.go new file mode 100644 index 0000000..6e00443 --- /dev/null +++ b/trunk/framework/managecenterModel/serverIdRule.go @@ -0,0 +1,30 @@ +package managecenterModel + +var ( + // 安卓正式服的服务器Id区间 + AndroidOfficial *ServerIdRange = NewServerIdRange(1, 5000) + + // 安卓删档测试服的服务器Id区间 + AndroidPreOfficial *ServerIdRange = NewServerIdRange(5001, 9000) + + // 安卓审核服的服务器Id区间 + AndroidAudit *ServerIdRange = NewServerIdRange(9001, 9500) + + // 安卓测试服的服务器Id区间 + AndroidTest *ServerIdRange = NewServerIdRange(9501, 10000) + + // IOS正式服的服务器Id区间 + IOSOfficial *ServerIdRange = NewServerIdRange(10001, 15000) + + // IOS删档测试服的服务器Id区间 + IOSPreOfficial *ServerIdRange = NewServerIdRange(15001, 19000) + + // IOS审核服的服务器Id区间 + IOSAudit *ServerIdRange = NewServerIdRange(19001, 19500) + + // IOS测试服的服务器Id区间 + IOSTest *ServerIdRange = NewServerIdRange(19501, 20000) + + // 开发服务器的服务器Id区间 + Develop *ServerIdRange = NewServerIdRange(20001, 30000) +) diff --git a/trunk/framework/managecenterModel/sysConfig.go b/trunk/framework/managecenterModel/sysConfig.go new file mode 100644 index 0000000..d2f0e91 --- /dev/null +++ b/trunk/framework/managecenterModel/sysConfig.go @@ -0,0 +1,13 @@ +package managecenterModel + +// MC系统配置列表对象 +type SysConfig struct { + // 新服有效天数 + NewServerValidDays int32 `json:"NewServerValidDays"` + + // 用户Id + WhiteListServerIds string `json:"WhiteListServerIds"` + + //登录本地验证key + DynamicLoginKey string `json:"DynamicLoginKey"` +} diff --git a/trunk/framework/managecenterModel/whiteList.go b/trunk/framework/managecenterModel/whiteList.go new file mode 100644 index 0000000..7ec6fc4 --- /dev/null +++ b/trunk/framework/managecenterModel/whiteList.go @@ -0,0 +1,10 @@ +package managecenterModel + +// 白名单列表对象 +type WhiteList struct { + // 合作商Id + PartnerId int32 `json:"PartnerId"` + + // 用户Id + UserId string `json:"UserId"` +} diff --git a/trunk/framework/monitorMgr/monitor.go b/trunk/framework/monitorMgr/monitor.go new file mode 100644 index 0000000..7df92b0 --- /dev/null +++ b/trunk/framework/monitorMgr/monitor.go @@ -0,0 +1,208 @@ +package monitorMgr + +import ( + "fmt" + "sync" + "time" + + "goutil/debugUtil" + "goutil/logUtil" + "goutil/webUtil" +) + +var ( + // 报告监控信息的URL + remoteURL = "http://maintenance.7qule.com/Manage/Monitor.ashx" + + // 服务器IP + serverIP string + + // 服务器名称 + serverName string + + // 监控时间间隔(单位:分钟) + monitorInterval = 5 + + // 重复消息发送的时间间隔(单位:分钟) + duplicateInterval = 5 + + // 已经发送的消息 + sentMessageMap = make(map[string]int64) + + // 已经发送消息的锁对象 + sentMessageMutex sync.Mutex + + // 监控方法列表 + monitorFuncList = make([]func() error, 0, 4) + + // 监控方法锁对象 + monitorFuncMutex sync.Mutex +) + +// SetURL ...设置监控信息发送的URL +// url:监控信息发送的URL +func SetURL(url string) { + remoteURL = url +} + +// SetDuplicateInterval ...设置重复消息发送的时间间隔(单位:分钟) +// _duplicateInterval:重复消息发送的时间间隔(单位:分钟) +func SetDuplicateInterval(_duplicateInterval int) { + duplicateInterval = _duplicateInterval +} + +// SetParam ...设置参数 +// _serverIP:服务器IP +// _serverName:服务器名称 +// _monitorInterval:监控时间间隔(单位:分钟) +func SetParam(_serverIP, _serverName string, _monitorInterval int) { + serverIP = _serverIP + serverName = _serverName + monitorInterval = _monitorInterval +} + +// RegisterMonitorFunc ...注册监控方法 +// f:监控方法 +func RegisterMonitorFunc(f func() error) { + monitorFuncMutex.Lock() + defer monitorFuncMutex.Unlock() + + monitorFuncList = append(monitorFuncList, f) +} + +// Start ...启动监控服务(obsolete,建议使用Start2) +// serverIp:服务器IP +// serverName:服务器名称 +// monitorInterval:监控时间间隔(单位:分钟) +func Start(serverIp, serverName string, monitorInterval int) { + monitorConfig := NewMonitorConfig(serverIp, serverName, monitorInterval) + Start2(monitorConfig) +} + +// Start ...启动监控服务2 +// monitorConfig:监控配置对象 +func Start2(monitorConfig *MonitorConfig) { + // 设置参数 + SetParam(monitorConfig.ServerIp, monitorConfig.ServerName, monitorConfig.Interval) + + // 实际的监控方法调用 + monitorFunc := func() { + monitorFuncMutex.Lock() + defer monitorFuncMutex.Unlock() + + for _, item := range monitorFuncList { + if err := item(); err != nil { + Report(err.Error()) + } + } + } + + go func() { + // 处理内部未处理的异常,以免导致主线程退出,从而导致系统崩溃 + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + }() + + for { + // 先休眠,避免系统启动时就进行报警 + time.Sleep(time.Minute * time.Duration(monitorInterval)) + + // 实际的监控方法调用 + monitorFunc() + } + }() +} + +// 判断指定时间内是否已经处理过 +// conent:报告内容 +func isDuplicate(content string) bool { + if ts, exists := sentMessageMap[content]; exists && time.Now().Unix()-ts < int64(60*duplicateInterval) { + return true + } + + return false +} + +// 添加到已发送集合中 +// conent:报告内容 +func addToMap(content string) { + sentMessageMap[content] = time.Now().Unix() +} + +// Report ...报告异常信息 +// format:报告内容格式 +// args:具体参数 +func Report(format string, args ...interface{}) { + if len(args) <= 0 { + Report2(format, "") + } else { + Report2(fmt.Sprintf(format, args...), "") + } +} + +// Report ...报告异常信息 +// title:上报的标题 +// contentFmt:报告内容 +// args:参数列表 +func Report2(title, contentFmt string, args ...interface{}) { + content := contentFmt + if len(args) > 0 { + content = fmt.Sprintf(contentFmt, args...) + } + + sentMessageMutex.Lock() + defer sentMessageMutex.Unlock() + + // 判断指定时间内是否已经处理过 + if isDuplicate(title) { + return + } + + logUtil.WarnLog(fmt.Sprintf("MonitorReport:ServerIP:%s,ServerName:%s,Title:%s Content:%s", serverIP, serverName, title, content)) + + // 判断是否是DEBUG模式 + if debugUtil.IsDebug() { + return + } + + detailMsg := title + if len(content) > 0 { + detailMsg = fmt.Sprintf("Title:%s Content:%s", title, content) + } + + // 定义请求参数 + postDict := make(map[string]string) + postDict["ServerIp"] = serverIP + postDict["ServerName"] = serverName + postDict["Content"] = detailMsg + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, returnBytes, err := webUtil.PostMapData(remoteURL, postDict, header, transport) + // 连接服务器,以获取数据 + //returnBytes, err := webUtil.PostWebData(remoteURL, postDict, nil) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("MonitorReport:,错误信息为:%s", err)) + return + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("MonitorReport:,状态吗为:%d", statusCode)) + return + } + + result := string(returnBytes) + logUtil.WarnLog(fmt.Sprintf("MonitorReport:Result:%s", result)) + + if result != "200" { + logUtil.ErrorLog(fmt.Sprintf("返回值不正确,当前返回值为:%s", result)) + } + + // 添加到已发送集合中 + addToMap(title) +} diff --git a/trunk/framework/monitorMgr/monitorConfig.go b/trunk/framework/monitorMgr/monitorConfig.go new file mode 100644 index 0000000..7531f24 --- /dev/null +++ b/trunk/framework/monitorMgr/monitorConfig.go @@ -0,0 +1,30 @@ +package monitorMgr + +import ( + "encoding/json" +) + +// 监控配置对象 +type MonitorConfig struct { + // 监控使用的服务器IP + ServerIp string + + // 监控使用的服务器名称 + ServerName string + + // 监控的时间间隔(单位:分钟) + Interval int +} + +func (this *MonitorConfig) String() string { + bytes, _ := json.Marshal(this) + return string(bytes) +} + +func NewMonitorConfig(serverIp, serverName string, interval int) *MonitorConfig { + return &MonitorConfig{ + ServerIp: serverIp, + ServerName: serverName, + Interval: interval, + } +} diff --git a/trunk/framework/monitorMgr/monitor_test.go b/trunk/framework/monitorMgr/monitor_test.go new file mode 100644 index 0000000..039612a --- /dev/null +++ b/trunk/framework/monitorMgr/monitor_test.go @@ -0,0 +1,19 @@ +package monitorMgr + +import ( + "testing" +) + +func TestDuplicate(t *testing.T) { + SetParam("20.255.0.7", "Test", 5) + + content := "content" + + if isDuplicate(content) == true { + t.Errorf("%s不应该重复却重复了", content) + } + addToMap(content) + if isDuplicate(content) == false { + t.Errorf("%s应该重复却没有重复", content) + } +} diff --git a/trunk/framework/monitorNewMgr/model.go b/trunk/framework/monitorNewMgr/model.go new file mode 100644 index 0000000..7df1797 --- /dev/null +++ b/trunk/framework/monitorNewMgr/model.go @@ -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, + } +} diff --git a/trunk/framework/monitorNewMgr/monitor.go b/trunk/framework/monitorNewMgr/monitor.go new file mode 100644 index 0000000..2bb475f --- /dev/null +++ b/trunk/framework/monitorNewMgr/monitor.go @@ -0,0 +1,316 @@ +package monitorNewMgr + +import ( + "encoding/json" + "fmt" + "sync" + "time" + + "goutil/logUtil" + "goutil/securityUtil" + "goutil/webUtil" +) + +var ( + // 报告监控信息的URL + remoteURL = "http://monitorservice.7qule.com/monitor" + + //组Id + groupId string + + //项目Id + projectId string + + //组密钥 + groupSecret string + + // 服务器IP + serverIP string + + // 服务器名称 + serverName string + + // 监控时间间隔(单位:秒钟) + monitorInterval = 10 + + // 监控方法列表 + monitorFuncList = make([]func() error, 0, 4) + + // 监控方法锁对象 + monitorFuncMutex sync.Mutex + + //未设置参数的提醒 + isWarning bool = false +) + +func init() { + // 实际的监控方法调用 + monitorFunc := func() { + monitorFuncMutex.Lock() + defer monitorFuncMutex.Unlock() + + for _, item := range monitorFuncList { + if err := item(); err != nil { + errContent := fmt.Sprintf("监控方法实际调用错误:%s", err.Error()) + report(-99, errContent) + } else { + report(0, "") + } + } + } + + go func() { + // monitorNewMgr不能引用goroutineMgr,以避免循环引用 + /* + goroutineName := fmt.Sprintf("%s.check", "monitorNewMgr") + goroutineMgr.Monitor(goroutineName) + defer goroutineMgr.ReleaseMonitor(goroutineName) + */ + + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + }() + + for { + // 先休眠,避免系统启动时就进行报警 + time.Sleep(time.Second * time.Duration(monitorInterval)) + + // 实际的监控方法调用 + monitorFunc() + } + }() +} + +//监控信息提交返回 +type ReportResponse struct { + //响应结果状态 + ResultStatus string + + // 响应结果数据 + Data string +} + +// SetURL ...设置监控信息发送的URL +// url:监控信息发送的URL +func SetURL(url string) { + remoteURL = url +} + +// SetParam ...设置参数 +//_groupId:组Id +//_groupSecret:组密钥 +//_projectId:项目Id +// _serverIP:服务器IP +// _serverName:服务器名称 +func SetParam(_groupId, _groupSecret, _projectId, _serverIP, _serverName string) { + groupId = _groupId + groupSecret = _groupSecret + projectId = _projectId + serverIP = _serverIP + serverName = _serverName +} + +// RegisterMonitorFunc ...注册监控方法 +// f:监控方法 +func RegisterMonitorFunc(f func() error) { + monitorFuncMutex.Lock() + defer monitorFuncMutex.Unlock() + + monitorFuncList = append(monitorFuncList, f) +} + +// Report ...报告异常信息 +// title:上报的标题 +// contentFmt:报告内容 +// args:参数列表 +func Report(title, contentFmt string, args ...interface{}) { + content := fmt.Sprintf("Title:%s Content:%s", title, contentFmt) + if len(args) > 0 { + content = fmt.Sprintf(content, args...) + } + + timestamp := time.Now().Unix() + + // 判断指定时间内是否已经处理过 + valid := checkSimilarity(content, timestamp) + if !valid { + return + } + + //上传错误 + report(-99, content) +} + +// Report2 ...报告异常信息 +// title:上报的标题 +// contentFmt:报告内容 +//code :9999为特殊状态,不加入心跳检测 +//isCheckSimilarity:是否去重 +// args:参数列表 +func Report2(title, contentFmt string, code int, isCheckSimilarity bool, args ...interface{}) { + content := fmt.Sprintf("Title:%s Content:%s", title, contentFmt) + if len(args) > 0 { + content = fmt.Sprintf(content, args...) + } + + if isCheckSimilarity { + timestamp := time.Now().Unix() + + // 判断指定时间内是否已经处理过 + valid := checkSimilarity(content, timestamp) + if !valid { + return + } + } + + //上传错误 + report(code, content) +} + +//report 统一上传信息 +//code 0为心跳,非零为错误信息 +//content 上报内容 +func report(code int, content string) { + if groupId == "" || projectId == "" || groupSecret == "" { + if !isWarning { + logUtil.WarnLog("monitorNewMgr没有初始化参数,请调用SetParam进行初始化") + isWarning = true + } + + return + } + + timestamp := time.Now().Unix() + rawString := fmt.Sprintf("Code=%d&GroupId=%s&ProjectId=%s&ServerIP=%s&ServerName=%s&Content=%s&Timestamp=%d&GroupSecret=%s", code, groupId, projectId, serverIP, serverName, content, timestamp, groupSecret) + sign := securityUtil.Md5String(rawString, true) + + //构造参数对象 + monitorModel := newMonitorModel(code, groupId, projectId, serverIP, serverName, content, timestamp, sign) + + //定义错误信息 + var logMessage string + + //定义参数 + jsonMonitorModel, err := json.Marshal(monitorModel) + if err != nil { + logMessage = fmt.Sprintf("json.Marshal(monitorModel),err:%s", err.Error()) + logUtil.ErrorLog(logMessage) + return + } + + postDataDict := make(map[string]string) + postDataDict["MonitorModel"] = string(jsonMonitorModel) + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, result, err := webUtil.PostMapData(remoteURL, postDataDict, header, transport) + //statusCode, result, err := webUtil.PostMapData(remoteURL, postDataDict, webUtil.ContentType_Form, nil) + if err != nil { + logMessage = fmt.Sprintf("MonitorModel:,错误信息为:%s", err.Error()) + logUtil.ErrorLog(logMessage) + return + } + if statusCode != 200 { + logMessage = fmt.Sprintf("MonitorReport:%d is wrong", statusCode) + logUtil.ErrorLog(logMessage) + return + } + + var reportResponseObj *ReportResponse + err = json.Unmarshal(result, &reportResponseObj) + if err != nil { + logMessage = fmt.Sprintf("json.Unmarshal(reportResponseObj), result:%s err:%s", result, err.Error()) + logUtil.ErrorLog(logMessage) + return + } + + if reportResponseObj.ResultStatus != "" { + logMessage = fmt.Sprintf("上传信息返回错误,ResultStatus %s", reportResponseObj.ResultStatus) + logUtil.ErrorLog(logMessage) + return + } +} + +//上报数据 +func ReportServiceStatus(data interface{}) { + var commInfo CommInfoModel + var isServerMsg bool + var isCenterMsg bool + //定义错误信息 + var logMessage string + + message, ok := (data).(ServerNodeMessage) + if ok { + isServerMsg = true + commInfo = message.CommInfo + //处理数据 + return + } else { + message, ok := (data).(MonitorCenterMessage) + if ok { + isCenterMsg = true + commInfo = message.CommInfo + } + } + + //如果类型都不符合,则说明数据格式有问题 + if !isServerMsg && !isCenterMsg { + logMessage = fmt.Sprintf("上传信息格式错误,ResultStatus %s", data) + logUtil.ErrorLog(logMessage) + } + + //构造签名字符串 + rawString := fmt.Sprintf("&GroupId=%s&ProjectId=%s&IP=%s&ServiceName=%s&InstanceId=%s&ClusterId=%s&Timestamp=%d&GroupSecret=%s", + commInfo.GroupId, commInfo.ProjectId, commInfo.IP, commInfo.ServiceName, commInfo.InstanceId, commInfo.ClusterId, commInfo.Tll, groupSecret) + // 签名赋值 + commInfo.Sign = rawString + + //定义参数 + jsonMonitorModel, err := json.Marshal(data) + if err != nil { + logMessage = fmt.Sprintf("json.Marshal(data),err:%s", err.Error()) + logUtil.ErrorLog(logMessage) + return + } + postDataDict := make(map[string]string) + postDataDict["MonitorModel"] = string(jsonMonitorModel) + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + statusCode, result, err := webUtil.PostMapData("http://monitorservice.7qule.com/serviceMonitor", postDataDict, header, transport) + //statusCode, result, err := webUtil.PostMapData(remoteURL, postDataDict, webUtil.ContentType_Form, nil) + if err != nil { + logMessage = fmt.Sprintf("ReportServiceStatus上报数据错误:,错误信息为:%s", err.Error()) + logUtil.ErrorLog(logMessage) + return + } + if statusCode != 200 { + logMessage = fmt.Sprintf("ReportServiceStatus上报数据错误:%d is wrong", statusCode) + logUtil.ErrorLog(logMessage) + return + } + + var reportResponseObj *ReportResponse + err = json.Unmarshal(result, &reportResponseObj) + if err != nil { + logMessage = fmt.Sprintf("ReportServiceStatus.json.Unmarshal(reportResponseObj), result:%s err:%s", result, err.Error()) + logUtil.ErrorLog(logMessage) + return + } + + if reportResponseObj.ResultStatus != "" { + logMessage = fmt.Sprintf("ReportServiceStatus上传信息返回错误,ResultStatus %s", reportResponseObj.ResultStatus) + logUtil.ErrorLog(logMessage) + return + } +} diff --git a/trunk/framework/monitorNewMgr/monitorHistory.go b/trunk/framework/monitorNewMgr/monitorHistory.go new file mode 100644 index 0000000..eb3d5c4 --- /dev/null +++ b/trunk/framework/monitorNewMgr/monitorHistory.go @@ -0,0 +1,18 @@ +package monitorNewMgr + +//历史监控信息 +type MonitorHistory struct { + //监控信息 + MonitorMessage string + + //时间戳 + Timestamp int64 +} + +//new一个监控历史信息 +func newMonitorHistory(monitorMessage string, timestamp int64) *MonitorHistory { + return &MonitorHistory{ + MonitorMessage: monitorMessage, + Timestamp: timestamp, + } +} diff --git a/trunk/framework/monitorNewMgr/monitor_history.go b/trunk/framework/monitorNewMgr/monitor_history.go new file mode 100644 index 0000000..e388016 --- /dev/null +++ b/trunk/framework/monitorNewMgr/monitor_history.go @@ -0,0 +1,63 @@ +package monitorNewMgr + +import ( + "sync" + "time" + + "goutil/stringUtil" +) + +const ( + // CON_MONITOR_HISTORY_SIZE 历史监控信息保存数量 + CON_MONITOR_HISTORY_SIZE = 3 + + // CON_MONITOR_HISTORY_VALID_SECONDS 历史监控信息有效的秒数 + CON_MONITOR_HISTORY_VALID_SECONDS = 30 + + // CON_MONITOR_HISTORY_SIMILARITY_THERSHOLD 历史监控信息相似度的阈值 + CON_MONITOR_HISTORY_SIMILARITY_THERSHOLD = 0.9 +) + +var ( + monitorHistoryList = make([]*MonitorHistory, 0, CON_MONITOR_HISTORY_SIZE) + monitorHistoryMutex sync.Mutex +) + +func checkSimilarity(monitorMessage string, timestamp int64) (valid bool) { + //默认监控信息有效 + valid = true + + monitorHistoryMutex.Lock() + defer monitorHistoryMutex.Unlock() + + // 从后往前(按时间从后往前)遍历,以便于可以及时退出 + for i := len(monitorHistoryList) - 1; i >= 0; i-- { + item := monitorHistoryList[i] + + // 如果已经过期,则不用处理; 返回之前的状态即可(当前的已经过期,则之前的也一定已经过期) + if time.Now().Unix()-item.Timestamp > CON_MONITOR_HISTORY_VALID_SECONDS { + break + } + + // 如果两个字符串的长度相差2倍,则无须继续判断,继续与下一条数据进行比较 + lenA, lenB := len(monitorMessage), len(item.MonitorMessage) + if lenA > 2*lenB || lenB > 2*lenA { + continue + } + + // 判断两个字符串的相似度(如果相似度超过阈值,则此日志无效) + _, similarity := stringUtil.Similarity(monitorMessage, item.MonitorMessage) + if similarity >= CON_MONITOR_HISTORY_SIMILARITY_THERSHOLD { + valid = false + return // 直接返回,无需将当前日志添加到历史列表中,以提高性能 + } + } + + // 将消息添加到历史消息列表中 + monitorHistoryList = append(monitorHistoryList, newMonitorHistory(monitorMessage, timestamp)) + if len(monitorHistoryList) > CON_MONITOR_HISTORY_SIZE { + monitorHistoryList = monitorHistoryList[len(monitorHistoryList)-CON_MONITOR_HISTORY_SIZE:] + } + + return +} diff --git a/trunk/framework/monitorNewMgr/monitor_test.go b/trunk/framework/monitorNewMgr/monitor_test.go new file mode 100644 index 0000000..135ef98 --- /dev/null +++ b/trunk/framework/monitorNewMgr/monitor_test.go @@ -0,0 +1,20 @@ +package monitorNewMgr + +import ( + "testing" +) + +func TestReport(t *testing.T) { + //var groupIdWrong = "wrong" + var groupId = "lj" + //var groupSecretWrong = "wrong" + var groupSecret = "8DD58C6C-E9C2-4DA3-8CBC-D5D5C6629CB8" + //var projectIdWrong = "wrong" + var projectId = "lj3" + var serverName = "testservername" + var serverIp = "127.0.0.1" + //设置完全正确的参数 + SetURL("http://monitorservice.7qule.com/monitor") + SetParam(groupId, groupSecret, projectId, serverIp, serverName) + Report("test", "test success") +} diff --git a/trunk/framework/monitorNewMgr/serviceMonitorModel.go b/trunk/framework/monitorNewMgr/serviceMonitorModel.go new file mode 100644 index 0000000..8583cf6 --- /dev/null +++ b/trunk/framework/monitorNewMgr/serviceMonitorModel.go @@ -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 +} diff --git a/trunk/framework/mqMgr/config.go b/trunk/framework/mqMgr/config.go new file mode 100644 index 0000000..e7e19ee --- /dev/null +++ b/trunk/framework/mqMgr/config.go @@ -0,0 +1,16 @@ +package mqMgr + +// 消息队列配置对象 +type QueueConfig struct { + // 地域 + Region string + + // 队列名称 + QueueName string + + // API密钥Id + SecretId string + + // API密钥key + SecretKey string +} diff --git a/trunk/framework/mqMgr/model/common_request.go b/trunk/framework/mqMgr/model/common_request.go new file mode 100644 index 0000000..e8434bb --- /dev/null +++ b/trunk/framework/mqMgr/model/common_request.go @@ -0,0 +1,109 @@ +/* +请求结构简介 +最近更新时间:2019-08-01 19:14:44 + +编辑 查看pdf +在这篇文章中: +服务地址 +通信协议 +请求方法 +请求参数 +字符编码 +对腾讯云的 API 接口的调用是通过向腾讯云 API 的服务端地址发送请求,并按照接口说明在请求中加入相应的请求参数来完成的。腾讯云 API 的请求结构由:服务地址、通信协议、请求方法、请求参数和字符编码组成。具体描述如下: + +服务地址 +腾讯云 API 的服务接入地址与具体模块相关,详细请参见各接口相关描述。 + +通信协议 +腾讯云 API 的大部分接口都通过 HTTPS 进行通信,为您提供高安全性的通信通道。 + +请求方法 +腾讯云 API 同时支持 POST 和 GET 请求。 + +注意: + +POST 和 GET 请求不能混合使用,若使用 GET 方式,则参数均从 Querystring 取得; +若使用 POST 方式,则参数均从 Request Body 中取得,而 Querystring 中的参数将忽略。 +两种请求方式的参数格式规则相同,一般情况下使用 GET 请求,当参数字符串过长时推荐使用 POST。 +如果用户的请求方法是 GET,则对所有请求参数值均需要做 URL 编码,若为 POST,则无需对参数编码。 +GET 请求的最大长度根据不同的浏览器和服务器设置有所不同,例如,传统 IE 浏览器限制为 2K,Firefox 限制为 8K;对于一些参数较多、长度较长的 API 请求,建议您使用 POST 方法以免在请求过程中会由于字符串超过最大长度而导致请求失败。 +对于 POST 请求,您需要使用 x-www-form-urlencoded 的形式传参,因为云 API 侧是从 $_POST 中取出请求参数的。 +请求参数 +腾讯云 API 的每个请求都需要指定两类参数:公共请求参数以及接口请求参数。其中公共请求参数是每个接口都要用到的请求参数,具体可参见 公共请求参数,而接口请求参数是各个接口所特有的,具体见各个接口的“请求参数”描述。 + +字符编码 +腾讯云 API 的请求及返回结果均使用 UTF-8 字符集进行编码。 +*/ +package model + +import ( + "fmt" + "time" + + "goutil/mathUtil" +) + +// CommonRequest 公共请求参数对象 +type CommonRequest struct { + // Action 指令接口名称(必须) + Action string + + // 地域参数(必须) + Region string + + // Timestamp 当前UNIX时间戳(必须) + Timestamp uint64 + + // Nonce 随机正整数(必须) + Nonce uint32 + + // SecretId 在云API密钥上申请的标识身份的SecretId(必须) + SecretId string + + // 请求签名,用来验证此次请求的合法性,需要用户根据实际的输入参数计算得出。 + Signature string + + // 签名方式,目前支持 HmacSHA256 和 HmacSHA1。只有指定此参数为 HmacSHA256 时,才使用 HmacSHA256 算法验证签名,其他情况均使用 HmacSHA1 验证签名。 + SignatureMethod string + + // 队列名称(此属性虽然不是API文档中的公共属性,但是在队列模型中确实事实上的公共属性,所以将其转移到此处) + queueName string +} + +// AssembleParamMap 组装请求参数字典 +// 返回值 +// map[string]interface{}:请求参数字 +func (this *CommonRequest) AssembleParamMap() map[string]string { + result := make(map[string]string) + + // 组装参数 + result["Action"] = this.Action + result["Region"] = this.Region + result["Timestamp"] = fmt.Sprintf("%d", this.Timestamp) + result["Nonce"] = fmt.Sprintf("%d", this.Nonce) + result["SecretId"] = this.SecretId + result["SignatureMethod"] = this.SignatureMethod + result["queueName"] = this.queueName + + return result +} + +// NewCommonRequest 新建公共请求参数对象 +// 参数 +// action:指令接口名称 +// region:地域 +// secretId:在云API密钥上申请的标识身份的SecretId +// queueName:队列名称 +// 返回值 +// *CommonRequest:公共请求参数对象 +func NewCommonRequest(action, region, secretId, queueName string) *CommonRequest { + return &CommonRequest{ + Action: action, + Region: region, + Timestamp: uint64(time.Now().Unix()), + Nonce: mathUtil.GetRand().Uint32(), + SecretId: secretId, + SignatureMethod: "HmacSHA256", + queueName: queueName, + } +} diff --git a/trunk/framework/mqMgr/model/common_response.go b/trunk/framework/mqMgr/model/common_response.go new file mode 100644 index 0000000..6f91617 --- /dev/null +++ b/trunk/framework/mqMgr/model/common_response.go @@ -0,0 +1,28 @@ +package model + +// CommonResponse 公共请求结果对象 +type CommonResponse struct { + // 错误码 + Code ResultStatus `json:"code"` + + // 错误提示信息 + Message string `json:"message"` + + // 服务器生成的请求Id + RequestId string `json:"requestId"` +} + +// IsSuccess 请求结果是否成功 +func (this *CommonResponse) IsSuccess() bool { + return this.Code == Success +} + +// IsFailure 请求结果是否失败 +func (this *CommonResponse) IsFailure() bool { + return this.Code != Success +} + +// HasMessage 请求结果是否有信息 +func (this *CommonResponse) HaveNoMessage() bool { + return this.Code == NoMessage +} diff --git a/trunk/framework/mqMgr/model/errorInfo.go b/trunk/framework/mqMgr/model/errorInfo.go new file mode 100644 index 0000000..d868a9d --- /dev/null +++ b/trunk/framework/mqMgr/model/errorInfo.go @@ -0,0 +1,13 @@ +package model + +// 错误信息对象 +type ErrorInfo struct { + // 错误码 + Code int `json:"code"` + + // 错误提示信息 + Message string `json:"message"` + + // 对应删除失败的消息句柄 + ReceiptHandle string `json:"receiptHandle"` +} diff --git a/trunk/framework/mqMgr/model/irequest.go b/trunk/framework/mqMgr/model/irequest.go new file mode 100644 index 0000000..ec4a54d --- /dev/null +++ b/trunk/framework/mqMgr/model/irequest.go @@ -0,0 +1,13 @@ +package model + +// IRequest 请求对象接口 +type IRequest interface { + // GetActionName 获取方法名 + GetActionName() string + + // SetCommonRequestObject 设置公共请求对象 + SetCommonRequest(*CommonRequest) + + // AssembleParamMap 组装参数字典 + AssembleParamMap() map[string]string +} diff --git a/trunk/framework/mqMgr/model/messageInfo.go b/trunk/framework/mqMgr/model/messageInfo.go new file mode 100644 index 0000000..6a1665b --- /dev/null +++ b/trunk/framework/mqMgr/model/messageInfo.go @@ -0,0 +1,25 @@ +package model + +// 消息对象 +type MessageInfo struct { + // 本次消费的消息正文 + MsgBody string `json:"msgBody"` + + // 服务器生成消息的唯一标识Id + MsgId string `json:"msgId"` + + // 每次消费返回唯一的消息句柄。用于删除该消息,仅上一次消费时产生的消息句柄能用于删除消息。 + ReceiptHandle string `json:"receiptHandle"` + + // 消费被生产出来,进入队列的时间 + EnqueueTime int64 `json:"enqueueTime"` + + // 第一次消费该消息的时间 + FirstDequeueTime int64 `json:"firstDequeueTime"` + + // 消息的下次可见(可再次被消费)时间 + NextVisibleTime int64 `json:"nextVisibleTime"` + + // 消息被消费的次数 + DequeueCount int64 `json:"dequeueCount"` +} diff --git a/trunk/framework/mqMgr/model/request_response_BatchDeleteMessage.go b/trunk/framework/mqMgr/model/request_response_BatchDeleteMessage.go new file mode 100644 index 0000000..b54cb3c --- /dev/null +++ b/trunk/framework/mqMgr/model/request_response_BatchDeleteMessage.go @@ -0,0 +1,55 @@ +package model + +import ( + "fmt" +) + +// 批量删除消息请求对象 +type BatchDeleteMessageRequest struct { + // 公共请求参数 + common *CommonRequest + + // 上次消费消息时返回的消息句柄列表 + receiptHandleList []string +} + +// 获取请求方法名 +func (this *BatchDeleteMessageRequest) GetActionName() string { + return "BatchDeleteMessage" +} + +// SetCommonRequestObject 设置公共请求对象 +func (this *BatchDeleteMessageRequest) SetCommonRequest(commonRequest *CommonRequest) { + this.common = commonRequest +} + +// AssembleParamMap 组装参数字典 +// 返回值 +// map[string]string:请求参数字典 +func (this *BatchDeleteMessageRequest) AssembleParamMap() map[string]string { + paramMap := this.common.AssembleParamMap() + for index, msg := range this.receiptHandleList { + key := fmt.Sprintf("receiptHandle.%d", index) + paramMap[key] = msg + } + + return paramMap +} + +func NewBatchDeleteMessageRequest(receiptHandleList []string) *BatchDeleteMessageRequest { + return &BatchDeleteMessageRequest{ + receiptHandleList: receiptHandleList, + } +} + +// 批量删除消息请求返回结果对象 +type BatchDeleteMessageResponse struct { + *CommonResponse + + // 无法成功删除的错误列表 + ErrorList []*ErrorInfo `json:"errorList"` +} + +func NewBatchDeleteMessageResponse() *BatchDeleteMessageResponse { + return &BatchDeleteMessageResponse{} +} diff --git a/trunk/framework/mqMgr/model/request_response_BatchReceiveMessage.go b/trunk/framework/mqMgr/model/request_response_BatchReceiveMessage.go new file mode 100644 index 0000000..bff2a56 --- /dev/null +++ b/trunk/framework/mqMgr/model/request_response_BatchReceiveMessage.go @@ -0,0 +1,57 @@ +package model + +import ( + "fmt" +) + +// 批量消费消息请求对象 +type BatchReceiveMessageRequest struct { + // 公共请求参数 + common *CommonRequest + + // 本次消费的消息数量。取值范围 1-16。(必须) + numOfMsg int + + // 本次请求的长轮询等待时间(非必须) + pollingWaitSeconds int +} + +// 获取请求方法名 +func (this *BatchReceiveMessageRequest) GetActionName() string { + return "BatchReceiveMessage" +} + +// SetCommonRequest 设置公共请求对象 +func (this *BatchReceiveMessageRequest) SetCommonRequest(commonRequest *CommonRequest) { + this.common = commonRequest +} + +// AssembleParamMap 组装参数字典 +// 返回值 +// map[string]string:请求参数字典 +func (this *BatchReceiveMessageRequest) AssembleParamMap() map[string]string { + paramMap := this.common.AssembleParamMap() + paramMap["numOfMsg"] = fmt.Sprintf("%d", this.numOfMsg) + paramMap["pollingWaitSeconds"] = fmt.Sprintf("%d", this.pollingWaitSeconds) + + return paramMap +} + +func NewBatchReceiveMessageRequest(numOfMsg, pollingWaitSeconds int) *BatchReceiveMessageRequest { + return &BatchReceiveMessageRequest{ + numOfMsg: numOfMsg, + pollingWaitSeconds: pollingWaitSeconds, + } +} + +// 批量消费消息请求返回结果对象 +type BatchReceiveMessageResponse struct { + *CommonResponse + + // message信息列表 + MsgInfoList []*MessageInfo `json:"msgInfoList"` +} + +func NewBatchReceiveMessageResponse() *BatchReceiveMessageResponse { + return &BatchReceiveMessageResponse{} +} diff --git a/trunk/framework/mqMgr/model/request_response_BatchSendMessage.go b/trunk/framework/mqMgr/model/request_response_BatchSendMessage.go new file mode 100644 index 0000000..da070d8 --- /dev/null +++ b/trunk/framework/mqMgr/model/request_response_BatchSendMessage.go @@ -0,0 +1,60 @@ +package model + +import ( + "fmt" +) + +// 批量发送消息请求对象 +type BatchSendMessageRequest struct { + // 公共请求参数 + common *CommonRequest + + // 消息正文列表 + msgBodyList []string + + // 单位为秒,表示该消息发送到队列后,需要延时多久用户才可见该消息。 + delaySeconds int +} + +// 获取请求方法名 +func (this *BatchSendMessageRequest) GetActionName() string { + return "BatchSendMessage" +} + +// SetCommonRequest 设置公共请求对象 +func (this *BatchSendMessageRequest) SetCommonRequest(commonRequest *CommonRequest) { + this.common = commonRequest +} + +// AssembleParamMap 组装参数字典 +// 返回值 +// map[string]string:请求参数字典 +func (this *BatchSendMessageRequest) AssembleParamMap() map[string]string { + paramMap := this.common.AssembleParamMap() + for index, msg := range this.msgBodyList { + key := fmt.Sprintf("msgBody.%d", index) + paramMap[key] = msg + } + paramMap["delaySeconds"] = fmt.Sprintf("%d", this.delaySeconds) + + return paramMap +} + +func NewBatchSendMessageRequest(msgBodyList []string, delaySeconds int) *BatchSendMessageRequest { + return &BatchSendMessageRequest{ + msgBodyList: msgBodyList, + delaySeconds: delaySeconds, + } +} + +// 批量发送消息请求返回结果对象 +type BatchSendMessageResponse struct { + *CommonResponse + + // message信息列表 + MsgInfoList []*MessageInfo `json:"msgInfoList"` +} + +func NewBatchSendMessageResponse() *BatchSendMessageResponse { + return &BatchSendMessageResponse{} +} diff --git a/trunk/framework/mqMgr/model/request_response_DeleteMessage.go b/trunk/framework/mqMgr/model/request_response_DeleteMessage.go new file mode 100644 index 0000000..ef0ff95 --- /dev/null +++ b/trunk/framework/mqMgr/model/request_response_DeleteMessage.go @@ -0,0 +1,46 @@ +package model + +// 删除消息请求对象l +type DeleteMessageRequest struct { + // 公共请求参数 + common *CommonRequest + + // 上次消费返回唯一的消息句柄,用于删除消息。(必须) + receiptHandle string +} + +// 获取请求方法名 +func (this *DeleteMessageRequest) GetActionName() string { + return "DeleteMessage" +} + +// SetCommonRequest 设置公共请求对象 +func (this *DeleteMessageRequest) SetCommonRequest(commonRequest *CommonRequest) { + this.common = commonRequest +} + +// AssembleParamMap 组装参数字典 +// 返回值 +// map[string]string:请求参数字典 +func (this *DeleteMessageRequest) AssembleParamMap() map[string]string { + paramMap := this.common.AssembleParamMap() + paramMap["receiptHandle"] = this.receiptHandle + + return paramMap +} + +func NewDeleteMessageRequest(receiptHandle string) *DeleteMessageRequest { + return &DeleteMessageRequest{ + receiptHandle: receiptHandle, + } +} + +// 删除消息请求返回结果对象 +type DeleteMessageResponse struct { + // 公共请求结果 + *CommonResponse +} + +func NewDeleteMessageResponse() *DeleteMessageResponse { + return &DeleteMessageResponse{} +} diff --git a/trunk/framework/mqMgr/model/request_response_GetQueueAttributes.go b/trunk/framework/mqMgr/model/request_response_GetQueueAttributes.go new file mode 100644 index 0000000..7fb2dac --- /dev/null +++ b/trunk/framework/mqMgr/model/request_response_GetQueueAttributes.go @@ -0,0 +1,58 @@ +package model + +// 消费消息请求对象 +type GetQueueAttributesRequest struct { + // 公共请求参数 + common *CommonRequest +} + +// 获取请求方法名 +func (this *GetQueueAttributesRequest) GetActionName() string { + return "GetQueueAttributes" +} + +// SetCommonRequestObject 设置公共请求对象 +func (this *GetQueueAttributesRequest) SetCommonRequest(commonRequest *CommonRequest) { + this.common = commonRequest +} + +// AssembleParamMap 组装参数字典 +// 返回值 +// map[string]string:请求参数字典 +func (this *GetQueueAttributesRequest) AssembleParamMap() map[string]string { + paramMap := this.common.AssembleParamMap() + + return paramMap +} + +func NewGetQueueAttributesRequest() *GetQueueAttributesRequest { + return &GetQueueAttributesRequest{} +} + +// 消费消息请求返回结果对象 +type GetQueueAttributesResponse struct { + *CommonResponse + + MaxMsgHeapNum int `json:"maxMsgHeapNum"` // 最大堆积消息数。取值范围在公测期间为 1,000,000 - 10,000,000,正式上线后范围可达到 1000,000-1000,000,000。默认取值在公测期间为 10,000,000,正式上线后为 100,000,000。 + PollingWaitSeconds int `json:"pollingWaitSeconds"` // 消息接收长轮询等待时间。取值范围0 - 30秒,默认值0。 + VisibilityTimeout int `json:"visibilityTimeout"` // 消息可见性超时。取值范围1 - 43200秒(即12小时内),默认值30。 + MaxMsgSize int `json:"maxMsgSize"` // 消息最大长度。取值范围1024 - 1048576 Byte(即1K - 1024K),默认值65536。 + MsgRetentionSeconds int `json:"msgRetentionSeconds"` // 消息保留周期。取值范围60-1296000秒(1min-15天),默认值345600秒(4 天)。 + CreateTime int `json:"createTime"` // 队列的创建时间。返回 Unix 时间戳,精确到秒。 + LastModifyTime int `json:"lastModifyTime"` // 最后一次修改队列属性的时间。返回 Unix 时间戳,精确到秒。 + ActiveMsgNum int `json:"activeMsgNum"` // 在队列中处于 Active 状态(不处于被消费状态)的消息总数,为近似值。 + InactiveMsgNum int `json:"inactiveMsgNum"` // 在队列中处于 Inactive 状态(正处于被消费状态)的消息总数,为近似值。 + RewindSeconds int `json:"rewindSeconds"` // 回溯队列的消息回溯时间最大值,取值范围0 - 43200秒,0表示不开启消息回溯。 + RewindmsgNum int `json:"rewindmsgNum"` // 已调用 DelMsg 接口删除,但还在回溯保留时间内的消息数量。 + MinMsgTime int `json:"minMsgTime"` // 消息最小未消费时间,单位为秒。 + QueueName string `json:"queueName"` // 消息队列名字。 + QueueId string `json:"queueId"` // 消息队列ID。 + CreateUin int64 `json:"createUin"` // 创建者Uin。 + Bps int `json:"Bps"` // 带宽限制。 + Qps int `json:"qps"` // 每秒钟生产消息条数的限制,消费消息的大小是该值的1.1倍。 + Tags []string `json:"tags"` // 关联的标签。 +} + +func NewGetQueueAttributesResponse() *GetQueueAttributesResponse { + return &GetQueueAttributesResponse{} +} diff --git a/trunk/framework/mqMgr/model/request_response_ReceiveMessage.go b/trunk/framework/mqMgr/model/request_response_ReceiveMessage.go new file mode 100644 index 0000000..3de36a4 --- /dev/null +++ b/trunk/framework/mqMgr/model/request_response_ReceiveMessage.go @@ -0,0 +1,52 @@ +package model + +import ( + "fmt" +) + +// 消费消息请求对象 +type ReceiveMessageRequest struct { + // 公共请求参数 + common *CommonRequest + + // 本次请求的长轮询等待时间。取值范围0 - 30秒,如果不设置该参数,则默认使用队列属性中的 pollingWaitSeconds 值。 + pollingWaitSeconds int +} + +// 获取请求方法名 +func (this *ReceiveMessageRequest) GetActionName() string { + return "ReceiveMessage" +} + +// SetCommonRequestObject 设置公共请求对象 +func (this *ReceiveMessageRequest) SetCommonRequest(commonRequest *CommonRequest) { + this.common = commonRequest +} + +// AssembleParamMap 组装参数字典 +// 返回值 +// map[string]string:请求参数字典 +func (this *ReceiveMessageRequest) AssembleParamMap() map[string]string { + paramMap := this.common.AssembleParamMap() + paramMap["pollingWaitSeconds"] = fmt.Sprintf("%d", this.pollingWaitSeconds) + + return paramMap +} + +func NewReceiveMessageRequest(pollingWaitSeconds int) *ReceiveMessageRequest { + return &ReceiveMessageRequest{ + pollingWaitSeconds: pollingWaitSeconds, + } +} + +// 消费消息请求返回结果对象 +type ReceiveMessageResponse struct { + *CommonResponse + + // 消息对象 + *MessageInfo +} + +func NewReceiveMessageResponse() *ReceiveMessageResponse { + return &ReceiveMessageResponse{} +} diff --git a/trunk/framework/mqMgr/model/request_response_SendMessage.go b/trunk/framework/mqMgr/model/request_response_SendMessage.go new file mode 100644 index 0000000..ecf8b95 --- /dev/null +++ b/trunk/framework/mqMgr/model/request_response_SendMessage.go @@ -0,0 +1,58 @@ +package model + +import ( + "fmt" +) + +// 发送消息请求对象 +type SendMessageRequest struct { + // 公共请求参数 + common *CommonRequest + + // 消息正文 + msgBody string + + // 单位为秒,表示该消息发送到队列后,需要延时多久用户才可见该消息。 + delaySeconds int +} + +// 获取请求方法名 +func (this *SendMessageRequest) GetActionName() string { + return "SendMessage" +} + +// SetCommonRequest 设置公共请求对象 +func (this *SendMessageRequest) SetCommonRequest(commonRequest *CommonRequest) { + this.common = commonRequest +} + +// AssembleParamMap 组装参数字典 +// 返回值 +// map[string]string:请求参数字典 +func (this *SendMessageRequest) AssembleParamMap() map[string]string { + paramMap := this.common.AssembleParamMap() + paramMap["msgBody"] = this.msgBody + paramMap["delaySeconds"] = fmt.Sprintf("%d", this.delaySeconds) + + return paramMap +} + +func NewSendMessageRequest(msgBody string, delaySeconds int) *SendMessageRequest { + return &SendMessageRequest{ + msgBody: msgBody, + delaySeconds: delaySeconds, + } +} + +// 发送消息请求返回结果对象 +type SendMessageResponse struct { + // 公共请求结果 + *CommonResponse + + // 服务器生成消息的唯一标识Id + MsgId string `json:"msgId"` +} + +func NewSendMessageResponse() *SendMessageResponse { + return &SendMessageResponse{} +} diff --git a/trunk/framework/mqMgr/model/resultStatus.go b/trunk/framework/mqMgr/model/resultStatus.go new file mode 100644 index 0000000..3935078 --- /dev/null +++ b/trunk/framework/mqMgr/model/resultStatus.go @@ -0,0 +1,9 @@ +package model + +// 返回结果状态 +type ResultStatus int + +const ( + Success = 0 + NoMessage = 7000 +) diff --git a/trunk/framework/mqMgr/network.go b/trunk/framework/mqMgr/network.go new file mode 100644 index 0000000..9057425 --- /dev/null +++ b/trunk/framework/mqMgr/network.go @@ -0,0 +1,7 @@ +package mqMgr + +// 网络类型:内网、外网 +const ( + MQ_NETWORK_INTERNAL = "tencentyun" + MQ_NETWORK_PUBLIC = "tencenttdmq" //"qcloud" //todo:切换成tdmq之后,该值需要修改成:tencenttdmq +) diff --git a/trunk/framework/mqMgr/queue.go b/trunk/framework/mqMgr/queue.go new file mode 100644 index 0000000..7e6917e --- /dev/null +++ b/trunk/framework/mqMgr/queue.go @@ -0,0 +1,378 @@ +/* +我们使用腾讯云的CMQ作为公司的消息队列基础服务 +产品的说明文档:https://cloud.tencent.com/document/product/406 +*/ +package mqMgr + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + . "Framework/mqMgr/model" + "goutil/webUtil" +) + +const ( + EMPTY_MESSAGE_ERROR = "消息为空" + EMPTY_HANDLE_ERROR = "句柄为空" + MIN_DELAY_SECONDS = 0 + MIN_POLLING_WAIT_SECONDS = 0 + MAX_POLLING_WAIT_SECONDS = 30 + MAX_BATCH_COUNT = 16 + EMPTY_BATCH_LIST_ERROR = "批量消息为空" + EXCEED_MAX_BATCH_COUNT_ERROR = "批量消息数量超过上限,最多为16条" +) + +// Queue对象 +type Queue struct { + // 地域 + region string + + // 网络类型:内网、外网 + network string + + // 队列名称 + queueName string + + // API密钥Id + secretId string + + // API密钥key + secretKey string +} + +// 执行操作 +func (this *Queue) action(requestObj IRequest, responseObj interface{}) error { + requestObj.SetCommonRequest(NewCommonRequest(requestObj.GetActionName(), this.region, this.secretId, this.queueName)) + + // 组装请求url + paramMap := requestObj.AssembleParamMap() + url, signature, err := AssembleUrl(this.region, this.network, MQ_TYPE_QUEUE, this.secretKey, paramMap) + if err != nil { + return err + } + paramMap["Signature"] = signature + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 10) + + statusCode, result, err := webUtil.PostMapData(url, paramMap, header, transport) + //statusCode, result, err := webUtil.PostMapData(url, paramMap, header, nil) + if err != nil { + return err + } + if statusCode != 200 { + return fmt.Errorf("Wrong status from server:%d", statusCode) + } + + // 解析请求结果 + err = json.Unmarshal(result, responseObj) + + return err +} + +// 处理发送消息延迟的时间 +func (this *Queue) handleDelaySeconds(delaySeconds int) int { + if delaySeconds < MIN_DELAY_SECONDS { + delaySeconds = MIN_DELAY_SECONDS + } + + return delaySeconds +} + +// 处理当没有消息是轮询等待的时间 +func (this *Queue) handlePollingWaitSeconds(pollingWaitSeconds int) int { + if pollingWaitSeconds < MIN_POLLING_WAIT_SECONDS { + pollingWaitSeconds = MIN_POLLING_WAIT_SECONDS + } else if pollingWaitSeconds > MAX_POLLING_WAIT_SECONDS { + pollingWaitSeconds = MAX_POLLING_WAIT_SECONDS + } + + return pollingWaitSeconds +} + +// 验证批量处理的列表 +func (this *Queue) validBatchList(list []string) error { + if list == nil || len(list) == 0 { + return errors.New(EMPTY_BATCH_LIST_ERROR) + } + if len(list) > MAX_BATCH_COUNT { + return errors.New(EXCEED_MAX_BATCH_COUNT_ERROR) + } + + return nil +} + +// error:错误对象 +func (this *Queue) GetQueueAttributes() (err error) { + // 逻辑处理 + requestObj := NewGetQueueAttributesRequest() + responseObj := NewGetQueueAttributesResponse() + + // 发送请求 + err = this.action(requestObj, &responseObj) + if err != nil { + return + } + + if responseObj.IsFailure() { + err = errors.New(responseObj.Message) + return + } + + return +} + +// SendMessage 发送单条消息 +// 参数 +// message:消息内容 +// delaySeconds:单位为秒,表示该消息发送到队列后,需要延时多久用户才可见该消息。 +// 返回值 +// error:错误对象 +func (this *Queue) SendMessage(message string, delaySeconds int) (err error) { + // 参数验证和处理 + if message == "" { + err = errors.New(EMPTY_MESSAGE_ERROR) + return + } + delaySeconds = this.handleDelaySeconds(delaySeconds) + + // 逻辑处理 + requestObj := NewSendMessageRequest(message, delaySeconds) + responseObj := NewSendMessageResponse() + + // 发送请求 + err = this.action(requestObj, &responseObj) + if err != nil { + return + } + + if responseObj.IsFailure() { + err = errors.New(responseObj.Message) + return + } + + return +} + +// BatchSendMessage 批量发送消息 +// 参数 +// messageList:消息内容列表 +// delaySeconds:单位为秒,表示该消息发送到队列后,需要延时多久用户才可见该消息。 +// 返回值 +// error:错误对象 +func (this *Queue) BatchSendMessage(messageList []string, delaySeconds int) (err error) { + // 参数验证和处理 + err = this.validBatchList(messageList) + if err != nil { + return + } + delaySeconds = this.handleDelaySeconds(delaySeconds) + + // 逻辑处理 + requestObj := NewBatchSendMessageRequest(messageList, delaySeconds) + responseObj := NewBatchSendMessageResponse() + + // 发送请求 + err = this.action(requestObj, &responseObj) + if err != nil { + return + } + + if responseObj.IsFailure() { + err = errors.New(responseObj.Message) + return + } + + return +} + +// Receive 消费单条消息 +// pollingWaitSeconds:本次请求的长轮询等待时间。取值范围0 - 30秒,如果不设置该参数,则默认使用队列属性中的 pollingWaitSeconds 值。 +// 返回值 +// receiptHandle:消息句柄 +// message:消息内容 +// exist:是否存在数据 +// err:错误对象 +func (this *Queue) ReceiveMessage(pollingWaitSeconds int) (receiptHandle, message string, exist bool, err error) { + // 参数验证和处理 + pollingWaitSeconds = this.handlePollingWaitSeconds(pollingWaitSeconds) + + // 逻辑处理 + requestObj := NewReceiveMessageRequest(pollingWaitSeconds) + responseObj := NewReceiveMessageResponse() + + // 发送请求 + err = this.action(requestObj, &responseObj) + if err != nil { + return + } + + // 忽略掉没有消息的错误 + if responseObj.HaveNoMessage() { + return + } + + if responseObj.IsFailure() { + err = errors.New(responseObj.Message) + return + } + + receiptHandle = responseObj.ReceiptHandle + message = responseObj.MsgBody + exist = true + + return +} + +// BatchReceiveMessage 批量消费消息 +// 参数 +// numOfMsg:本次消费的消息数量 +// pollingWaitSeconds:本次请求的长轮询等待时间。取值范围0 - 30秒,如果不设置该参数,则默认使用队列属性中的 pollingWaitSeconds 值。 +// 返回值 +// receiptHandleList:消息句柄列表 +// messageList:消息内容列表 +// exist:是否存在数据 +// err:错误对象 +func (this *Queue) BatchReceiveMessage(numOfMsg, pollingWaitSeconds int) (receiptHandleList, messageList []string, exist bool, err error) { + // 参数验证和处理 + if numOfMsg > MAX_BATCH_COUNT { + err = errors.New(EXCEED_MAX_BATCH_COUNT_ERROR) + return + } + pollingWaitSeconds = this.handlePollingWaitSeconds(pollingWaitSeconds) + + // 逻辑处理 + requestObj := NewBatchReceiveMessageRequest(numOfMsg, pollingWaitSeconds) + responseObj := NewBatchReceiveMessageResponse() + + // 发送请求 + err = this.action(requestObj, &responseObj) + if err != nil { + return + } + + // 忽略掉没有消息的错误 + if responseObj.HaveNoMessage() { + return + } + + if responseObj.IsFailure() { + err = errors.New(responseObj.Message) + return + } + + // 组装返回 + receiptHandleList = make([]string, 0, numOfMsg) + messageList = make([]string, 0, numOfMsg) + for _, msgInfo := range responseObj.MsgInfoList { + receiptHandleList = append(receiptHandleList, msgInfo.ReceiptHandle) + messageList = append(messageList, msgInfo.MsgBody) + } + exist = true + + return +} + +// DeleteMessage 删除单条消息 +// 参数 +// receiptHandle:消息句柄 +// 返回值 +// error:错误对象 +func (this *Queue) DeleteMessage(receiptHandle string) (err error) { + // 参数验证和处理 + if receiptHandle == "" { + err = errors.New(EMPTY_HANDLE_ERROR) + return + } + + // 逻辑处理 + requestObj := NewDeleteMessageRequest(receiptHandle) + responseObj := NewDeleteMessageResponse() + + // 发送请求 + err = this.action(requestObj, &responseObj) + if err != nil { + return err + } + + if responseObj.IsFailure() { + err = errors.New(responseObj.Message) + return + } + + return +} + +// BatchDeleteMessage 批量删除消息 +// 参数 +// receiptHandleList:消息句柄列表 +// 返回值 +// errorMap:删除错误的字典(key:删除失败的消息句柄;value:删除失败的原因) +// err:错误对象 +func (this *Queue) BatchDeleteMessage(receiptHandleList []string) (errorMap map[string]string, err error) { + // 参数验证和处理 + err = this.validBatchList(receiptHandleList) + if err != nil { + return + } + + // 逻辑处理 + requestObj := NewBatchDeleteMessageRequest(receiptHandleList) + responseObj := NewBatchDeleteMessageResponse() + + // 发送请求 + err = this.action(requestObj, &responseObj) + if err != nil { + return + } + + if responseObj.IsFailure() { + err = errors.New(responseObj.Message) + // 组装返回 + errorMap = make(map[string]string) + for _, errInfo := range responseObj.ErrorList { + errorMap[errInfo.ReceiptHandle] = errInfo.Message + } + return + } + + return +} + +// 创建新的Queue对象 +func NewQueue(region, queueName, secretId, secretKey string) *Queue { + queueConfigObj := &QueueConfig{ + Region: region, + QueueName: queueName, + SecretId: secretId, + SecretKey: secretKey, + } + + return NewQueueByConfig(queueConfigObj) +} + +// 通过队列配置对象创建新的Queue对象 +func NewQueueByConfig(queueConfigObj *QueueConfig) *Queue { + queueObj := &Queue{ + region: queueConfigObj.Region, + network: MQ_NETWORK_INTERNAL, + queueName: queueConfigObj.QueueName, + secretId: queueConfigObj.SecretId, + secretKey: queueConfigObj.SecretKey, + } + + err := queueObj.GetQueueAttributes() + if err != nil { + if strings.Contains(err.Error(), MQ_NETWORK_INTERNAL) { + queueObj.network = MQ_NETWORK_PUBLIC + } + } + + return queueObj +} diff --git a/trunk/framework/mqMgr/queueHSY.go b/trunk/framework/mqMgr/queueHSY.go new file mode 100644 index 0000000..7f68c50 --- /dev/null +++ b/trunk/framework/mqMgr/queueHSY.go @@ -0,0 +1,183 @@ +package mqMgr + +import ( + "context" + "errors" + "fmt" + "time" + + amqp "github.com/rabbitmq/amqp091-go" +) + +// Queue对象 +type HSYQueue struct { + conn *amqp.Connection + channel *amqp.Channel + // 队列名称 + queueName string + // 交换机 + exchange string + // routing Key + routingKey string + //MQ链接字符串 + mqurl string +} + +// 消息队列配置对象 +type HSYQueueConfig struct { + QueueName string + Exchange string + RoutingKey string + Network string + Port int + UserName string + Password string +} + +// 创建新的Queue对象 +func NewHSYQueue(queueName, exchange, routingKey, userName, password string, network string, port int) *HSYQueue { + queueConfigObj := &HSYQueueConfig{ + QueueName: queueName, + Exchange: exchange, + RoutingKey: routingKey, + UserName: userName, + Password: password, + Network: network, + Port: port, + } + + return NewHSYQueueByConfig(queueConfigObj) +} + +// 通过队列配置对象创建新的Queue对象 +func NewHSYQueueByConfig(queueConfigObj *HSYQueueConfig) *HSYQueue { + queueObj := &HSYQueue{ + queueName: queueConfigObj.QueueName, + exchange: queueConfigObj.Exchange, + routingKey: queueConfigObj.RoutingKey, + mqurl: fmt.Sprintf("amqp://%s:%s@%s:%d/", queueConfigObj.UserName, queueConfigObj.Password, queueConfigObj.Network, queueConfigObj.Port), + } + + if err := queueObj.GetQueueAttributes(); err != nil { + return nil + } + + return queueObj +} + +// 连接RabbitMQ服务器 +func (this *HSYQueue) GetQueueAttributes() (err error) { + this.conn, err = amqp.Dial(this.mqurl) + if err != nil { + return + } + + this.channel, err = this.conn.Channel() + if err != nil { + return + } + + return +} + +// 释放连接 +func (this *HSYQueue) ReleaseRes() { + this.conn.Close() + this.channel.Close() +} + +// SendMessage 发送单条消息 +func (this *HSYQueue) SendMessage(message string, ex string) (err error) { + // 声明队列 + _, err = this.channel.QueueDeclare( + this.queueName, // 队列名 + true, // 是否持久化 + false, // 是否自动删除(前提是至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。注意:生产者客户端创建这个队列,或者没有消费者客户端与这个队列连接时,都不会自动删除这个队列) + false, // 是否为排他队列(排他的队列仅对“首次”声明的conn可见[一个conn中的其他channel也能访问该队列],conn结束后队列删除) + false, // 是否阻塞 + nil, // 额外属性 + ) + if err != nil { + err = errors.New("声明队列失败") + return + } + + // 声明交换器 + err = this.channel.ExchangeDeclare( + this.exchange, //交换器名 + ex, //exchange type:一般用fanout、direct、topic + true, // 是否持久化 + false, //是否自动删除(自动删除的前提是至少有一个队列或者交换器与这和交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑) + false, //设置是否内置的。true表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式 + false, // 是否阻塞 + nil, // 额外属性 + ) + if err != nil { + err = errors.New("声明交换器失败") + return + } + + // Binding + err = this.channel.QueueBind( + this.queueName, // 绑定的队列名称 + this.routingKey, // bindkey 用于消息路由分发的key + this.exchange, // 绑定的exchange名 + false, // 是否阻塞 + nil, // 额外属性 + ) + if err != nil { + err = errors.New("绑定队列和交换器失败") + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err = this.channel.PublishWithContext(ctx, + this.exchange, + this.routingKey, + false, + false, + amqp.Publishing{ + ContentType: "text/plain", + Body: []byte(message), + }) + if err != nil { + err = errors.New("发布消息失败") + return + } + + return +} + +// ReceiveMessage 消费单条消息 +func (this *HSYQueue) ReceiveMessage() (msgs <-chan amqp.Delivery, err error) { + _, err = this.channel.QueueDeclare( + this.queueName, // 队列名 + true, // 是否持久化 + false, // 是否自动删除(前提是至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。注意:生产者客户端创建这个队列,或者没有消费者客户端与这个队列连接时,都不会自动删除这个队列) + false, // 是否为排他队列(排他的队列仅对“首次”声明的conn可见[一个conn中的其他channel也能访问该队列],conn结束后队列删除) + false, // 是否阻塞 + nil, // 额外属性(我还不会用) + ) + if err != nil { + err = errors.New("声明队列失败") + return + } + + msgs, err = this.channel.Consume( + this.queueName, // 队列名 + "", // 消费者名,用来区分多个消费者,以实现公平分发或均等分发策略 + true, // 是否自动应答 + false, // 是否排他 + false, // 是否接收只同一个连接中的消息,若为true,则只能接收别的conn中发送的消息 + true, // 队列消费是否阻塞 + nil, // 额外属性 + ) + if err != nil { + err = errors.New("获取消息失败") + return + } + + return +} diff --git a/trunk/framework/mqMgr/queueHSY_test.go b/trunk/framework/mqMgr/queueHSY_test.go new file mode 100644 index 0000000..dd558cd --- /dev/null +++ b/trunk/framework/mqMgr/queueHSY_test.go @@ -0,0 +1,44 @@ +package mqMgr + +import ( + "fmt" + "testing" + "time" +) + +var ( + queueHSYObj = NewHSYQueue("q-1", "ex-1", "rkey-1", "guest", "guest", "127.0.0.1", 5672) +) + +func TestHSYSendMessage(t *testing.T) { + message := "这是测试内容. Test" + + if queueHSYObj == nil { + fmt.Println("初始化失败") + return + } + defer queueHSYObj.ReleaseRes() + + err := queueHSYObj.SendMessage(message, "direct") + if err != nil { + t.Errorf("There should be no error, but now there is: %s", err) + return + } + + time.Sleep(time.Second) + + go func() { + msgs, err := queueHSYObj.ReceiveMessage() + if err != nil { + t.Errorf("There should be no error, but now there is: %s", err) + return + } + + for msg := range msgs { + fmt.Println("收到消息", string(msg.Body)) + } + + }() + + time.Sleep(time.Second) +} diff --git a/trunk/framework/mqMgr/queue_test.go b/trunk/framework/mqMgr/queue_test.go new file mode 100644 index 0000000..3f7ccc9 --- /dev/null +++ b/trunk/framework/mqMgr/queue_test.go @@ -0,0 +1,179 @@ +package mqMgr + +import ( + "fmt" + "testing" +) + +var ( + queueObj = NewQueue(MQ_REGION_GUANGZHOU, "AD-Click", "AKIDjBUWGmIkDPJHCqRb2DCQM2RbUUL1MWMx", "RcldV6PRCBwUkjGVdVjiPq0IJ2VhEZtO") +) + +func TestHandleDelaySeconds(t *testing.T) { + delaySeconds := -1 + expected := 0 + delaySeconds = queueObj.handleDelaySeconds(delaySeconds) + if delaySeconds != expected { + t.Errorf("Expected %d, but now got %d", expected, delaySeconds) + } + + delaySeconds = 10 + expected = 10 + delaySeconds = queueObj.handleDelaySeconds(delaySeconds) + if delaySeconds != expected { + t.Errorf("Expected %d, but now got %d", expected, delaySeconds) + } +} + +func TestHandlePollingWaitSeconds(t *testing.T) { + pollingWaitSeconds := -1 + expected := 0 + pollingWaitSeconds = queueObj.handlePollingWaitSeconds(pollingWaitSeconds) + if pollingWaitSeconds != expected { + t.Errorf("Expected %d, but now got %d", expected, pollingWaitSeconds) + } + + pollingWaitSeconds = 100 + expected = 30 + pollingWaitSeconds = queueObj.handlePollingWaitSeconds(pollingWaitSeconds) + if pollingWaitSeconds != expected { + t.Errorf("Expected %d, but now got %d", expected, pollingWaitSeconds) + } + + pollingWaitSeconds = 15 + expected = 15 + pollingWaitSeconds = queueObj.handlePollingWaitSeconds(pollingWaitSeconds) + if pollingWaitSeconds != expected { + t.Errorf("Expected %d, but now got %d", expected, pollingWaitSeconds) + } +} + +func TestValidBatchList(t *testing.T) { + var list []string + expected := EMPTY_BATCH_LIST_ERROR + err := queueObj.validBatchList(list) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + } + if err.Error() != expected { + t.Errorf("Expected %s, but now got %s", EMPTY_BATCH_LIST_ERROR, err.Error()) + } + + list = make([]string, 0, 16) + expected = EMPTY_BATCH_LIST_ERROR + err = queueObj.validBatchList(list) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + } + if err.Error() != expected { + t.Errorf("Expected %s, but now got %s", EMPTY_BATCH_LIST_ERROR, err.Error()) + } + + for i := 0; i <= 2*MAX_BATCH_COUNT; i++ { + list = append(list, fmt.Sprintf("Test.%d", i)) + } + + expected = EXCEED_MAX_BATCH_COUNT_ERROR + err = queueObj.validBatchList(list) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + } + if err.Error() != expected { + t.Errorf("Expected %s, but now got %s", EMPTY_BATCH_LIST_ERROR, err.Error()) + } +} + +func TestSendMessage(t *testing.T) { + message := "这是测试内容. Test" + + // SendMessage + err := queueObj.SendMessage(message, 0) + if err != nil { + t.Errorf("There should be no error, but now there is: %s", err) + return + } + + // ReceiveMessage + receiptHandle, actualMessage, exist, err := queueObj.ReceiveMessage(3) + if err != nil { + t.Errorf("There should be no error, but now there is: %s", err) + return + } + if exist == false { + t.Errorf("There should be one message, but now it's empty.") + return + } + if message != actualMessage { + t.Errorf("Expected %s, but got %s", message, actualMessage) + return + } + + // DeleteMessage + err = queueObj.DeleteMessage(receiptHandle) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + // Verify + _, _, exist, err = queueObj.ReceiveMessage(3) + if err != nil { + t.Errorf("There should be no error, but now there is: %s", err) + return + } + if exist { + t.Errorf("There should be no message, but now there is.") + return + } +} + +func TestBatchSendMessage(t *testing.T) { + count := 5 + messageList := make([]string, 0, count) + for i := 0; i < count; i++ { + messageList = append(messageList, fmt.Sprintf("This is for test:%d", i+1)) + } + + // BatchSendMessage + err := queueObj.BatchSendMessage(messageList, 0) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + // BatchReceiveMessage + receiptHandleList, retMessageList, exist, err := queueObj.BatchReceiveMessage(count, 3) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + if !exist || messageList == nil || len(messageList) != count { + t.Errorf("There should be %d messages, but now there isn't.", count) + return + } + + for i := 0; i < count; i++ { + if messageList[i] != retMessageList[i] { + t.Errorf("Expected %s, but got %s", messageList[i], retMessageList[i]) + return + } + } + + // BatchDeleteMessage + _, err = queueObj.BatchDeleteMessage(receiptHandleList) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + // Verify + _, _, exist, err = queueObj.ReceiveMessage(3) + if err != nil { + t.Errorf("There should be no error, but now there is: %s", err) + return + } + if exist { + t.Errorf("There should be no message, but now there is.") + return + } +} diff --git a/trunk/framework/mqMgr/region.go b/trunk/framework/mqMgr/region.go new file mode 100644 index 0000000..a634f51 --- /dev/null +++ b/trunk/framework/mqMgr/region.go @@ -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" +) diff --git a/trunk/framework/mqMgr/type.go b/trunk/framework/mqMgr/type.go new file mode 100644 index 0000000..7c518a7 --- /dev/null +++ b/trunk/framework/mqMgr/type.go @@ -0,0 +1,7 @@ +package mqMgr + +// 消息队列类型:消息队列、消息主题 +const ( + MQ_TYPE_QUEUE = "queue" + MQ_TYPE_TOPIC = "topic" +) diff --git a/trunk/framework/mqMgr/url.go b/trunk/framework/mqMgr/url.go new file mode 100644 index 0000000..e274a21 --- /dev/null +++ b/trunk/framework/mqMgr/url.go @@ -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 +} diff --git a/trunk/framework/notify-util/mgr.go b/trunk/framework/notify-util/mgr.go new file mode 100644 index 0000000..616ce39 --- /dev/null +++ b/trunk/framework/notify-util/mgr.go @@ -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 +} diff --git a/trunk/framework/notify-util/notity.go b/trunk/framework/notify-util/notity.go new file mode 100644 index 0000000..afc3ba7 --- /dev/null +++ b/trunk/framework/notify-util/notity.go @@ -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 ¬ifyCenter{ + 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 +} diff --git a/trunk/framework/reloadMgr/doc.go b/trunk/framework/reloadMgr/doc.go new file mode 100644 index 0000000..cd7f37e --- /dev/null +++ b/trunk/framework/reloadMgr/doc.go @@ -0,0 +1,6 @@ +package reloadMgr + +// 重新加载包,提供重新加载的功能 +// 使用方法: +// 1、先调用RegisterReloadFunc方法,将重新加载时需要调用的方法进行注册。 +// 2、在需要重新加载时调用Reload()方法 diff --git a/trunk/framework/reloadMgr/reload.go b/trunk/framework/reloadMgr/reload.go new file mode 100644 index 0000000..0c2a186 --- /dev/null +++ b/trunk/framework/reloadMgr/reload.go @@ -0,0 +1,39 @@ +package reloadMgr + +import ( + "fmt" + + "goutil/logUtil" +) + +var ( + reloadFuncMap = make(map[string]func() error) +) + +// RegisterReloadFunc ...注册Reload方法 +// funcName:方法名称 +// reloadFunc:reload方法 +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 +} diff --git a/trunk/framework/sensitiveWordsMgr/sensitiveWordsUtil.go b/trunk/framework/sensitiveWordsMgr/sensitiveWordsUtil.go new file mode 100644 index 0000000..311256d --- /dev/null +++ b/trunk/framework/sensitiveWordsMgr/sensitiveWordsUtil.go @@ -0,0 +1,236 @@ +package sensitiveWordsMgr + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "Framework/goroutineMgr" + . "Framework/managecenterModel" + "goutil/dfaUtil" + "goutil/logUtil" + "goutil/mathUtil" + "goutil/webUtil" + "goutil/zlibUtil" +) + +// const GetForbidWordURL string = "http://10.253.0.186:10090/Query" +type ForbidWords struct { + //游戏ID + GameId int + //屏蔽字 + Words string +} + +// 请求屏蔽字库地址 +const GetForbidWordURL string = "http://forbidword.7qule.com/Query" + +var ( + mHashValue string + mDFAUtil *dfaUtil.DFAUtil + mGameId int + rand *mathUtil.Rand + mGameOnly bool = false +) + +// 获取屏蔽字 +func refreshSensitiveWord() error { + //定义参数 + requestParamMap := make(map[string]string, 0) + requestParamMap["GameId"] = strconv.Itoa(mGameId) + requestParamMap["HashValue"] = mHashValue + requestParamMap["DataType"] = "1" + requestParamMap["IsResultCompressed"] = "true" + if mGameOnly { + requestParamMap["GameOnly"] = "true" + } + //data, _ := json.Marshal(requestParamMap) + + //请求url,请求头 + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 1000) + + statusCode, returnBytesTemp, err := webUtil.PostMapData(GetForbidWordURL, requestParamMap, header, transport) + //statusCode, returnBytes, err := webUtil.PostMapData(GetForbidWordURL, requestParamMap, header, nil) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取敏感字出错,url:%s,错误信息为:%s", GetForbidWordURL, err)) + return err + } + if statusCode != 200 { + logUtil.ErrorLog(fmt.Sprintf("获取敏感字出错,url:%s,错误码为:%d", GetForbidWordURL, statusCode)) + return err + } + + //解压 + returnBytes, err := zlibUtil.Decompress(returnBytesTemp) + if err != nil { + logUtil.ErrorLog("返回结果解压失败") + return err + } + + // 解析返回值 + returnObj := new(ReturnObject) + if err = json.Unmarshal(returnBytes, &returnObj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取敏感字反序列化返回值出错,错误信息为:%s, str:%s", err, string(returnBytes))) + return err + } + + // 判断返回状态是否为成功 + if returnObj.Code != 0 { + msg := fmt.Sprintf("获取敏感字出错,返回状态:%d,信息为:%s", returnObj.Code, returnObj.Message) + logUtil.ErrorLog(msg) + return errors.New(msg) + } else if returnObj.Code == 0 && len(returnObj.HashValue) == 0 { //表示没有更新 + return nil + } + + // 解析Data + var tmpSensitiveWordList []*ForbidWords + if data, ok := returnObj.Data.(string); !ok { + msg := "获取敏感字出错,返回的数据不是string类型" + logUtil.ErrorLog(msg) + return errors.New(msg) + } else { + if err = json.Unmarshal([]byte(data), &tmpSensitiveWordList); err != nil { + logUtil.ErrorLog(fmt.Sprintf("获取敏感字反序列化数据出错,错误信息为:%s", err)) + return err + } + } + + //缓存hasvalue + mHashValue = returnObj.HashValue + + //获取敏感字数组 + var temWordArray []string + for _, item := range tmpSensitiveWordList { + temWordArray = append(temWordArray, strings.ToUpper(item.Words)) + } + //dfa + mDFAUtil = dfaUtil.NewDFAUtil(temWordArray) + + return nil +} + +// 是否包含屏蔽字 +func IsSensitiveWords(input string) (exist bool) { + input = strings.ToUpper(input) + exist = mDFAUtil.IsMatch(input) + + return +} + +// 取出敏感字及开始位置 +func SensitiveWords(input string) (words []string, pos []int, exist bool) { + input2 := strings.ToUpper(input) + inputRune := []rune(input) + startIndexList, endIndexList := mDFAUtil.SearchSentence(input2) + if len(startIndexList) > 0 { + exist = true + words = make([]string, 0, len(startIndexList)) + pos = make([]int, 0, len(startIndexList)) + for i := 0; i < len(startIndexList); i++ { + start := startIndexList[i] + end := endIndexList[i] + words = append(words, string(inputRune[start:end+1])) + pos = append(pos, start) + } + } + + return +} + +// 取出敏感字及开始及结束位置 +func SensitiveWordsEndStartPos(input string) (words []string, starts, ends []int, exist bool) { + input2 := strings.ToUpper(input) + inputRune := []rune(input) + startIndexList, endIndexList := mDFAUtil.SearchSentence(input2) + if len(startIndexList) > 0 { + exist = true + words = make([]string, 0, len(startIndexList)) + starts = startIndexList + ends = endIndexList + for i := 0; i < len(startIndexList); i++ { + start := startIndexList[i] + end := endIndexList[i] + words = append(words, string(inputRune[start:end+1])) + //pos = append(pos, start) + } + } + + return +} + +// 根据用户输入的替换词替换敏感词 +func ReplaceSendsitiveWords(input, replaceStr string) (newStr string) { + words, _, _, exist := SensitiveWordsEndStartPos(input) + newStr = input + //如果不存在敏感词,则直接返回 + if !exist { + return + } + //循环替换 + for _, sendsitiveWord := range words { + newStr = strings.Replace(newStr, sendsitiveWord, replaceStr, -1) + } + + return +} + +// 判断服务器是否存在 +func IfServerExists() (exist bool) { + + return +} + +// 处理敏感字 +func HandleSendsitiveWords(input string) (newStr string) { + newStr = mDFAUtil.HandleWord(input, rune('*')) + + return +} + +// 定时刷新敏感字库 +func StartRefreshSensitiveWordListTread(gameId int) { + rand = mathUtil.GetRand() + mGameId = gameId + // 定时刷新数据 + go func() { + goroutineName := "sensitiveWordsMgr.StartRefreshSensitiveWordListTread" + goroutineMgr.Monitor(goroutineName) + defer goroutineMgr.ReleaseMonitor(goroutineName) + + for { + // 刷新屏蔽字 + refreshSensitiveWord() + + // 每5分钟刷新一次 + time.Sleep(time.Duration(rand.GetRandRangeInt64(120, 300)) * time.Second) + } + }() +} + +// 定时刷新敏感字库(排除公共字库,只刷新游戏内的) +func StartRefreshSensitiveListTreadExcludeComm(gameId int) { + rand = mathUtil.GetRand() + mGameId = gameId + mGameOnly = true + // 定时刷新数据 + go func() { + goroutineName := "sensitiveWordsMgr.StartRefreshSensitiveWordListTread" + goroutineMgr.Monitor(goroutineName) + defer goroutineMgr.ReleaseMonitor(goroutineName) + + for { + // 刷新屏蔽字 + refreshSensitiveWord() + + // 每5分钟刷新一次 + time.Sleep(time.Duration(rand.GetRandRangeInt64(120, 300)) * time.Second) + } + }() +} diff --git a/trunk/framework/sensitiveWordsMgr/sensitiveWords_test.go b/trunk/framework/sensitiveWordsMgr/sensitiveWords_test.go new file mode 100644 index 0000000..610a0e8 --- /dev/null +++ b/trunk/framework/sensitiveWordsMgr/sensitiveWords_test.go @@ -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") +} diff --git a/trunk/framework/shortUrlMgr/shortUrl.go b/trunk/framework/shortUrlMgr/shortUrl.go new file mode 100644 index 0000000..a6b18ae --- /dev/null +++ b/trunk/framework/shortUrlMgr/shortUrl.go @@ -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 +} diff --git a/trunk/framework/shortUrlMgr/shortUrl_test.go b/trunk/framework/shortUrlMgr/shortUrl_test.go new file mode 100644 index 0000000..7761db8 --- /dev/null +++ b/trunk/framework/shortUrlMgr/shortUrl_test.go @@ -0,0 +1,47 @@ +package shortUrlMgr + +import ( + "fmt" + "testing" +) + +func TestShortUrl(t *testing.T) { + // ShortUrl_SERVICE_URL = "http://a.app366.com/get" + + appId := "unittest" + appId_wrong := "wrong" + appSecret := "c5746980-5d52-4ba9-834f-13d0066ad1d0" + appSecret_wrong := "wrong" + fullUrl := "http://unittest.PlayerId=123&Name=Jordan" + fullUrl_wrong := "" + timeout := 3 + + // Test with wrong AppId + shortUrl, err := GetShortUrl(appId_wrong, appSecret, fullUrl, timeout) + if err == nil { + t.Errorf("There should be an error, but now there is no error") + return + } + + // Test with wrong AppSecret + shortUrl, err = GetShortUrl(appId, appSecret_wrong, fullUrl, timeout) + if err == nil { + t.Errorf("There should be an error, but now there is no error") + return + } + + // Test with wrong FullUrl + shortUrl, err = GetShortUrl(appId, appSecret, fullUrl_wrong, 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 + shortUrl, err = GetShortUrl(appId, appSecret, fullUrl, timeout) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + fmt.Printf("shortUrl:%v\n", shortUrl) +} diff --git a/trunk/framework/signalMgr/doc.go b/trunk/framework/signalMgr/doc.go new file mode 100644 index 0000000..585afa0 --- /dev/null +++ b/trunk/framework/signalMgr/doc.go @@ -0,0 +1,10 @@ +package signalMgr + +// 系统信号管理包 +// 提供对操作系统信号的管理,支持三种信号:syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP +// 其中syscall.SIGTERM, syscall.SIGINT表示程序终止信号,可以绑定一个方法在系统终止时进行调用 +// syscall.SIGHUP表示程序重启信号,可以绑定一个方法在系统重启时进行调用 + +// 使用方法: +// 调用func Start(reloadFunc func() []error, exitFunc func() error) +// 传入在重启和终止时需要调用的方法,如果不需要则传入nil diff --git a/trunk/framework/signalMgr/signal.go b/trunk/framework/signalMgr/signal.go new file mode 100644 index 0000000..5a4cf46 --- /dev/null +++ b/trunk/framework/signalMgr/signal.go @@ -0,0 +1,56 @@ +package signalMgr + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "Framework/exitMgr" + "Framework/goroutineMgr" + "Framework/reloadMgr" + "goutil/debugUtil" + "goutil/logUtil" +) + +// Start ...启动信号管理器 +func Start() { + go func() { + goroutineName := "signalMgr.Start" + goroutineMgr.Monitor(goroutineName) + defer goroutineMgr.ReleaseMonitor(goroutineName) + + sigs := make(chan os.Signal) + signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP) + + for { + // 准备接收信息 + sig := <-sigs + + // 输出信号 + debugUtil.Println("sig:", sig) + + if sig == syscall.SIGHUP { + logUtil.InfoLog("收到重启的信号,准备重新加载配置") + + // 重新加载 + errList := reloadMgr.Reload() + for _, err := range errList { + logUtil.ErrorLog(fmt.Sprintf("重启失败,错误信息为:%s", err)) + } + + logUtil.InfoLog("收到重启的信号,重新加载配置完成") + } else { + logUtil.InfoLog("收到退出程序的信号,开始退出……") + + // 调用退出的方法 + exitMgr.Exit() + + logUtil.InfoLog("收到退出程序的信号,退出完成……") + + // 一旦收到信号,则表明管理员希望退出程序,则先保存信息,然后退出 + os.Exit(0) + } + } + }() +} diff --git a/trunk/framework/sqlAsyncMgr/sqlAsyncIdentityStatistics.go b/trunk/framework/sqlAsyncMgr/sqlAsyncIdentityStatistics.go new file mode 100644 index 0000000..1030862 --- /dev/null +++ b/trunk/framework/sqlAsyncMgr/sqlAsyncIdentityStatistics.go @@ -0,0 +1,77 @@ +package sqlAsyncMgr + +import ( + "sync" + "sync/atomic" +) + +// SqlAsyncIdentityStatistics sql标识统计类 +type SqlAsyncIdentityStatistics struct { + // 锁对象 + m sync.Mutex + + // 当前正在同步的sql数量 + syncCount int32 + + // 待同步的统计 + statisticData map[string]*StatisticModel +} + +// newSqlAsyncIdentityStatistics 创建新StatisticModel对象 +func newSqlAsyncIdentityStatistics() *SqlAsyncIdentityStatistics { + return &SqlAsyncIdentityStatistics{ + statisticData: make(map[string]*StatisticModel), + } +} + +// getItem 获取指定统计对象 +func (this *SqlAsyncIdentityStatistics) getItem(_identityId string, isCreate bool) *StatisticModel { + this.m.Lock() + defer this.m.Unlock() + + if item, exists := this.statisticData[_identityId]; exists { + return item + } + + if isCreate == false { + return nil + } + + newItem := newStatisticModel(_identityId) + this.statisticData[_identityId] = newItem + + return newItem +} + +// AddCount 指定标识添加数量 +func (this *SqlAsyncIdentityStatistics) AddCount(_identityId string, count int32) { + model := this.getItem(_identityId, true) + model.AddCount(count) + + // 原子操作相加 + atomic.AddInt32(&this.syncCount, count) +} + +// Reduce 指定标识减少数量 +func (this *SqlAsyncIdentityStatistics) Reduce(_identityId string, count int32) { + if model := this.getItem(_identityId, false); model != nil { + model.ReduceCount(count) + } + + // 原子操作相减 + atomic.AddInt32(&this.syncCount, -count) +} + +// GetCount 获取指定标识的数量 +func (this *SqlAsyncIdentityStatistics) GetCount(_identityId string) int32 { + if model := this.getItem(_identityId, false); model != nil { + return model.Count + } + + return 0 +} + +// GetAllCount 获取总的待写入数据 +func (this *SqlAsyncIdentityStatistics) GetAllCount() int32 { + return this.syncCount +} diff --git a/trunk/framework/sqlAsyncMgr/sqlAsyncItemModel.go b/trunk/framework/sqlAsyncMgr/sqlAsyncItemModel.go new file mode 100644 index 0000000..d5f5cea --- /dev/null +++ b/trunk/framework/sqlAsyncMgr/sqlAsyncItemModel.go @@ -0,0 +1,22 @@ +package sqlAsyncMgr + +// SqlAsyncItemModel 异步sql模型 +type SqlAsyncItemModel struct { + // 表名 + TableName string + + // 唯一标识 + IdentityId string + + // 待执行的sql + Sql string +} + +// newSqlAsyncItemModel 构造异步sql模型 +func newSqlAsyncItemModel(tableName, identityId, sql string) *SqlAsyncItemModel { + return &SqlAsyncItemModel{ + TableName: tableName, + IdentityId: identityId, + Sql: sql, + } +} diff --git a/trunk/framework/sqlAsyncMgr/sqlAsyncListModel.go b/trunk/framework/sqlAsyncMgr/sqlAsyncListModel.go new file mode 100644 index 0000000..666c6e0 --- /dev/null +++ b/trunk/framework/sqlAsyncMgr/sqlAsyncListModel.go @@ -0,0 +1,75 @@ +package sqlAsyncMgr + +import ( + "sync" +) + +// SqlAsyncListModel 待同步sql列表模型对象 +type SqlAsyncListModel struct { + // 锁对象 + m sync.Mutex + + // 待同步的sql对象 + sqlData []*SqlAsyncItemModel +} + +// newSqlAsyncListModel 创建新SqlAsyncListModel对象 +func newSqlAsyncListModel() *SqlAsyncListModel { + return &SqlAsyncListModel{ + sqlData: make([]*SqlAsyncItemModel, 0, 4), + } +} + +// SqlCount 待同步的sql数量 +func (this *SqlAsyncListModel) SqlCount() int32 { + this.m.Lock() + defer this.m.Unlock() + + return int32(len(this.sqlData)) +} + +// Add 添加同步sql +func (this *SqlAsyncListModel) Add(newSqlModel *SqlAsyncItemModel) { + this.m.Lock() + defer this.m.Unlock() + + this.sqlData = append(this.sqlData, newSqlModel) +} + +// GetItem 获取同步对象 +func (this *SqlAsyncListModel) GetItem() (*SqlAsyncItemModel, bool) { + this.m.Lock() + defer this.m.Unlock() + + if len(this.sqlData) > 0 { + return this.sqlData[0], true + } + + return nil, false +} + +// RemoveFirst 移除首位元素 +func (this *SqlAsyncListModel) RemoveFirst() { + this.m.Lock() + defer this.m.Unlock() + + if len(this.sqlData) == 0 { + return + } + + this.sqlData = this.sqlData[1:] +} + +// GetAllSqlModel 获取所有的sql +func (this *SqlAsyncListModel) GetAllSqlModel() []*SqlAsyncItemModel { + this.m.Lock() + defer this.m.Unlock() + + if len(this.sqlData) == 0 { + return nil + } + + r := this.sqlData[0:] + + return r +} diff --git a/trunk/framework/sqlAsyncMgr/sqlAsyncUtil.go b/trunk/framework/sqlAsyncMgr/sqlAsyncUtil.go new file mode 100644 index 0000000..cc7a306 --- /dev/null +++ b/trunk/framework/sqlAsyncMgr/sqlAsyncUtil.go @@ -0,0 +1,243 @@ +package sqlAsyncMgr + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/mysql" + "goutil/logUtil" + "goutil/stringUtil" +) + +// sql异步util类 +type SqlAsyncUtil struct { + // 文件路径 + filePath string + + // 名称 + name string + + // 工作者最大数量 + max_Worker_Nums int32 + + // 工作池 + workerPool *SqlAsyncWorkerPool + + // 缓存统计 + sqlStatistics *SqlAsyncIdentityStatistics + + // 日志处理方法 + logAction func(logUtil.LogType, string) + + // db驱动 + db *gorm.DB +} + +// NewSqlAsyncUtil 创建新SqlAsyncUtil对象 +func NewSqlAsyncUtil(_name, _filePath string, maxWorker int32, dbConnectionStr string, _logAction func(logUtil.LogType, string)) *SqlAsyncUtil { + if stringUtil.IsEmpty(_name) || stringUtil.IsEmpty(_filePath) || stringUtil.IsEmpty(dbConnectionStr) { + panic(fmt.Sprintf("NewSqlAsyncUtil方法_name/_filePath/dbConnectionStr参数不能为空")) + } + if maxWorker <= 0 { + panic(fmt.Sprintf("NewSqlAsyncUtil方法maxWorker参数必须>0")) + } + if _logAction == nil { + panic(fmt.Sprintf("NewSqlAsyncUtil方法_logAction参数并能为nil")) + } + + result := &SqlAsyncUtil{ + name: _name, + filePath: _filePath, + max_Worker_Nums: maxWorker, + logAction: _logAction, + } + result.db = newGormDb(dbConnectionStr, maxWorker) + result.workerPool = newSqlAsyncWorkerPool(maxWorker, result.reduceStatistics, _logAction, func() string { return result.name }, result.db) + result.sqlStatistics = newSqlAsyncIdentityStatistics() + + return result +} + +// NewSqlAsyncUtil2 创建新SqlAsyncUtil对象 +func NewSqlAsyncUtil2(_name, _filePath string, maxWorker int32, db *gorm.DB, _logAction func(logUtil.LogType, string)) *SqlAsyncUtil { + if stringUtil.IsEmpty(_name) || stringUtil.IsEmpty(_filePath) || db == nil { + panic(fmt.Sprintf("NewSqlAsyncUtil方法_name/_filePath/db参数不能为空")) + } + if maxWorker <= 0 { + panic(fmt.Sprintf("NewSqlAsyncUtil方法maxWorker参数必须>0")) + } + if _logAction == nil { + panic(fmt.Sprintf("NewSqlAsyncUtil方法_logAction参数并能为nil")) + } + + result := &SqlAsyncUtil{ + name: _name, + filePath: _filePath, + max_Worker_Nums: maxWorker, + logAction: _logAction, + } + result.db = db + result.workerPool = newSqlAsyncWorkerPool(maxWorker, result.reduceStatistics, _logAction, func() string { return result.name }, result.db) + result.sqlStatistics = newSqlAsyncIdentityStatistics() + + return result +} + +// Start 启动 +func (this *SqlAsyncUtil) Start() { + // 启动工作线程 + this.workerPool.Start() + + // 读取待同步sql + waitSyncSqls := this.readWaitSql() + if waitSyncSqls == nil { + return + } + + // 将数据加入写入队列 + for _, item := range waitSyncSqls { + if item == nil { + continue + } + this.Write(item) + } +} + +// Stop 停止 +func (this *SqlAsyncUtil) Stop(save bool) { + saveList := this.workerPool.Stop() + if saveList == nil || len(saveList) == 0 { + return + } + + if save == false { + return + } + + this.waitSqlFlushFile(saveList) +} + +// WaitSqlSyncDone 等待剩余sql同步完成 +func (this *SqlAsyncUtil) WaitSqlSyncDone() { + for { + num := this.GetAllCount() + this.logAction(logUtil.Debug, fmt.Sprintf("SqlAsyncUtil(%s)当前剩余sql数量为:%v", this.name, num)) + + if num > 0 { + time.Sleep(time.Millisecond * 100) + continue + } + + // 删除文件 + this.delFile() + return + } +} + +// Write1 写入Sql +func (this *SqlAsyncUtil) Write1(tableName, identityId, sql string) { + newItem := newSqlAsyncItemModel(tableName, identityId, sql) + this.Write(newItem) +} + +// Write 写入Sql +func (this *SqlAsyncUtil) Write(item *SqlAsyncItemModel) { + worker, err := this.workerPool.GetWork(item.TableName) + if err != nil { + this.logAction(logUtil.Error, err.Error()) + return + } + + worker.Add(item) + this.sqlStatistics.AddCount(item.IdentityId, 1) +} + +// GetCount 获取指定标识的待执行sql +func (this *SqlAsyncUtil) GetCount(identityId string) int32 { + return this.sqlStatistics.GetCount(identityId) +} + +// GetAllCount 获取总的待执行sql +func (this *SqlAsyncUtil) GetAllCount() int32 { + return this.workerPool.GetWaitSyncCount() +} + +// ReduceStatistics 减少统计 +func (this *SqlAsyncUtil) reduceStatistics(item *SqlAsyncItemModel) { + this.sqlStatistics.Reduce(item.IdentityId, 1) +} + +// readWaitSql 读取待同步sql +func (this *SqlAsyncUtil) readWaitSql() []*SqlAsyncItemModel { + _, err := os.Stat(this.filePath) + if err != nil { + return nil + } + + var bytes []byte + bytes, err = ioutil.ReadFile(this.filePath) + if err != nil { + panic(fmt.Sprintf("readWaitSql错误 file:%s err:%s", this.filePath, err.Error())) + } + + result := make([]*SqlAsyncItemModel, 0) + err = json.Unmarshal(bytes, &result) + if err != nil { + panic(fmt.Sprintf("readWaitSql json反序列化错误 file:%s err:%s", this.filePath, err.Error())) + } + + return result +} + +// waitSqlFlushFile 待同步sql写入文件 +func (this *SqlAsyncUtil) waitSqlFlushFile(waitSqlList []*SqlAsyncItemModel) { + dir := filepath.Dir(this.filePath) + + // 创建文件夹 + _, err := os.Stat(dir) + if err != nil { + os.MkdirAll(dir, os.ModePerm) + } + + data, err := json.Marshal(waitSqlList) + if err != nil { + this.logAction(logUtil.Error, fmt.Sprintf("SqlAsyncUtil保存sql时,json出错,name:%s err:%s", this.name, err.Error())) + return + } + + err = ioutil.WriteFile(this.filePath, data, os.ModePerm) + if err != nil { + this.logAction(logUtil.Error, fmt.Sprintf("SqlAsyncUtil保存sql时,写入出错,name:%s err:%s", this.name, err.Error())) + } +} + +// delFile 删除文件 +func (this *SqlAsyncUtil) delFile() { + // 创建文件夹 + _, err := os.Stat(this.filePath) + if err == nil { + os.Remove(this.filePath) + } +} + +// @title newGormDb +// @description 构造新gorm.DB对象 +// @Param connectionString 数据库连接字符串 +// @Param maxOpenCount 最大打开连接数 +func newGormDb(connectionString string, maxOpenCount int32) *gorm.DB { + dbObj, err := gorm.Open("mysql", connectionString) + if err != nil { + panic(fmt.Sprintf("连接mysql出错 connectionString:%s err:%s", connectionString, err)) + } + + dbObj.DB().SetMaxOpenConns(int(maxOpenCount)) + dbObj.DB().SetMaxIdleConns(int(maxOpenCount)) + dbObj.DB().SetConnMaxLifetime(time.Minute * 4) + + return dbObj +} diff --git a/trunk/framework/sqlAsyncMgr/sqlAsyncWorker.go b/trunk/framework/sqlAsyncMgr/sqlAsyncWorker.go new file mode 100644 index 0000000..0845a4a --- /dev/null +++ b/trunk/framework/sqlAsyncMgr/sqlAsyncWorker.go @@ -0,0 +1,143 @@ +package sqlAsyncMgr + +import ( + "fmt" + "time" + + "framework/goroutineMgr" + "github.com/jinzhu/gorm" + "goutil/logUtil" +) + +const ( + // 同步多少次打印待同步sql数量 + LogSyncCount = 1000 +) + +// sql同步的worker +type SqlAsyncWorker struct { + // 工作者Id + Id int32 + + // 是否停止工作 + IsTop bool + + // 当前同步的条数 + CurSyncCount int32 + + // 待执行对象 + cachModel *SqlAsyncListModel + + // 刷新到时候的回调方法 + flushDbCallBack func(*SqlAsyncItemModel) + + // 日志处理方法 + logAction func(logUtil.LogType, string) + + // 获取同步标识 + getSyncName func() string + + // 数据库驱动 + db *gorm.DB + + tempCount int +} + +// newSqlAsyncWorker 创建新SqlAsyncWorker对象 +func newSqlAsyncWorker(_id int32, _flushCallBack func(*SqlAsyncItemModel), _logAction func(logUtil.LogType, string), _getSyncName func() string, _db *gorm.DB) *SqlAsyncWorker { + return &SqlAsyncWorker{ + Id: _id, + cachModel: newSqlAsyncListModel(), + flushDbCallBack: _flushCallBack, + logAction: _logAction, + getSyncName: _getSyncName, + db: _db, + } +} + +// Start 启动工作 +func (this *SqlAsyncWorker) Start() { + go this.handler() +} + +// Stop 停止工作 +func (this *SqlAsyncWorker) Stop() []*SqlAsyncItemModel { + this.IsTop = true + + return this.cachModel.GetAllSqlModel() +} + +// Add 添加执行的sql对象 +func (this *SqlAsyncWorker) Add(item *SqlAsyncItemModel) { + if this.IsTop { + return + } + + this.cachModel.Add(item) +} + +// WaitSqlCount 获取待同步的sql数量 +func (this *SqlAsyncWorker) WaitSqlCount() int32 { + return this.cachModel.SqlCount() +} + +// FlushDB 刷新到DB +func (this *SqlAsyncWorker) handler() { + // 处理goroutine数量 + goroutineName := fmt.Sprintf("%s-%v", this.getSyncName(), this.Id) + goroutineMgr.Monitor(goroutineName) + defer goroutineMgr.ReleaseMonitor(goroutineName) + + for { + if this.IsTop { + this.logAction(logUtil.Debug, fmt.Sprintf("sql异步线程停止工作 name=%s", goroutineName)) + return + } + + if this.flushItem() == false { + time.Sleep(time.Millisecond * 100) + } + } +} + +// flushItem 刷新到DB +func (this *SqlAsyncWorker) flushItem() bool { + defer func() { + if r := recover(); r != nil { + this.logAction(logUtil.Error, fmt.Sprintf("flushItem err:%s", r)) + } + }() + + // 获取同步对象 + item, exists := this.cachModel.GetItem() + if exists == false { + return false + } + + this.tempCount = this.tempCount + 1 + + // 写入数据库 + for i := 0; i < 3; i++ { + err := this.db.Exec(item.Sql).Error + if err == nil { + break + } + + this.logAction(logUtil.Error, fmt.Sprintf("写入数据库出现错误 sql:%s err:%s", item.Sql, err.Error())) + } + + // 移除缓存 + this.cachModel.RemoveFirst() + + // 减少统计 + this.flushDbCallBack(item) + + // 统计当前待同步数量 + this.CurSyncCount = this.CurSyncCount + 1 + if this.CurSyncCount%LogSyncCount == 0 { + this.logAction(logUtil.Debug, fmt.Sprintf("当前待同步的sql数量为:%v Name:%s id:%v", this.cachModel.SqlCount(), this.getSyncName(), this.Id)) + this.CurSyncCount = 0 + } + + return true +} diff --git a/trunk/framework/sqlAsyncMgr/sqlAsyncWorkerPool.go b/trunk/framework/sqlAsyncMgr/sqlAsyncWorkerPool.go new file mode 100644 index 0000000..e24aa88 --- /dev/null +++ b/trunk/framework/sqlAsyncMgr/sqlAsyncWorkerPool.go @@ -0,0 +1,72 @@ +package sqlAsyncMgr + +import ( + "fmt" + + "github.com/jinzhu/gorm" + "goutil/logUtil" + "goutil/stringUtil" +) + +type SqlAsyncWorkerPool struct { + // 工作者数量 + wcount int32 + + // 工作者列表 + workers []*SqlAsyncWorker +} + +// newSqlAsyncWorkerPool 构造同步池 +func newSqlAsyncWorkerPool(workerCount int32, _flushCallBack func(*SqlAsyncItemModel), _logAction func(logUtil.LogType, string), _getSyncName func() string, db *gorm.DB) *SqlAsyncWorkerPool { + result := &SqlAsyncWorkerPool{ + wcount: workerCount, + workers: make([]*SqlAsyncWorker, workerCount, workerCount), + } + + var i int32 + for i = 0; i < workerCount; i++ { + newWorker := newSqlAsyncWorker(i, _flushCallBack, _logAction, _getSyncName, db) + result.workers[i] = newWorker + } + + return result +} + +// Start 启动 +func (this *SqlAsyncWorkerPool) Start() { + for _, w := range this.workers { + w.Start() + } +} + +// Stop 停止工作 +func (this *SqlAsyncWorkerPool) Stop() []*SqlAsyncItemModel { + result := make([]*SqlAsyncItemModel, 0) + for _, w := range this.workers { + if tempList := w.Stop(); tempList != nil { + result = append(result, tempList...) + } + } + + return result +} + +// GetWork 获取指定work +func (this *SqlAsyncWorkerPool) GetWork(tableName string) (*SqlAsyncWorker, error) { + index := stringUtil.HashCode(tableName) % int(this.wcount) + if index < 0 || index >= int(this.wcount) { + return nil, fmt.Errorf("SqlAsyncWorkerPool中work数量为:%v,不存在id=%v的worker", this.wcount, index) + } + + return this.workers[index], nil +} + +// GetWaitSyncCount 获取待同步的sql数量 +func (this *SqlAsyncWorkerPool) GetWaitSyncCount() int32 { + var count int32 + for _, w := range this.workers { + count = count + w.WaitSqlCount() + } + + return count +} diff --git a/trunk/framework/sqlAsyncMgr/statisticModel.go b/trunk/framework/sqlAsyncMgr/statisticModel.go new file mode 100644 index 0000000..51cfd01 --- /dev/null +++ b/trunk/framework/sqlAsyncMgr/statisticModel.go @@ -0,0 +1,40 @@ +package sqlAsyncMgr + +import ( + "sync" +) + +// 标识内容模型对象 +type StatisticModel struct { + // 锁对象 + m sync.Mutex + + // 标识 + IdentityId string + + // 待同步的数量 + Count int32 +} + +// AddCount 添加指定数量 +func (this *StatisticModel) AddCount(addcount int32) { + this.m.Lock() + defer this.m.Unlock() + + this.Count = this.Count + addcount +} + +// ReduceCount 添加指定数量 +func (this *StatisticModel) ReduceCount(recount int32) { + this.m.Lock() + defer this.m.Unlock() + + this.Count = this.Count - recount +} + +// newStatisticModel 创建新StatisticModel对象 +func newStatisticModel(ident string) *StatisticModel { + return &StatisticModel{ + IdentityId: ident, + } +} diff --git a/trunk/framework/startMgr/applyMgr/apply.go b/trunk/framework/startMgr/applyMgr/apply.go new file mode 100644 index 0000000..8a663f5 --- /dev/null +++ b/trunk/framework/startMgr/applyMgr/apply.go @@ -0,0 +1,67 @@ +package applyMgr + +import ( + "fmt" + "sync" + + . "Framework/startMgr" +) + +var ( + funcMap = make(map[string]*FuncItem) + mutex sync.Mutex +) + +// 注册方法(如果名称重复会panic) +// name:方法名称(唯一标识) +// moduleType:模块类型 +// definition:方法定义 +func Register(name string, moduleType ModuleType, definition func() error) { + mutex.Lock() + defer mutex.Unlock() + + if _, exists := funcMap[name]; exists { + panic(fmt.Sprintf("%s已经存在,请重新命名", name)) + } + + funcMap[name] = NewFuncItem(name, moduleType, definition) +} + +// 调用所有方法 +// 返回值: +// 错误列表 +func CallAll() (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + for _, item := range funcMap { + // 调用方法 + if err := item.Call(); err != nil { + errList = append(errList, err) + } + } + + return +} + +// 按照模块类型进行调用 +// moduleType:模块类型 +// 返回值: +// errList:错误列表 +func CallType(moduleType ModuleType) (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + for _, item := range funcMap { + if item.ModuleType != moduleType { + continue + } + + // 调用方法 + if err := item.Call(); err != nil { + errList = append(errList, err) + } + } + + return +} diff --git a/trunk/framework/startMgr/checkMgr/check.go b/trunk/framework/startMgr/checkMgr/check.go new file mode 100644 index 0000000..42513e8 --- /dev/null +++ b/trunk/framework/startMgr/checkMgr/check.go @@ -0,0 +1,80 @@ +package checkMgr + +import ( + "fmt" + "sync" + "time" + + . "Framework/startMgr" + "goutil/logUtil" +) + +var ( + funcMap = make(map[string]*FuncItem) + mutex sync.Mutex + operateName = "Check" +) + +// 注册方法(如果名称重复会panic) +// name:方法名称(唯一标识) +// moduleType:模块类型 +// definition:方法定义 +func Register(name string, moduleType ModuleType, definition func() error) { + mutex.Lock() + defer mutex.Unlock() + + if _, exists := funcMap[name]; exists { + panic(fmt.Sprintf("%s已经存在,请重新命名", name)) + } + + funcMap[name] = NewFuncItem(name, moduleType, definition) +} + +// 调用所有方法 +// 返回值: +// 错误列表 +func CallAll() (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + startTime := time.Now() + defer func() { + logUtil.InfoLog("%s 执行总时间:%s", operateName, time.Since(startTime)) + }() + + for _, item := range funcMap { + // 调用方法 + if err := item.Call2(operateName); err != nil { + errList = append(errList, err) + } + } + + return +} + +// 按照模块类型进行调用 +// moduleType:模块类型 +// 返回值: +// errList:错误列表 +func CallType(moduleType ModuleType) (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + startTime := time.Now() + defer func() { + logUtil.InfoLog("%v %s 执行总时间:%s", moduleType, operateName, time.Since(startTime)) + }() + + for _, item := range funcMap { + if item.ModuleType != moduleType { + continue + } + + // 调用方法 + if err := item.Call2(operateName); err != nil { + errList = append(errList, err) + } + } + + return +} diff --git a/trunk/framework/startMgr/convertMgr/convert.go b/trunk/framework/startMgr/convertMgr/convert.go new file mode 100644 index 0000000..e445218 --- /dev/null +++ b/trunk/framework/startMgr/convertMgr/convert.go @@ -0,0 +1,75 @@ +package convertMgr + +import ( + "fmt" + "sync" + "time" + + . "Framework/startMgr" + "goutil/logUtil" +) + +var ( + funcMap = make(map[string]*FuncItem) + mutex sync.Mutex + operateName = "Convert" +) + +// 注册方法(如果名称重复会panic) +// name:方法名称(唯一标识) +// moduleType:模块类型 +// definition:方法定义 +func Register(name string, moduleType ModuleType, definition func() error) { + mutex.Lock() + defer mutex.Unlock() + + if _, exists := funcMap[name]; exists { + panic(fmt.Sprintf("%s已经存在,请重新命名", name)) + } + + funcMap[name] = NewFuncItem(name, moduleType, definition) +} + +// 调用所有方法 +// 返回值: +// 错误列表 +func CallAll() (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + for _, item := range funcMap { + // 调用方法 + if err := item.Call2(operateName); err != nil { + errList = append(errList, err) + } + } + + return +} + +// 按照模块类型进行调用 +// moduleType:模块类型 +// 返回值: +// errList:错误列表 +func CallType(moduleType ModuleType) (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + startTime := time.Now() + defer func() { + logUtil.InfoLog("%v %s 执行总时间:%s", moduleType, operateName, time.Since(startTime)) + }() + + for _, item := range funcMap { + if item.ModuleType != moduleType { + continue + } + + // 调用方法 + if err := item.Call2(operateName); err != nil { + errList = append(errList, err) + } + } + + return +} diff --git a/trunk/framework/startMgr/doc.go b/trunk/framework/startMgr/doc.go new file mode 100644 index 0000000..250ff1c --- /dev/null +++ b/trunk/framework/startMgr/doc.go @@ -0,0 +1,44 @@ +package startMgr + +/* +startMgr是用于管理应用程序启动的一个模块。 +应用程序启动,对配置的处理大体可以包含如下三个过程: +1、配置初始化:即将配置从配置文件、数据库中读取到内存中 +2、配置检验:即验证配置是否是合法、有效的 +3、配置转换:即将以A形式存在的配置转换为以B形式存在;如将以,分隔的字符串转换为[]int +4、配置打印:将配置进行打印 +5、配置赋值:将初始化的临时的数据赋值给正式的数据 + +三个过程分别对应五个子包: +1、配置初始化:initMgr +说明:根据名称来注册方法,可以根据名称来调用方法,以实现方法的提前调用。 +也可以无序地调用所有的方法 + +错误处理方式: +以panic的方式来对外暴露名称重复,名称不存在的错误 +以error的方式来返回方法执行过程中的错误 + +2、配置检验:checkMgr +说明:以无序地调用所有的方法 +错误处理方式: +以panic的方式来对外暴露名称重复,名称不存在的错误 +以error的方式来返回方法执行过程中的错误 + +3、配置转换:convertMgr +说明:以无序地调用所有的方法 +错误处理方式: +以panic的方式来对外暴露名称重复,名称不存在的错误 +以error的方式来返回方法执行过程中的错误 + +4、配置打印:printMgr +说明:以无序地调用所有的方法 +错误处理方式: +以panic的方式来对外暴露名称重复,名称不存在的错误 +以error的方式来返回方法执行过程中的错误 + +5、配置赋值:applyMgr +说明:以无序地调用所有的方法 +错误处理方式: +以panic的方式来对外暴露名称重复,名称不存在的错误 +以error的方式来返回方法执行过程中的错误 +*/ diff --git a/trunk/framework/startMgr/funcItem.go b/trunk/framework/startMgr/funcItem.go new file mode 100644 index 0000000..c487c7c --- /dev/null +++ b/trunk/framework/startMgr/funcItem.go @@ -0,0 +1,62 @@ +package startMgr + +import ( + "fmt" + "time" + + "goutil/logUtil" + "goutil/runtimeUtil" +) + +// 模块类型 +// 具体有哪些模块类型需要对应的程序自己定义 +type ModuleType int + +// 方法项定义 +type FuncItem struct { + // 方法名称 + Name string + + // 模块类型 + ModuleType ModuleType + + // 方法定义 + Definition func() error +} + +// 调用方法 +// 返回值: +// 错误对象 +func (this *FuncItem) Call() error { + err := this.Definition() + + return err +} + +// 调用方法 +// operateName:操作名称 +// 返回值: +// 错误对象 +func (this *FuncItem) Call2(operateName string) error { + startTime := time.Now() + defer func() { + logUtil.InfoLog(fmt.Sprintf("%s %s 执行耗时:%v 执行后内存总占用:%vMB", this.Name, operateName, time.Since(startTime).String(), runtimeUtil.GetMemSize()/1024.0/1024.0)) + }() + + logUtil.InfoLog(fmt.Sprintf("%v %s 开始执行", this.Name, operateName)) + err := this.Definition() + + return err +} + +// 新建函数项 +// name:模块名 +// moduleType:对应的模块类型 +// definition:目标处理函数 +func NewFuncItem(name string, moduleType ModuleType, definition func() error) *FuncItem { + return &FuncItem{ + Name: name, + ModuleType: moduleType, + Definition: definition, + } +} diff --git a/trunk/framework/startMgr/initMgr/init.go b/trunk/framework/startMgr/initMgr/init.go new file mode 100644 index 0000000..b61b64a --- /dev/null +++ b/trunk/framework/startMgr/initMgr/init.go @@ -0,0 +1,121 @@ +package initMgr + +import ( + "fmt" + "sync" + "time" + + . "Framework/startMgr" + "goutil/logUtil" +) + +var ( + funcMap = make(map[string]*FuncItem) + mutex sync.Mutex + operateName = "Init" +) + +// 注册方法(如果名称重复会panic) +// name:方法名称(唯一标识) +// moduleType:模块类型 +// definition:方法定义 +func Register(name string, moduleType ModuleType, definition func() error) { + mutex.Lock() + defer mutex.Unlock() + + if _, exists := funcMap[name]; exists { + panic(fmt.Sprintf("%s已经存在,请重新命名", name)) + } + + funcMap[name] = NewFuncItem(name, moduleType, definition) +} + +// 调用一个方法 +// name:方法名称(唯一标识) +// 返回值: +// 错误对象 +func CallOne(name string) (err error) { + mutex.Lock() + defer mutex.Unlock() + + if item, exists := funcMap[name]; !exists { + panic(fmt.Sprintf("%s不存在", name)) + } else { + // 调用方法 + err = item.Call2(operateName) + } + + return +} + +// 调用任意数量的方法 +// nameList:任意数量的方法名称 +// 返回值: +// 错误列表 +func CallAny(nameList ...string) (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + for _, name := range nameList { + if item, exists := funcMap[name]; !exists { + panic(fmt.Sprintf("%s不存在", name)) + } else { + // 调用方法 + if err := item.Call2(operateName); err != nil { + errList = append(errList, err) + } + } + } + + return +} + +// 调用所有方法 +// 返回值: +// 错误列表 +func CallAll() (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + startTime := time.Now() + defer func() { + logUtil.InfoLog(fmt.Sprintf("%s 执行总时间:%s", operateName, time.Since(startTime))) + }() + + for _, item := range funcMap { + // 调用方法 + if err := item.Call2(operateName); err != nil { + errList = append(errList, err) + } + } + + return +} + +// 按照模块类型进行调用 +// moduleType:模块类型 +// 返回值: +// errList:错误列表 +func CallType(moduleType ModuleType) (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + startTime := time.Now() + defer func() { + logUtil.InfoLog(fmt.Sprintf("%v %s 执行总时间:%s", moduleType, operateName, time.Since(startTime))) + }() + + for _, item := range funcMap { + + if item.ModuleType != moduleType { + continue + } + + // 调用方法 + if err := item.Call2(operateName); err != nil { + errList = append(errList, err) + } + } + + return +} diff --git a/trunk/framework/startMgr/initMgr/init_test.go b/trunk/framework/startMgr/initMgr/init_test.go new file mode 100644 index 0000000..12de232 --- /dev/null +++ b/trunk/framework/startMgr/initMgr/init_test.go @@ -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") +} diff --git a/trunk/framework/startMgr/printMgr/print.go b/trunk/framework/startMgr/printMgr/print.go new file mode 100644 index 0000000..5d69495 --- /dev/null +++ b/trunk/framework/startMgr/printMgr/print.go @@ -0,0 +1,67 @@ +package printMgr + +import ( + "fmt" + "sync" + + . "Framework/startMgr" +) + +var ( + funcMap = make(map[string]*FuncItem) + mutex sync.Mutex +) + +// 注册方法(如果名称重复会panic) +// name:方法名称(唯一标识) +// moduleType:模块类型 +// definition:方法定义 +func Register(name string, moduleType ModuleType, definition func() error) { + mutex.Lock() + defer mutex.Unlock() + + if _, exists := funcMap[name]; exists { + panic(fmt.Sprintf("%s已经存在,请重新命名", name)) + } + + funcMap[name] = NewFuncItem(name, moduleType, definition) +} + +// 调用所有方法 +// 返回值: +// 错误列表 +func CallAll() (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + for _, item := range funcMap { + // 调用方法 + if err := item.Call(); err != nil { + errList = append(errList, err) + } + } + + return +} + +// 按照模块类型进行调用 +// moduleType:模块类型 +// 返回值: +// errList:错误列表 +func CallType(moduleType ModuleType) (errList []error) { + mutex.Lock() + defer mutex.Unlock() + + for _, item := range funcMap { + if item.ModuleType != moduleType { + continue + } + + // 调用方法 + if err := item.Call(); err != nil { + errList = append(errList, err) + } + } + + return +} diff --git a/trunk/framework/verifyMgr/doc.go b/trunk/framework/verifyMgr/doc.go new file mode 100644 index 0000000..681aa64 --- /dev/null +++ b/trunk/framework/verifyMgr/doc.go @@ -0,0 +1,6 @@ +package verifyMgr + +/* +此包用于对指定url进行访问验证,以便于在程序启动时可以提前知道目标地址的可访问性, +而不用等到实际需要时再验证;从而造成既定的影响。 +*/ diff --git a/trunk/framework/verifyMgr/type.go b/trunk/framework/verifyMgr/type.go new file mode 100644 index 0000000..c7fd644 --- /dev/null +++ b/trunk/framework/verifyMgr/type.go @@ -0,0 +1,11 @@ +package verifyMgr + +type VerifyType int8 + +const ( + // Method=GET + Con_Get VerifyType = 1 + + // Method=POST + Con_Post VerifyType = 2 +) diff --git a/trunk/framework/verifyMgr/verify.go b/trunk/framework/verifyMgr/verify.go new file mode 100644 index 0000000..92ef79f --- /dev/null +++ b/trunk/framework/verifyMgr/verify.go @@ -0,0 +1,103 @@ +package verifyMgr + +import ( + "fmt" + "sync" + + "Framework/managecenterMgr" + "goutil/webUtil" +) + +var ( + verifyUrlMap = make(map[string]VerifyType) + mutex sync.RWMutex + con_SuccessStatusCode = 200 +) + +// 初始化需要验证的Url及对应的验证方式 +func Init(url string, _type VerifyType) { + mutex.Lock() + defer mutex.Unlock() + + if _, exists := verifyUrlMap[url]; exists { + panic(fmt.Errorf("%s已经存在,请检查", url)) + } + + verifyUrlMap[url] = _type +} + +// 验证 +func Verify() (errList []error) { + errList1 := verifyInternal() + errList2 := verifyServerGroup() + + if len(errList1) > 0 { + errList = append(errList, errList1...) + } + if len(errList2) > 0 { + errList = append(errList, errList2...) + } + + return +} + +// 验证内部的url +func verifyInternal() (errList []error) { + // 验证单独指定的url + mutex.RLock() + defer mutex.RUnlock() + + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + for url, _type := range verifyUrlMap { + switch _type { + case Con_Get: + if statusCode, _, err := webUtil.GetWebData3(url, "", header, transport); statusCode != con_SuccessStatusCode || err != nil { + errList = append(errList, fmt.Errorf("access %s failed. StatusCode:%d, err:%s", url, statusCode, err)) + } + case Con_Post: + if statusCode, _, err := webUtil.PostMapData(url, nil, header, transport); statusCode != con_SuccessStatusCode || err != nil { + errList = append(errList, fmt.Errorf("access %s failed. StatusCode:%d, err:%s", url, statusCode, err)) + } + default: + errList = append(errList, fmt.Errorf("the type of %s is wrong, not it's %d", url, _type)) + } + } + + return +} + +// 验证服务器组的url +func verifyServerGroup() (errList []error) { + // 验证配置在ManageCenter中的所有ServerGroup + serverGroupList := managecenterMgr.GetServerGroupList() + chList := make([]chan bool, 0, len(serverGroupList)) + + header := webUtil.GetFormHeader() + transport := webUtil.NewTransport() + transport.DisableKeepAlives = true + transport = webUtil.GetTimeoutTransport(transport, 30) + + for _, item := range serverGroupList { + url := item.GetGSCallbackUrl("Verify.ashx") + ch := make(chan bool) + chList = append(chList, ch) + go func(_url string, _ch chan bool) { + fmt.Printf("VerifyUrl:%s\n", _url) + if statusCode, _, err := webUtil.PostMapData(_url, nil, header, transport); statusCode != con_SuccessStatusCode || err != nil { + errList = append(errList, fmt.Errorf("access %s failed. StatusCode:%d, err:%s", _url, statusCode, err)) + } + _ch <- true + }(url, ch) + } + + // 等待所有的ch返回 + for _, ch := range chList { + <-ch + } + + return +} diff --git a/trunk/framework/verifyMgr/verify_test.go b/trunk/framework/verifyMgr/verify_test.go new file mode 100644 index 0000000..160dfa8 --- /dev/null +++ b/trunk/framework/verifyMgr/verify_test.go @@ -0,0 +1,32 @@ +package verifyMgr + +import ( + "fmt" + "testing" + + "Framework/managecenterMgr" +) + +func init() { + manageCenterDataSwitchObj := &managecenterMgr.ManageCenterDataSwitch{ + AllDataSwitch: true, + } + managecenterMgr.Start("https://managecentertest-fzxh.public.com", manageCenterDataSwitchObj) +} + +func TestVerify(t *testing.T) { + if errList := Verify(); len(errList) > 0 { + t.Errorf("there should be no error, but now has") + for _, err := range errList { + fmt.Println(err) + } + } + + Init("http://www.baidu.com", Con_Get) + if errList := Verify(); len(errList) > 0 { + t.Errorf("there should be no error, but now has") + for _, err := range errList { + fmt.Println(err) + } + } +} diff --git a/trunk/framework/voicemsgMgr/config.go b/trunk/framework/voicemsgMgr/config.go new file mode 100644 index 0000000..0fcaa33 --- /dev/null +++ b/trunk/framework/voicemsgMgr/config.go @@ -0,0 +1,19 @@ +package voicemsgMgr + +//语音配置参数 +type VoiceMessageConfig struct { + //API密钥Id + SecretId string + + //API密钥key + SecretKey string + + //地域 + Region string + + //模板ID + TemplateId string + + //应用后生成的实际SdkAppid + VoiceSdkAppid string +} diff --git a/trunk/framework/voicemsgMgr/voicemessage.go b/trunk/framework/voicemsgMgr/voicemessage.go new file mode 100644 index 0000000..0bf3973 --- /dev/null +++ b/trunk/framework/voicemsgMgr/voicemessage.go @@ -0,0 +1,141 @@ +/*获取对应sdk +go get -u github.com/tencentcloud/tencentcloud-sdk-go +*/ +package voicemsgMgr + +import ( + "fmt" + + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + vms "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vms/v20200902" // 引入 vms + "goutil/logUtil" +) + +const ( + //请求方法 + REG_METHOD = "POST" + //域名 + END_POINT = "vms.tencentcloudapi.com" + //签名方式 + SIGN_METHOD = "TC3-HMAC-SHA256" +) + +//语音电话结构体 +type VoiceMessage struct { + //语音发送客户端 + client *vms.Client + + //配置 + voiceMessageConfig *VoiceMessageConfig +} + +//拨打语音电话 +//telephone:电话号码(格式:+[国家或地区码][用户号码]) +//playTimes:播放次数,可选,最多3次,默认2次 +//para:参数,根据配置的模板传入参数格式,按顺序传入 +func (this *VoiceMessage) SendVoice(telephone string, playTimes uint64, param ...string) (err error) { + //初始化请求参数 + request := vms.NewSendTtsVoiceRequest() + + // 模板 ID,必须填写在控制台审核通过的模板 ID,可登录 [语音消息控制台] 查看模板 ID + request.TemplateId = common.StringPtr(this.voiceMessageConfig.TemplateId) + + //模板构造参数,模板参数,若模板没有参数,则为空数组 + paramLit := []string{} + if len(param) > 0 { + for _, val := range param { + paramLit = append(paramLit, val) + } + } + request.TemplateParamSet = common.StringPtrs(paramLit) + + /* 被叫手机号码,采用 e.164 标准,格式为+[国家或地区码][用户号码] + * 例如:+8613711112222,其中前面有一个+号,86为国家码,13711112222为手机号*/ + request.CalledNumber = common.StringPtr(telephone) + + //在语音控制台添加应用后生成的实际SdkAppid,示例如1400006666 + request.VoiceSdkAppid = common.StringPtr(this.voiceMessageConfig.VoiceSdkAppid) + + //播放次数,可选,最多3次,默认2次 + request.PlayTimes = common.Uint64Ptr(playTimes) + + //用户的 session 内容,腾讯 server 回包中会原样返回 + request.SessionContext = common.StringPtr("test") + + //通过 client 对象调用想要访问的接口,需要传入请求对象 + _, err = this.client.SendTtsVoice(request) + // 处理异常 + if _, ok := err.(*errors.TencentCloudSDKError); ok { + logUtil.ErrorLog(fmt.Sprintf("SDK异常,语音电话拨打失败,错误信息为:%s", err)) + return + } + //非SDK异常,直接失败。实际代码中可以加入其他的处理 + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("非SDK异常,语音电话拨打失败,错误信息为:%s", err)) + return + } + + return +} + +//创建新的对象 +//secretId:API密钥Id +//secretKey:API密钥key +//region:地域 +//templateId:模板ID +//appid:应用后生成的实际SdkAppid +func NewVoiceMessage(secretId, secretKey, region, templateId, appid string) *VoiceMessage { + //构造配置模型 + voiceMessageConfigObj := &VoiceMessageConfig{ + SecretId: secretId, + SecretKey: secretKey, + Region: region, + TemplateId: templateId, + VoiceSdkAppid: appid, + } + + //通过配置实例化 + return NewVoiceMessageBuConfig(voiceMessageConfigObj) +} + +//通过配置实例化 +func NewVoiceMessageBuConfig(voiceMessageConfigObj *VoiceMessageConfig) *VoiceMessage { + //实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId 和 secretKey + credential := common.NewCredential( + voiceMessageConfigObj.SecretId, + voiceMessageConfigObj.SecretKey, + ) + + /* 非必要步骤: + * 实例化一个客户端配置对象,可以指定超时时间等配置 */ + cpf := profile.NewClientProfile() + /* SDK 默认使用 POST 方法 + * 如需使用 GET 方法,可以在此处设置,但 GET 方法无法处理较大的请求 */ + cpf.HttpProfile.ReqMethod = REG_METHOD + /* SDK 有默认的超时时间,非必要请不要进行调整 + * 如有需要请在代码中查阅以获取最新的默认值 */ + //cpf.HttpProfile.ReqTimeout = 5 + /* SDK 会自动指定域名,通常无需指定域名,但访问金融区的服务时必须手动指定域名 + * 例如 VMS 的上海金融区域名为 vms.ap-shanghai-fsi.tencentcloudapi.com */ + cpf.HttpProfile.Endpoint = END_POINT + /* SDK 默认用 TC3-HMAC-SHA256 进行签名,非必要请不要修改该字段 */ + cpf.SignMethod = SIGN_METHOD + + //初始化客户端 + vmsclient, err := vms.NewClient(credential, voiceMessageConfigObj.Region, cpf) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("初始化VMS的client 对象失败,错误信息为:%s", err)) + return nil + } + + //构造对象 + voiceMessageObj := &VoiceMessage{ + client: vmsclient, + voiceMessageConfig: voiceMessageConfigObj, + } + + //返回对象 + return voiceMessageObj +} diff --git a/trunk/framework/webServer/IWebServer.go b/trunk/framework/webServer/IWebServer.go new file mode 100644 index 0000000..9f8eaa4 --- /dev/null +++ b/trunk/framework/webServer/IWebServer.go @@ -0,0 +1,71 @@ +package webServer + +import ( + "net/http" + + "goutil/typeUtil" +) + +// webserver接口 +type IWebServer interface { + // 注册API + // path:注册的访问路径 + // callback:回调方法 + // configObj:Handler配置对象 + RegisterHandler(path string, handlerFuncObj handlerFunc, configObj *HandlerConfig) + + // 注册回调-增加用户自定义数据 + RegisterHandlerWithUserData(path string, handlerFuncObj handlerFunc, configObj *HandlerConfig, userData interface{}) + + RegisterRegexHandler(path string, handlerFuncObj handlerFunc, configObj *HandlerConfig) + + // 注册正则回调-增加用户自定义数据 + RegisterRegexHandlerWithUserData(path string, handlerFuncObj handlerFunc, configObj *HandlerConfig, userData interface{}) + + // 设定Http Header信息 + SetHeader(header map[string]string) + + // 设定HTTP请求方法 + SetMethod(method string) + + // 设定当HTTP请求方法无效时,调用的处理器 + SetInvalidMethodHandler(handler func(*Context)) + + // 设置WebServer请求是否经过了负载均衡中转 + SetIfDelegate(ifDelegate bool) + + // 设定默认页的处理器 + SetDefaultPageHandler(handler func(*Context)) + + // 设定未找到指定的回调时的处理器 + SetNotFoundPageHandler(handler func(*Context)) + + // 设置异常处理函数(默认只会记录在文件) + SetUnHandledPanicHandler(handler func(context *Context, errObj interface{})) + + // 设定在Debug模式下是否需要验证IP地址 + SetIfCheckIPWhenDebug(value bool) + + // 设定当IP无效时调用的处理器 + SetIPInvalidHandler(handler func(*Context)) + + // 设定用于检测参数是否有效的处理器 + SetParamCheckHandler(handler func(typeUtil.MapData, []string, []string) ([]string, []string, bool)) + + // 设定当检测到参数无效时调用的处理器 + SetParamInvalidHandler(handler func(*Context, []string, []string)) + + // 设定处理请求数据的处理器(例如压缩、解密等) + SetRequestDataHandler(handler func(*Context, []byte) ([]byte, error)) + + // 设定处理响应数据的处理器(例如压缩、加密等) + SetResponseDataHandler(handler func(*Context, []byte) ([]byte, error)) + + // 设定请求执行时间的处理器 + SetExecuteTimeHandler(handler func(*Context)) + + // http应答处理 + // responseWriter:应答对象 + // request:请求对象 + ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) +} diff --git a/trunk/framework/webServer/context.go b/trunk/framework/webServer/context.go new file mode 100644 index 0000000..58f127b --- /dev/null +++ b/trunk/framework/webServer/context.go @@ -0,0 +1,374 @@ +package webServer + +import ( + "bytes" + "compress/zlib" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "time" + + "goutil/logUtil" + "goutil/netUtil" + "goutil/typeUtil" + "goutil/zlibUtil" +) + +const ( + defaultMaxMemory = 32 << 20 // 32 MB +) + +// 请求上下文对象 +type Context struct { + // 用户自定义数据(注册回调时设置,通过Context传到用户回调) Add:raojianhua Time:2022-02-16 17:00 + userData interface{} + + // 请求对象 + request *http.Request + + // 响应对象 + responseWriter http.ResponseWriter + + // 数据是否已经解析数据 + ifBodyParsed bool + + // 请求数据 + bodyContent []byte + + // Form的数据是否已经解析 + ifFormParsed bool + + // MultipleForm的数据是否已经解析 + ifMultipartFormParsed bool + + // 是否经过了负载均衡的中转,如果是,则需要通过header获取客户端实际的请求地址 + ifDelegate bool + + // 请求的开始时间 + StartTime time.Time + + // 请求的结束时间 + EndTime time.Time + + // 处理请求数据的处理器(例如压缩、解密等) + requestDataHandler func(*Context, []byte) ([]byte, error) + + // 处理响应数据的处理器(例如压缩、加密等) + responseDataHandler func(*Context, []byte) ([]byte, error) +} + +// 获取用户自定义数据(注册回调时设置,通过Context传到用户回调) Add:raojianhua Time:2022-02-16 17:00 +func (this *Context) GetUserData() interface{} { + return this.userData +} + +// 获取请求对象 +func (this *Context) GetRequest() *http.Request { + return this.request +} + +// 获取响应对象 +func (this *Context) GetResponseWriter() http.ResponseWriter { + return this.responseWriter +} + +// 获取请求路径(不带参数) +func (this *Context) GetRequestPath() string { + return this.request.URL.Path +} + +// 获取请求的客户端的IP地址 +func (this *Context) GetRequestIP() string { + if this.ifDelegate { + return netUtil.GetHttpAddr2(this.request).Host + } + + return netUtil.GetHttpAddr(this.request).Host +} + +// 获取请求执行的秒数 +func (this *Context) GetExecuteSeconds() int64 { + return this.EndTime.Unix() - this.StartTime.Unix() +} + +// 格式化context对象 +func (this *Context) String() string { + var bodyContent string + if bytes, exist, err := this.GetRequestBytes(); err == nil && exist { + bodyContent = string(bytes) + } + + return fmt.Sprintf("IP:%s, URL:%s, FormValue:%#v, BodyContent:%s", + this.GetRequestIP(), this.GetRequestPath(), this.GetFormValueData(), bodyContent) +} + +func (this *Context) parseForm() { + if !this.ifFormParsed { + // 先保存一份body + this.parseBodyContent() + + this.request.ParseMultipartForm(32 << 20) + this.ifFormParsed = true + } +} + +// 获取请求的参数值(包括GET/POST/PUT/DELETE等所有参数) +func (this *Context) FormValue(key string) (value string) { + this.parseForm() + + return this.request.FormValue(key) +} + +// 获取POST的参数值 +func (this *Context) PostFormValue(key string) (value string) { + this.parseForm() + + return this.request.PostFormValue(key) +} + +// 获取所有参数的MapData类型(包括GET/POST/PUT/DELETE等所有参数) +func (this *Context) GetFormValueData() typeUtil.MapData { + this.parseForm() + + valueMap := make(map[string]interface{}) + for k, v := range this.request.Form { + valueMap[k] = v[0] + } + + return typeUtil.MapData(valueMap) +} + +// 获取POST参数的MapData类型 +func (this *Context) GetPostFormValueData() typeUtil.MapData { + this.parseForm() + + valueMap := make(map[string]interface{}) + for k, v := range this.request.PostForm { + valueMap[k] = v[0] + } + + return typeUtil.MapData(valueMap) +} + +func (this *Context) parseMultipartForm() { + if !this.ifMultipartFormParsed { + this.request.ParseMultipartForm(defaultMaxMemory) + this.ifMultipartFormParsed = true + } +} + +// 获取MultipartForm的MapData类型 +func (this *Context) GetMultipartFormValueData() typeUtil.MapData { + this.parseMultipartForm() + + valueMap := make(map[string]interface{}) + if this.request.MultipartForm != nil { + for k, v := range this.request.MultipartForm.Value { + valueMap[k] = v[0] + } + } + + return typeUtil.MapData(valueMap) +} + +func (this *Context) parseBodyContent() (err error) { + if this.ifBodyParsed { + return + } + + defer func() { + this.request.Body.Close() + this.ifBodyParsed = true + + if len(this.bodyContent) > 0 { + this.request.Body = ioutil.NopCloser(bytes.NewBuffer(this.bodyContent)) + } + }() + + this.bodyContent, err = ioutil.ReadAll(this.request.Body) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("url:%s,read body failed. Err:%s", this.GetRequestPath(), err)) + return + } + + return +} + +// 获取请求字节数据 +// 返回值: +// []byte:请求字节数组 +// exist:是否存在数据 +// error:错误信息 +func (this *Context) GetRequestBytes() (result []byte, exist bool, err error) { + if err = this.parseBodyContent(); err != nil { + return + } + + result = this.bodyContent + if result == nil || len(result) == 0 { + return + } + + // handle request data + if this.requestDataHandler != nil { + if result, err = this.requestDataHandler(this, result); err != nil { + return + } + } + + exist = true + + return +} + +// 获取请求字符串数据 +// 返回值: +// result:请求字符串数据 +// exist:是否存在数据 +// error:错误信息 +func (this *Context) GetRequestString() (result string, exist bool, err error) { + var data []byte + if data, exist, err = this.GetRequestBytes(); err != nil || !exist { + return + } + + result = string(data) + exist = true + + return +} + +// 反序列化为对象(JSON) +// obj:反序列化结果数据 +// isCompressed:数据是否已经被压缩 +// 返回值: +// 错误对象 +func (this *Context) Unmarshal(obj interface{}) (exist bool, err error) { + var data []byte + if data, exist, err = this.GetRequestBytes(); err != nil || !exist { + return + } + + // Unmarshal + if err = json.Unmarshal(data, &obj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("Unmarshal %s failed. Err::%s", string(data), err)) + return + } + + exist = true + + return +} + +// 反序列化为对象(zlib+JSON) +// obj:反序列化结果数据 +// isCompressed:数据是否已经被压缩 +// 返回值: +// 错误对象 +func (this *Context) UnmarshalZlib(obj interface{}) (exist bool, err error) { + var data []byte + if data, exist, err = this.GetRequestBytes(); err != nil || !exist { + return + } + + zlibBytes, err := zlibUtil.Decompress(data) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("Decompress %v failed. Err::%s", string(data), err)) + return + } + + // Unmarshal + if err = json.Unmarshal(zlibBytes, &obj); err != nil { + logUtil.ErrorLog(fmt.Sprintf("Unmarshal %s failed. Err::%s", string(data), err)) + return + } + + exist = true + + return +} + +func (this *Context) writeBytes(data []byte) error { + var err error + if this.responseDataHandler != nil { + if data, err = this.responseDataHandler(this, data); err != nil { + return err + } + } + + _, err = this.responseWriter.Write(data) + + return err +} + +// 输出字符串给客户端 +func (this *Context) WriteString(result string) error { + return this.writeBytes([]byte(result)) +} + +// 输出json数据给客户端 +func (this *Context) WriteJson(result interface{}) error { + data, err := json.Marshal(result) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("Marshal %v failed. Err::%s", result, err)) + return err + } + + return this.writeBytes(data) +} + +// 输出zlib+json数据给客户端 +func (this *Context) WriteZlibJson(result interface{}) error { + data, err := json.Marshal(result) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("Marshal %v failed. Err::%s", result, err)) + return err + } + + zlibBytes, err := zlibUtil.Compress(data, zlib.DefaultCompression) + if err != nil { + logUtil.ErrorLog(fmt.Sprintf("Compress %v failed. Err::%s", string(data), err)) + return err + } + + return this.writeBytes(zlibBytes) +} + +/* +Http状态码301和302概念简单区别 +302重定向表示临时性转移(Temporarily Moved ),当一个网页URL需要短期变化时使用。 301重定向是永久的重定向,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址。 302重定向是临时的重定向,搜索引擎会抓取新的内容而保留旧的网址。 +如果是短链,需要每次都访问原来的短链地址,以便于进行统计,则使用302重定向 +*/ +// 重定向到其它页面(301) +func (this *Context) RedirectTo(url string) { + this.responseWriter.Header().Set("Location", url) + this.responseWriter.WriteHeader(301) +} + +// 重定向到其它页面(302) +func (this *Context) RedirectTo302(url string) { + this.responseWriter.Header().Set("Location", url) + this.responseWriter.WriteHeader(302) +} + +// 新建API上下文对象 +// request:请求对象 +// responseWriter:应答写对象 +// _ifDelegate:是否经过负载均衡 +// 返回值: +// *Context:上下文 +func newContext(request *http.Request, responseWriter http.ResponseWriter, + requestDataHandler func(*Context, []byte) ([]byte, error), + responseDataHandler func(*Context, []byte) ([]byte, error), + _ifDelegate bool) *Context { + return &Context{ + request: request, + responseWriter: responseWriter, + StartTime: time.Now(), + EndTime: time.Now(), + requestDataHandler: requestDataHandler, + responseDataHandler: responseDataHandler, + ifDelegate: _ifDelegate, + } +} diff --git a/trunk/framework/webServer/doc.go b/trunk/framework/webServer/doc.go new file mode 100644 index 0000000..d54c4b9 --- /dev/null +++ b/trunk/framework/webServer/doc.go @@ -0,0 +1,82 @@ +/* + 此包提供通用的HTTP/HTTPS服务器功能; + 使用方法如下: + 1、初始化一个HttpServer/HttpsServer + server := NewHttpServer(addr string, isCheckIP bool) + 或 + server := NewHttpsServer(addr, certFileName, keyFileName string, isCheckIP bool) + 其中参数说明如下: + addr:服务器监听地址 + isCheckIP:是否需要验证客户端IP(此设置针对所有的请求,对于每个请求也可以单独设置;以此设置为优先) + certFileName:证书文件的路径 + keyFileName:密钥文件的路径 + + 2、设置server的属性 + 在服务器运行过程中,会有一些默认的行为,如果想要改变默认行为,可以通过调用以下的方法进行设置 + + 1)、设定Http Header信息 + SetHeader(header map[string]string) + 默认的Http Header为空;如果设定了Http Header,则在每次请求时,都会给ResponseWriter加上该header属性 + 2)、设定HTTP请求方法 + SetMethod(method string) + 默认情况下,对请求的方法没有任何限制;如果设定了Method,则只允许该Method访问 + 3)、设定当HTTP请求方法无效时,调用的处理器 + SetInvalidMethodHandler(handler func(*Context)) + 默认情况下,如果请求方法与所设置的方法不一致时,会返回406错误;如果设置了此属性,则会调用此属性进行处理 + 4)、设定默认页的处理器 + SetDefaultPageHandler(handler func(*Context)) + 默认页指的是/, /favicon.ico这两个页面;默认情况下,仅仅是输出Welcome to home page.;如果需要针对做一些处理,可以设置此属性 + 5)、设定未找到指定的回调时的处理器 + SetNotFoundPageHandler(handler func(*Context)) + 当服务器找不到对应的地址的Handler时,会返回404错误。如果需要处理一些非固定的地址时,可以使用此方法;比如回调地址中包含AppId,所以导致地址可变 + 6)、设定在Debug模式下是否需要验证IP地址 + SetIfCheckIPWhenDebug(value bool) + 默认情况下,在DEBUG模式时不验证IP;可以通过此属性改变此此行为 + 7)、设定当IP无效时调用的处理器 + SetIPInvalidHandler(handler func(*Context)) + 当需要验证IP并且IP无效时,默认情况下会返回401错误;如果设定了此属性,则可以改变该行为 + 8)、设定当检测到参数无效时调用的处理器 + SetParamInvalidHandler(handler func(*Context)) + 当检测到参数无效时,默认情况下会返回500错误;如果设置了此属性,则可以改变该行为 + 9)、设定处理请求数据的处理器(例如压缩、解密等) + SetRequestDataHandler(handler func(*Context, []byte) ([]byte, error)) + 如果设定此属性,则在处理接收到的请求数据时,会调用此属性 + 10)、设定处理响应数据的处理器(例如压缩、加密等) + SetResponseDataHandler(handler func(*Context, []byte) ([]byte, error)) + 如果设定此属性,则在处理返回给客户端的数据时,会调用此属性 + 11)、设定请求执行时间的处理器 + SetExecuteTimeHandler(handler func(*Context)) + 如果在请求结束后想要处理调用的时间,则需要设置此属性;例如请求时间过长则记录日志等 + + 3、注册handler + server.RegisterHandler(path string, handlerFuncObj handlerFunc, configObj *HandlerConfig) + 参数如下: + // path:注册的访问路径 + // callback:回调方法 + // configObj:Handler配置对象 + 例如:server.RegisterHandler("/get/notice", getNoticeConfig, &webServer.HandlerConfig{IsCheckIP: false, ParamNameList: []string{"appid"}}) + + 4、启动对应的服务器 + server.Start(wg *sync.WaitGroup) + + 5、context中提供了很多实用的方法 + 1)、GetRequestPath() string:获取请求路径(该路径不带参数) + 2)、GetRequestIP() string:获取请求的客户端的IP地址 + 3)、GetExecuteSeconds() int64:获取请求执行的秒数。当然也可以通过获取StartTime, EndTime属性自己进行更高精度的处理 + 4)、String() string:将context里面的内容进行格式化,主要用于记录日志 + 5)、FormValue(key string) string:获取请求的参数值(包括GET/POST/PUT/DELETE等所有参数) + 6)、PostFormValue(key string) string:获取POST的参数值 + 7)、GetFormValueData() typeUtil.MapData:获取所有参数的MapData类型(包括GET/POST/PUT/DELETE等所有参数) + 8)、GetPostFormValueData() typeUtil.MapData:获取POST参数的MapData类型 + 9)、GetMultipartFormValueData() typeUtil.MapData:获取MultipartForm的MapData类型 + 10)、GetRequestBytes() (result []byte, exists bool, err error):获取请求字节数据 + 11)、GetRequestString() (result string, exists bool, err error):获取请求字符串数据 + 12)、Unmarshal(obj interface{}) (exists bool, err error):反序列化为对象(JSON) + 13)、WriteString(result string):输出字符串给客户端 + 14)、WriteJson(result interface{}):输出json数据给客户端 + 15)、RedirectTo(url string):重定向到其它页面 + 如果以上的方法不能满足需求,则可以调用以下的方法来获取原始的Request/ResponseWriter对象进行处理 + 16)、GetRequest() *http.Request:获取请求对象 + 17)、GetResponseWriter() http.ResponseWriter:获取响应对象 +*/ +package webServer diff --git a/trunk/framework/webServer/handler.go b/trunk/framework/webServer/handler.go new file mode 100644 index 0000000..b21400c --- /dev/null +++ b/trunk/framework/webServer/handler.go @@ -0,0 +1,162 @@ +package webServer + +import ( + "Framework/ipMgr" + "goutil/debugUtil" + "goutil/typeUtil" +) + +// 参数传输类型 +type ParamTransferType string + +const ( + // 以Form表单形式来传递参数 + Con_Form ParamTransferType = "Form" + // 以二进制流形式来传递参数 + Con_Stream ParamTransferType = "Stream" +) + +type handlerFunc func(*Context) + +// 请求处理配置对象 +type HandlerConfig struct { + // 是否需要验证IP + IsCheckIP bool + + // 参数类型 + ParamTransferType ParamTransferType + + // 方法参数名称集合 + ParamNameList []string + + // 非空参数名称集合 + NotEmptyParamNameList []string +} + +// 请求处理对象 +type handler struct { + // 用户自定义数据(注册回调时设置,通过Context传到用户回调) Add:raojianhua Time:2022-02-16 17:00 + userData interface{} + + // 注册的访问路径 + path string + + // 方法定义 + funcObj handlerFunc + + // 请求处理配置对象 + configObj *HandlerConfig + + // 正则表达式 + regex bool +} + +// 检查IP是否合法 +func (this *handler) checkIP(context *Context, ifCheckIPWhenDebug bool) bool { + if this.configObj == nil { + return true + } + + if this.configObj.IsCheckIP == false { + return true + } + + if debugUtil.IsDebug() == true && ifCheckIPWhenDebug == false { + return true + } + + if ipMgr.IsIpValid(context.GetRequestIP()) { + return true + } + + return false +} + +// 检测参数 +func (this *handler) checkParam(context *Context, methodName string, paramCheckHandler func(paramMap typeUtil.MapData, paramNameList []string, notEmptyParamList []string) ([]string, []string, bool)) (missParamList []string, emptyParamList []string, valid bool) { + valid = true + + if this.configObj == nil { + return + } + + // 获取方法的参数集合 + var dataMap typeUtil.MapData + switch this.configObj.ParamTransferType { + case Con_Stream: + data := new(map[string]interface{}) + if exist, err := context.Unmarshal(data); err != nil || !exist { + valid = false + return + } + dataMap = typeUtil.NewMapData(*data) + default: + if methodName == "POST" { + dataMap = context.GetPostFormValueData() + } else { + dataMap = context.GetFormValueData() + } + } + + // 定义默认的参数验证方法 + defaultParamCheckHandler := func(paramMap typeUtil.MapData, paramNameList []string, notEmptyParamList []string) (_missParamList []string, _emptyParamList []string, _valid bool) { + _valid = true + + // 遍历判断每一个参数是否存在;为了搜集所有的参数,所以不会提前返回 + for _, name := range paramNameList { + if _, exist := paramMap[name]; !exist { + _missParamList = append(_missParamList, name) + _valid = false + } + } + + for _, name := range notEmptyParamList { + if value, _ := paramMap[name]; value == "" { + _emptyParamList = append(_emptyParamList, name) + _valid = false + } + } + + return + } + + // 如果没有指定验证参数的方法,就使用默认方法 + if paramCheckHandler == nil { + missParamList, emptyParamList, valid = defaultParamCheckHandler(dataMap, this.configObj.ParamNameList, this.configObj.NotEmptyParamNameList) + } else { + missParamList, emptyParamList, valid = paramCheckHandler(dataMap, this.configObj.ParamNameList, this.configObj.NotEmptyParamNameList) + } + + return +} + +// 检测POST参数 +func (this *handler) checkPostParam(context *Context) bool { + if this.configObj == nil { + return true + } + + for _, name := range this.configObj.ParamNameList { + if _, exist := context.GetPostFormValueData()[name]; !exist { + return false + } + } + + for _, name := range this.configObj.NotEmptyParamNameList { + if value, _ := context.GetPostFormValueData()[name]; value == "" { + return false + } + } + + return true +} + +// 创建新的请求方法对象 +func newHandler(path string, funcObj handlerFunc, configObj *HandlerConfig) *handler { + return &handler{ + path: path, + funcObj: funcObj, + configObj: configObj, + regex: false, + } +} diff --git a/trunk/framework/webServer/httpServer.go b/trunk/framework/webServer/httpServer.go new file mode 100644 index 0000000..6e0c3d7 --- /dev/null +++ b/trunk/framework/webServer/httpServer.go @@ -0,0 +1,82 @@ +package webServer + +import ( + "fmt" + "net/http" + "sync" + "time" + + "goutil/logUtil" +) + +// Http服务器对象 +type HttpServer struct { + addr string + IWebServer + server http.Server +} + +func (this *HttpServer) SetAddr(addr string) { + this.addr = addr + this.server.Addr = addr +} + +// 启动HttpServer +func (this *HttpServer) Start(wg *sync.WaitGroup) { + defer func() { + wg.Done() + }() + + // 开启监听 + msg := fmt.Sprintf("http server begins to listen on: %s...", this.addr) + fmt.Println(msg) + logUtil.InfoLog(msg) + + if err := this.server.ListenAndServe(); err != nil { + panic(fmt.Sprintf("http server ListenAndServe Error:%v", err)) + } +} + +// 创建新的HttpServer +// isCheckIP:该属性已丢弃,可以任意赋值 +func NewHttpServer(addr string, isCheckIP bool) *HttpServer { + webServerObj := NewWebServer(isCheckIP) + + return &HttpServer{ + addr: addr, + IWebServer: webServerObj, + server: http.Server{ + Addr: addr, + Handler: webServerObj, + }, + } +} + +// 创建新的HttpServer +func NewHttpServer2(addr string, webServerObj IWebServer) *HttpServer { + return &HttpServer{ + addr: addr, + IWebServer: webServerObj, + server: http.Server{ + Addr: addr, + Handler: webServerObj, + }, + } +} + +// 创建新的HttpServer +// isCheckIP:该属性已丢弃,可以任意赋值 +func NewHttpServer3(addr string, isCheckIP bool, readTimeout time.Duration, readHeaderTimeout time.Duration, writeTimeout time.Duration) *HttpServer { + webServerObj := NewWebServer(isCheckIP) + return &HttpServer{ + addr: addr, + IWebServer: webServerObj, + server: http.Server{ + Addr: addr, + Handler: webServerObj, + ReadTimeout: readTimeout, + ReadHeaderTimeout: readHeaderTimeout, + WriteTimeout: writeTimeout, + }, + } +} diff --git a/trunk/framework/webServer/httpsServer.go b/trunk/framework/webServer/httpsServer.go new file mode 100644 index 0000000..d582285 --- /dev/null +++ b/trunk/framework/webServer/httpsServer.go @@ -0,0 +1,70 @@ +package webServer + +import ( + "fmt" + "net/http" + "sync" + + "goutil/logUtil" +) + +// Https服务器对象 +type HttpsServer struct { + addr string + certFileName string + keyFileName string + IWebServer + server http.Server +} + +func (this *HttpsServer) SetAddr(addr string) { + this.addr = addr + this.server.Addr = addr +} + +// 启动HttpsServer +func (this *HttpsServer) Start(wg *sync.WaitGroup) { + defer func() { + wg.Done() + }() + + // 开启监听 + msg := fmt.Sprintf("http server begins to listen on: %s...", this.addr) + fmt.Println(msg) + logUtil.InfoLog(msg) + + if err := this.server.ListenAndServeTLS(this.certFileName, this.keyFileName); err != nil { + panic(fmt.Sprintf("https server ListenAndServeTLS Error:%v", err)) + } +} + +// 创建新的HttpsServer +// isCheckIP:该属性已丢弃,可以任意赋值 +func NewHttpsServer(addr, certFileName, keyFileName string, isCheckIP bool) *HttpsServer { + webServerObj := NewWebServer(isCheckIP) + + return &HttpsServer{ + addr: addr, + certFileName: certFileName, + keyFileName: keyFileName, + IWebServer: webServerObj, + server: http.Server{ + Addr: addr, + Handler: webServerObj, + }, + } +} + +// 创建新的HttpsServer +func NewHttpsServer2(addr, certFileName, keyFileName string, webServerObj IWebServer) *HttpsServer { + return &HttpsServer{ + addr: addr, + certFileName: certFileName, + keyFileName: keyFileName, + IWebServer: webServerObj, + server: http.Server{ + Addr: addr, + Handler: webServerObj, + }, + } +} diff --git a/trunk/framework/webServer/webServer.go b/trunk/framework/webServer/webServer.go new file mode 100644 index 0000000..a61c8a2 --- /dev/null +++ b/trunk/framework/webServer/webServer.go @@ -0,0 +1,353 @@ +package webServer + +import ( + "fmt" + "net/http" + "regexp" + "strings" + "time" + + "goutil/logUtil" + "goutil/typeUtil" +) + +var ( + defaultPageMap = make(map[string]bool) +) + +func init() { + defaultPageMap["/"] = true + defaultPageMap["/favicon.ico"] = true +} + +// web服务对象 +type WebServer struct { + // 所有对外提供的处理器集合 + handlerMap map[string]*handler + + // 需要应用的Header集合 + headerMap map[string]string + + // 指定的HTTP请求方法,如果不指定则任意方法均有效 + specifiedMethod string + + // 当HTTP请求方法无效时,调用的处理器 + invalidMethodHandler func(*Context) + + // 默认页的处理器(默认页指的是/或/favicon.ico,通常是被负载均衡器调用或在浏览器中直接访问,而不会被程序调用) + defaultPageHandler func(*Context) + + // 未找到指定的回调时的处理器,通常是因为该url是可变的,而不是固定的 + notFoundPageHandler func(*Context) + + // 未处理的异常处理函数 + unHandledPanicHandler func(context *Context, errObj interface{}) + + // ifDelegate:是否经过负责均衡中转(经过负载均衡中转后,Request的地址需要从Header获取) + ifDelegate bool + + // 在Debug模式下是否需要验证IP地址,默认情况下不验证 + ifCheckIPWhenDebug bool + + // 当IP无效时调用的处理器 + ipInvalidHandler func(*Context) + + // 用于检测参数是否有效的处理器 + paramCheckHandler func(typeUtil.MapData, []string, []string) ([]string, []string, bool) + + // 当检测到参数无效时调用的处理器 + paramInvalidHandler func(*Context, []string, []string) + + // 处理请求数据的处理器(例如压缩、解密等) + requestDataHandler func(*Context, []byte) ([]byte, error) + + // 处理响应数据的处理器(例如压缩、加密等) + responseDataHandler func(*Context, []byte) ([]byte, error) + + // 请求执行时间的处理器 + executeTimeHandler func(*Context) +} + +// 注册API +// path:注册的访问路径 +// callback:回调方法 +// configObj:Handler配置对象 +func (this *WebServer) RegisterHandler(path string, handlerFuncObj handlerFunc, configObj *HandlerConfig) { + // 判断是否已经注册过,避免命名重复 + if _, exist := this.handlerMap[path]; exist { + panic(fmt.Sprintf("%s has been registered, please try a new path", path)) + } + + this.handlerMap[path] = newHandler(path, handlerFuncObj, configObj) +} + +// 注册回调-增加用户自定义数据 +func (this *WebServer) RegisterHandlerWithUserData(path string, handlerFuncObj handlerFunc, configObj *HandlerConfig, userData interface{}) { + // 判断是否已经注册过,避免命名重复 + if _, exist := this.handlerMap[path]; exist { + panic(fmt.Sprintf("%s has been registered, please try a new path", path)) + } + + handler := newHandler(path, handlerFuncObj, configObj) + handler.userData = userData + this.handlerMap[path] = handler +} + +// 注册正则表式路径API +// path:注册的访问路径 +// callback:回调方法 +// configObj:Handler配置对象 +func (this *WebServer) RegisterRegexHandler(path string, handlerFuncObj handlerFunc, configObj *HandlerConfig) { + // 判断是否已经注册过,避免命名重复 + if _, exist := this.handlerMap[path]; exist { + panic(fmt.Sprintf("%s has been registered, please try a new path", path)) + } + + handler := newHandler(path, handlerFuncObj, configObj) + handler.regex = true + this.handlerMap[path] = handler +} + +// 注册正则回调-增加用户自定义数据 +func (this *WebServer) RegisterRegexHandlerWithUserData(path string, handlerFuncObj handlerFunc, configObj *HandlerConfig, userData interface{}) { + // 判断是否已经注册过,避免命名重复 + if _, exist := this.handlerMap[path]; exist { + panic(fmt.Sprintf("%s has been registered, please try a new path", path)) + } + + handler := newHandler(path, handlerFuncObj, configObj) + handler.userData = userData + handler.regex = true + this.handlerMap[path] = handler +} + +// 获取请求方法 +// path:方法名称 +// 返回值: +// 请求方法 +// 是否存在 +func (this *WebServer) getHandler(path string) (handlerObj *handler, exist bool) { + handlerObj, exist = this.handlerMap[path] + + if !exist { + for _, handler := range this.handlerMap { + if handler.regex { + match, _ := regexp.MatchString(handler.path, path) + if match { + return handler, true + } + } + } + } + return +} + +// 设定Http Header信息 +func (this *WebServer) SetHeader(header map[string]string) { + this.headerMap = header +} + +// 处理Http Header信息 +func (this *WebServer) handleHeader(context *Context) { + if this.headerMap != nil && len(this.headerMap) > 0 { + for k, v := range this.headerMap { + context.responseWriter.Header().Set(k, v) + } + } +} + +// 设定HTTP请求方法 +func (this *WebServer) SetMethod(method string) { + this.specifiedMethod = method +} + +// 设定当HTTP请求方法无效时,调用的处理器 +func (this *WebServer) SetInvalidMethodHandler(handler func(*Context)) { + this.invalidMethodHandler = handler +} + +// 处理HTTP请求方法 +// 返回值 +// isTerminate:是否终止本次请求 +func (this *WebServer) handleMethod(context *Context) (isTerminate bool) { + if this.specifiedMethod != "" { + if context.request.Method != this.specifiedMethod { + if this.invalidMethodHandler != nil { + this.invalidMethodHandler(context) + } else { + http.Error(context.responseWriter, fmt.Sprintf("Expected %s Method", this.specifiedMethod), 406) + } + isTerminate = true + } + } + + return +} + +// 设定默认页的处理器 +func (this *WebServer) SetDefaultPageHandler(handler func(*Context)) { + this.defaultPageHandler = handler +} + +// 处理默认页 +// 返回值 +// isTerminate:是否终止本次请求 +func (this *WebServer) handleDefaultPage(context *Context) (isTerminate bool) { + // 首页进行特别处理 + if _, exist := defaultPageMap[context.GetRequestPath()]; exist { + if this.defaultPageHandler != nil { + this.defaultPageHandler(context) + } else { + context.WriteString("Welcome to home page.") + } + isTerminate = true + } + + return +} + +// 设定未找到指定的回调时的处理器 +func (this *WebServer) SetNotFoundPageHandler(handler func(*Context)) { + this.notFoundPageHandler = handler +} + +// 设置异常处理函数(默认只会记录在文件) +func (this *WebServer) SetUnHandledPanicHandler(handler func(context *Context, errObj interface{})) { + this.unHandledPanicHandler = handler +} + +// 设定在Debug模式下是否需要验证IP地址 +func (this *WebServer) SetIfCheckIPWhenDebug(value bool) { + this.ifCheckIPWhenDebug = value +} + +// 设定当IP无效时调用的处理器 +func (this *WebServer) SetIPInvalidHandler(handler func(*Context)) { + this.ipInvalidHandler = handler +} + +// 设定用于检测参数是否有效的处理器 +func (this *WebServer) SetParamCheckHandler(handler func(typeUtil.MapData, []string, []string) ([]string, []string, bool)) { + this.paramCheckHandler = handler +} + +// 设定当检测到参数无效时调用的处理器 +func (this *WebServer) SetParamInvalidHandler(handler func(*Context, []string, []string)) { + this.paramInvalidHandler = handler +} + +// 设定处理请求数据的处理器(例如压缩、解密等) +func (this *WebServer) SetRequestDataHandler(handler func(*Context, []byte) ([]byte, error)) { + this.requestDataHandler = handler +} + +// 设定处理响应数据的处理器(例如压缩、加密等) +func (this *WebServer) SetResponseDataHandler(handler func(*Context, []byte) ([]byte, error)) { + this.responseDataHandler = handler +} + +// 设定请求执行时间的处理器 +func (this *WebServer) SetExecuteTimeHandler(handler func(*Context)) { + this.executeTimeHandler = handler +} + +// 处理请求执行时间逻辑 +func (this *WebServer) handleExecuteTime(context *Context) { + if this.executeTimeHandler != nil { + this.executeTimeHandler(context) + } +} + +// 设置WebServer请求是否经过了负载均衡中转 +func (this *WebServer) SetIfDelegate(ifDelegate bool) { + this.ifDelegate = ifDelegate +} + +// http应答处理 +// responseWriter:应答对象 +// request:请求对象 +func (this *WebServer) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) { + // 构造上下文对象 + context := newContext(request, responseWriter, this.requestDataHandler, this.responseDataHandler, this.ifDelegate) + defer func() { + if r := recover(); r != nil { + if this.unHandledPanicHandler != nil { + this.unHandledPanicHandler(context, r) + } else { + logUtil.LogUnknownError(r) + } + } + }() + + // 处理Http Header信息 + this.handleHeader(context) + + // 处理默认页 + if this.handleDefaultPage(context) { + return + } + + // 处理返回值信息 + defer func() { + // 处理请求执行时间逻辑 + this.handleExecuteTime(context) + }() + + // 处理请求方法 + if this.handleMethod(context) { + return + } + + // 根据路径选择不同的处理方法 + var handlerObj *handler + var exist bool + if handlerObj, exist = this.getHandler(context.GetRequestPath()); !exist { + if this.notFoundPageHandler != nil { + this.notFoundPageHandler(context) + } else { + http.Error(context.responseWriter, "访问的页面不存在", 404) + } + return + } + + // 将用户自定义数据通过Context传入回调 + context.userData = handlerObj.userData + + // Check IP + if handlerObj.checkIP(context, this.ifCheckIPWhenDebug) == false { + if this.ipInvalidHandler != nil { + this.ipInvalidHandler(context) + } else { + http.Error(context.responseWriter, "你的IP不允许访问,请联系管理员", 401) + } + return + } + + // Check Param + if missParamList, emptyParamList, valid := handlerObj.checkParam(context, this.specifiedMethod, this.paramCheckHandler); valid == false { + if this.paramInvalidHandler != nil { + this.paramInvalidHandler(context, missParamList, emptyParamList) + } else { + if missParamList != nil && len(missParamList) > 0 { + http.Error(context.responseWriter, fmt.Sprintf("你的参数不正确,请检查,缺少以下参数:%s", strings.Join(missParamList, ",")), 500) + } else { + http.Error(context.responseWriter, fmt.Sprintf("你的参数不正确,请检查,以下参数为空:%s", strings.Join(emptyParamList, ",")), 500) + } + } + return + } + + // Call Function + handlerObj.funcObj(context) + + context.EndTime = time.Now() +} + +// 新建WebServer对象 +// isCheckIP:该属性已丢弃,可以任意赋值 +func NewWebServer(isCheckIP bool) *WebServer { + return &WebServer{ + handlerMap: make(map[string]*handler), + headerMap: make(map[string]string), + } +} diff --git a/trunk/framework/websocketServer/IServer.go b/trunk/framework/websocketServer/IServer.go new file mode 100644 index 0000000..941f258 --- /dev/null +++ b/trunk/framework/websocketServer/IServer.go @@ -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() +} diff --git a/trunk/framework/websocketServer/connManager.go b/trunk/framework/websocketServer/connManager.go new file mode 100644 index 0000000..1fff6e6 --- /dev/null +++ b/trunk/framework/websocketServer/connManager.go @@ -0,0 +1,450 @@ +// ************************************ +// @package: websocketServer +// @description: websocket管理 +// @author: +// @revision history: +// @create date: 2022-02-18 15:38:17 +// ************************************ +package websocketServer + +import ( + "errors" + "github.com/gorilla/websocket" + routineCtrlUtil "goutil/routineCtrlUtil" + "sync" + "time" +) + +const ( + // 默认广播并发数 + con_DEFAULT_BROADCAST_CONCURRENT = 10 +) + +// connManager +// @description: websocket连接管理 +type connManager struct { + // 是否禁止新连接 + disableNewConn bool + + // 广播并发数 + broadcastConcurrent int + + // websocket服务端配置结构 + upgrader *websocket.Upgrader + + // 广播锁-限制 消息广播/关闭所有连接 并发访问 + muBroadcast sync.Mutex + + // 连接池map写锁(增加/删除) + muAllConns sync.Mutex + + // 连接池-所有已连接的websocket + allConns map[*websocket.Conn]*Context + + //------------------- + // 心跳控制 + + // 接收到Ping消息时,是否自动回复Pong + autuPong bool + + // 心跳周期 + heartbeatCycle time.Duration + + // 断连周期数(超过几个心跳周期即自动关闭连接);设置为0即关闭心跳检测功能 + heartbeatCloseCount int + + // 是否已开启心跳检测协程 + isHeartbeatDetectStart bool +} + +// heartbeatDetect +// @description: 开启心跳检测协程 +// parameter: +// +// @receiver connMgr: +// +// return: +func (connMgr *connManager) heartbeatDetect() { + // 限制每个websocket连接管理只开启一个心跳检测协程 + if !connMgr.isHeartbeatDetectStart { + connMgr.isHeartbeatDetectStart = true + + // 开启心跳检测协程 + go func() { + for { + if connMgr.heartbeatCloseCount <= 0 { + // 心跳检测功能已关闭;每秒检测此标志 + time.Sleep(time.Second) + continue + } + + // 心跳检测功能已开启 + + connMgr.muAllConns.Lock() // 连接池map锁 + ctxs_timeout := make([]*Context, 0, len(connMgr.allConns)) // 存放心跳超时,需要关闭的websocket环境 + for _, ctx := range connMgr.allConns { + if time.Since(ctx.heartbeat) > (connMgr.heartbeatCycle*time.Duration(connMgr.heartbeatCloseCount) + 1) { + // 心跳超时,需要关闭的websocket环境加入列表 + ctxs_timeout = append(ctxs_timeout, ctx) + } + } + connMgr.muAllConns.Unlock() // 连接池map及时解锁 + + // 关闭所有心跳超时的连接 + func() { + // 获取广播并发数 + broadcastConcurrent := connMgr.broadcastConcurrent + if broadcastConcurrent <= 0 { + broadcastConcurrent = con_DEFAULT_BROADCAST_CONCURRENT + } + + // 协程并发限制 + rtCtrl := routineCtrlUtil.New(broadcastConcurrent) + for _, ctx := range ctxs_timeout { + ctxTemp := ctx + rtCtrl.Run(func(arg interface{}) { + // 执行受限并发函数 + ctxTemp.Close() + }, nil) + } + + // 等待完成 + rtCtrl.Wait() + }() + + // 休眠半个心跳周期 + slpTime := time.Duration(connMgr.heartbeatCycle / 2) + if slpTime < time.Second { + slpTime = time.Second + } + time.Sleep(slpTime) + } + }() + } +} + +// upgrade +// @description: 升级为websocket +// parameter: +// +// @receiver connMgr: +// @ctx: websocket环境 +// +// return: +// +// @*websocket.Conn: 建立的连接对象 +// @error: +func (connMgr *connManager) upgrade(ctx *Context) (conn *websocket.Conn, err error) { + if connMgr.disableNewConn { + // 禁止新连接 + return nil, errors.New("connManager(disableNewConn)") + } + + // 建立websocket连接 + conn, err = connMgr.upgrader.Upgrade(ctx.GetWebServerContext().GetResponseWriter(), ctx.GetWebServerContext().GetRequest(), nil) + if err != nil { + return + } + + // 添加到连接池 + ctx.conn = conn + connMgr.addConn(conn, ctx) + + return +} + +// addConn +// @description: 添加到连接池 +// parameter: +// +// @receiver connMgr: +// @conn: +// @ctx: +// +// return: +func (connMgr *connManager) addConn(conn *websocket.Conn, ctx *Context) { + connMgr.muAllConns.Lock() + defer connMgr.muAllConns.Unlock() + + connMgr.allConns[conn] = ctx +} + +// delConn +// @description: 将连接从连接池删除 +// parameter: +// +// @receiver connMgr: +// @conn: +// +// return: +func (connMgr *connManager) delConn(conn *websocket.Conn) { + connMgr.muAllConns.Lock() + defer connMgr.muAllConns.Unlock() + + delete(connMgr.allConns, conn) +} + +// renewAllConnsMap +// @description: 重新替换一个新allConns的map结构,以免被标记删除的冗余信息造成存储和性能问题 +// parameter: +// +// @receiver connMgr: +// +// return: +// +// @map[*websocket.Conn]*Context: 返回原内部使用的连接池(现内部已不再使用) +func (connMgr *connManager) renewAllConnsMap() map[*websocket.Conn]*Context { + connMgr.muAllConns.Lock() + defer connMgr.muAllConns.Unlock() + + // map拷贝 + allConnsCopy := make(map[*websocket.Conn]*Context, len(connMgr.allConns)) + for conn, ctx := range connMgr.allConns { + allConnsCopy[conn] = ctx + } + // map替换;因map删除时只是标记,并非真正删除,使用一段时间后可能会出现大量未使用信息;这里顺便更新一下map + connMgr.allConns, allConnsCopy = allConnsCopy, connMgr.allConns + + return allConnsCopy +} + +// SetBroadcastConcurrent +// @description: 设置广播并发数 +// parameter: +// +// @receiver connMgr: +// @n: 广播并发数 +// +// return: +func (connMgr *connManager) SetBroadcastConcurrent(n int) { + connMgr.broadcastConcurrent = n +} + +// EnableNewConn +// @description: 允许新连接 +// parameter: +// +// @receiver connMgr: +// +// return: +func (connMgr *connManager) EnableNewConn() { + connMgr.disableNewConn = false +} + +// DisableNewConn +// @description: 禁用新连接 +// parameter: +// +// @receiver connMgr: +// +// return: +func (connMgr *connManager) DisableNewConn() { + connMgr.disableNewConn = true +} + +// MulticastMessage +// @description: 多播消息(给指定多用户发送消息) +// parameter: +// +// @receiver connMgr: +// @ctxs: 指定多用户的*Context切片 +// @messageType: websocket类型 +// @data: 发送的数据 +// +// return: +// +// @err: 若有错误,则为最后一个错误 +func (connMgr *connManager) MulticastMessage(ctxs []*Context, messageType int, data []byte) (err error) { + // 广播锁,防重入 + connMgr.muBroadcast.Lock() + defer connMgr.muBroadcast.Unlock() + + // 获取广播并发数 + broadcastConcurrent := connMgr.broadcastConcurrent + if broadcastConcurrent <= 0 { + broadcastConcurrent = con_DEFAULT_BROADCAST_CONCURRENT + } + + // 协程并发限制 + rtCtrl := routineCtrlUtil.New(broadcastConcurrent) + var mu sync.Mutex + for _, ctx := range ctxs { + // 执行受限并发函数 + // 注意:这里的ctx需要使用参数传入Run;否则随时变化的ctx在闭包内使用时,会出现不符合程序要求逻辑的结果 + rtCtrl.Run(func(arg interface{}) { + ctxTemp, _ := arg.(*Context) + e := ctxTemp.SendMessage(messageType, data) + if e != nil { + mu.Lock() + err = e + mu.Unlock() + } + }, ctx) + } + + // 等待完成 + rtCtrl.Wait() + + return +} + +// BroadcastMessage +// @description: 消息广播 +// parameter: +// +// @receiver connMgr: +// @messageType: websocket类型 +// @data: 发送的数据 +// +// return: +// +// @err: 若有错误,则为最后一个错误 +func (connMgr *connManager) BroadcastMessage(messageType int, data []byte) (err error) { + // 广播锁,防重入 + connMgr.muBroadcast.Lock() + defer connMgr.muBroadcast.Unlock() + + // 重新替换一个新allConns的map结构,以免被标记删除的冗余信息造成存储和性能问题 + allConnsCopy := connMgr.renewAllConnsMap() + + // 获取广播并发数 + broadcastConcurrent := connMgr.broadcastConcurrent + if broadcastConcurrent <= 0 { + broadcastConcurrent = con_DEFAULT_BROADCAST_CONCURRENT + } + + // 协程并发限制 + rtCtrl := routineCtrlUtil.New(broadcastConcurrent) + var mu sync.Mutex + for _, ctx := range allConnsCopy { + // 执行受限并发函数 + // 注意:这里的ctx需要使用参数传入Run;否则随时变化的ctx在闭包内使用时,会出现不符合程序要求逻辑的结果 + rtCtrl.Run(func(arg interface{}) { + ctxTemp, _ := arg.(*Context) + e := ctxTemp.SendMessage(messageType, data) + if e != nil { + mu.Lock() + err = e + mu.Unlock() + } + }, ctx) + } + + // 等待完成 + rtCtrl.Wait() + + return +} + +// CloseAll +// @description: 关闭所有连接 +// parameter: +// +// @receiver connMgr: +// +// return: +func (connMgr *connManager) CloseAll() { + // 广播锁,防重入 + connMgr.muBroadcast.Lock() + defer connMgr.muBroadcast.Unlock() + + // 重新替换一个新allConns的map结构,以免被标记删除的冗余信息造成存储和性能问题 + allConnsCopy := connMgr.renewAllConnsMap() + + // 获取广播并发数 + broadcastConcurrent := connMgr.broadcastConcurrent + if broadcastConcurrent <= 0 { + broadcastConcurrent = con_DEFAULT_BROADCAST_CONCURRENT + } + + // 协程并发限制 + rtCtrl := routineCtrlUtil.New(broadcastConcurrent) + for _, ctx := range allConnsCopy { + rtCtrl.Run(func(arg interface{}) { + ctxTemp, _ := arg.(*Context) + // 执行受限并发函数 + ctxTemp.Close() + }, ctx) + } + + // 等待完成 + rtCtrl.Wait() +} + +// SetUpgrader +// @description: 设置websocket参数结构 +// parameter: +// +// @receiver connMgr: +// @upgrader: websocket中的websocket.Upgrader结构体指针(可以设置握手超时/读写缓存/是否允许跨域等) +// +// return: +func (connMgr *connManager) SetUpgrader(upgrader *websocket.Upgrader) { + connMgr.upgrader = upgrader +} + +// GetUpgrader +// @description: 获取websocket参数结构 +// parameter: +// +// @receiver connMgr: +// +// return: +// +// @*websocket.Upgrader: websocket中的websocket.Upgrader结构体指针(可以设置握手超时/读写缓存/是否允许跨域等) +func (connMgr *connManager) GetUpgrader() *websocket.Upgrader { + return connMgr.upgrader +} + +// SetAutoPong +// @description: 设置接收到Ping消息时,是否自动回复Pong信息 +// parameter: +// +// @receiver connMgr: +// @autuPong: +// +// return: +func (connMgr *connManager) SetAutoPong(autuPong bool) { + connMgr.autuPong = autuPong +} + +// GetAutoPong +// @description: 获取接收到Ping消息时,是否自动回复Pong信息 +// parameter: +// +// @receiver connMgr: +// +// return: +// +// @bool: +func (connMgr *connManager) GetAutoPong() bool { + return connMgr.autuPong +} + +// SetHeartbeatDetectInfo +// @description: 设置心跳检测信息 +// parameter: +// +// @receiver connMgr: +// @heartbeatCloseCount: 断连周期数(超过几个心跳周期即自动关闭连接);设置为0即关闭心跳检测功能 +// @heartbeatCycle: 心跳周期 +// +// return: +func (connMgr *connManager) SetHeartbeatDetectInfo(heartbeatCloseCount int, heartbeatCycle time.Duration) { + connMgr.heartbeatCycle = heartbeatCycle + connMgr.heartbeatCloseCount = heartbeatCloseCount +} + +// GetHeartbeatDetectInfo +// @description: 获取心跳检测信息 +// parameter: +// +// @receiver connMgr: +// +// return: +// +// @heartbeatCloseCount: 断连周期数(超过几个心跳周期即自动关闭连接) +// @heartbeatCycle: 心跳周期 +func (connMgr *connManager) GetHeartbeatDetectInfo() (heartbeatCloseCount int, heartbeatCycle time.Duration) { + return connMgr.heartbeatCloseCount, connMgr.heartbeatCycle +} diff --git a/trunk/framework/websocketServer/context.go b/trunk/framework/websocketServer/context.go new file mode 100644 index 0000000..77b3796 --- /dev/null +++ b/trunk/framework/websocketServer/context.go @@ -0,0 +1,163 @@ +// ************************************ +// @package: websocketServer +// @description: websocket环境 +// @author: +// @revision history: +// @create date: 2022-02-18 15:59:08 +// ************************************ +package websocketServer + +import ( + "context" + "sync" + "time" + + "github.com/gorilla/websocket" + webServer "Framework/webServer" +) + +// Context +// @description: websocket环境 +type Context struct { + ctx context.Context + + // web_server环境 + webServerCtx *webServer.Context + + // 存放用户自定义数据 + userData interface{} + + // websocket连接 + conn *websocket.Conn + + // 防止多协程同时写/关闭 + mu sync.Mutex + + // 最近一次收到心跳包的时间 + heartbeat time.Time + + // 指示是否已经关闭 + isClosed bool + + // 关闭连接信号 + cls chan<- struct{} +} + +// GetWebServerContext +// @description: 获取web_server环境 +// parameter: +// +// @receiver ctx: +// +// return: +// +// @*web_server.Context: web_server环境 +func (ctx *Context) GetWebServerContext() *webServer.Context { + return ctx.webServerCtx +} + +// GetUserData +// @description: 获取用户自定义数据 +// parameter: +// +// @receiver ctx: +// +// return: +// +// @interface{}: +func (ctx *Context) GetUserData() interface{} { + return ctx.userData +} + +// SetUserData +// @description: 设置用户自定义数据 +// parameter: +// +// @receiver ctx: +// @userData: +// +// return: +func (ctx *Context) SetUserData(userData interface{}) { + ctx.userData = userData +} + +// SendMessage +// @description: 发送websocket数据 +// parameter: +// +// @receiver ctx: +// @messageType: +// @data: +// +// return: +// +// @err: +func (ctx *Context) SendMessage(messageType int, data []byte) (err error) { + ctx.mu.Lock() + defer ctx.mu.Unlock() + + return ctx.conn.WriteMessage(messageType, data) +} + +// Close +// @description: 关闭websocket连接 +// parameter: +// +// @receiver ctx: +// +// return: +func (ctx *Context) Close() { + ctx.mu.Lock() + defer ctx.mu.Unlock() + + if !ctx.isClosed { + // 设置已关闭标志 + ctx.isClosed = true + // 发送退出信号 + // 所有向cls写入,都使用select超时结构,以保证这儿不会一直阻塞,确保此协程能退出 + select { + case <-time.After(time.Millisecond * 10): + case ctx.cls <- struct{}{}: + } + //ctx.cls <- struct{}{} + } +} + +// Ctx +// @description: 返回Context +// parameter: +// +// @receiver ctx: +// +// return: +func (ctx *Context) Ctx() context.Context { + ctx.mu.Lock() + defer ctx.mu.Unlock() + + return ctx.ctx +} + +// SetCtx +// @description: 设置新的Ctx +// parameter: +// +// @receiver ctx: +// +// return: +func (ctx *Context) SetCtx(c context.Context) { + ctx.mu.Lock() + defer ctx.mu.Unlock() + + ctx.ctx = c +} + +// GetConnType +// @description: 获取连接类型 +// parameter: +// +// @receiver ctx: +// +// return: +func (ctx *Context) GetConnType() string { + return "websocket" +} diff --git a/trunk/framework/websocketServer/doc.go b/trunk/framework/websocketServer/doc.go new file mode 100644 index 0000000..dcd00b4 --- /dev/null +++ b/trunk/framework/websocketServer/doc.go @@ -0,0 +1,73 @@ +package websocketServer + +/* +此包是对web_server包的封装,并扩展出websocket相关功能 + +使用方法如下: + 初始化一个WsServer/WssServer + server := NewWsServer(addr string, isCheckIP bool) + 或 + server := NewWssServer(addr, certFileName, keyFileName string, isCheckIP bool) + + 其它调用方法见 webServer + +websocket扩展方法: + // 注册websocket回调 + server.RegisterWebsocketHandler(path string, eventCallback *eventCallbackFuncs, configObj *web_server.HandlerConfig) + server.RegisterRegexWebsocketHandler(path string, eventCallback *eventCallbackFuncs, configObj *web_server.HandlerConfig) + + // 设置websocket配置信息(可以设置握手超时/读写缓存/是否允许跨域等) + server.SetUpgrader(upgrader *websocket.Upgrader) + + // 获取websocket配置信息 + server.GetUpgrader() *websocket.Upgrader + + // 设置接收到Ping消息时,是否自动回复Pong信息 + server.SetAutoPong(autuPong bool) + + // 获取接收到Ping消息时,是否自动回复Pong信息 + server.GetAutoPong() bool + + // 设置心跳检测信息 + server.SetHeartbeatDetectInfo(heartbeatCloseCount int, heartbeatCycle time.Duration) + + // 获取心跳检测信息 + server.GetHeartbeatDetectInfo() (heartbeatCloseCount int, heartbeatCycle time.Duration) + + // 设置广播并发数 + server.SetBroadcastConcurrent(n int) + + // 允许新连接 + server.EnableNewConn() + + // 禁用新连接 + server.DisableNewConn() + + // 消息广播 + server.BroadcastMessage(messageType int, data []byte) + + // 关闭所有连接 + server.CloseAll() + + eventCallbackFuncs 回调: + // websocket连接事件 + OnConnFunc func(ctx *Context) + + // websocket关闭事件 + OnCloseFunc func(ctx *Context) + + // websocket接收事件 + OnMsgFunc func(ctx *Context, msgType int, msgData []byte) + + 回调参数说明: + ctx - websocket环境,提供了以下方法: + GetWebServerContext() *web_server.Context - 获取web_server环境 + GetUserData() interface{} - 获取用户自定义数据 + SetUserData(userData interface{}) - 设置用户自定义数据 + SendMessage(messageType int, data []byte) (err error) - 发送websocket数据 + Close() - 关闭websocket连接 + msgType - 接收到的websocket类型 + msgData - 接收到的websocket数据 + + 使用示例,见 wsServer_test.go +*/ diff --git a/trunk/framework/websocketServer/hookHandler.go b/trunk/framework/websocketServer/hookHandler.go new file mode 100644 index 0000000..899fa1c --- /dev/null +++ b/trunk/framework/websocketServer/hookHandler.go @@ -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) + } +} diff --git a/trunk/framework/websocketServer/websocketTestClient.html b/trunk/framework/websocketServer/websocketTestClient.html new file mode 100644 index 0000000..3a4043c --- /dev/null +++ b/trunk/framework/websocketServer/websocketTestClient.html @@ -0,0 +1,80 @@ + + + + go websocket + + + + +

WebSocket Test

+ + + +
+ + + \ No newline at end of file diff --git a/trunk/framework/websocketServer/wsServer.go b/trunk/framework/websocketServer/wsServer.go new file mode 100644 index 0000000..62f5371 --- /dev/null +++ b/trunk/framework/websocketServer/wsServer.go @@ -0,0 +1,100 @@ +// ************************************ +// @package: websocketServer +// @description: websocket服务端 +// @author: +// @revision history: +// @create date: 2022-02-15 14:10:51 +// ************************************ +package websocketServer + +import ( + "github.com/gorilla/websocket" + webServer "Framework/webServer" + "time" +) + +type WsServer struct { + *webServer.HttpServer + + // websocket连接管理 + *connManager +} + +// RegisterWebsocketHandler +// @description: 注册websocket回调 +// parameter: +// +// @receiver ws: +// @path:注册的访问路径 +// @handlerFuncObj:回调方法 +// @configObj:Handler配置对象 +// +// return: +func (ws *WsServer) RegisterWebsocketHandler(path string, eventCallback *EventCallbackFuncs, configObj *webServer.HandlerConfig) { + ws.RegisterHandlerWithUserData(path, hookHandler, configObj, &userDatas{ + server: ws, + eventCallback: eventCallback, + }) +} + +// RegisterRegexWebsocketHandler +// @description: 注册正则websocket回调 +// parameter: +// +// @receiver wss: +// @path:注册的正则访问路径 +// @eventCallback:回调方法 +// @configObj:Handler配置对象 +// +// return: +func (ws *WsServer) RegisterRegexWebsocketHandler(path string, eventCallback *EventCallbackFuncs, configObj *webServer.HandlerConfig) { + ws.RegisterRegexHandlerWithUserData(path, hookHandler, configObj, &userDatas{ + server: ws, + eventCallback: eventCallback, + }) +} + +func NewWsServer(addr string, isCheckIP bool) (server *WsServer) { + server = &WsServer{ + HttpServer: webServer.NewHttpServer(addr, isCheckIP), + connManager: &connManager{ + upgrader: &websocket.Upgrader{}, + allConns: make(map[*websocket.Conn]*Context), + }, + } + + // 开启心跳检测协程 + server.connManager.heartbeatDetect() + + return +} + +func NewWsServer2(addr string, webServerObj webServer.IWebServer) (server *WsServer) { + server = &WsServer{ + HttpServer: webServer.NewHttpServer2(addr, webServerObj), + connManager: &connManager{ + upgrader: &websocket.Upgrader{}, + allConns: make(map[*websocket.Conn]*Context), + }, + } + + // 开启心跳检测协程 + server.connManager.heartbeatDetect() + + return +} + +func NewWsServer3(addr string, isCheckIP bool, readTimeout time.Duration, readHeaderTimeout time.Duration, writeTimeout time.Duration) (server *WsServer) { + server = &WsServer{ + HttpServer: webServer.NewHttpServer3(addr, isCheckIP, readTimeout, readHeaderTimeout, writeTimeout), + connManager: &connManager{ + upgrader: &websocket.Upgrader{}, + allConns: make(map[*websocket.Conn]*Context), + }, + } + + // 开启心跳检测协程 + server.connManager.heartbeatDetect() + + return +} diff --git a/trunk/framework/websocketServer/wsServer_test.go b/trunk/framework/websocketServer/wsServer_test.go new file mode 100644 index 0000000..6bb4790 --- /dev/null +++ b/trunk/framework/websocketServer/wsServer_test.go @@ -0,0 +1,100 @@ +package websocketServer + +import ( + "fmt" + "github.com/gorilla/websocket" + "net/http" + webServer "Framework/webServer" + "strconv" + "sync" + "testing" + "time" +) + +// 处理HTTP请求 +func httpHandler(cxt *webServer.Context) { + msg := "test msg" + cxt.GetResponseWriter().Write([]byte(msg)) +} + +// websocket连接事件 +func onConnFunc(ctx *Context) { + fmt.Printf("new conn, ip: %s %p\n", ctx.GetWebServerContext().GetRequestIP(), ctx) + ctx.SendMessage(websocket.TextMessage, []byte("Hello")) // 返回数据 +} + +// websocket关闭事件 +func onCloseFunc(ctx *Context) { + fmt.Printf("close conn, ip: %s %p\n", ctx.GetWebServerContext().GetRequestIP(), ctx) +} + +// websocket接收事件 +func onMsgFunc(ctx *Context, msgType int, msgData []byte) { + fmt.Printf("%p ", ctx) + fmt.Println("msg ip:", ctx.GetWebServerContext().GetRequestIP(), "msg type:", msgType, "msg:", string(msgData)) + ctx.SendMessage(msgType, []byte("SVR: "+string(msgData))) // 返回数据 + + // 主动关闭连接测试 + if string(msgData) == "close" { + ctx.SendMessage(msgType, []byte("close")) // 返回数据 + //time.Sleep(time.Microsecond * 100) + ctx.Close() + } +} + +func TestWsServer(t *testing.T) { + var ssl bool = false + var server IServer + if ssl { + server = NewWssServer(":22222", "cert.pem", "key.pem", false) + } else { + server = NewWsServer(":22222", false) + } + + server.SetIfDelegate(false) + + // 注册HTTP处理函数 + server.RegisterHandler("/http", httpHandler, &webServer.HandlerConfig{}) + + // 设置websocket允许跨域访问 + server.GetUpgrader().CheckOrigin = func(r *http.Request) bool { + return true + } + //upgrader := &websocket.Upgrader{ + // // 允许跨域 + // CheckOrigin: func(r *http.Request) bool { + // return true + // }, + //} + //server.SetUpgrader(upgrader) + + // 注册websocket处理事件 + server.RegisterWebsocketHandler("/websocket", &EventCallbackFuncs{ + OnConnFunc: onConnFunc, + OnCloseFunc: onCloseFunc, + OnMsgFunc: onMsgFunc, + }, &webServer.HandlerConfig{}) + + // 设置广播并发数 + server.SetBroadcastConcurrent(2) + + // 设置接收到Ping消息时,自动回复Pong信息 + server.SetAutoPong(true) + + // 设置心跳检测信息(心跳检测周期10秒;缺少2个心跳周期断开连接) + server.SetHeartbeatDetectInfo(2, time.Second*10) + + // 广播测试 + go func() { + var i = 0 + for { + time.Sleep(time.Second * 10) + // 广播测试 + server.BroadcastMessage(websocket.TextMessage, []byte(strconv.Itoa(i))) + i++ + } + }() + + wg := sync.WaitGroup{} + server.Start(&wg) +} diff --git a/trunk/framework/websocketServer/wssServer.go b/trunk/framework/websocketServer/wssServer.go new file mode 100644 index 0000000..e60cbe6 --- /dev/null +++ b/trunk/framework/websocketServer/wssServer.go @@ -0,0 +1,84 @@ +// ************************************ +// @package: websocketServer +// @description: websocket加密服务端 +// @author: +// @revision history: +// @create date: 2022-02-15 16:18:45 +// ************************************ +package websocketServer + +import ( + "github.com/gorilla/websocket" + webServer "Framework/webServer" +) + +type WssServer struct { + *webServer.HttpsServer + + // websocket连接管理 + *connManager +} + +// RegisterWebsocketHandler +// @description: 注册websocket回调 +// parameter: +// +// @receiver ws: +// @path:注册的访问路径 +// @handlerFuncObj:回调方法 +// @configObj:Handler配置对象 +// +// return: +func (wss *WssServer) RegisterWebsocketHandler(path string, eventCallback *EventCallbackFuncs, configObj *webServer.HandlerConfig) { + wss.RegisterHandlerWithUserData(path, hookHandler, configObj, &userDatas{ + server: wss, + eventCallback: eventCallback, + }) +} + +// RegisterRegexWebsocketHandler +// @description: 注册正则websocket回调 +// parameter: +// +// @receiver wss: +// @path:注册的正则访问路径 +// @eventCallback:回调方法 +// @configObj:Handler配置对象 +// +// return: +func (wss *WssServer) RegisterRegexWebsocketHandler(path string, eventCallback *EventCallbackFuncs, configObj *webServer.HandlerConfig) { + wss.RegisterRegexHandlerWithUserData(path, hookHandler, configObj, &userDatas{ + server: wss, + eventCallback: eventCallback, + }) +} + +func NewWssServer(addr, certFileName, keyFileName string, isCheckIP bool) (server *WssServer) { + server = &WssServer{ + HttpsServer: webServer.NewHttpsServer(addr, certFileName, keyFileName, isCheckIP), + connManager: &connManager{ + upgrader: &websocket.Upgrader{}, + allConns: make(map[*websocket.Conn]*Context), + }, + } + + // 开启心跳检测协程 + server.connManager.heartbeatDetect() + + return +} + +func NewHttpsServer2(addr, certFileName, keyFileName string, webServerObj webServer.IWebServer) (server *WssServer) { + server = &WssServer{ + HttpsServer: webServer.NewHttpsServer2(addr, certFileName, keyFileName, webServerObj), + connManager: &connManager{ + upgrader: &websocket.Upgrader{}, + allConns: make(map[*websocket.Conn]*Context), + }, + } + + // 开启心跳检测协程 + server.connManager.heartbeatDetect() + + return +} diff --git a/trunk/framework/zooKeeperMgr/create.go b/trunk/framework/zooKeeperMgr/create.go new file mode 100644 index 0000000..d4c7767 --- /dev/null +++ b/trunk/framework/zooKeeperMgr/create.go @@ -0,0 +1,50 @@ +package zooKeeperMgr + +import ( + "fmt" + "strings" + + "github.com/samuel/go-zookeeper/zk" +) + +// 级联创建path节点(如果中间节点不存在,则创建) +func CascadeCreate(conn *zk.Conn, path string) error { + if path == "" { + return nil + } + + subPathArr := strings.Split(path, "/") + path = "" + var data = []byte("") + var flags int32 = 0 + var acls = zk.WorldACL(zk.PermAll) + + for i := 0; i < len(subPathArr); i++ { + // Omit the first one which is / + if subPathArr[i] == "" { + continue + } + + path += fmt.Sprintf("/%s",subPathArr[i]) + fmt.Printf("Path:%s\n",path) + exists, _, err := conn.Exists(path) + if err != nil { + return err + } + if exists { + continue + } + + retPath, err := conn.Create(path, data, flags, acls) + if err != nil { + if err == zk.ErrNodeExists { + fmt.Println(err) + continue + } + return err + } + fmt.Printf("ZooKeeper created:%s\n", retPath) + } + + return nil +} diff --git a/trunk/framework/zooKeeperMgr/create_test.go b/trunk/framework/zooKeeperMgr/create_test.go new file mode 100644 index 0000000..efc382b --- /dev/null +++ b/trunk/framework/zooKeeperMgr/create_test.go @@ -0,0 +1,27 @@ +package zooKeeperMgr + +import ( + "testing" + "time" + + "github.com/samuel/go-zookeeper/zk" +) + +func TestCascadeCreate(t *testing.T) { + zkConfigObj := &ZooKeeperConfig { + Servers: "172.27.0.4:2181,172.27.0.6:2181,172.27.0.9:2181", + StartPath: "/Develop/GameServer", + SessionTimeout: 10 * time.Second, + } + + conn, _, err := zk.Connect(zkConfigObj.GetServerList(), zkConfigObj.SessionTimeout) + if err != nil { + panic(err) + } + defer conn.Close() + + err = CascadeCreate(conn, zkConfigObj.StartPath) + if err != nil { + t.Errorf("There should be no error, but now there is:%v", err) + } +} \ No newline at end of file diff --git a/trunk/framework/zooKeeperMgr/zooKeeperConfig.go b/trunk/framework/zooKeeperMgr/zooKeeperConfig.go new file mode 100644 index 0000000..167b0bc --- /dev/null +++ b/trunk/framework/zooKeeperMgr/zooKeeperConfig.go @@ -0,0 +1,27 @@ +package zooKeeperMgr + +import ( + "strings" + "time" +) + +// ZooKeeper配置对象 +type ZooKeeperConfig struct { + // ZooKeeper的服务器地址(如果有多个地址,则用,分隔) + Servers string + + // 起始节点路径 + StartPath string + + // 会话超时时间(单位:秒) + SessionTimeout time.Duration +} + +// 获取所有的ZooKeeper服务器列表 +func (this *ZooKeeperConfig) GetServerList() []string { + return strings.Split(this.Servers, ",") +} + +func (this *ZooKeeperConfig) Parse() { + this.SessionTimeout = this.SessionTimeout * time.Second +} diff --git a/trunk/goutil/.idea/.gitignore b/trunk/goutil/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/trunk/goutil/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/trunk/goutil/.idea/goutil.iml b/trunk/goutil/.idea/goutil.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/trunk/goutil/.idea/goutil.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/trunk/goutil/.idea/misc.xml b/trunk/goutil/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/trunk/goutil/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/trunk/goutil/.idea/modules.xml b/trunk/goutil/.idea/modules.xml new file mode 100644 index 0000000..5079912 --- /dev/null +++ b/trunk/goutil/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/trunk/goutil/.idea/vcs.xml b/trunk/goutil/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/trunk/goutil/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/trunk/goutil/app-environment/env.go b/trunk/goutil/app-environment/env.go new file mode 100644 index 0000000..105f46e --- /dev/null +++ b/trunk/goutil/app-environment/env.go @@ -0,0 +1,416 @@ +// ************************************ +// @package: app_environment +// @description: 模拟进程级别的环境变量功能 +// 注意,此文件非线程安全,请注意使用,设计上是在init的过程中存放,在运行过程中只读取 +// @author: byron +// @revision history: +// @create date: 2022-01-25 16:54:47 +// ************************************ +package app_environment + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" +) + +var ( + kv map[string]string +) + +func init() { + kv = make(map[string]string) +} + +// Reset +// @description: 重置环境变量 +// parameter: +// @kvmap: +// return: +// @error: +func Reset(kvmap map[string]string) error { + if kvmap == nil { + return fmt.Errorf("app_environment.Reset 不允许使用nil map做参数") + } + + kv = kvmap + return nil +} + +// Set +// @description: 设置环境变量,如果存在,会返回error;如果要覆盖,请使用 SetForCover 方法 +// parameter: +// @k:key +// @v:value +// return: +// @error:如果已经存在,则返回error +func Set(k, v string) error { + if _, eixsts := kv[k]; eixsts { + return errors.New(fmt.Sprintf("已经存在k=%s的缓存", k)) + } + + kv[k] = v + + return nil +} + +// SetMap +// @description: 设置环境变量,如果存在,会返回error;如果要覆盖,请使用 SetForCoverMap 方法 +// parameter: +// @kvmap: +// return: +// @error: +func SetMap(kvmap map[string]string) error { + for k, v := range kvmap { + if err := Set(k, v); err != nil { + return err + } + } + + return nil +} + +// SetForCover +// @description: 设置环境变量,如果存在,则覆盖 +// parameter: +// @k:key +// @v:value +// return: +func SetForCover(k, v string) { + kv[k] = v +} + +// SetForCoverMap +// @description: 设置环境变量,如果存在,则覆盖 +// parameter: +// @kvmap: +// return: +func SetForCoverMap(kvmap map[string]string) { + for k, v := range kvmap { + SetForCover(k, v) + } +} + +// SetForNoExists +// @description: 如果不存在,则设置 +// parameter: +// @k:key +// @v:value +// return: +func SetForNoExists(k, v string) { + if _, eixsts := kv[k]; eixsts { + return + } + + kv[k] = v +} + +// SetForNoExistsMap +// @description: 如果不存在,则设置 +// parameter: +// @kvmap:要设置的字典 +// return: +func SetForNoExistsMap(kvmap map[string]string) { + for k, v := range kvmap { + SetForNoExists(k, v) + } +} + +// GetAll +// @description: 获取所有的环境变量 +// parameter: +// return: +// @map[string]string:环境变量内容 +func GetAll() map[string]string { + tempKv := make(map[string]string, len(kv)) + for k, v := range kv { + tempKv[k] = v + } + + return tempKv +} + +// Get +// @description: 获取环境变量 +// parameter: +// @k:配置项 +// return: +// @string:value +// @bool:是否存在对应的值 +func Get(k string) (string, bool) { + v, exists := kv[k] + + return v, exists +} + +// GetOrDefault +// @description: 获取环境变量,未配置情况下使用默认值 +// parameter: +// @k:配置项 +// @defaultValue:默认值 +// return: +// @string:结果值 +func GetOrDefault(k string, defaultValue string) string { + v, ok := Get(k) + if !ok { + return defaultValue + } + + return v +} + +// GetInt +// @description: 获取环境变量 +// parameter: +// @k:key +// return: +// @int:value +// @bool:是否存在对应的值 +// @error:配置值解析失败时返回 +func GetInt(k string) (int, bool, error) { + v, isok := Get(k) + if isok == false { + return 0, false, nil + } + + i, err := strconv.Atoi(v) + if err != nil { + return 0, true, err + } + + return i, true, nil +} + +// GetIntOrDefault +// @description: 获取环境变量,未配置情况下使用默认值 +// parameter: +// @k:配置项 +// @defaultValue:默认值 +// return: +// @int:结果值 +// @error:配置值解析失败时返回 +func GetIntOrDefault(k string, defaultValue int) (int, error) { + val, ok, err := GetInt(k) + if err != nil || !ok { + return defaultValue, err + } + + return val, nil +} + +// GetInt32 +// @description: 获取环境变量 +// parameter: +// @k:key +// return: +// @int:value +// @bool:是否存在对应的值 +// @error:配置值解析失败时返回 +func GetInt32(k string) (int32, bool, error) { + v, isok := Get(k) + if isok == false { + return 0, false, nil + } + + i, err := strconv.ParseInt(v, 10, 32) + if err != nil { + return 0, true, err + } + + return int32(i), true, nil +} + +// GetInt32OrDefault +// @description: 获取环境变量,未配置情况下使用默认值 +// parameter: +// @k:配置项 +// @defaultValue:默认值 +// return: +// @int:结果值 +// @error:配置值解析失败时返回 +func GetInt32OrDefault(k string, defaultValue int32) (int32, error) { + val, ok, err := GetInt32(k) + if err != nil || !ok { + return defaultValue, err + } + + return val, nil +} + +// GetInt64 +// @description: 获取环境变量 +// parameter: +// @k:key +// return: +// @int64:value +// @bool:是否存在对应的值 +// @error:过程是否出错 +func GetInt64(k string) (int64, bool, error) { + v, isok := Get(k) + if isok == false { + return 0, false, nil + } + + i64, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return 0, true, err + } + + return i64, true, nil +} + +// GetInt64OrDefault +// @description: 获取环境变量,未配置情况下使用默认值 +// parameter: +// @k:配置项 +// @defaultValue:默认值 +// return: +// @int:结果值 +// @error:配置值解析失败时返回 +func GetInt64OrDefault(k string, defaultValue int64) (int64, error) { + val, ok, err := GetInt64(k) + if err != nil || !ok { + return defaultValue, err + } + + return val, nil +} + +// GetFloat32 +// @description: 获取环境变量 +// parameter: +// @k:key +// return: +// @int64:value +// @bool:是否存在对应的值 +// @error:过程是否出错 +func GetFloat32(k string) (float32, bool, error) { + v, isok := Get(k) + if isok == false { + return 0, false, nil + } + + f64, err := strconv.ParseFloat(v, 32) + if err != nil { + return 0, true, err + } + + return float32(f64), true, nil +} + +// GetFloat32OrDefault +// @description: 获取环境变量,未配置情况下使用默认值 +// parameter: +// @k:配置项 +// @defaultValue:默认值 +// return: +// @int:结果值 +// @error:配置值解析失败时返回 +func GetFloat32OrDefault(k string, defaultValue float32) (float32, error) { + val, ok, err := GetFloat32(k) + if err != nil || !ok { + return defaultValue, err + } + + return val, nil +} + +// GetFloat64 +// @description: 获取环境变量 +// parameter: +// @k:key +// return: +// @int64:value +// @bool:是否存在对应的值 +// @error:过程是否出错 +func GetFloat64(k string) (float64, bool, error) { + v, isok := Get(k) + if isok == false { + return 0, false, nil + } + + f64, err := strconv.ParseFloat(v, 64) + if err != nil { + return 0, true, err + } + + return f64, true, nil +} + +// GetFloat64OrDefault +// @description: 获取环境变量,未配置情况下使用默认值 +// parameter: +// @k:配置项 +// @defaultValue:默认值 +// return: +// @int:结果值 +// @error:配置值解析失败时返回 +func GetFloat64OrDefault(k string, defaultValue float64) (float64, error) { + val, ok, err := GetFloat64(k) + if err != nil || !ok { + return defaultValue, err + } + + return val, nil +} + +// GetBool +// @description: 获取环境变量 +// parameter: +// @k:key +// return: +// @bool:value +// @bool:是否存在对应的值 +// @error:过程是否出错 +func GetBool(k string) (bool, bool, error) { + v, isok := Get(k) + if isok == false { + return false, false, nil + } + + i, err := strconv.ParseBool(v) + if err != nil { + return false, true, err + } + + return i, true, nil +} + +// GetBoolOrDefault +// @description: 获取环境变量,未配置情况下使用默认值 +// parameter: +// @k:配置项 +// @defaultValue:默认值 +// return: +// @int:结果值 +// @error:配置值解析失败时返回 +func GetBoolOrDefault(k string, defaultValue bool) (bool, error) { + val, ok, err := GetBool(k) + if err != nil || !ok { + return defaultValue, err + } + + return val, nil +} + +// GetModel +// @description: 获取环境变量,并将内容序列化到obj对象中返回 +// parameter: +// @k:key +// @obj:value +// return: +// @bool:是否存在对应的值 +// @error:过程是否出错 +func GetModel(k string, obj interface{}) (bool, error) { + v, isok := Get(k) + if isok == false { + return false, nil + } + + if err := json.Unmarshal([]byte(v), obj); err != nil { + return true, err + } + + return true, nil +} diff --git a/trunk/goutil/app-environment/env_test.go b/trunk/goutil/app-environment/env_test.go new file mode 100644 index 0000000..6edb5a1 --- /dev/null +++ b/trunk/goutil/app-environment/env_test.go @@ -0,0 +1,265 @@ +package app_environment + +import ( + "encoding/json" + "fmt" + "testing" +) + +func Test_Set(t *testing.T) { + var err error + err = Set("a", "a") + if err != nil { + t.Error(err) + } + + err = Set("b", "b") + if err != nil { + t.Error(err) + } + + err = Set("c", "1") + if err != nil { + t.Error(err) + } + + err = Set("d", "2") + if err != nil { + t.Error(err) + } + + err = Set("e", "true") + if err != nil { + t.Error(err) + } + + err = Set("a", "a") + if err == nil { + t.Error("Set覆盖存在问题") + } + + tm := &tempkv{ + Name: "byron", + } + str, _ := json.Marshal(tm) + Set("str", string(str)) + + kvmap := make(map[string]string) + kvmap["a_1"] = "aaa" + err = SetMap(kvmap) + if err != nil { + t.Error(err) + } + + err = SetMap(kvmap) + if err == nil { + t.Error("SetMap覆盖存在问题") + } + + kvmap = make(map[string]string) + kvmap["b_aa"] = "b_aa" + SetForCoverMap(kvmap) + SetForCoverMap(kvmap) + + SetForCover("a", "a") +} + +func Test_Get(t *testing.T) { + m := GetAll() + if len(m) != 8 { + t.Error(fmt.Sprintf("getall错误 m=%v", len(m))) + } + + v, isok := Get("a") + if isok == false { + t.Error("未获取到a") + } + if v != "a" { + t.Error("get值错误") + } + + var i int + var err error + i, isok, err = GetInt("c") + if err != nil { + t.Error("GetInt 出错 err:", err) + } + if isok == false { + t.Error("GetInt 获取失败") + } + if i != 1 { + t.Error("GetInt值失败") + } + + var i32 int32 + i32, isok, err = GetInt32("c") + if err != nil { + t.Error("GetInt 出错 err:", err) + } + if isok == false { + t.Error("GetInt 获取失败") + } + if i32 != 1 { + t.Error("GetInt值失败") + } + + var i64 int64 + i64, isok, err = GetInt64("d") + if err != nil { + t.Error("GetInt64 出错 err:", err) + } + if isok == false { + t.Error("GetInt64 获取失败") + } + if i64 != 2 { + t.Error("GetInt64 值失败") + } + + var b bool + b, isok, err = GetBool("e") + if err != nil { + t.Error("GetBool 出错 err:", err) + } + if isok == false { + t.Error("GetBool 获取失败") + } + if b == false { + t.Error("GetBool 值失败") + } + + tm := &tempkv{} + isok, err = GetModel("str", tm) + if err != nil { + t.Error("GetModel 出错 err:", err) + } + if isok == false { + t.Error("GetModel 获取失败") + } + if tm.Name != "byron" { + t.Error("GetModel 值失败") + } +} + +func Test_GetOrDefault(t *testing.T) { + v := GetOrDefault("notExists", "default") + if v != "default" { + t.Error("GetOrDefault 返回默认值不正确") + } + + Set("stringTest", "ok") + v = GetOrDefault("stringTest", "default") + if v != "ok" { + t.Error("GetOrDefault 返回值不正确") + } + + i, err := GetIntOrDefault("notExists", -99) + if err != nil { + t.Error(err) + } + + if i != -99 { + t.Error("GetIntOrDefault 返回默认值不正确") + } + + Set("intTest", "99") + i, err = GetIntOrDefault("intTest", -99) + if err != nil { + t.Error(err) + } + if i != 99 { + t.Error("GetIntOrDefault 返回值不正确") + } + + Set("intTestError", "a99") + i, err = GetIntOrDefault("intTestError", -99) + if err == nil { + t.Error("GetIntOrDefault 解析时错误未抛出") + } + + i32, err := GetInt32OrDefault("notExists", -99) + if err != nil { + t.Error(err) + } + + if i32 != -99 { + t.Error("GetInt32OrDefault 返回默认值不正确") + } + + Set("int32Test", "99") + i32, err = GetInt32OrDefault("int32Test", -99) + if err != nil { + t.Error(err) + } + if i32 != 99 { + t.Error("GetInt32OrDefault 返回值不正确") + } + + Set("int32TestError", "a99") + i32, err = GetInt32OrDefault("int32TestError", -99) + if err == nil { + t.Error("GetInt32OrDefault 解析时错误未抛出") + } + + i64, err := GetInt64OrDefault("notExists", -99) + if err != nil { + t.Error(err) + } + + if i64 != -99 { + t.Error("GetInt64OrDefault 返回默认值不正确") + } + + Set("int64Test", "99") + i64, err = GetInt64OrDefault("int64Test", -99) + if err != nil { + t.Error(err) + } + if i64 != 99 { + t.Error("GetInt64OrDefault 返回值不正确") + } + + Set("int64TestError", "a99") + i64, err = GetInt64OrDefault("int64TestError", -99) + if err == nil { + t.Error("GetInt64OrDefault 解析时错误未抛出") + } + + b, err := GetBoolOrDefault("notExists", true) + if err != nil { + t.Error(err) + } + + if !b { + t.Error("GetBoolOrDefault 返回默认值不正确") + } + + Set("boolTest", "true") + b, err = GetBoolOrDefault("boolTest", false) + if err != nil { + t.Error(err) + } + if !b { + t.Error("GetBoolOrDefault 返回值不正确") + } + + Set("boolTestError", "sss") + b, err = GetBoolOrDefault("boolTestError", true) + if err == nil { + t.Error("GetBoolOrDefault 解析时错误未抛出") + } +} + +func Test_Reset(t *testing.T) { + if err := Reset(nil); err == nil { + t.Error("Reset nil参数错误未抛出") + } + + m := make(map[string]string) + if err := Reset(m); err != nil { + t.Error("Reset 错误的抛出错误") + } +} + +type tempkv struct { + Name string +} diff --git a/trunk/goutil/appChargeUtil/doc.go b/trunk/goutil/appChargeUtil/doc.go new file mode 100644 index 0000000..c9bfdad --- /dev/null +++ b/trunk/goutil/appChargeUtil/doc.go @@ -0,0 +1,4 @@ +/* +用于提供验证AppStore充值的逻辑 +*/ +package appChargeUtil diff --git a/trunk/goutil/appChargeUtil/receipt.go b/trunk/goutil/appChargeUtil/receipt.go new file mode 100644 index 0000000..195fd01 --- /dev/null +++ b/trunk/goutil/appChargeUtil/receipt.go @@ -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 +} diff --git a/trunk/goutil/appChargeUtil/receipt_test.go b/trunk/goutil/appChargeUtil/receipt_test.go new file mode 100644 index 0000000..3e7eb5a --- /dev/null +++ b/trunk/goutil/appChargeUtil/receipt_test.go @@ -0,0 +1,57 @@ +package appChargeUtil + +import ( + "fmt" + "testing" +) + +func TestValidateCharge(t *testing.T) { + bundleIdentifierList := make([]string, 0, 4) + productId := "" + + receiptObj, isValid, _ := ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false) + fmt.Printf("1. receiptObj:%s, isValid:%t\n", receiptObj, isValid) + if isValid != false { + t.Errorf("it should be invalid, but now valid\n") + return + } + + productId = "xh_6" + receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false) + fmt.Printf("2. receiptObj:%s, isValid:%t\n", receiptObj, isValid) + if isValid != false { + t.Errorf("it should be invalid, but now valid\n") + return + } + + bundleIdentifierList = append(bundleIdentifierList, "com.mqkk.game.xhs") + receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false) + fmt.Printf("3. receiptObj:%s, isValid:%t\n", receiptObj, isValid) + if isValid != false { + t.Errorf("it should be invalid, but now valid\n") + return + } + + bundleIdentifierList = append(bundleIdentifierList, "com.yh.game.xhs") + receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false) + fmt.Printf("4. receiptObj:%s, isValid:%t\n", receiptObj, isValid) + if isValid != true { + t.Errorf("it should be valid, but now invalid\n") + return + } + + bundleIdentifierList = append(bundleIdentifierList, "com.yh.game.xhs") + receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", true) + fmt.Printf("5. receiptObj:%s, isValid:%t\n", receiptObj, isValid) + if isValid == false { + receiptObj, isValid, _ = ValidateCharge(bundleIdentifierList, productId, "ewoJInNpZ25hdHVyZSIgPSAiQXlTRlJQTmEwUWljZTc3SzFpZ0QzMzZxa1JvdE5yRUlpMXlFMmdvYk9WSWg3Rm1tRS9DeGJFdDFzT0p5NEdXcFd3YUNDa1dhTDJ6OWRQek9xbXJ1TGMzc2E3NkR1ck5yTVNWbXNiR0paelltYWt0VjJGMnV4U2tIckp5bExWSk5QVG5jMHBZV1VXUi9CS3NHRTJxSm9ONG54ako0VTdWbUVwU0hoN1NxRVU5a24wUU5mcjhJbFNLUWFsR29mZXA4V3I5RUNqUGUrNHQ1b3NvUG5XZ3NDbGNiZ0dGYXkyamphcHFybWlUeW5sM3lQb3lLcWFYK0tsQlpTaFpTRHVkSXJRU2Z3bHZvb3BBcjZ5dGVNQTRITmZXVXU0RkFrcnZGZkpQVndpSXl4dDh6RVNlcSt0WVZXd1lmb24rSndXeHVndE1IaTRNNVZraE5ZR1F4dzlGaEkvTUFBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ERTJMVEV5TFRFMUlERTRPalEyT2pFMElFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5CMWNtTm9ZWE5sTFdSaGRHVXRiWE1pSUQwZ0lqRTBPREU0TlRZek56UTNNeklpT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTldJNE9HUmtZamxsTURjeVl6RTNNV1poWldFME56TTVNakppTmpNNU5tSXpaak0wT0dSaklqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKaWRuSnpJaUE5SUNJeExqTXVOVEFpT3dvSkltRndjQzFwZEdWdExXbGtJaUE5SUNJeE1UTTNPVEF3TXpjeElqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTWpRd01EQXdNamc1TXpZMU5qRXpJanNLQ1NKeGRXRnVkR2wwZVNJZ1BTQWlNU0k3Q2draWIzSnBaMmx1WVd3dGNIVnlZMmhoYzJVdFpHRjBaUzF0Y3lJZ1BTQWlNVFE0TVRnMU5qTTNORGN6TWlJN0Nna2lkVzVwY1hWbExYWmxibVJ2Y2kxcFpHVnVkR2xtYVdWeUlpQTlJQ0l5TXpKRVJFUkdOUzAzTVRBd0xUUXdRVE10T1RKRk5pMHlOVUl6TjBVd05rVTBRamNpT3dvSkltbDBaVzB0YVdRaUlEMGdJakV4TXpnd01qTTVOVE1pT3dvSkluWmxjbk5wYjI0dFpYaDBaWEp1WVd3dGFXUmxiblJwWm1sbGNpSWdQU0FpT0RFNU5qZzFOelE0SWpzS0NTSndjbTlrZFdOMExXbGtJaUE5SUNKNGFGODJJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxJaUE5SUNJeU1ERTJMVEV5TFRFMklEQXlPalEyT2pFMElFVjBZeTlIVFZRaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1USXRNVFlnTURJNk5EWTZNVFFnUlhSakwwZE5WQ0k3Q2draVltbGtJaUE5SUNKamIyMHVlV2d1WjJGdFpTNTRhSE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURFMkxURXlMVEUxSURFNE9qUTJPakUwSUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93cDkiOwoJInBvZCIgPSAiMjQiOwoJInNpZ25pbmctc3RhdHVzIiA9ICIwIjsKfQ==", false) + fmt.Printf("6. receiptObj:%s, isValid:%t\n", receiptObj, isValid) + if isValid != true { + t.Errorf("it should be valid, but now invalid\n") + return + } + } else { + t.Errorf("it should be invalid, but now valid\n") + return + } +} diff --git a/trunk/goutil/appChargeUtil/verify.go b/trunk/goutil/appChargeUtil/verify.go new file mode 100644 index 0000000..7a7e053 --- /dev/null +++ b/trunk/goutil/appChargeUtil/verify.go @@ -0,0 +1,82 @@ +package appChargeUtil + +import ( + "errors" + "fmt" + + "goutil/webUtil" +) + +const ( + con_SandBoxUrl = "https://sandbox.itunes.apple.com/verifyReceipt" + con_ProductionUrl = "https://buy.itunes.apple.com/verifyReceipt" +) + +var ( + NetworkError = errors.New("NetWorkError") +) + +// 验证充值是否有效 +// bundleIdentifierList:配置的BundleIdentifier列表 +// productId:输入的ProductId +// receiptData:订单数据 +// isSandBox:是否为沙盒模式 +// 返回值: +// 充值收据对象 +// 是否有效 +// 错误对象(如果err==NetWorkError,则表明为网络错误) +func ValidateCharge(bundleIdentifierList []string, productId, receiptData string, isSandBox bool) (receiptObj *Receipt, isValid bool, err error) { + // 判断参数是否为空 + if len(bundleIdentifierList) == 0 || productId == "" || receiptData == "" { + return + } + + // 获取Receipt对象 + receiptObj, err = getReceipt(receiptData, isSandBox) + if err != nil { + return + } + + if receiptObj.IsBundleIdentifierValid(bundleIdentifierList) == false { + return + } + + if receiptObj.IsProductIdValid(productId) == false { + return + } + + isValid = true + + return +} + +func getReceipt(receiptData string, isSandBox bool) (receiptObj *Receipt, err error) { + weburl := con_ProductionUrl + if isSandBox { + weburl = con_SandBoxUrl + } + + data := []byte(convertReceiptToPost(receiptData)) + statusCode, returnBytes, err := webUtil.PostByteData2(weburl, data, webUtil.GetFormHeader(), nil) + if err != nil { + err = NetworkError + return + } + if statusCode != 200 { + err = fmt.Errorf("StatusCode is wrong:%d", statusCode) + return + } + + if len(returnBytes) == 0 { + err = fmt.Errorf("返回的数据为空") + return + } + + receiptObj, err = newReceipt(string(returnBytes)) + + return +} + +func convertReceiptToPost(receiptData string) string { + return fmt.Sprintf("{\"receipt-data\":\"%s\"}", receiptData) +} diff --git a/trunk/goutil/baseUtil/base.go b/trunk/goutil/baseUtil/base.go new file mode 100644 index 0000000..f7cbff3 --- /dev/null +++ b/trunk/goutil/baseUtil/base.go @@ -0,0 +1,119 @@ +/* +一个处理不同进制的工具包;用于将十进制和其它进制进行互相转换 +*/ +package baseUtil + +import ( + "fmt" + "math" +) + +// 进制对象定义 +type Base struct { + elementList []string + base uint64 +} + +// 将10进制的uint64类型数据转换为字符串形式 +// source:10进制的uint64类型数据 +// 返回值: +// 对应进制的字符串形式 +func (this *Base) Transform(source uint64) (result string) { + quotient, remainder := uint64(0), source + + for { + quotient, remainder = remainder/this.base, remainder%this.base + result = this.elementList[remainder] + result + if quotient == 0 { + break + } + remainder = quotient + } + + return +} + +// 将字符串解析为10进制的uint64类型 +// source:对应进制的字符串形式 +// 返回值:10进制的uint64类型数据 +func (this *Base) Parse(source string) (result uint64) { + if source == "" { + return + } + + sourceList := make([]string, 0, len(source)) + for _, v := range source { + sourceList = append(sourceList, string(v)) + } + + for idx, exp := len(sourceList)-1, 0; idx >= 0; idx, exp = idx-1, exp+1 { + sourceItem := sourceList[idx] + + // Find the source item in the elementList + for i, v := range this.elementList { + if sourceItem == v { + result += uint64(float64(i) * math.Pow(float64(this.base), float64(exp))) + break + } + } + } + + return +} + +// 以指定的任意非重复的数组,来指定基于的进制数 +func New(elements string) (baseObj *Base, err error) { + if len(elements) == 0 { + err = fmt.Errorf("输入的字符数串为空") + return + } + + elementList := make([]string, 0, len(elements)) + elementMap := make(map[rune]struct{}, len(elements)) + for _, v := range elements { + if _, exist := elementMap[v]; exist { + err = fmt.Errorf("输入的字符串中含有重复的字符:%s", string(v)) + return + } else { + elementMap[v] = struct{}{} + elementList = append(elementList, string(v)) + } + } + + baseObj = &Base{ + elementList: elementList, + base: uint64(len(elementList)), + } + + return +} + +// 包含01 +func NewBase2() (baseObj *Base, err error) { + return New("01") +} + +// 包含0-7 +func NewBase8() (baseObj *Base, err error) { + return New("01234567") +} + +// 包含0-9,a-x +func NewBase16() (baseObj *Base, err error) { + return New("0123456789abcdef") +} + +// 包含a-z +func NewBase26() (baseObj *Base, err error) { + return New("abcdefghijklmnopqrstuvwxyz") +} + +// 包含0-9,a-z +func NewBase36() (baseObj *Base, err error) { + return New("0123456789abcdefghijklmnopqrstuvwxyz") +} + +// 包含0-9,a-z,A-Z +func NewBase62() (baseObj *Base, err error) { + return New("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") +} diff --git a/trunk/goutil/baseUtil/base_test.go b/trunk/goutil/baseUtil/base_test.go new file mode 100644 index 0000000..ce4bd83 --- /dev/null +++ b/trunk/goutil/baseUtil/base_test.go @@ -0,0 +1,394 @@ +package baseUtil + +import ( + "testing" +) + +func TestNew(t *testing.T) { + elements := "" + _, err := New(elements) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + elements = "00" + _, err = New(elements) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + elements = "01" + _, err = New(elements) + if err != nil { + t.Errorf("There should be no error, but now there is one:%v.", err) + return + } +} + +func TestNewBase2(t *testing.T) { + _, err := NewBase2() + if err != nil { + t.Errorf("There should be no error, but now there is one:%v.", err) + return + } +} + +func TestNewBase8(t *testing.T) { + _, err := NewBase8() + if err != nil { + t.Errorf("There should be no error, but now there is one:%v.", err) + return + } +} + +func TestNewBase16(t *testing.T) { + _, err := NewBase16() + if err != nil { + t.Errorf("There should be no error, but now there is one:%v.", err) + return + } +} + +func TestNewBase26(t *testing.T) { + _, err := NewBase26() + if err != nil { + t.Errorf("There should be no error, but now there is one:%v.", err) + return + } +} + +func TestNewBase36(t *testing.T) { + _, err := NewBase36() + if err != nil { + t.Errorf("There should be no error, but now there is one:%v.", err) + return + } +} + +func TestNewBase62(t *testing.T) { + _, err := NewBase62() + if err != nil { + t.Errorf("There should be no error, but now there is one:%v.", err) + return + } +} + +func TestTransform(t *testing.T) { + base2, _ := NewBase2() + base8, _ := NewBase8() + base16, _ := NewBase16() + base26, _ := NewBase26() + base36, _ := NewBase36() + base62, _ := NewBase62() + + var source uint64 = 0 + expected := "0" + got := base2.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "0" + got = base8.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "0" + got = base16.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "a" + got = base26.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "0" + got = base36.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "0" + got = base62.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + source = 1 + expected = "1" + got = base2.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "1" + got = base8.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "1" + got = base16.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "b" + got = base26.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "1" + got = base36.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "1" + got = base62.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + source = 2 + expected = "10" + got = base2.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "2" + got = base8.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "2" + got = base16.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "c" + got = base26.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "2" + got = base36.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "2" + got = base62.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + source = 100 + expected = "1100100" + got = base2.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "144" + got = base8.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "64" + got = base16.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "dw" + got = base26.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "2s" + got = base36.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + expected = "1C" + got = base62.Transform(source) + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } +} + +func TestParse(t *testing.T) { + base2, _ := NewBase2() + base8, _ := NewBase8() + base16, _ := NewBase16() + base26, _ := NewBase26() + base36, _ := NewBase36() + base62, _ := NewBase62() + + expected := uint64(0) + got := base2.Parse("0") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base8.Parse("0") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base16.Parse("0") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base26.Parse("a") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base36.Parse("0") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base62.Parse("0") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + + expected = uint64(1) + got = base2.Parse("1") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base8.Parse("1") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base16.Parse("1") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base26.Parse("b") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base36.Parse("1") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base62.Parse("1") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + + expected = uint64(2) + got = base2.Parse("10") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base8.Parse("2") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base16.Parse("2") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base26.Parse("c") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base36.Parse("2") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base62.Parse("2") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + + expected = uint64(100) + got = base2.Parse("1100100") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base8.Parse("144") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base16.Parse("64") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base26.Parse("dw") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base36.Parse("2s") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + got = base62.Parse("1C") + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } +} diff --git a/trunk/goutil/configUtil/doc.go b/trunk/goutil/configUtil/doc.go new file mode 100644 index 0000000..4d1f7af --- /dev/null +++ b/trunk/goutil/configUtil/doc.go @@ -0,0 +1,4 @@ +/* +配置助手类,用于处理以JSON格式存储的配置文件 +*/ +package configUtil diff --git a/trunk/goutil/configUtil/json.go b/trunk/goutil/configUtil/json.go new file mode 100644 index 0000000..b4a8d46 --- /dev/null +++ b/trunk/goutil/configUtil/json.go @@ -0,0 +1,99 @@ +package configUtil + +import ( + "encoding/json" + "fmt" + "io/ioutil" +) + +// 读取JSON格式的配置文件 +// config_file_path:配置文件路径 +// 返回值: +// 配置内容的map格式 +// 错误对象 +func ReadJsonConfig(config_file_path string) (config map[string]interface{}, err error) { + // 读取配置文件(一次性读取整个文件,则使用ioutil) + bytes, err := ioutil.ReadFile(config_file_path) + if err != nil { + err = fmt.Errorf("读取配置文件的内容出错:%s", err) + return + } + + // 使用json反序列化 + config = make(map[string]interface{}) + if err = json.Unmarshal(bytes, &config); err != nil { + err = fmt.Errorf("反序列化配置文件的内容出错:%s", err) + return + } + + return +} + +// 从config配置中获取int类型的配置值 +// config:从config文件中反序列化出来的map对象 +// configName:配置名称 +// 返回值: +// 配置值 +// 错误对象 +func ReadIntJsonValue(config map[string]interface{}, configName string) (value int, err error) { + configValue, exist := config[configName] + if !exist { + err = fmt.Errorf("不存在名为%s的配置或配置为空", configName) + return + } + + configValue_float, ok := configValue.(float64) + if !ok { + err = fmt.Errorf("%s必须为int型", configName) + return + } + value = int(configValue_float) + + return +} + +// 从config配置中获取string类型的配置值 +// config:从config文件中反序列化出来的map对象 +// configName:配置名称 +// 返回值: +// 配置值 +// 错误对象 +func ReadStringJsonValue(config map[string]interface{}, configName string) (value string, err error) { + configValue, exist := config[configName] + if !exist { + err = fmt.Errorf("不存在名为%s的配置或配置为空", configName) + return + } + + configValue_string, ok := configValue.(string) + if !ok { + err = fmt.Errorf("%s必须为string型", configName) + return + } + value = configValue_string + + return +} + +// 从config配置中获取string类型的配置值 +// config:从config文件中反序列化出来的map对象 +// configName:配置名称 +// 返回值: +// 配置值 +// 错误对象 +func ReadBoolJsonValue(config map[string]interface{}, configName string) (value bool, err error) { + configValue, exist := config[configName] + if !exist { + err = fmt.Errorf("不存在名为%s的配置或配置为空", configName) + return + } + + configValue_bool, ok := configValue.(bool) + if !ok { + err = fmt.Errorf("%s必须为bool型", configName) + return + } + value = configValue_bool + + return +} diff --git a/trunk/goutil/configUtil/jsonArray.go b/trunk/goutil/configUtil/jsonArray.go new file mode 100644 index 0000000..5eb1109 --- /dev/null +++ b/trunk/goutil/configUtil/jsonArray.go @@ -0,0 +1,109 @@ +package configUtil + +import ( + "encoding/json" + "fmt" + "io/ioutil" +) + +// 读取JSON格式的配置文件 +// config_file_path:配置文件路径 +// 返回值: +// 配置内容的map格式数组 +// 错误对象 +func ReadJsonConfig_Array(config_file_path string) ([]map[string]interface{}, error) { + // 读取配置文件(一次性读取整个文件,则使用ioutil) + bytes, err := ioutil.ReadFile(config_file_path) + if err != nil { + return nil, fmt.Errorf("读取配置文件的内容出错:%s", err) + } + + // 使用json反序列化 + config := make([]map[string]interface{}, 0, 4) + if err = json.Unmarshal(bytes, &config); err != nil { + return nil, fmt.Errorf("反序列化配置文件的内容出错:%s", err) + } + + return config, nil +} + +func getConfigValue(config []map[string]interface{}, configName string) (configValue interface{}, err error) { + var exist bool + for _, configItem := range config { + if configValue, exist = configItem[configName]; exist { + break + } + } + + if !exist { + err = fmt.Errorf("不存在名为%s的配置或配置为空", configName) + } + + return +} + +// 从config配置中获取int类型的配置值 +// config:从config文件中反序列化出来的map对象 +// configName:配置名称 +// 返回值: +// 配置值 +// 错误对象 +func ReadIntJsonValue_Array(config []map[string]interface{}, configName string) (value int, err error) { + configValue, err := getConfigValue(config, configName) + if err != nil { + return + } + + configValue_float, ok := configValue.(float64) + if !ok { + err = fmt.Errorf("%s必须为int型", configName) + return + } + value = int(configValue_float) + + return +} + +// 从config配置中获取string类型的配置值 +// config:从config文件中反序列化出来的map对象 +// configName:配置名称 +// 返回值: +// 配置值 +// 错误对象 +func ReadStringJsonValue_Array(config []map[string]interface{}, configName string) (value string, err error) { + configValue, err := getConfigValue(config, configName) + if err != nil { + return + } + + configValue_string, ok := configValue.(string) + if !ok { + err = fmt.Errorf("%s必须为string型", configName) + return + } + value = configValue_string + + return +} + +// 从config配置中获取string类型的配置值 +// config:从config文件中反序列化出来的map对象 +// configName:配置名称 +// 返回值: +// 配置值 +// 错误对象 +func ReadBoolJsonValue_Array(config []map[string]interface{}, configName string) (value bool, err error) { + configValue, err := getConfigValue(config, configName) + if err != nil { + return + } + + configValue_bool, ok := configValue.(bool) + if !ok { + err = fmt.Errorf("%s必须为bool型", configName) + return + } + value = configValue_bool + + return +} diff --git a/trunk/goutil/configUtil/jsonArray_test.go b/trunk/goutil/configUtil/jsonArray_test.go new file mode 100644 index 0000000..fbb3373 --- /dev/null +++ b/trunk/goutil/configUtil/jsonArray_test.go @@ -0,0 +1,53 @@ +package configUtil + +import ( + "testing" +) + +var ( + config_Array []map[string]interface{} + err_Array error +) + +func TestReadJsonConfig_Array(t *testing.T) { + config_Array, err_Array = ReadJsonConfig_Array("testdata/jsonConfigArray.ini") + if err_Array != nil { + t.Errorf("读取JSON配置失败,错误信息为:%s", err_Array) + } +} + +func TestReadIntJsonValue_Array(t *testing.T) { + actualValue, err_Array := ReadIntJsonValue_Array(config_Array, "ServerGroupId") + if err_Array != nil { + t.Errorf("读取JSON配置失败,错误信息为:%s", err_Array) + } + + expectedValue := 1 + if actualValue != expectedValue { + t.Errorf("期望的值为%d,实际的值为%d", expectedValue, actualValue) + } +} + +func TestReadStringJsonValue_Array(t *testing.T) { + actualValue, err_Array := ReadStringJsonValue_Array(config_Array, "ChatDBConnection") + if err_Array != nil { + t.Errorf("读取JSON配置失败,错误信息为:%s", err_Array) + } + + expectedValue := "root:moqikaka@tcp(192.168.1.226:3306)/chatserver?charset=utf8&parseTime=true&loc=Local&timeout=30s" + if actualValue != expectedValue { + t.Errorf("期望的值为%s,实际的值为%s", expectedValue, actualValue) + } +} + +func TestReadBoolJsonValue_Array(t *testing.T) { + actualValue, err_Array := ReadBoolJsonValue_Array(config_Array, "IfRecordMessage") + if err_Array != nil { + t.Errorf("读取JSON配置失败,错误信息为:%s", err_Array) + } + + expectedValue := true + if actualValue != expectedValue { + t.Errorf("期望的值为%v,实际的值为%v", expectedValue, actualValue) + } +} diff --git a/trunk/goutil/configUtil/json_test.go b/trunk/goutil/configUtil/json_test.go new file mode 100644 index 0000000..b77992c --- /dev/null +++ b/trunk/goutil/configUtil/json_test.go @@ -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) + } +} diff --git a/trunk/goutil/configUtil/testdata/jsonConfig.ini b/trunk/goutil/configUtil/testdata/jsonConfig.ini new file mode 100644 index 0000000..4b61100 --- /dev/null +++ b/trunk/goutil/configUtil/testdata/jsonConfig.ini @@ -0,0 +1,13 @@ +{ + "ServerGroupId": 1, + "ChatDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/chatserver?charset=utf8&parseTime=true&loc=Local&timeout=30s", + "ChatDBMaxOpenConns": 10, + "ChatDBMaxIdleConns": 5, + "ModelDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/dzz_model_online?charset=utf8&parseTime=true&loc=Local&timeout=30s", + "ModelDBMaxOpenConns": 0, + "ModelDBMaxIdleConns": 0, + "GameDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/dzz_online?charset=utf8&parseTime=true&loc=Local&timeout=30s", + "GameDBMaxOpenConns": 10, + "GameDBMaxIdleConns": 5, + "IfRecordMessage": true +} \ No newline at end of file diff --git a/trunk/goutil/configUtil/testdata/jsonConfigArray.ini b/trunk/goutil/configUtil/testdata/jsonConfigArray.ini new file mode 100644 index 0000000..c76b7ca --- /dev/null +++ b/trunk/goutil/configUtil/testdata/jsonConfigArray.ini @@ -0,0 +1,18 @@ +[ + { + "ServerGroupId": 1, + "ChatDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/chatserver?charset=utf8&parseTime=true&loc=Local&timeout=30s", + "ChatDBMaxOpenConns": 10, + "ChatDBMaxIdleConns": 5, + "ModelDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/dzz_model_online?charset=utf8&parseTime=true&loc=Local&timeout=30s", + "ModelDBMaxOpenConns": 0, + "ModelDBMaxIdleConns": 0, + "GameDBConnection": "root:moqikaka@tcp(192.168.1.226:3306)/dzz_online?charset=utf8&parseTime=true&loc=Local&timeout=30s", + "GameDBMaxOpenConns": 10, + "GameDBMaxIdleConns": 5 + }, + { + "AppId": "DZZ", + "IfRecordMessage": true + } +] \ No newline at end of file diff --git a/trunk/goutil/configUtil/xmlConfig.go b/trunk/goutil/configUtil/xmlConfig.go new file mode 100644 index 0000000..b7bd95a --- /dev/null +++ b/trunk/goutil/configUtil/xmlConfig.go @@ -0,0 +1,299 @@ +package configUtil + +import ( + "fmt" + "goutil/typeUtil" + "goutil/xmlUtil" + "reflect" + "strings" +) + +type XmlConfig struct { + root *xmlUtil.Node +} + +// 从文件加载 +// xmlFilePath:xml文件路径 +// 返回值: +// error:错误信息 +func (this *XmlConfig) LoadFromFile(xmlFilePath string) error { + if this.root != nil { + return fmt.Errorf("There has been an xml file loaded.") + } + + root, err := xmlUtil.LoadFromFile(xmlFilePath) + if err != nil { + return err + } + + this.root = root + + return nil +} + +// 从node节点加载(会取其根节点) +// xmlRoot:xml节点 +// 返回值: +// error:错误信息 +func (this *XmlConfig) LoadFromXmlNode(xmlRoot *xmlUtil.Node) error { + if this.root != nil { + return fmt.Errorf("There has been an xml file loaded.") + } + + if xmlRoot == nil { + return fmt.Errorf("xmlRoot is nil") + } + + this.root = xmlRoot + + return nil +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// bool:结果 +// error:错误信息 +func (this *XmlConfig) Bool(xpath string, attrName string) (bool, error) { + value, err := this.getVal(xpath, attrName) + if err != nil { + return false, err + } + + return typeUtil.Bool(value) +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +//  返回值: +// bool:结果 +func (this *XmlConfig) DefaultBool(xpath string, attrName string, defaultVal bool) bool { + value, err := this.Bool(xpath, attrName) + if err != nil { + return defaultVal + } + + return value +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// int:结果 +// error:错误信息 +func (this *XmlConfig) Int(xpath string, attrName string) (int, error) { + value, err := this.getVal(xpath, attrName) + if err != nil { + return 0, err + } + + return typeUtil.Int(value) +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +//  返回值: +// int:结果 +func (this *XmlConfig) DefaultInt(xpath string, attrName string, defaultVal int) int { + value, err := this.Int(xpath, attrName) + if err != nil { + return defaultVal + } + + return value +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// int64:结果 +// error:错误信息 +func (this *XmlConfig) Int64(xpath string, attrName string) (int64, error) { + value, err := this.getVal(xpath, attrName) + if err != nil { + return 0, err + } + + return typeUtil.Int64(value) +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +//  返回值: +// int64:结果 +func (this *XmlConfig) DefaultInt64(xpath string, attrName string, defaultVal int64) int64 { + value, err := this.Int64(xpath, attrName) + if err != nil { + return defaultVal + } + + return value + +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// float64:结果 +// error:错误信息 +func (this *XmlConfig) Float(xpath string, attrName string) (float64, error) { + value, err := this.getVal(xpath, attrName) + if err != nil { + return 0, err + } + + return typeUtil.Float64(value) +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +//  返回值: +// float64:结果 +func (this *XmlConfig) DefaultFloat(xpath string, attrName string, defaultVal float64) float64 { + value, err := this.Float(xpath, attrName) + if err != nil { + return defaultVal + } + + return value +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// string:结果 +// error:错误信息 +func (this *XmlConfig) String(xpath string, attrName string) (string, error) { + return this.getVal(xpath, attrName) +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +//  返回值: +// string:结果 +func (this *XmlConfig) DefaultString(xpath string, attrName string, defaultVal string) string { + value, err := this.String(xpath, attrName) + if err != nil { + return defaultVal + } + + return value +} + +// 获取指定位置的节点 +// xpath:xpath路径 +// 返回值: +// []*xmlUtil.Node:结果 +func (this *XmlConfig) Nodes(xpath string) []*xmlUtil.Node { + return this.root.SelectElements(xpath) +} + +// 获取指定位置的节点 +// xpath:xpath路径 +// 返回值: +// *xmlUtil.Node:结果 +func (this *XmlConfig) Node(xpath string) *xmlUtil.Node { + return this.root.SelectElement(xpath) +} + +// 反序列化指定的整个节点 +// xpath:xml的path +// data:反序列化得到的数据 +// 返回值: +// error:错误信息 +func (this *XmlConfig) Unmarshal(xpath string, data interface{}) error { + nodeItem := this.Node(xpath) + + //不存在节点,这里直接返回空的就行了 + if nodeItem == nil { + data = nil + return nil + //return fmt.Errorf("节点不存在,XPATH:%s", xpath) + } + + value := reflect.ValueOf(data) + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + dataType := value.Type() + + // 依次设置字段值 + var err error + fieldCount := value.NumField() + for i := 0; i < fieldCount; i++ { + fieldItem := value.Field(i) + fieldName := dataType.Field(i).Name + + // 读取数据 + var valueString string + tmpXpath := fmt.Sprintf("%s/%s", xpath, fieldName) + if valueString, err = this.getVal(tmpXpath, ""); err != nil { + valueString, err = this.getVal(xpath, fieldName) + if err != nil { + // 压根儿无此字段的配置数据,则略过 + continue + } + } + + // 字符串转换成目标值 + fieldValue, err := typeUtil.Convert(valueString, fieldItem.Kind()) + if err != nil { + return fmt.Errorf("读取字段失败, DataType:%s FieldName:%s Value:%v 错误信息:%v ", dataType.Name(), fieldName, valueString, err) + } + + // 设置到字段上面 + valType := reflect.ValueOf(fieldValue) + if valType.Type() == fieldItem.Type() { + fieldItem.Set(valType) + } else { + fieldItem.Set(valType.Convert(fieldItem.Type())) + } + } + + return nil +} + +// 获取指定路径的之 +// xpath:xpath路径 +// attrName:要获取的属性值,如果为空,则返回内部文本 +func (this *XmlConfig) getVal(xpath string, attrName string) (val string, err error) { + targetRoot := this.root.SelectElement(xpath) + if targetRoot == nil { + err = fmt.Errorf("no find target node:%v", xpath) + return + } + + if attrName == "" { + val = strings.TrimSpace(targetRoot.InnerText()) + return + } + + exist := false + val, exist = targetRoot.SelectAttr(attrName) + if exist == false { + err = fmt.Errorf("no find target attr, node:%v attr:%v", xpath, attrName) + return + } + + return +} + +// 创建新的xml配置对象 +func NewXmlConfig() *XmlConfig { + return &XmlConfig{} +} diff --git a/trunk/goutil/configUtil/xmlConfigList.go b/trunk/goutil/configUtil/xmlConfigList.go new file mode 100644 index 0000000..1b9eb7d --- /dev/null +++ b/trunk/goutil/configUtil/xmlConfigList.go @@ -0,0 +1,329 @@ +package configUtil + +import ( + "fmt" + "strings" + + "goutil/typeUtil" +) + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// []bool:结果 +// error:错误信息 +func (this *XmlConfig) BoolList(xpath string, attrName string) (result []bool, err error) { + result = make([]bool, 0) + + // 获取值列表 + valList, err := this.getValList(xpath, attrName) + if err != nil { + return + } + + // 转换成指定类型 + for _, valItem := range valList { + resultItem, err1 := typeUtil.Bool(valItem) + if err1 != nil { + err = err1 + return + } + + result = append(result, resultItem) + } + + return +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中 +//  返回值: +// []bool:结果 +func (this *XmlConfig) DefaultBoolList(xpath string, attrName string, defaultVal bool, ifAdddefaultVal bool) (result []bool) { + result = make([]bool, 0) + + // 获取值列表 + valList, err := this.getValList(xpath, attrName) + if err != nil { + if ifAdddefaultVal { + result = append(result, defaultVal) + } + + return result + } + + // 转换成指定类型 + for _, valItem := range valList { + resultItem, err := typeUtil.Bool(valItem) + if err != nil { + if ifAdddefaultVal { + result = append(result, defaultVal) + } + + continue + } + + result = append(result, resultItem) + } + + return result +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// []int:结果 +// error:错误信息 +func (this *XmlConfig) IntList(xpath string, attrName string) (result []int, err error) { + result = make([]int, 0) + + // 获取值列表 + valList, err := this.getValList(xpath, attrName) + if err != nil { + return result, err + } + + // 转换成指定类型 + for _, valItem := range valList { + resultItem, err := typeUtil.Int(valItem) + if err != nil { + return result, err + } + + result = append(result, resultItem) + } + + return result, nil +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中 +//  返回值: +// []int:结果 +func (this *XmlConfig) DefaultIntList(xpath string, attrName string, defaultVal int, ifAdddefaultVal bool) []int { + result := make([]int, 0) + + // 获取值列表 + valList, err := this.getValList(xpath, attrName) + if err != nil { + if ifAdddefaultVal { + result = append(result, defaultVal) + } + + return result + } + + // 转换成指定类型 + for _, valItem := range valList { + resultItem, err := typeUtil.Int(valItem) + if err != nil { + if ifAdddefaultVal { + result = append(result, defaultVal) + } + + continue + } + + result = append(result, resultItem) + } + + return result +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// []int64:结果 +// error:错误信息 +func (this *XmlConfig) Int64List(xpath string, attrName string) ([]int64, error) { + result := make([]int64, 0) + + // 获取值列表 + valList, err := this.getValList(xpath, attrName) + if err != nil { + return result, err + } + + // 转换成指定类型 + for _, valItem := range valList { + resultItem, err := typeUtil.Int64(valItem) + if err != nil { + return result, err + } + + result = append(result, resultItem) + } + + return result, nil +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中 +//  返回值: +// []int64:结果 +func (this *XmlConfig) DefaultInt64List(xpath string, attrName string, defaultVal int64, ifAdddefaultVal bool) []int64 { + result := make([]int64, 0) + + // 获取值列表 + valList, err := this.getValList(xpath, attrName) + if err != nil { + if ifAdddefaultVal { + result = append(result, defaultVal) + } + + return result + } + + // 转换成指定类型 + for _, valItem := range valList { + resultItem, err := typeUtil.Int64(valItem) + if err != nil { + if ifAdddefaultVal { + result = append(result, defaultVal) + } + + continue + } + + result = append(result, resultItem) + } + + return result +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// []float64:结果 +// error:错误信息 +func (this *XmlConfig) FloatList(xpath string, attrName string) ([]float64, error) { + result := make([]float64, 0) + + // 获取值列表 + valList, err := this.getValList(xpath, attrName) + if err != nil { + return result, err + } + + // 转换成指定类型 + for _, valItem := range valList { + resultItem, err := typeUtil.Float64(valItem) + if err != nil { + return result, err + } + + result = append(result, resultItem) + } + + return result, nil +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中 +//  返回值: +// []float64:结果 +func (this *XmlConfig) DefaultFloatList(xpath string, attrName string, defaultVal float64, ifAdddefaultVal bool) []float64 { + result := make([]float64, 0) + + // 获取值列表 + valList, err := this.getValList(xpath, attrName) + if err != nil { + if ifAdddefaultVal { + result = append(result, defaultVal) + } + + return result + } + + // 转换成指定类型 + for _, valItem := range valList { + resultItem, err := typeUtil.Float64(valItem) + if err != nil { + if ifAdddefaultVal { + result = append(result, defaultVal) + } + + continue + } + + result = append(result, resultItem) + } + + return result +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +//  返回值: +// string:结果 +// error:错误信息 +func (this *XmlConfig) StringList(xpath string, attrName string) ([]string, error) { + // 获取值列表 + return this.getValList(xpath, attrName) +} + +// 获取指定xpath路径下的值 +// xpath:xpath路径 +// attrName:属性名,如果为空,则返回节点的内部文本 +// defaultVal:默认值 +// ifAdddefaultVal:如果某项值转换失败,是否把默认值添加到结果集合中 +//  返回值: +// string:结果 +func (this *XmlConfig) DefaultStringList(xpath string, attrName string, defaultVal string, ifAdddefaultVal bool) []string { + result := make([]string, 0) + + // 获取值列表 + valList, err := this.getValList(xpath, attrName) + if err != nil { + if ifAdddefaultVal { + result = append(result, defaultVal) + } + + return result + } else { + return valList + } +} + +// 获取指定路径的之 +// xpath:xpath路径 +// attrName:要获取的属性值,如果为空,则返回内部文本 +func (this *XmlConfig) getValList(xpath string, attrName string) ([]string, error) { + result := make([]string, 0) + + targetNodeList := this.root.SelectElements(xpath) + if targetNodeList == nil { + return result, fmt.Errorf("no find target node:%v", xpath) + } + + // 依次获取各个节点 + for _, nodeItem := range targetNodeList { + val := "" + if attrName == "" { + val = strings.TrimSpace(nodeItem.InnerText()) + } else { + val, _ = nodeItem.SelectAttr(attrName) + } + + result = append(result, val) + } + + return result, nil +} diff --git a/trunk/goutil/configUtil/xmlConfigList_test.go b/trunk/goutil/configUtil/xmlConfigList_test.go new file mode 100644 index 0000000..13ca2b8 --- /dev/null +++ b/trunk/goutil/configUtil/xmlConfigList_test.go @@ -0,0 +1,135 @@ +package configUtil + +import ( + "fmt" + "testing" + + "goutil/xmlUtil" +) + +// bool值读取测试 +func TestBoolList(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigListData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + booList, errMsg := xmlConfigData.BoolList("html/body/ul/li/a", "id") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + fmt.Println("TestBoolList读取到的值:", booList) +} + +// int值读取测试 +func TestIntList(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigListData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + valList, errMsg := xmlConfigData.IntList("html/body/ul/li/a", "id") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + fmt.Println("TestInt读取到的值:", valList) +} + +// int64值读取测试 +func TestInt64List(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigListData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + valList, errMsg := xmlConfigData.Int64List("html/body/ul/li/a", "id") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + fmt.Println("TestInt64读取到的值:", valList) +} + +// Float值读取测试 +func TestFloatList(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigListData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + valList, errMsg := xmlConfigData.FloatList("html/body/ul/li/a", "id") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + fmt.Println("TestFloat读取到的值:", valList) +} + +// 字符串读取测试 +func TestStringList(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigListData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + valList, errMsg := xmlConfigData.StringList("html/body/ul/li/a", "id") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + fmt.Println("TestString读取到的值:", valList) +} + +func getxmlConfigListData() (xmlConfigData *XmlConfig, errMsg error) { + content := ` + + + Hello + + + +

This is a H1

+ +

+ Hello,This is an example for gxpath. +

+ + + + ` + var root *xmlUtil.Node + root, errMsg = xmlUtil.LoadFromString(content) + if errMsg == nil { + xmlConfigData = NewXmlConfig() + xmlConfigData.LoadFromXmlNode(root) + } + + return +} diff --git a/trunk/goutil/configUtil/xmlConfig_test.go b/trunk/goutil/configUtil/xmlConfig_test.go new file mode 100644 index 0000000..6790f46 --- /dev/null +++ b/trunk/goutil/configUtil/xmlConfig_test.go @@ -0,0 +1,321 @@ +package configUtil + +import ( + "fmt" + "testing" + + "goutil/xmlUtil" +) + +// bool值读取测试 +func TestBool(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + var ispost bool + ispost, errMsg = xmlConfigData.Bool("html/body", "IsPost") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + fmt.Println("读取到的值:", ispost) + if ispost == false { + t.Error("html/body的isPost读取错误") + t.Fail() + return + } + + ispost = xmlConfigData.DefaultBool("html/body", "IsPost", false) + if ispost == false { + t.Error("html/body的isPost读取错误") + t.Fail() + } +} + +// int值读取测试 +func TestInt(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + var id int + id, errMsg = xmlConfigData.Int("html/body/ul/li/a[@id=1]", "id") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + + if id != 1 { + t.Errorf("html/body的isPost读取错误,读取到的值:%v", id) + t.Fail() + return + } + + id = xmlConfigData.DefaultInt("html/body", "id", 2) + if id != 2 { + t.Error("TestInt html/body的id读取错误") + t.Fail() + } +} + +// int64值读取测试 +func TestInt64(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + var id int64 + id, errMsg = xmlConfigData.Int64("html/body/ul/li/a[@id=1]", "id") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + + if id != 1 { + t.Errorf("TestInt64 html/body/ul/li/a[@id=1]的id读取错误,读取到的值:%v", id) + t.Fail() + return + } + + id = xmlConfigData.DefaultInt64("html/body", "id", 2) + if id != 2 { + t.Error("TestInt64 html/body的id读取错误") + t.Fail() + } +} + +// Float值读取测试 +func TestFloat(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + var id float64 + id, errMsg = xmlConfigData.Float("html/body/ul/li/a[@id=1]", "dd") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + + if id != 1.1 { + t.Errorf("TestFloat html/body/ul/li/a[@id=1]的id读取错误,读取到的值:%v", id) + t.Fail() + return + } + + id = xmlConfigData.DefaultFloat("html/body", "id", 2) + if id != 2 { + t.Error("TestFloat html/body的id读取错误") + t.Fail() + } +} + +// 字符串读取测试 +func TestString(t *testing.T) { + xmlConfigData, errMsg := getxmlConfigData() + if errMsg != nil { + t.Error(errMsg) + t.Fail() + + return + } + + var id string + id, errMsg = xmlConfigData.String("html/body/ul/li/a[@id=1]", "dd") + if errMsg != nil { + t.Error(errMsg) + t.Fail() + return + } + + if id != "1.1" { + t.Errorf("TestString html/body/ul/li/a[@id=1]的id读取错误,读取到的值:%v", id) + t.Fail() + return + } + + id = xmlConfigData.DefaultString("html/body", "id", "2") + if id != "2" { + t.Error("TestString html/body的id读取错误") + t.Fail() + } +} + +type HelloStruct struct { + // 连接字符串 + ConnectionString string + + // 最大开启连接数量 + MaxOpenConns int `xml:",attr"` + + // 最大空闲连接数量 + MaxIdleConns int `xml:",attr"` +} + +func (this *HelloStruct) Equal(other *HelloStruct) bool { + return this.MaxOpenConns == other.MaxOpenConns && this.MaxIdleConns == other.MaxIdleConns +} + +func TestUnmarshal(t *testing.T) { + data, _ := getxmlConfigData2(` + + + + + + `) + + val := &HelloStruct{} + err := data.Unmarshal("/DBConnection/GameServerCenterDB", val) + if err != nil { + t.Error(err) + return + } + + want := &HelloStruct{ + ConnectionString: "root:moqikaka3312@tcp(10.1.0.10:3312)/2_gsc_develop?charset=utf8&parseTime=true&loc=Local&timeout=60s", + MaxOpenConns: 10, + MaxIdleConns: 5, + } + if want.Equal(val) == false { + t.Errorf("Expected %v, but now got %v", want, val) + } +} + +type ConnsNum int +type Hello2Struct struct { + // 连接字符串 + ConnectionString string + + // 最大开启连接数量 + MaxOpenConns ConnsNum + + // 最大空闲连接数量 + MaxIdleConns ConnsNum +} + +func (this *Hello2Struct) Equal(other *Hello2Struct) bool { + return this.MaxOpenConns == other.MaxOpenConns && this.MaxIdleConns == other.MaxIdleConns +} + +func TestUnmarshal2(t *testing.T) { + data, _ := getxmlConfigData2(` + + + + + + `) + + val := &Hello2Struct{} + err := data.Unmarshal("/DBConnection/GameServerCenterDB", val) + if err != nil { + t.Error(err) + return + } + + want := &Hello2Struct{ + ConnectionString: "root:moqikaka3312@tcp(10.1.0.10:3312)/2_gsc_develop?charset=utf8&parseTime=true&loc=Local&timeout=60s", + MaxOpenConns: 10, + MaxIdleConns: 5, + } + if want.Equal(val) == false { + t.Errorf("Expected %v, but now got %v", want, val) + } +} + +func TestUnmarshal3(t *testing.T) { + data, _ := getxmlConfigData2(` + + + + + + `) + + val := &HelloStruct{} + err := data.Unmarshal("/DBConnection/GameServerCenterDB", val) + if err != nil { + t.Error(err) + return + } + + want := &HelloStruct{ + ConnectionString: "root:moqikaka3312@tcp(10.1.0.10:3312)/2_gsc_develop?charset=utf8&parseTime=true&loc=Local&timeout=60s", + MaxOpenConns: 10, + MaxIdleConns: 0, + } + if want.Equal(val) == false { + t.Errorf("Expected %v, but now got %v", want, val) + } +} + +func getxmlConfigData() (xmlConfigData *XmlConfig, errMsg error) { + content := ` + + + Hello + + + +

This is a H1

+ +

+ Hello,This is an example for gxpath. +

+ + + + ` + var root *xmlUtil.Node + root, errMsg = xmlUtil.LoadFromString(content) + if errMsg == nil { + xmlConfigData = NewXmlConfig() + xmlConfigData.LoadFromXmlNode(root) + } + + return +} + +func getxmlConfigData2(xml string) (xmlConfigData *XmlConfig, errMsg error) { + var root *xmlUtil.Node + root, errMsg = xmlUtil.LoadFromString(xml) + if errMsg == nil { + xmlConfigData = NewXmlConfig() + xmlConfigData.LoadFromXmlNode(root) + } + + return +} diff --git a/trunk/goutil/coroutine-timer/.gitignore b/trunk/goutil/coroutine-timer/.gitignore new file mode 100644 index 0000000..1a1566e --- /dev/null +++ b/trunk/goutil/coroutine-timer/.gitignore @@ -0,0 +1,2 @@ +Log/* +logs/* \ No newline at end of file diff --git a/trunk/goutil/coroutine-timer/cmd.go b/trunk/goutil/coroutine-timer/cmd.go new file mode 100644 index 0000000..1ddcb42 --- /dev/null +++ b/trunk/goutil/coroutine-timer/cmd.go @@ -0,0 +1,47 @@ +package coroutine_timer + +const ( + // 添加 + cmd_add = 1 + + // 删除 + cmd_del = 2 +) + +// cmdModel +// @description: 命令对象 +type cmdModel struct { + // cmd 指令 + cmd int + + // paramObj 指令参数 + paramObj interface{} + + // resObj 指令返回对象 + resObj interface{} + + // err 指令返回的错误 + err error + + // waitChan 等待channel + waitChan chan struct{} +} + +// newCmdModel +// @description: 创建cmd模型对象 +// parameter: +// @c:cmd命令 +// @po:参数 +// return: +// @*cmdModel: +func newCmdModel(c int, po interface{}) *cmdModel { + result := &cmdModel{ + cmd: c, + paramObj: po, + resObj: nil, + err: nil, + waitChan: make(chan struct{}, 1), + } + + return result +} diff --git a/trunk/goutil/coroutine-timer/model.go b/trunk/goutil/coroutine-timer/model.go new file mode 100644 index 0000000..58209b8 --- /dev/null +++ b/trunk/goutil/coroutine-timer/model.go @@ -0,0 +1,73 @@ +package coroutine_timer + +// timersModel +// @description: timer卡槽对象 +type timersModel struct { + timers map[string]*timerObj +} + +// newTimersModel +// @description: 构造timer卡槽对象 +// parameter: +// return: +// @*timersModel: +func newTimersModel() *timersModel { + return &timersModel{timers: map[string]*timerObj{}} +} + +// addTimer +// @description: 添加定时器 +// parameter: +// @receiver this: +// @t: +// return: +func (this *timersModel) addTimer(t *timerObj) { + this.timers[t.id] = t +} + +// delTimer +// @description: 删除定时器 +// parameter: +// @receiver this: +// @id: +// return: +func (this *timersModel) delTimer(id string) { + delete(this.timers, id) +} + +// exist +// @description: 判断id是否存在 +// parameter: +// @receiver this: +// @id: +// return: +// @exist: +func (this *timersModel) exist(id string) (exist bool) { + _, exist = this.timers[id] + return +} + +// getAllTimers +// @description: 获取所有定时器 +// parameter: +// @receiver this: +// return: +// @map[string]*timerObj: +func (this *timersModel) getAllTimers() map[string]*timerObj { + return this.timers +} + +// getAllTimers2 +// @description: 获取所有定时器 +// parameter: +// @receiver this: +// return: +// @result: +func (this *timersModel) getAllTimers2() (result []*timerObj) { + result = make([]*timerObj, 0, len(this.timers)) + for _, v := range this.timers { + result = append(result, v) + } + + return +} diff --git a/trunk/goutil/coroutine-timer/readme.md b/trunk/goutil/coroutine-timer/readme.md new file mode 100644 index 0000000..02eaad6 --- /dev/null +++ b/trunk/goutil/coroutine-timer/readme.md @@ -0,0 +1,20 @@ +coroutine-timer支持如下工作: +定时触发设定的回调,最小精度秒级 + +## 使用方式 + +### 增加回调 + +> 1. 导入包 +> 2. 调用AddTimerx添加定时回调,传入相关参数 + +ps: +> 1. AddTimer1,AddTimer2,AddTimer3是内部自动生成的id,内部保证唯一性。外部如果后续要删除该添加的timer,需要持有返回的id信息 +> 2. AddTimer4 需要外部传入id,外部需要保证id的唯一性。并且这个方法会在内部校验id是否已经存在,所以性能上会比其他AddTimer方法慢 + +### 删除回调 + +```go + DeleteTimer(id) +``` + diff --git a/trunk/goutil/coroutine-timer/timer-mgr.go b/trunk/goutil/coroutine-timer/timer-mgr.go new file mode 100644 index 0000000..5808105 --- /dev/null +++ b/trunk/goutil/coroutine-timer/timer-mgr.go @@ -0,0 +1,409 @@ +package coroutine_timer + +import ( + "fmt" + "math" + "time" + + "goutil/logUtil" + "goutil/stringUtil" +) + +const ( + // 启动暂停时间 + con_STAR_SLEEP_NUM = 3 + + // 秒级定时器卡槽数量 + con_SECOND_SLOT_NUM = 60 + + //分钟级定时器卡槽数量 + con_MINUTES_SLOT_NUM = 60 +) + +var ( + // 秒级定时器下标 + secIndex = 0 + + // 秒级定时器当前开始时间 + secondStarTime int64 + + // 秒级定时器槽 + secondsTimers [con_SECOND_SLOT_NUM]*timersModel + + // 分钟级定时器下标 + minIndex = 0 + + // 分钟级定时器当前开始时间 + minStarTime int64 + + // 分钟级定时器槽 + minutesTimers [con_MINUTES_SLOT_NUM]*timersModel + + // 其他定时器存放槽 + otherTimers *timersModel + + // 操作通道 + cmdChan chan *cmdModel +) + +func init() { + for i := 0; i < con_SECOND_SLOT_NUM; i++ { + secondsTimers[i] = newTimersModel() + } + for i := 0; i < con_MINUTES_SLOT_NUM; i++ { + minutesTimers[i] = newTimersModel() + } + + otherTimers = newTimersModel() + cmdChan = make(chan *cmdModel, 1000) + secondStarTime = time.Now().Unix() + minStarTime = secondStarTime + con_SECOND_SLOT_NUM + + go chanHandler() +} + +// AddTimer +// @description: 添加定时回调 +// parameter: +// +// @afterSecond:延后多少时间执行 +// @exfun:执行方法 +// @obj:执行传入的参数 +// +// return: +// +// @string: +func AddTimer(afterSecond int, exfun func(interface{}), obj interface{}) string { + tick := time.Now().Unix() + int64(afterSecond) + return AddTimer3(tick, exfun, obj) +} + +// AddTimer2 +// @description: 添加定时回调 +// parameter: +// +// @t:执行时间点 +// @exfun:执行方法 +// @obj:执行传入的参数 +// +// return: +// +// @string: +func AddTimer2(t time.Time, exfun func(interface{}), obj interface{}) string { + tick := t.Unix() + return AddTimer3(tick, exfun, obj) +} + +// AddTimer3 +// @description: 添加定时回调 +// parameter: +// +// @tick:执行时间点 +// @exfun:执行方法 +// @obj:执行传入的参数 +// +// return: +// +// @newId: +func AddTimer3(tick int64, exfun func(interface{}), obj interface{}) (newId string) { + newId = stringUtil.GetNewUUID() + newObj := newTimerObj(newId, tick, exfun, obj) + + cnm := newCmdModel(cmd_add, newObj) + cmdChan <- cnm + + return +} + +// AddTimer4 +// @description: 添加定时回调(此方法会在内部校验id,所以性能会比其他AddTimer方法低) +// parameter: +// +// @id:定时id(外部需要自行保证id唯一) +// @tick:执行时间点 +// @exfun:执行方法 +// @obj:执行传入的参数 +// +// return: +// +// @err: +func AddTimer4(id string, tick int64, exfun func(interface{}), obj interface{}) (err error) { + newObj := newTimerObj(id, tick, exfun, obj) + newObj.needCheckId = true + + // 加入处理队列 + cnm := newCmdModel(cmd_add, newObj) + cmdChan <- cnm + + // 等待处理结束 + <-cnm.waitChan + + // 返回处理结果 + err = cnm.err + + return +} + +// DeleteTimer +// @description: 删除定时器 +// parameter: +// +// @id: +// +// return: +func DeleteTimer(id string) { + cnm := newCmdModel(cmd_del, id) + cmdChan <- cnm +} + +// chanHandler +// @description: channel处理 +// parameter: +// return: +func chanHandler() { + defer func() { + if err := recover(); err != nil { + logUtil.ErrorLog("coroutine-timer.excute err:%s", err) + } + }() + + // 暂停一下再处理,避免启动立即处理,其他数据还没准备好 + time.Sleep(con_STAR_SLEEP_NUM * time.Second) + + at := time.After(time.Second * 1) + for { + select { + case cm := <-cmdChan: + switch cm.cmd { + case cmd_add: + cmdAdd(cm) + case cmd_del: + cmdDel(cm) + } + case <-at: + // byron:需要处理时间后调导致跳时间的问题:调整后应该马上执行的 + + // 计算需要执行的次数 + n := time.Now().Unix() - secondStarTime - int64(secIndex) + if n > 0 { + + // 执行对应次数的方法 --- 正常应该只执行1此,调时间后,此处会追时间 + var i int64 + for i = 0; i < n; i++ { + cmdRun() + } + } + + at = time.After(time.Second * 1) + } + } +} + +// cmdAdd +// @description: 添加定时器 +// parameter: +// +// @cm: +// +// return: +func cmdAdd(cm *cmdModel) { + newObj := cm.paramObj.(*timerObj) + if newObj.needCheckId && checkTimerExist(newObj.id) { + cm.err = fmt.Errorf("已经存在id=%s的timer", newObj.id) + cm.waitChan <- struct{}{} + return + } + + // 如果执行时间比当前时间小,则放入最近的调度卡槽,以便尽快执行 + tick := newObj.tick + if tick <= (secondStarTime + int64(secIndex)) { + tick = (secondStarTime + int64(secIndex)) + 1 + } + + // 落在秒钟级别定时器上 + if tick < (secondStarTime + con_SECOND_SLOT_NUM) { + index := (int)(tick - secondStarTime) + secondsTimers[index].addTimer(newObj) + cm.waitChan <- struct{}{} + return + } + + // 落在分钟级别定时器上 + if tick < (minStarTime + con_MINUTES_SLOT_NUM*con_SECOND_SLOT_NUM) { + index := (int)(tick-minStarTime) / con_SECOND_SLOT_NUM + minutesTimers[index].addTimer(newObj) + cm.waitChan <- struct{}{} + return + } + + //落在小时级别定时器上 + otherTimers.addTimer(newObj) + + // 返回操作完成 + cm.waitChan <- struct{}{} +} + +// cmdDel +// @description: 删除timer +// parameter: +// +// @cm: +// +// return: +func cmdDel(cm *cmdModel) { + id := cm.paramObj.(string) + + // 移除秒级别定时器 + for _, item := range secondsTimers { + item.delTimer(id) + } + + // 移除分种级定时器 + for _, item := range minutesTimers { + item.delTimer(id) + } + + // 移除时钟级定时器 + otherTimers.delTimer(id) + + // 返回操作完成 + cm.waitChan <- struct{}{} +} + +// cmdRun +// @description: 运行定时器 +// parameter: +// return: +func cmdRun() { + defer func() { + if err := recover(); err != nil { + logUtil.ErrorLog("coroutine-timer.inExcute err:%s", err) + } + }() + + // 执行秒级定时器 + timers := getSencondTimers() + if len(timers) == 0 { + return + } + + for _, t := range timers { + go safeRun(t) + } +} + +// checkTimerExist +// @description: 校验timer是否存在 +// parameter: +// +// @id:id +// +// return: +// +// @bool: +func checkTimerExist(id string) bool { + // 秒级别定时器检测 + for _, item := range secondsTimers { + if item.exist(id) { + return true + } + } + + // 分种级定时器检测 + for _, item := range minutesTimers { + if item.exist(id) { + return true + } + } + + // 时钟级定时器检测 + return otherTimers.exist(id) +} + +// getSencondTimers +// @description: 获取秒级定时器 +// parameter: +// return: +// +// @result: +func getSencondTimers() (result []*timerObj) { + // 获取对应slot里面的定时对象 + result = secondsTimers[secIndex].getAllTimers2() + secondsTimers[secIndex] = newTimersModel() + secIndex++ + + // 如果达到最大,则重新填装新的调度对象 + if secIndex == con_SECOND_SLOT_NUM { + secIndex = 0 + secondStarTime = secondStarTime + con_SECOND_SLOT_NUM + minTaskList := getMinutesTasks() + for _, t := range minTaskList { + index := t.tick - secondStarTime + secondsTimers[index].addTimer(t) + } + } + + return +} + +// getMinutesTasks +// @description: 获取分钟级定时器 +// parameter: +// return: +// +// @result: +func getMinutesTasks() (result []*timerObj) { + // 获取对应slot里面的定时对象 + result = minutesTimers[minIndex].getAllTimers2() + minutesTimers[minIndex] = newTimersModel() + minIndex++ + + // 如果达到最大,则重新填装新的调度对象 + if minIndex == con_MINUTES_SLOT_NUM { + reInputMin() + } + + return +} + +// reInputMin +// @description: 重新填入分钟级定时器 +// parameter: +// return: +func reInputMin() { + minIndex = 0 + minStarTime = minStarTime + con_MINUTES_SLOT_NUM*con_SECOND_SLOT_NUM + + delMap := make(map[string]struct{}) + for _, t := range otherTimers.getAllTimers() { + index := (t.tick - minStarTime) / con_SECOND_SLOT_NUM + if index > math.MaxInt || index >= con_MINUTES_SLOT_NUM { + continue + } + minutesTimers[index].addTimer(t) + delMap[t.id] = struct{}{} + } + + if len(delMap) > 0 { + for k := range delMap { + otherTimers.delTimer(k) + } + } +} + +// safeRun +// @description: 安全运行定时器回调 +// parameter: +// +// @t: +// +// return: +func safeRun(t *timerObj) { + defer func() { + if err := recover(); err != nil { + logUtil.ErrorLog("coroutine-timer.safeRun id:%s err:%s", t.id, err) + } + }() + + t.excuteAction(t.paramObj) +} diff --git a/trunk/goutil/coroutine-timer/timer-obj.go b/trunk/goutil/coroutine-timer/timer-obj.go new file mode 100644 index 0000000..a60cc69 --- /dev/null +++ b/trunk/goutil/coroutine-timer/timer-obj.go @@ -0,0 +1,40 @@ +package coroutine_timer + +// timerObj +// @description: 定时调度对象 +type timerObj struct { + // id 调度id + id string + + // tick 执行时间 + tick int64 + + // excuteAction 执行方法 + excuteAction func(interface{}) + + // paramObj 携带的参数 + paramObj interface{} + + // needCheckId 是否需要校验id + needCheckId bool +} + +// newTimerObj +// @description: 构造调度对象 +// parameter: +// @_id:id +// @t:调度时间 +// @ea:调度方法 +// @pm:调度参数 +// return: +// @*timerObj: +func newTimerObj(_id string, t int64, ea func(interface{}), pm interface{}) *timerObj { + result := &timerObj{ + id: _id, + tick: t, + excuteAction: ea, + paramObj: pm, + } + + return result +} diff --git a/trunk/goutil/coroutine-timer/timer_test.go b/trunk/goutil/coroutine-timer/timer_test.go new file mode 100644 index 0000000..869b391 --- /dev/null +++ b/trunk/goutil/coroutine-timer/timer_test.go @@ -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) + } + +} diff --git a/trunk/goutil/counter-util/counter.go b/trunk/goutil/counter-util/counter.go new file mode 100644 index 0000000..4250b89 --- /dev/null +++ b/trunk/goutil/counter-util/counter.go @@ -0,0 +1,60 @@ +package counter_util + +import ( + "time" +) + +// CounterUtil +// @description: 固定窗口计数器辅助类 +type CounterUtil struct { + tag string // tag counter标识 + num int // num 当前计数 + warnNum int // warnNum 警告数量 + windowsTime time.Time // windowsTime 窗口时间 + checkSameWindowsFun func(t1, t2 time.Time) bool // checkSameWindowsFun 比较是否同一个时间窗口 + warnAction func(tag string, num int, t time.Time) // warnAction 监控回调方法 +} + +// NewCounterUtil +// @description: 构造计数器 +// parameter: +// @_tag:tag标识,会在WarnAction中传递回来 +// @_warnNum:告警数量 +// @_checkWindowFun:比较是否同一个时间窗口 +// @_warnAction:指定数量触发回调 +// return: +// @*CounterUtil:固定窗口计数器辅助类 +func NewCounterUtil(_tag string, _warnNum int, _checkWindowFun func(t1, t2 time.Time) bool, _warnAction func(tag string, num int, t time.Time)) *CounterUtil { + r := &CounterUtil{ + tag: _tag, + warnNum: _warnNum, + windowsTime: time.Now(), + checkSameWindowsFun: _checkWindowFun, + warnAction: _warnAction, + } + + return r +} + +// AddNum +// @description: 添加数量 +// parameter: +// @receiver c:计数器 +// @n:增加的数量 +// return: +// @int:计数器当前的数量 +func (c *CounterUtil) AddNum(n int) int { + if !c.checkSameWindowsFun(c.windowsTime, time.Now()) { + c.num = 0 + c.windowsTime = time.Now() + } + + // 增加次数 + c.num += n + if c.num >= c.warnNum { + c.warnAction(c.tag, c.num, time.Now()) + c.num = 0 + } + + return c.num +} diff --git a/trunk/goutil/counter-util/counter_test.go b/trunk/goutil/counter-util/counter_test.go new file mode 100644 index 0000000..0bcfe2e --- /dev/null +++ b/trunk/goutil/counter-util/counter_test.go @@ -0,0 +1,33 @@ +package counter_util + +import ( + "fmt" + "testing" + "time" +) + +func TestInfoLog(t *testing.T) { + var iserr bool = true + c := NewCounterUtil("test", 2, checkId, func(tag string, num int, ti time.Time) { + msg := fmt.Sprintf("tag:%s 当前数量为num:%v ti:%v", tag, num, ti) + if iserr { + t.Error(msg) + } else { + t.Log(msg) + } + + }) + + c.AddNum(1) + iserr = false + c.AddNum(1) + time.Sleep(time.Second * 1) + iserr = true + c.AddNum(1) + time.Sleep(time.Second * 1) + c.AddNum(1) +} + +func checkId(t1, t2 time.Time) bool { + return t1.Second() == t2.Second() +} diff --git a/trunk/goutil/counter-util/readme.md b/trunk/goutil/counter-util/readme.md new file mode 100644 index 0000000..316ff00 --- /dev/null +++ b/trunk/goutil/counter-util/readme.md @@ -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() +} + +``` \ No newline at end of file diff --git a/trunk/goutil/dbUtil/dataRow.go b/trunk/goutil/dbUtil/dataRow.go new file mode 100644 index 0000000..36cc8d9 --- /dev/null +++ b/trunk/goutil/dbUtil/dataRow.go @@ -0,0 +1,104 @@ +package dbUtil + +import ( + "errors" + "time" +) + +// 数据行结果 +type DataRow struct { + // 所属数据表 + table *DataTable + + // 行的所有值 + cells []interface{} +} + +// 行的所有原始值 +func (this *DataRow) CellOriginValues() []interface{} { + return this.cells +} + +// 值的个数 +func (this *DataRow) Len() int { + return len(this.cells) +} + +// 单元格的字符串值(可能为nil),如果有设置连接字符串:parseTime=true,则会有time.Time +// celIndex:单元格序号 +// 返回值: +// interface{}:单元格的字符串值 +// error:错误信息 +func (this *DataRow) CellValue(celIndex int) (interface{}, error) { + if len(this.cells) <= celIndex { + return nil, errors.New("cell out of range") + } + + // 检查是否为nil + if this.cells[celIndex] == nil { + return nil, nil + } + + // 转换为字符串 + switch this.cells[celIndex].(type) { + case []byte: + return string(this.cells[celIndex].([]byte)), nil + case string: + return this.cells[celIndex].(string), nil + case time.Time: + return this.cells[celIndex].(time.Time), nil + } + + return nil, errors.New("unknown value type") +} + +// 单元格的字符串值(可能为nil),如果有设置连接字符串:parseTime=true,则会有time.Time +// cellName:单元格名称 +// 返回值: +// interface{}:单元格的字符串值 +// error:错误信息 +func (this *DataRow) CellValueByName(cellName string) (interface{}, error) { + celIndex := this.table.cellIndex(cellName) + if celIndex < 0 { + return nil, errors.New("cell name no exist") + } + + return this.CellValue(celIndex) +} + +// 单元格的原始值 +// celIndex:单元格序号 +// 返回值: +// interface{}:单元格的字符串值 +// error:错误信息 +func (this *DataRow) OriginCellValue(celIndex int) (interface{}, error) { + if len(this.cells) <= celIndex { + return nil, errors.New("cell out of range") + } + + return this.cells[celIndex], nil +} + +// 单元格的原始值 +// cellName:单元格名称 +// 返回值: +// interface{}:单元格的字符串值 +// error:错误信息 +func (this *DataRow) OriginCellValueByName(cellName string) (interface{}, error) { + celIndex := this.table.cellIndex(cellName) + if celIndex < 0 { + return nil, errors.New("cell name no exist") + } + + return this.OriginCellValue(celIndex) +} + +// 创建单元格对象 +// _table:所属表对象 +// _cells:单元格的值集合 +func newDataRow(_table *DataTable, _cells []interface{}) *DataRow { + return &DataRow{ + table: _table, + cells: _cells, + } +} diff --git a/trunk/goutil/dbUtil/dataTable.go b/trunk/goutil/dbUtil/dataTable.go new file mode 100644 index 0000000..6e7f3c5 --- /dev/null +++ b/trunk/goutil/dbUtil/dataTable.go @@ -0,0 +1,189 @@ +package dbUtil + +import ( + "database/sql" + "errors" +) + +// 数据表结构 +type DataTable struct { + // 行对象集合 + rowData []*DataRow + + // 列名称集合 + columnNames map[string]int +} + +// 数据表初始化 +// rows:原始的数据行信息 +// 返回值: +// error:初始化的错误信息 +func (this *DataTable) init(rows *sql.Rows) error { + defer func() { + rows.Close() + }() + + // 读取列信息和保存列名称 + tmpColumns, errMsg := rows.Columns() + if errMsg != nil { + return errMsg + } + this.columnNames = make(map[string]int) + for index, val := range tmpColumns { + this.columnNames[val] = index + } + + // 读取行数据 + this.rowData = make([]*DataRow, 0) + columnCount := len(this.columnNames) + + args := make([]interface{}, columnCount) + for rows.Next() { + values := make([]interface{}, columnCount) + for i := 0; i < columnCount; i++ { + args[i] = &values[i] + } + rows.Scan(args...) + + this.rowData = append(this.rowData, newDataRow(this, values)) + } + + return nil +} + +// 获取原始单元格值(一般为:string或[]byte) +// rowIndex:行序号 +// cellIndex:单元格序号 +// 返回值: +// interface{}:原始单元格值(一般为:string或[]byte) +// error:获取错误信息 +func (this *DataTable) OriginCellValueByIndex(rowIndex int, cellIndex int) (interface{}, error) { + if len(this.rowData) <= rowIndex { + return nil, errors.New("row out of range") + } + + rowItem := this.rowData[rowIndex] + if len(rowItem.cells) <= cellIndex { + return nil, errors.New("column out of range") + } + + return rowItem.OriginCellValue(cellIndex) +} + +// 获取原始单元格值(一般为:string或[]byte) +// rowIndex:行序号 +// cellIndex:单元格序号 +// 返回值: +// interface{}:原始单元格值(一般为:string或[]byte) +// error:获取错误信息 +func (this *DataTable) OriginCellValueByCellName(rowIndex int, cellName string) (interface{}, error) { + if len(this.rowData) <= rowIndex { + return nil, errors.New("row out of range") + } + + rowItem := this.rowData[rowIndex] + + return rowItem.OriginCellValueByName(cellName) +} + +// 获取字符串的单元格值(有可能为nil) +// rowIndex:行序号 +// cellIndex:单元格序号 +// 返回值: +// interface{}:字符串的单元格值(有可能为nil) +// error:获取错误信息 +func (this *DataTable) CellValueByIndex(rowIndex int, cellIndex int) (interface{}, error) { + if len(this.rowData) <= rowIndex { + return nil, errors.New("row out of range") + } + + rowItem := this.rowData[rowIndex] + if len(rowItem.cells) <= cellIndex { + return nil, errors.New("column out of range") + } + + return rowItem.CellValue(cellIndex) +} + +// 获取字符串的单元格值(有可能为nil) +// rowIndex:行序号 +// cellIndex:单元格序号 +// 返回值: +// interface{}:字符串的单元格值(有可能为nil) +// error:获取错误信息 +func (this *DataTable) CellValueByName(rowIndex int, cellName string) (interface{}, error) { + if len(this.rowData) <= rowIndex { + return nil, errors.New("row out of range") + } + + rowItem := this.rowData[rowIndex] + + return rowItem.CellValueByName(cellName) +} + +// 获取行对象 +// rowIndex:行序号 +// 返回值: +// *DataRow:行对象 +// error:错误信息 +func (this *DataTable) Row(rowIndex int) (*DataRow, error) { + if len(this.rowData) <= rowIndex { + return nil, errors.New("row out of range") + } + + return this.rowData[rowIndex], nil +} + +// 根据列名获取列序号 +// cellName:列名 +// 返回值: +// int:列序号 +func (this *DataTable) cellIndex(cellName string) int { + cellIndex, isExist := this.columnNames[cellName] + if isExist == false { + return -1 + } + + return cellIndex +} + +// 获取所有列的名字 +// 返回值: +// []string:列字段名集合 +func (this *DataTable) Columns() []string { + result := make([]string, len(this.columnNames)) + for key, val := range this.columnNames { + result[val] = key + } + + return result +} + +// 获取列数量 +// 返回值: +// int:列数量 +func (this *DataTable) ColumnCount() int { + return len(this.columnNames) +} + +// 获取数据行数 +// 返回值: +// int:行数 +func (this *DataTable) RowCount() int { + return len(this.rowData) +} + +// 新建数据表对象 +// rows:数据行对象 +// 返回值: +// *DataTable:数据表对象 +// error:错误信息 +func NewDataTable(rows *sql.Rows) (*DataTable, error) { + table := &DataTable{} + errMsg := table.init(rows) + if errMsg != nil { + return nil, errMsg + } + + return table, nil +} diff --git a/trunk/goutil/dbUtil/valueConvert.go b/trunk/goutil/dbUtil/valueConvert.go new file mode 100644 index 0000000..3d0d496 --- /dev/null +++ b/trunk/goutil/dbUtil/valueConvert.go @@ -0,0 +1,195 @@ +package dbUtil + +import ( + "fmt" + "time" + + "goutil/typeUtil" +) + +// 类型转换为byte +// 返回值: +// byte:结果 +// error:错误数据 +func Byte(row *DataRow, key string) (byte, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return 0, errMsg + } + + if val == nil { + return 0, fmt.Errorf("value is nil") + } + + return typeUtil.Byte(val) +} + +// 类型转换为int +// 返回值: +// int:结果 +// error:错误数据 +func Int32(row *DataRow, key string) (int32, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return 0, errMsg + } + + if val == nil { + return 0, fmt.Errorf("value is nil") + } + + return typeUtil.Int32(val) +} + +// 类型转换为uint32 +// 返回值: +// int:结果 +// error:错误数据 +func Uint32(row *DataRow, key string) (uint32, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return 0, errMsg + } + + if val == nil { + return 0, fmt.Errorf("value is nil") + } + + return typeUtil.Uint32(val) +} + +// 类型转换为int +// 返回值: +// int:结果 +// error:错误数据 +func Int(row *DataRow, key string) (int, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return 0, errMsg + } + + if val == nil { + return 0, fmt.Errorf("value is nil") + } + + return typeUtil.Int(val) +} + +// 类型转换为int +// 返回值: +// int:结果 +// error:错误数据 +func Uint(row *DataRow, key string) (uint, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return 0, errMsg + } + + if val == nil { + return 0, fmt.Errorf("value is nil") + } + + return typeUtil.Uint(val) +} + +// 类型转换为int +// 返回值: +// int:结果 +// error:错误数据 +func Int64(row *DataRow, key string) (int64, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return 0, errMsg + } + + if val == nil { + return 0, fmt.Errorf("value is nil") + } + + return typeUtil.Int64(val) +} + +// 类型转换为int +// 返回值: +// int:结果 +// error:错误数据 +func Uint64(row *DataRow, key string) (uint64, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return 0, errMsg + } + + if val == nil { + return 0, fmt.Errorf("value is nil") + } + + return typeUtil.Uint64(val) +} + +// 类型转换为int +// 返回值: +// float64:结果 +// error:错误数据 +func Float64(row *DataRow, key string) (float64, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return 0, errMsg + } + + if val == nil { + return 0, fmt.Errorf("value is nil") + } + + return typeUtil.Float64(val) +} + +// 类型转换为bool +// 返回值: +// bool:结果 +// error:错误信息 +func Bool(row *DataRow, key string) (bool, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return false, errMsg + } + + if val == nil { + return false, fmt.Errorf("value is nil") + } + + return typeUtil.Bool(val) +} + +// 类型转换为字符串 +// 返回值: +// string:结果 +// error:错误信息 +func String(row *DataRow, key string) (string, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return "", errMsg + } + + if val == nil { + return "", fmt.Errorf("value is nil") + } + + return typeUtil.String(val) +} + +// 转换为时间格式,如果是字符串,则要求内容格式形如:2017-02-14 05:20:00 +// 返回值: +// bool:结果 +// error:错误信息 +func DateTime(row *DataRow, key string) (time.Time, error) { + val, errMsg := row.CellValueByName(key) + if errMsg != nil { + return time.Time{}, errMsg + } + + if val == nil { + return time.Time{}, fmt.Errorf("value is nil") + } + + return typeUtil.DateTime(val) +} diff --git a/trunk/goutil/debugUtil/debug.go b/trunk/goutil/debugUtil/debug.go new file mode 100644 index 0000000..0b20060 --- /dev/null +++ b/trunk/goutil/debugUtil/debug.go @@ -0,0 +1,66 @@ +package debugUtil + +import ( + "github.com/fatih/color" +) + +var ( + isDebug = false + code Code = Code_Bold + foregroundColor ForegroundColor = Foreground_Purple + backgroundColor BackgroundColor = BackgroundColor_Black + + colorObj = color.New(foregroundColor, code) +) + +// 设置DEBUG状态 +// _isDebug:是否是DEBUG +func SetDebug(_isDebug bool) { + isDebug = _isDebug +} + +// 是否处于调试状态 +func IsDebug() bool { + return isDebug +} + +// 设置显示信息 +func SetDisplayInfo(_code Code, _foregroundColor ForegroundColor, _backgroundColor BackgroundColor) { + code = _code + foregroundColor = _foregroundColor + backgroundColor = _backgroundColor + + colorObj = color.New(foregroundColor, code) +} + +// Print formats using the default formats for its operands and writes to standard output. +// Spaces are added between operands when neither is a string. +// It returns the number of bytes written and any write error encountered. +func Print(a ...interface{}) { + if !isDebug { + return + } + + _, _ = colorObj.Print(a...) +} + +// Printf formats according to a format specifier and writes to standard output. +// It returns the number of bytes written and any write error encountered. +func Printf(format string, a ...interface{}) { + if !isDebug { + return + } + + _, _ = colorObj.Printf(format, a...) +} + +// Println formats using the default formats for its operands and writes to standard output. +// Spaces are always added between operands and a newline is appended. +// It returns the number of bytes written and any write error encountered. +func Println(a ...interface{}) { + if !isDebug { + return + } + + _, _ = colorObj.Println(a...) +} diff --git a/trunk/goutil/debugUtil/display.go b/trunk/goutil/debugUtil/display.go new file mode 100644 index 0000000..071d1a4 --- /dev/null +++ b/trunk/goutil/debugUtil/display.go @@ -0,0 +1,47 @@ +package debugUtil + +import "github.com/fatih/color" + +// Code 显示代码 +type Code = color.Attribute + +const ( + Code_Reset Code = color.Reset + Code_Bold Code = color.Bold + Code_Faint Code = color.Faint + Code_Italic Code = color.Italic + Code_Underline Code = color.Underline + Code_BlinkSlow Code = color.BlinkSlow + Code_BlinkRapid Code = color.BlinkRapid + Code_ReverseVideo Code = color.ReverseVideo + Code_Concealed Code = color.Concealed + Code_CrossedOut Code = color.CrossedOut +) + +// ForegroundColor 前景色 +type ForegroundColor = color.Attribute + +const ( + Foreground_Black ForegroundColor = color.FgBlack + Foreground_Red ForegroundColor = color.FgRed + Foreground_Green ForegroundColor = color.FgGreen + Foreground_Yellow ForegroundColor = color.FgYellow + Foreground_Blue ForegroundColor = color.FgBlue + Foreground_Purple ForegroundColor = color.FgMagenta + Foreground_Cyan ForegroundColor = color.FgCyan + Foreground_White ForegroundColor = color.FgWhite +) + +// BackgroundColor 背景色 +type BackgroundColor = color.Attribute + +const ( + BackgroundColor_Black = color.BgBlack + BackgroundColor_Red = color.BgRed + BackgroundColor_Green = color.BgGreen + BackgroundColor_Yellow = color.BgYellow + BackgroundColor_Blue = color.BgBlue + BackgroundColor_Purple = color.BgMagenta + BackgroundColor_Cyan = color.BgCyan + BackgroundColor_White = color.BgWhite +) diff --git a/trunk/goutil/debugUtil/doc.go b/trunk/goutil/debugUtil/doc.go new file mode 100644 index 0000000..6e476fb --- /dev/null +++ b/trunk/goutil/debugUtil/doc.go @@ -0,0 +1,4 @@ +/* +提供调试功能的助手包 +*/ +package debugUtil diff --git a/trunk/goutil/deviceUtil/deviceUtil.go b/trunk/goutil/deviceUtil/deviceUtil.go new file mode 100644 index 0000000..28fa721 --- /dev/null +++ b/trunk/goutil/deviceUtil/deviceUtil.go @@ -0,0 +1,81 @@ +package deviceUtil + +import ( + "strings" +) + +// 将MAC地址转化为标准格式 +func ConvertMacToStandardFormat(mac string) string { + if mac == "" || mac == "00:00:00:00:00:00" || mac == "02:00:00:00:00:00" { + return "" + } + + //如果mac的长度不为12或17,则是不正确的格式 + if len(mac) != 12 && len(mac) != 17 { + return "" + } + + + //转化为大写 + mac = strings.ToUpper(mac) + + //如果mac地址的长度为17(已经有:),则直接返回 + if len(mac) == 17 { + return mac + } + + //如果没有分隔符,则添加分隔符 + newMac := make([]rune, 0, 17) + for i, v := range []rune(mac) { + newMac = append(newMac, v) + if i < len(mac) - 1 && i % 2 == 1 { + newMac = append(newMac, ':') + } + } + + return string(newMac) +} + +func ConvertIdfaToStandardFormat(idfa string) string { + //如果是空或默认值,则返回String.Empty + if idfa == "" || idfa == "00000000-0000-0000-0000-000000000000" { + return "" + } + + //如果idfa的长度不为32或36,则代表是Android的数据,则可以直接返回 + if len(idfa) != 32 && len(idfa) != 36 { + return idfa + } + + //转化为大写 + idfa = strings.ToUpper(idfa); + + //如果idfa地址的长度为36(已经有:),则直接返回 + if len(idfa) == 36 { + return idfa + } + + //如果没有分隔符,则添加分隔符 + newIdfa := make([]rune, 0, 36) + for i, v := range []rune(idfa) { + newIdfa = append(newIdfa, v) + if i == 7 || i == 11 || i == 15 || i == 19 { + newIdfa = append(newIdfa, '-') + } + } + + return string(newIdfa) +} + +// 根据MAC和IDFA获取唯一标识 +func GetIdentifier(mac, idfa string) string { + mac = ConvertMacToStandardFormat(mac) + idfa = ConvertIdfaToStandardFormat(idfa); + + //如果idfa不为空,则使用idfa,否则使用mac + if idfa != "" { + return idfa + } else { + return mac + } +} diff --git a/trunk/goutil/deviceUtil/deviceUtil_test.go b/trunk/goutil/deviceUtil/deviceUtil_test.go new file mode 100644 index 0000000..271273f --- /dev/null +++ b/trunk/goutil/deviceUtil/deviceUtil_test.go @@ -0,0 +1,131 @@ +package deviceUtil + +import ( + "testing" +) + +func TestConvertMacToStarndardFormat(t *testing.T) { + mac := "" + expected := "" + got := ConvertMacToStandardFormat(mac) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + mac = "00:00:00:00:00:00" + expected = "" + got = ConvertMacToStandardFormat(mac) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + mac = "02:00:00:00:00:00" + expected = "" + got = ConvertMacToStandardFormat(mac) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + mac = "02:00:00:00:00" + expected = "" + got = ConvertMacToStandardFormat(mac) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + mac = "020000000020" + expected = "02:00:00:00:00:20" + got = ConvertMacToStandardFormat(mac) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + mac = "02:00:00:00:00:20" + expected = "02:00:00:00:00:20" + got = ConvertMacToStandardFormat(mac) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } +} + +func TestConvertIdfaToStandardFormat(t *testing.T) { + idfa := "" + expected := "" + got := ConvertIdfaToStandardFormat(idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + idfa = "00000000-0000-0000-0000-000000000000" + expected = "" + got = ConvertIdfaToStandardFormat(idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + idfa = "00000000-0000-0000-0000-000000000000-123" + expected = "00000000-0000-0000-0000-000000000000-123" + got = ConvertIdfaToStandardFormat(idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + idfa = "00000000-1234-5678-0000-000000000000" + expected = "00000000-1234-5678-0000-000000000000" + got = ConvertIdfaToStandardFormat(idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + idfa = "00000000123456780000000000000000" + expected = "00000000-1234-5678-0000-000000000000" + got = ConvertIdfaToStandardFormat(idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } +} + +func TestGetIdentifier(t *testing.T) { + mac := "" + idfa := "" + expected := "" + got := GetIdentifier(mac, idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + mac = "00:00:00:00:00:00" + expected = "" + got = GetIdentifier(mac, idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + mac = "02:00:00:00:00:00" + expected = "" + got = GetIdentifier(mac, idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + mac = "020000000020" + expected = "02:00:00:00:00:20" + got = GetIdentifier(mac, idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + mac = "02:00:00:00:00:20" + expected = "02:00:00:00:00:20" + got = GetIdentifier(mac, idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } + + idfa = "00000000123456780000000000000000" + expected = "00000000-1234-5678-0000-000000000000" + got = GetIdentifier(mac, idfa) + if got != expected { + t.Errorf("Expected: %s, but got:%s", expected, got) + } +} diff --git a/trunk/goutil/dfaExUtil/dfaEx.go b/trunk/goutil/dfaExUtil/dfaEx.go new file mode 100644 index 0000000..98967e3 --- /dev/null +++ b/trunk/goutil/dfaExUtil/dfaEx.go @@ -0,0 +1,240 @@ +package dfaExUtil + +/* + * 扩展DFA算法 + * + * 一种二层树实现类DFA算法(DFA为多层树结构;go语言特性中的map结构过于“重量级”导致内存占用很大;此外还可能存在大量相同字符结点) + * + * 第一层map为所有字母/汉字作为key;value为第二层map + * 第二层map为第一层冲突字母/汉字的自定义hash作为key;value指示是否为敏感词结束标识 + * + * 测试结果:50万+的敏感词: + * 构造树耗时稍优于原始DFA; + * 内存使用为原DFA的不到1/4:原DFA占用495M内存,此算法使用111M; + * 查询效率比原DFA低10%~20%左右;主要是多一次map查询和多一次hash计算; + * + */ + +/* + * 注意使用[]rune的问题(此问题已通过使用固定位数hash解决): + * []rune中文使用的是unicode编码;若“中”编码为#4E2D;而#4E2D对应“N-”; + * 即:"N-"与"中"unicode编码均为#4E2D,即会产生hash冲突 + * + */ + +import ( + "fmt" + "strings" + + hash "goutil/dfaExUtil/hash64" +) + +// hash使用的类型(uint64对应hash64函数;uint32对应hash32函数) +type hashType = uint64 + +type DFAEx struct { + // 忽略大小写;true-忽略大小写;false-大小写敏感 + ignoreCase bool + // hash冲突个数 + hashCollisions int + // 树根 + // 字符/hash/uint8(b10000000)(最高字符表示是否结束/低7位表示字符位置) + root map[rune]map[hashType]uint8 +} + +// 新建敏感词对象 +// wordList - 敏感词列表 +// ignoreCase - [可选;默认false] 是否忽略大小写 +func NewDFAEx(wordList []string, ignoreCase ...bool) (dfaEx *DFAEx) { + var iCase bool + var mapSize int + + if len(ignoreCase) > 0 { + iCase = ignoreCase[0] + } + + mapSize = len(wordList) * 10 + // 防止过小 + if mapSize < 1_000 { + mapSize = 1_000 + } + // 通常各语言的单rune非重复数不超过1万 + if mapSize > 10_000 { + mapSize = 10_000 + } + + dfaEx = &DFAEx{ + ignoreCase: iCase, + root: make(map[rune]map[hashType]uint8, mapSize), + } + + for _, v := range wordList { + word := v + if iCase { + // 忽略大小写;所有字母转大写 + word = strings.ToUpper(word) + } + wordRune := []rune(word) + if len(wordRune) > 0 { + dfaEx.InsertWord(wordRune) + } + } + + return dfaEx +} + +// 添加敏感词 +func (dfaEx *DFAEx) InsertWord(word []rune) { + var hs hashType + var lastWord rune + var lastHash hashType + for i, v := range word { + lastWord = v + lastHash = hs + if wdInfo, ok := dfaEx.root[v]; ok { + // "字"已存在 + if hsV, ok := wdInfo[hs]; !ok { + // hash不存在,添加hash + wdInfo[hs] = uint8(i & 0x7F) // 第i位 + } else { + // hash已存在,检测是否冲突 + if (hsV & 0x7F) != uint8(i) { + // hash冲突 + dfaEx.hashCollisions++ + // fmt.Printf("hash冲突 %s %016X %d %d\n", string(v), hs, i+1, hsV&0x7F+1) + } + } + } else { + // "字"不存在,添加"字"和hash + wdInfo = make(map[hashType]uint8) + wdInfo[hs] = uint8(i & 0x7F) // 第i位 + dfaEx.root[v] = wdInfo + } + hs = hash.FastSumByRune2(v, hs) // hash更新 + } + + // 敏感词结束标志(uint8最高位置1) + dfaEx.root[lastWord][lastHash] |= 0x80 +} + +// 字符串查找敏感词 +func (dfaEx *DFAEx) IsMatch(str string) bool { + starts, _ := dfaEx.SearchSentence(str, true) + return len(starts) > 0 +} + +// 指定字符替换敏感词 +func (dfaEx *DFAEx) HandleWord(str string, replace rune) string { + starts, ends := dfaEx.SearchSentence(str) + if len(starts) == 0 { + return str + } + + strRune := []rune(str) + for i := 0; i < len(starts); i++ { + for idx := starts[i]; idx <= ends[i]; idx++ { + strRune[idx] = replace + } + } + + return string(strRune) +} + +// 字符串查找敏感词 +func (dfaEx *DFAEx) SearchSentence(str string, firstOpt ...bool) (starts, ends []int) { + var first bool // 是否首次匹配就返回 + if len(firstOpt) > 0 { + first = firstOpt[0] + } + strBak := str + if dfaEx.ignoreCase { + // 忽略大小写;所有字母转大写 + strBak = strings.ToUpper(str) + } + runeStr := []rune(strBak) + for i := 0; i < len(runeStr); { + end := dfaEx.searchByStart(i, runeStr) + if end < 0 { + // 继续下一个进行匹配 + i++ + } else { + // 记录匹配位置;从匹配到的下一个位置继续 + starts = append(starts, i) + ends = append(ends, end) + if first { + // 首次匹配就返回 + break + } + + i = end + 1 + } + } + + return +} + +// 从指定的开始位置搜索语句 +// start - 开始匹配的位置 +// str - 待检测字符串 +// 返回:匹配到的结束位置,未匹配到返回-1 +func (dfaEx *DFAEx) searchByStart(start int, runeStr []rune) (end int) { + var hs hashType + end = -1 // 未匹配到返回值 + + for i := start; i < len(runeStr); i++ { + wd := runeStr[i] + wdInfo, ok := dfaEx.root[wd] + if !ok { + // "字"不存在 + break + } + + hsV, ok := wdInfo[hs] + if !ok { + // hash不存在 + break + } + + // 检测是否句尾 + if (hsV & 0x80) != 0 { + // 找到句尾,继续匹配,直到匹配到最长敏感词为止 + end = i + } + + hs = hash.FastSumByRune2(wd, hs) // hash更新 + } + + return +} + +// 获取hash冲突数 +func (dfaEx *DFAEx) GetHashCollisions() int { + return dfaEx.hashCollisions +} + +// 调试接口 +func (dfaEx *DFAEx) Print() { + fmt.Println(dfaEx) +} + +// 调试接口 +func (dfaEx *DFAEx) PrintFmt(verbose bool) { + var keys int + var hashs int + for k, v := range dfaEx.root { + keys++ + if verbose { + fmt.Println("---------------------------") + fmt.Println(string(k)) + } + for kk, vv := range v { + hashs++ + if verbose { + fmt.Printf("%016X %02X\n", kk, vv) + } + } + } + + fmt.Println("================================") + fmt.Println("keys:", keys, "hashs", hashs, "map count", keys+1, "hashCollisions Count", dfaEx.hashCollisions) +} diff --git a/trunk/goutil/dfaExUtil/dfaEx_test.go b/trunk/goutil/dfaExUtil/dfaEx_test.go new file mode 100644 index 0000000..e7496b7 --- /dev/null +++ b/trunk/goutil/dfaExUtil/dfaEx_test.go @@ -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) +} diff --git a/trunk/goutil/dfaExUtil/hash32/hash32.go b/trunk/goutil/dfaExUtil/hash32/hash32.go new file mode 100644 index 0000000..fa84949 --- /dev/null +++ b/trunk/goutil/dfaExUtil/hash32/hash32.go @@ -0,0 +1,98 @@ +package hash32 + +/* + * ***注意*** + * + * Sum 使用的是[]byte参数;string中文是utf-8编码 + * SumByRune 使用的是[]rune参数;中文使用的是unicode编码 + * + * 两种参数中文编码不同;同一个string调用两个接口得到的hash是不同的!!! 这是要特别注意的 + * + * 对string进行for range操作会自动被[]rune化;一定要注意!!! + * + */ + +const shiftBit = 11 // 每个字节移位数(测试经验值11) +const reverseBit = 32 - shiftBit + +// 快速Hash计算(*** 注意:只取了rune低16位进行hash计算;计算结果与SumByRune不一致 ***) +func FastSumByRune2(in rune, hs uint32) (out uint32) { + out = ((hs << shiftBit) | (hs >> reverseBit)) + uint32(byte(in>>8)) + out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in)) + return +} + +// 快速Hash计算(*** 此计算结果与SumByRune一致 ***) +func FastSumByRune4(in rune, hs uint32) (out uint32) { + out = ((hs << shiftBit) | (hs >> reverseBit)) + uint32(byte(in>>24)) + out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in>>16)) + out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in>>8)) + out = ((out << shiftBit) | (out >> reverseBit)) + uint32(byte(in)) + return +} + +// 原Hash值参数重 +func hsArg(hsOpt ...uint32) (out uint32) { + out = uint32(0) + if len(hsOpt) > 0 { + out = hsOpt[0] + } + return +} + +// Hash计算 +// in - 待hash串 +// hsOpt - 原hash值(在此基础上继续hash) +func Sum(in []byte, hsOpt ...uint32) (out uint32) { + out = hsArg(hsOpt...) + for _, v := range in { + out = ((out << shiftBit) | (out >> reverseBit)) + uint32(v) + } + + return +} + +// Hash计算 +func SumByRune(in rune, hsOpt ...uint32) (out uint32) { + // rune转[]byte + inVal := make([]byte, 4) + inVal[0] = byte(in >> 24) + inVal[1] = byte(in >> 16) + inVal[2] = byte(in >> 8) + inVal[3] = byte(in) + + // *** 经实际测试:不加以下代码运行效率更高 *** + + // 去除前面多余的\x00 + // for { + // if len(inVal) <= 1 { + // // 以免全0异常;至少要保留1位 + // break + // } + // if inVal[0] == 0 { + // inVal = inVal[1:] + // } else { + // break + // } + // } + // 规避hash冲突,如:"N-"与"中"unicode编码均为#4E2D,即会产生hash冲突 + // 若长度>1(即非常规ASCII),所有字节最高位置1 + // if len(inVal) > 1 { + // for i := 0; i < len(inVal); i++ { + // inVal[i] |= 0x80 + // } + // } + out = Sum(inVal, hsOpt...) + + return +} + +// Hash计算 +func SumByRunes(in []rune, hsOpt ...uint32) (out uint32) { + out = hsArg(hsOpt...) + for _, v := range in { + out = SumByRune(v, out) + } + + return +} diff --git a/trunk/goutil/dfaExUtil/hash64/hash64.go b/trunk/goutil/dfaExUtil/hash64/hash64.go new file mode 100644 index 0000000..17eb0fa --- /dev/null +++ b/trunk/goutil/dfaExUtil/hash64/hash64.go @@ -0,0 +1,98 @@ +package hash64 + +/* + * ***注意*** + * + * Sum 使用的是[]byte参数;string中文是utf-8编码 + * SumByRune 使用的是[]rune参数;中文使用的是unicode编码 + * + * 两种参数中文编码不同;同一个string调用两个接口得到的hash是不同的!!! 这是要特别注意的 + * + * 对string进行for range操作会自动被[]rune化;一定要注意!!! + * + */ + +const shiftBit = 23 // 每个字节移位数(测试经验值23) +const reverseBit = 64 - shiftBit + +// 快速Hash计算(*** 注意:只取了rune低16位进行hash计算;计算结果与SumByRune不一致 ***) +func FastSumByRune2(in rune, hs uint64) (out uint64) { + out = ((hs << shiftBit) | (hs >> reverseBit)) + uint64(byte(in>>8)) + out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in)) + return +} + +// 快速Hash计算(*** 此计算结果与SumByRune一致 ***) +func FastSumByRune4(in rune, hs uint64) (out uint64) { + out = ((hs << shiftBit) | (hs >> reverseBit)) + uint64(byte(in>>24)) + out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in>>16)) + out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in>>8)) + out = ((out << shiftBit) | (out >> reverseBit)) + uint64(byte(in)) + return +} + +// 原Hash值参数重 +func hsArg(hsOpt ...uint64) (out uint64) { + out = uint64(0) + if len(hsOpt) > 0 { + out = hsOpt[0] + } + return +} + +// Hash计算 +// in - 待hash串 +// hsOpt - 原hash值(在此基础上继续hash) +func Sum(in []byte, hsOpt ...uint64) (out uint64) { + out = hsArg(hsOpt...) + for _, v := range in { + out = ((out << shiftBit) | (out >> reverseBit)) + uint64(v) + } + + return +} + +// Hash计算 +func SumByRune(in rune, hsOpt ...uint64) (out uint64) { + // rune转[]byte + inVal := make([]byte, 4) + inVal[0] = byte(in >> 24) + inVal[1] = byte(in >> 16) + inVal[2] = byte(in >> 8) + inVal[3] = byte(in) + + // *** 经实际测试:不加以下代码运行效率更高 *** + + // 去除前面多余的\x00 + // for { + // if len(inVal) <= 1 { + // // 以免全0异常;至少要保留1位 + // break + // } + // if inVal[0] == 0 { + // inVal = inVal[1:] + // } else { + // break + // } + // } + // 规避hash冲突,如:"N-"与"中"unicode编码均为#4E2D,即会产生hash冲突 + // 若长度>1(即非常规ASCII),所有字节最高位置1 + // if len(inVal) > 1 { + // for i := 0; i < len(inVal); i++ { + // inVal[i] |= 0x80 + // } + // } + out = Sum(inVal, hsOpt...) + + return +} + +// Hash计算 +func SumByRunes(in []rune, hsOpt ...uint64) (out uint64) { + out = hsArg(hsOpt...) + for _, v := range in { + out = SumByRune(v, out) + } + + return +} diff --git a/trunk/goutil/dfaExUtil/三维树转二维树.bmp b/trunk/goutil/dfaExUtil/三维树转二维树.bmp new file mode 100644 index 0000000..f3af9dd Binary files /dev/null and b/trunk/goutil/dfaExUtil/三维树转二维树.bmp differ diff --git a/trunk/goutil/dfaUtil/dfa.go b/trunk/goutil/dfaUtil/dfa.go new file mode 100644 index 0000000..f093bfa --- /dev/null +++ b/trunk/goutil/dfaUtil/dfa.go @@ -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 +} diff --git a/trunk/goutil/dfaUtil/dfa_test.go b/trunk/goutil/dfaUtil/dfa_test.go new file mode 100644 index 0000000..362ce75 --- /dev/null +++ b/trunk/goutil/dfaUtil/dfa_test.go @@ -0,0 +1,2088 @@ +package dfaUtil + +import ( + "testing" +) + +var ( + sensitiveList = make([]string, 0, 1024) +) + +func init() { + sensitiveList = append(sensitiveList, "&") + sensitiveList = append(sensitiveList, "*鏉?娲?蹇?闃挎墎") + sensitiveList = append(sensitiveList, "01gh.com") + sensitiveList = append(sensitiveList, "025game.cn") + sensitiveList = append(sensitiveList, "0571qq.com") + sensitiveList = append(sensitiveList, "09city.com") + sensitiveList = append(sensitiveList, "1000scarf.com") + sensitiveList = append(sensitiveList, "10mb.cn") + sensitiveList = append(sensitiveList, "1100y.com") + sensitiveList = append(sensitiveList, "11xp.1243.net.cn") + sensitiveList = append(sensitiveList, "1234chengren.1249.net.cn") + sensitiveList = append(sensitiveList, "131.com") + sensitiveList = append(sensitiveList, "13888wg.com") + sensitiveList = append(sensitiveList, "13ml.net") + sensitiveList = append(sensitiveList, "158le.com") + sensitiveList = append(sensitiveList, "15wy.com") + sensitiveList = append(sensitiveList, "166578.cn") + sensitiveList = append(sensitiveList, "16dy-鍥惧簱") + sensitiveList = append(sensitiveList, "16dy-鍦栧韩") + sensitiveList = append(sensitiveList, "16澶?17173dl.net") + sensitiveList = append(sensitiveList, "17173yxdl.cn") + sensitiveList = append(sensitiveList, "173at.com") + sensitiveList = append(sensitiveList, "17youle.com") + sensitiveList = append(sensitiveList, "18900.com") + sensitiveList = append(sensitiveList, "18鎽?18绂?1999浜氬お鏂版柊闂?1qmsj.com") + sensitiveList = append(sensitiveList, "1t1t.com") + sensitiveList = append(sensitiveList, "202333.com") + sensitiveList = append(sensitiveList, "208.43.198.56") + sensitiveList = append(sensitiveList, "21涓栫邯涓浗鍩洪噾浼?222se鍥剧墖") + sensitiveList = append(sensitiveList, "222se鍦栫墖") + sensitiveList = append(sensitiveList, "2feiche.cn") + sensitiveList = append(sensitiveList, "30鏉傚織") + sensitiveList = append(sensitiveList, "33bbb璧板厜") + sensitiveList = append(sensitiveList, "365gn") + sensitiveList = append(sensitiveList, "365tttyx.com") + sensitiveList = append(sensitiveList, "37447.cn") + sensitiveList = append(sensitiveList, "3kwow.com") + sensitiveList = append(sensitiveList, "3p鐐浘") + sensitiveList = append(sensitiveList, "3p鐐湒") + sensitiveList = append(sensitiveList, "47513.cn") + sensitiveList = append(sensitiveList, "51hdw.com") + sensitiveList = append(sensitiveList, "51jiafen.cn") + sensitiveList = append(sensitiveList, "51juezhan.com") + sensitiveList = append(sensitiveList, "52ppsa.cn") + sensitiveList = append(sensitiveList, "52sxhy.cn") + sensitiveList = append(sensitiveList, "54hero.com") + sensitiveList = append(sensitiveList, "55sss鍋锋媿鍖?55sss鍋锋媿鍗€") + sensitiveList = append(sensitiveList, "567567aa.cn") + sensitiveList = append(sensitiveList, "58.253.67.74") + sensitiveList = append(sensitiveList, "597ft.com") + sensitiveList = append(sensitiveList, "5d6d.com") + sensitiveList = append(sensitiveList, "5m5m5m.com") + sensitiveList = append(sensitiveList, "6-4tianwang") + sensitiveList = append(sensitiveList, "661661.com") + sensitiveList = append(sensitiveList, "69nb.com") + sensitiveList = append(sensitiveList, "71ka.com") + sensitiveList = append(sensitiveList, "77bbb") + sensitiveList = append(sensitiveList, "7gg") + sensitiveList = append(sensitiveList, "7mmo.com") + sensitiveList = append(sensitiveList, "8 浠?8188mu.com") + sensitiveList = append(sensitiveList, "888895.com") + sensitiveList = append(sensitiveList, "88kx.com") + sensitiveList = append(sensitiveList, "88mysf.com") + sensitiveList = append(sensitiveList, "89-64cdjp") + sensitiveList = append(sensitiveList, "8浠?91bysd.cn") + sensitiveList = append(sensitiveList, "92ey.com") + sensitiveList = append(sensitiveList, "92klgh.cn") + sensitiveList = append(sensitiveList, "92wydl.com") + sensitiveList = append(sensitiveList, "97sese") + sensitiveList = append(sensitiveList, "991game.com") + sensitiveList = append(sensitiveList, "999鏃ユ湰濡?99sa.com") + sensitiveList = append(sensitiveList, "a33.com") + sensitiveList = append(sensitiveList, "a4u") + sensitiveList = append(sensitiveList, "a4y") + sensitiveList = append(sensitiveList, "aa.yazhousetu.hi.9705.net.cn") + sensitiveList = append(sensitiveList, "aaac.s.51524.com") + sensitiveList = append(sensitiveList, "aaad.s.59764.com") + sensitiveList = append(sensitiveList, "abian") + sensitiveList = append(sensitiveList, "abianwansui") + sensitiveList = append(sensitiveList, "admin") + sensitiveList = append(sensitiveList, "administrator") + sensitiveList = append(sensitiveList, "aids") + sensitiveList = append(sensitiveList, "aiort澧撳湴") + sensitiveList = append(sensitiveList, "aisenhaoweier") + sensitiveList = append(sensitiveList, "ai婊?alafate") + sensitiveList = append(sensitiveList, "aluoyue") + sensitiveList = append(sensitiveList, "anbeijinsan") + sensitiveList = append(sensitiveList, "annan") + sensitiveList = append(sensitiveList, "any2000.com") + sensitiveList = append(sensitiveList, "aobama") + sensitiveList = append(sensitiveList, "aomaer") + sensitiveList = append(sensitiveList, "arqus浼氳鍦?asgardcn.com") + sensitiveList = append(sensitiveList, "asshole") + sensitiveList = append(sensitiveList, "atan鐨勭Щ鍔ㄧ煶") + sensitiveList = append(sensitiveList, "AV") + sensitiveList = append(sensitiveList, "a鐗?baichi") + sensitiveList = append(sensitiveList, "baijie.1249.net.cn") + sensitiveList = append(sensitiveList, "banchan") + sensitiveList = append(sensitiveList, "baopi") + sensitiveList = append(sensitiveList, "baoweier") + sensitiveList = append(sensitiveList, "bao鐨?bastard") + sensitiveList = append(sensitiveList, "bazhuwg.cn") + sensitiveList = append(sensitiveList, "BBC") + sensitiveList = append(sensitiveList, "bbs.766.com") + sensitiveList = append(sensitiveList, "bbs.7gg.cn") + sensitiveList = append(sensitiveList, "bbs.butcn.com") + sensitiveList = append(sensitiveList, "bbs.pkmmo") + sensitiveList = append(sensitiveList, "bc") + sensitiveList = append(sensitiveList, "bcd.s.59764.com") + sensitiveList = append(sensitiveList, "benladeng") + sensitiveList = append(sensitiveList, "biaozi") + sensitiveList = append(sensitiveList, "biao瀛?bibidu.com") + sensitiveList = append(sensitiveList, "bignews") + sensitiveList = append(sensitiveList, "bitch") + sensitiveList = append(sensitiveList, "bi鏍?bl62.com") + sensitiveList = append(sensitiveList, "blacksee.com.cn") + sensitiveList = append(sensitiveList, "blowjob") + sensitiveList = append(sensitiveList, "blowjobs") + sensitiveList = append(sensitiveList, "bobaoping") + sensitiveList = append(sensitiveList, "boxilai") + sensitiveList = append(sensitiveList, "boxun") + sensitiveList = append(sensitiveList, "bulaier") + sensitiveList = append(sensitiveList, "bulaoge.com") + sensitiveList = append(sensitiveList, "bushi") + sensitiveList = append(sensitiveList, "bwowd.com") + sensitiveList = append(sensitiveList, "bz176.com") + sensitiveList = append(sensitiveList, "b鏍?c a o") + sensitiveList = append(sensitiveList, "C-SPAN") + sensitiveList = append(sensitiveList, "c5c8.cn") + sensitiveList = append(sensitiveList, "canyaa.com") + sensitiveList = append(sensitiveList, "cao") + sensitiveList = append(sensitiveList, "caob") + sensitiveList = append(sensitiveList, "caobi") + sensitiveList = append(sensitiveList, "caogangchuan") + sensitiveList = append(sensitiveList, "cao浣?cao浣犲ぇ鐖?cao浣犲") + sensitiveList = append(sensitiveList, "carrefour") + sensitiveList = append(sensitiveList, "cc灏忛洩") + sensitiveList = append(sensitiveList, "cdream.com") + sensitiveList = append(sensitiveList, "chailing") + sensitiveList = append(sensitiveList, "cha浣?chenduxiu") + sensitiveList = append(sensitiveList, "chengrenmanhua.1242.net.cn") + sensitiveList = append(sensitiveList, "chengrenwangzhi.1242.net.cn") + sensitiveList = append(sensitiveList, "chenliangyu") + sensitiveList = append(sensitiveList, "chenyi") + sensitiveList = append(sensitiveList, "chenyun") + sensitiveList = append(sensitiveList, "chenzhili") + sensitiveList = append(sensitiveList, "chinaliberal") + sensitiveList = append(sensitiveList, "chinamz") + sensitiveList = append(sensitiveList, "chinavfx.net") + sensitiveList = append(sensitiveList, "chinesenewsnet") + sensitiveList = append(sensitiveList, "chongxianmu.cn") + sensitiveList = append(sensitiveList, "chunyilang") + sensitiveList = append(sensitiveList, "cikcatv.2om") + sensitiveList = append(sensitiveList, "client") + sensitiveList = append(sensitiveList, "clockgemstone") + sensitiveList = append(sensitiveList, "cnaicheng.1174.net.cn") + sensitiveList = append(sensitiveList, "cnd") + sensitiveList = append(sensitiveList, "CND鍒婄墿鍜岃鍧?CNN/NHK") + sensitiveList = append(sensitiveList, "consignment5173") + sensitiveList = append(sensitiveList, "creaders") + sensitiveList = append(sensitiveList, "crestbone") + sensitiveList = append(sensitiveList, "cs") + sensitiveList = append(sensitiveList, "cvceo.com") + sensitiveList = append(sensitiveList, "d666.com") + sensitiveList = append(sensitiveList, "dadati.com") + sensitiveList = append(sensitiveList, "dafa") + sensitiveList = append(sensitiveList, "dajiyuan") + sensitiveList = append(sensitiveList, "dalai") + sensitiveList = append(sensitiveList, "dalailama") + sensitiveList = append(sensitiveList, "damn") + sensitiveList = append(sensitiveList, "dang") + sensitiveList = append(sensitiveList, "dengxiaoping") + sensitiveList = append(sensitiveList, "dfdz") + sensitiveList = append(sensitiveList, "dick") + sensitiveList = append(sensitiveList, "digitallongking.com") + sensitiveList = append(sensitiveList, "dingxiang.1243.net.cn") + sensitiveList = append(sensitiveList, "dingxiang.1249.net") + sensitiveList = append(sensitiveList, "Discuss") + sensitiveList = append(sensitiveList, "dishun") + sensitiveList = append(sensitiveList, "DJY") + sensitiveList = append(sensitiveList, "dl.com") + sensitiveList = append(sensitiveList, "dolbbs.com") + sensitiveList = append(sensitiveList, "dongtiaoyingji") + sensitiveList = append(sensitiveList, "dpp") + sensitiveList = append(sensitiveList, "dulumen") + sensitiveList = append(sensitiveList, "duowan") + sensitiveList = append(sensitiveList, "duowan.com") + sensitiveList = append(sensitiveList, "e7sw.cn") + sensitiveList = append(sensitiveList, "eee.xaoh.cn") + sensitiveList = append(sensitiveList, "eerdeni") + sensitiveList = append(sensitiveList, "Engadget涓枃缃?engesi") + sensitiveList = append(sensitiveList, "ent365.com") + sensitiveList = append(sensitiveList, "event") + sensitiveList = append(sensitiveList, "falu") + sensitiveList = append(sensitiveList, "falun") + sensitiveList = append(sensitiveList, "falundafa") + sensitiveList = append(sensitiveList, "falundafahao") + sensitiveList = append(sensitiveList, "falungong") + sensitiveList = append(sensitiveList, "fa杞?feelmistone") + sensitiveList = append(sensitiveList, "feitengdl.com") + sensitiveList = append(sensitiveList, "fisonet.com") + sensitiveList = append(sensitiveList, "fku") + sensitiveList = append(sensitiveList, "flg") + sensitiveList = append(sensitiveList, "freechina") + sensitiveList = append(sensitiveList, "freedom") + sensitiveList = append(sensitiveList, "freenet") + sensitiveList = append(sensitiveList, "fuck") + sensitiveList = append(sensitiveList, "fulankelin") + sensitiveList = append(sensitiveList, "fxjsqc.com") + sensitiveList = append(sensitiveList, "fy371.com") + sensitiveList = append(sensitiveList, "g365.net") + sensitiveList = append(sensitiveList, "game") + sensitiveList = append(sensitiveList, "gamemaster") + sensitiveList = append(sensitiveList, "gangcunningci") + sensitiveList = append(sensitiveList, "gangcunxiushu") + sensitiveList = append(sensitiveList, "gan浣?gaolipiao") + sensitiveList = append(sensitiveList, "gcd") + sensitiveList = append(sensitiveList, "geocities.com") + sensitiveList = append(sensitiveList, "gigabyte.cn") + sensitiveList = append(sensitiveList, "gm") + sensitiveList = append(sensitiveList, "gogo.net") + sensitiveList = append(sensitiveList, "gongchandang") + sensitiveList = append(sensitiveList, "gruepin") + sensitiveList = append(sensitiveList, "gssmtt.com") + sensitiveList = append(sensitiveList, "guobaxiong") + sensitiveList = append(sensitiveList, "guojia") + sensitiveList = append(sensitiveList, "g鍏?g鍙?g宸?g鎺?g鐐?g鐗?g鑺?g榛?hacking") + sensitiveList = append(sensitiveList, "hao1788.co") + sensitiveList = append(sensitiveList, "haog8.cn") + sensitiveList = append(sensitiveList, "haosilu.com") + sensitiveList = append(sensitiveList, "haouse.cn") + sensitiveList = append(sensitiveList, "heguoqiang") + sensitiveList = append(sensitiveList, "helong") + sensitiveList = append(sensitiveList, "heluxiaofu") + sensitiveList = append(sensitiveList, "hexun.com") + sensitiveList = append(sensitiveList, "homexf.cn") + sensitiveList = append(sensitiveList, "Hong Kong Herald. 鍙版咕鎶ョ焊") + sensitiveList = append(sensitiveList, "hongzhi") + sensitiveList = append(sensitiveList, "host800.com") + sensitiveList = append(sensitiveList, "hrichina") + sensitiveList = append(sensitiveList, "http") + sensitiveList = append(sensitiveList, "huajianmin") + sensitiveList = append(sensitiveList, "huanet") + sensitiveList = append(sensitiveList, "huangju") + sensitiveList = append(sensitiveList, "huangsexiaoshuo.1242.net.cn") + sensitiveList = append(sensitiveList, "huashengdun") + sensitiveList = append(sensitiveList, "huihuangtx.com") + sensitiveList = append(sensitiveList, "huiliangyu") + sensitiveList = append(sensitiveList, "huimin") + sensitiveList = append(sensitiveList, "hujintao") + sensitiveList = append(sensitiveList, "huyaobang") + sensitiveList = append(sensitiveList, "huzhiming") + sensitiveList = append(sensitiveList, "hypermart.net") + sensitiveList = append(sensitiveList, "h鍔ㄦ极") + sensitiveList = append(sensitiveList, "h鍕曟极") + sensitiveList = append(sensitiveList, "h绔?i9game.com") + sensitiveList = append(sensitiveList, "ice") + sensitiveList = append(sensitiveList, "icpcn.com") + sensitiveList = append(sensitiveList, "id666.uqc.cn") + sensitiveList = append(sensitiveList, "ieboy.cn") + sensitiveList = append(sensitiveList, "incest") + sensitiveList = append(sensitiveList, "ising99.com") + sensitiveList = append(sensitiveList, "islam") + sensitiveList = append(sensitiveList, "item") + sensitiveList = append(sensitiveList, "j8") + sensitiveList = append(sensitiveList, "jb") + sensitiveList = append(sensitiveList, "jdyou.com") + sensitiveList = append(sensitiveList, "jiangdongriji") + sensitiveList = append(sensitiveList, "jiangjieshi") + sensitiveList = append(sensitiveList, "jiangjingguo") + sensitiveList = append(sensitiveList, "jiangqing") + sensitiveList = append(sensitiveList, "jiangzemin") + sensitiveList = append(sensitiveList, "jiangzhongzheng") + sensitiveList = append(sensitiveList, "jian浣?jiaochuang") + sensitiveList = append(sensitiveList, "jiaochun") + sensitiveList = append(sensitiveList, "jiaqinglin") + sensitiveList = append(sensitiveList, "jiayyou.cn") + sensitiveList = append(sensitiveList, "jiayyou.com") + sensitiveList = append(sensitiveList, "jiba") + sensitiveList = append(sensitiveList, "jinpaopao.com") + sensitiveList = append(sensitiveList, "jinricheng") + sensitiveList = append(sensitiveList, "jinv") + sensitiveList = append(sensitiveList, "jinzhengen") + sensitiveList = append(sensitiveList, "jinzhengri") + sensitiveList = append(sensitiveList, "ji濂?jjdlw.com") + sensitiveList = append(sensitiveList, "jooplay.com") + sensitiveList = append(sensitiveList, "kao") + sensitiveList = append(sensitiveList, "kartt.cn") + sensitiveList = append(sensitiveList, "kasiteluo") + sensitiveList = append(sensitiveList, "kefu") + sensitiveList = append(sensitiveList, "kelindun") + sensitiveList = append(sensitiveList, "kissmyass") + sensitiveList = append(sensitiveList, "kkk.xaoh.cn") + sensitiveList = append(sensitiveList, "ko180") + sensitiveList = append(sensitiveList, "kod920.cn") + sensitiveList = append(sensitiveList, "k绮?l2jsom.cn") + sensitiveList = append(sensitiveList, "laichangxing") + sensitiveList = append(sensitiveList, "lamusifeierde") + sensitiveList = append(sensitiveList, "langyou.info") + sensitiveList = append(sensitiveList, "laola") + sensitiveList = append(sensitiveList, "lianggezhongguo") + sensitiveList = append(sensitiveList, "lianzhan") + sensitiveList = append(sensitiveList, "lichangchun") + sensitiveList = append(sensitiveList, "lidashi") + sensitiveList = append(sensitiveList, "lidazhao") + sensitiveList = append(sensitiveList, "lidenghui") + sensitiveList = append(sensitiveList, "liening") + sensitiveList = append(sensitiveList, "ligen") + sensitiveList = append(sensitiveList, "lihongzhi") + sensitiveList = append(sensitiveList, "likeko.com") + sensitiveList = append(sensitiveList, "likeqiang") + sensitiveList = append(sensitiveList, "lilanqing") + sensitiveList = append(sensitiveList, "linbiao") + sensitiveList = append(sensitiveList, "lingjihua") + sensitiveList = append(sensitiveList, "linken") + sensitiveList = append(sensitiveList, "lipeng") + sensitiveList = append(sensitiveList, "liruihuan") + sensitiveList = append(sensitiveList, "liubocheng") + sensitiveList = append(sensitiveList, "liulijun") + sensitiveList = append(sensitiveList, "liuqi") + sensitiveList = append(sensitiveList, "liushaoqi") + sensitiveList = append(sensitiveList, "liusi") + sensitiveList = append(sensitiveList, "liuyunshan") + sensitiveList = append(sensitiveList, "lll.xaoh.cn") + sensitiveList = append(sensitiveList, "lordren.com") + sensitiveList = append(sensitiveList, "lunlidianying.1274.net.cn") + sensitiveList = append(sensitiveList, "lunlidianyingxiazai.1174.net.cn") + sensitiveList = append(sensitiveList, "luogan") + sensitiveList = append(sensitiveList, "luoronghuan") + sensitiveList = append(sensitiveList, "luoxialu.cn") + sensitiveList = append(sensitiveList, "luxiulian") + sensitiveList = append(sensitiveList, "luyi") + sensitiveList = append(sensitiveList, "lxsm888.com") + sensitiveList = append(sensitiveList, "lybbs.info") + sensitiveList = append(sensitiveList, "lyx-game.cn") + sensitiveList = append(sensitiveList, "mai楠?majiajue") + sensitiveList = append(sensitiveList, "makesi") + sensitiveList = append(sensitiveList, "making") + sensitiveList = append(sensitiveList, "mandela") + sensitiveList = append(sensitiveList, "maozedong") + sensitiveList = append(sensitiveList, "master") + sensitiveList = append(sensitiveList, "mayingjiu") + sensitiveList = append(sensitiveList, "meideweijiefu") + sensitiveList = append(sensitiveList, "meiguiqingren.1274.net.cn") + sensitiveList = append(sensitiveList, "mgjzybj.com") + sensitiveList = append(sensitiveList, "minghui") + sensitiveList = append(sensitiveList, "minghuinews") + sensitiveList = append(sensitiveList, "mm灞?mm缇庡浘") + sensitiveList = append(sensitiveList, "mm缇庡湒") + sensitiveList = append(sensitiveList, "mohanmode") + sensitiveList = append(sensitiveList, "mokeer") + sensitiveList = append(sensitiveList, "mosuolini") + sensitiveList = append(sensitiveList, "moyi520.cn") + sensitiveList = append(sensitiveList, "mpceggs.cn") + sensitiveList = append(sensitiveList, "my3q.com") + sensitiveList = append(sensitiveList, "MyRadio") + sensitiveList = append(sensitiveList, "nacb") + sensitiveList = append(sensitiveList, "naive") + sensitiveList = append(sensitiveList, "napolun") + sensitiveList = append(sensitiveList, "narcotics") + sensitiveList = append(sensitiveList, "nbfib.cn") + sensitiveList = append(sensitiveList, "ncyh.cn") + sensitiveList = append(sensitiveList, "neckromancer") + sensitiveList = append(sensitiveList, "newsmth.net") + sensitiveList = append(sensitiveList, "nierongzhen") + sensitiveList = append(sensitiveList, "nikesong") + sensitiveList = append(sensitiveList, "niuniujidi.1174.net.cn") + sensitiveList = append(sensitiveList, "njgamecollege.org") + sensitiveList = append(sensitiveList, "nmis") + sensitiveList = append(sensitiveList, "npc") + sensitiveList = append(sensitiveList, "okaygood.cn") + sensitiveList = append(sensitiveList, "ooo.xaoh.cn") + sensitiveList = append(sensitiveList, "osisa.cn") + sensitiveList = append(sensitiveList, "paper64") + sensitiveList = append(sensitiveList, "parke888.com") + sensitiveList = append(sensitiveList, "pcikchina.com") + sensitiveList = append(sensitiveList, "peacehall") + sensitiveList = append(sensitiveList, "pengdehuai") + sensitiveList = append(sensitiveList, "pengliyuan") + sensitiveList = append(sensitiveList, "penis") + sensitiveList = append(sensitiveList, "petgirl") + sensitiveList = append(sensitiveList, "pk200.com") + sensitiveList = append(sensitiveList, "pkmmo.com") + sensitiveList = append(sensitiveList, "playboy") + sensitiveList = append(sensitiveList, "porn") + sensitiveList = append(sensitiveList, "ppt.cc") + sensitiveList = append(sensitiveList, "prettyirene.net") + sensitiveList = append(sensitiveList, "pujing") + sensitiveList = append(sensitiveList, "pussy") + sensitiveList = append(sensitiveList, "qgqm.org") + sensitiveList = append(sensitiveList, "qiangjian") + sensitiveList = append(sensitiveList, "qingsewuyuetian.1174.net.cn") + sensitiveList = append(sensitiveList, "qixingnet.com") + sensitiveList = append(sensitiveList, "qqsg.org") + sensitiveList = append(sensitiveList, "qtoy.com") + sensitiveList = append(sensitiveList, "quannengshenjiao") + sensitiveList = append(sensitiveList, "rape") + sensitiveList = append(sensitiveList, "reddidi.com.cn") + sensitiveList = append(sensitiveList, "renaya") + sensitiveList = append(sensitiveList, "renminbao") + sensitiveList = append(sensitiveList, "renmindahuitang") + sensitiveList = append(sensitiveList, "renmingbao") + sensitiveList = append(sensitiveList, "rfa") + sensitiveList = append(sensitiveList, "RFI") + sensitiveList = append(sensitiveList, "rivals") + sensitiveList = append(sensitiveList, "rrr.xaoh.cn") + sensitiveList = append(sensitiveList, "sadamu") + sensitiveList = append(sensitiveList, "safeweb") + sensitiveList = append(sensitiveList, "samalanqi") + sensitiveList = append(sensitiveList, "saobi") + sensitiveList = append(sensitiveList, "saqieer") + sensitiveList = append(sensitiveList, "sb") + sensitiveList = append(sensitiveList, "server") + sensitiveList = append(sensitiveList, "sewuyuetian.1174.net.cn") + sensitiveList = append(sensitiveList, "sewuyuetian.1274.net.cn") + sensitiveList = append(sensitiveList, "sex") + sensitiveList = append(sensitiveList, "sex鑱婂ぉ瀹?sf") + sensitiveList = append(sensitiveList, "shanbenwushiliu") + sensitiveList = append(sensitiveList, "shenycs.co") + sensitiveList = append(sensitiveList, "shijiamouni") + sensitiveList = append(sensitiveList, "shit") + sensitiveList = append(sensitiveList, "showka8.com") + sensitiveList = append(sensitiveList, "sidalin") + sensitiveList = append(sensitiveList, "simple") + sensitiveList = append(sensitiveList, "siwameitui.1274.net.cn") + sensitiveList = append(sensitiveList, "sjlike.com") + sensitiveList = append(sensitiveList, "sm") + sensitiveList = append(sensitiveList, "sm濂崇帇") + sensitiveList = append(sensitiveList, "sm鎻翠氦") + sensitiveList = append(sensitiveList, "sm瑾挎暀") + sensitiveList = append(sensitiveList, "sm璋冩暀") + sensitiveList = append(sensitiveList, "songchuyu") + sensitiveList = append(sensitiveList, "stlmbbs.cn") + sensitiveList = append(sensitiveList, "suck") + sensitiveList = append(sensitiveList, "sucker") + sensitiveList = append(sensitiveList, "sunwen") + sensitiveList = append(sensitiveList, "sunyixian") + sensitiveList = append(sensitiveList, "sunzhongshan") + sensitiveList = append(sensitiveList, "svdc") + sensitiveList = append(sensitiveList, "system") + sensitiveList = append(sensitiveList, "szlmgh.cn") + sensitiveList = append(sensitiveList, "t.m.d") + sensitiveList = append(sensitiveList, "t9wg.cn") + sensitiveList = append(sensitiveList, "taidu") + sensitiveList = append(sensitiveList, "taip") + sensitiveList = append(sensitiveList, "Taipei Times") + sensitiveList = append(sensitiveList, "taiwanduli") + sensitiveList = append(sensitiveList, "tangjiaxuan") + sensitiveList = append(sensitiveList, "taobao") + sensitiveList = append(sensitiveList, "tb.hi.4024.net.cn") + sensitiveList = append(sensitiveList, "TD") + sensitiveList = append(sensitiveList, "teen") + sensitiveList = append(sensitiveList, "test") + sensitiveList = append(sensitiveList, "The Standard") + sensitiveList = append(sensitiveList, "the9") + sensitiveList = append(sensitiveList, "the9city") + sensitiveList = append(sensitiveList, "thec.cn") + sensitiveList = append(sensitiveList, "tiananmen") + sensitiveList = append(sensitiveList, "tianlong4f.cn") + sensitiveList = append(sensitiveList, "tibetalk") + sensitiveList = append(sensitiveList, "tmd") + sensitiveList = append(sensitiveList, "tnnd") + sensitiveList = append(sensitiveList, "tont.cn") + sensitiveList = append(sensitiveList, "triangle") + sensitiveList = append(sensitiveList, "triangleboy") + sensitiveList = append(sensitiveList, "tringel") + sensitiveList = append(sensitiveList, "txwswind") + sensitiveList = append(sensitiveList, "u r") + sensitiveList = append(sensitiveList, "u-r") + sensitiveList = append(sensitiveList, "u/r") + sensitiveList = append(sensitiveList, "ul86.com") + sensitiveList = append(sensitiveList, "ultrasurf") + sensitiveList = append(sensitiveList, "unixbox") + sensitiveList = append(sensitiveList, "ur") + sensitiveList = append(sensitiveList, "urban") + sensitiveList = append(sensitiveList, "urban-rivals") + sensitiveList = append(sensitiveList, "ustibet") + sensitiveList = append(sensitiveList, "uu1001.com") + sensitiveList = append(sensitiveList, "vip886.com") + sensitiveList = append(sensitiveList, "voa") + sensitiveList = append(sensitiveList, "voachinese") + sensitiveList = append(sensitiveList, "wangce") + sensitiveList = append(sensitiveList, "wangdan") + sensitiveList = append(sensitiveList, "wanggang") + sensitiveList = append(sensitiveList, "wanghongwen") + sensitiveList = append(sensitiveList, "wanglequan") + sensitiveList = append(sensitiveList, "wanglijun") + sensitiveList = append(sensitiveList, "wangqishan") + sensitiveList = append(sensitiveList, "wangyoudl.com") + sensitiveList = append(sensitiveList, "wangzhaoguo") + sensitiveList = append(sensitiveList, "webgame.com.cn") + sensitiveList = append(sensitiveList, "webzen") + sensitiveList = append(sensitiveList, "web鎴樼墝") + sensitiveList = append(sensitiveList, "web鐗屾埌") + sensitiveList = append(sensitiveList, "weijianxing") + sensitiveList = append(sensitiveList, "wenjiabao") + sensitiveList = append(sensitiveList, "wg") + sensitiveList = append(sensitiveList, "wg7766.cn") + sensitiveList = append(sensitiveList, "wgbobo.cn") + sensitiveList = append(sensitiveList, "wgxiaowu.com") + sensitiveList = append(sensitiveList, "whoyo.com") + sensitiveList = append(sensitiveList, "woerkaixi") + sensitiveList = append(sensitiveList, "woyinwose.1243.net.cn") + sensitiveList = append(sensitiveList, "wstaiji") + sensitiveList = append(sensitiveList, "wubangguo") + sensitiveList = append(sensitiveList, "wuguanzheng") + sensitiveList = append(sensitiveList, "wuyi") + sensitiveList = append(sensitiveList, "www") + sensitiveList = append(sensitiveList, "www.") + sensitiveList = append(sensitiveList, "www.23nice.com.cn") + sensitiveList = append(sensitiveList, "www.2q5q.com") + sensitiveList = append(sensitiveList, "www.315ts.net") + sensitiveList = append(sensitiveList, "www.365jw.com") + sensitiveList = append(sensitiveList, "www.50spcombaidu1828adyou97sace.co.cc") + sensitiveList = append(sensitiveList, "www.567yx.com") + sensitiveList = append(sensitiveList, "www.70yx.com") + sensitiveList = append(sensitiveList, "www.766.com") + sensitiveList = append(sensitiveList, "www.91wangyou.com") + sensitiveList = append(sensitiveList, "www.976543.com") + sensitiveList = append(sensitiveList, "www.978808.com") + sensitiveList = append(sensitiveList, "www.99game.net") + sensitiveList = append(sensitiveList, "www.anule.cn") + sensitiveList = append(sensitiveList, "www.bin5.cn") + sensitiveList = append(sensitiveList, "www.dy6789.cn") + sensitiveList = append(sensitiveList, "www.foyeye.com") + sensitiveList = append(sensitiveList, "www.gamelifeclub.cn") + sensitiveList = append(sensitiveList, "www.gardcn.com") + sensitiveList = append(sensitiveList, "www.gw17173.cn") + sensitiveList = append(sensitiveList, "www.legu.com") + sensitiveList = append(sensitiveList, "www.maituan.com") + sensitiveList = append(sensitiveList, "www.neicehao.cn") + sensitiveList = append(sensitiveList, "www.sanjidianying.com.cn") + sensitiveList = append(sensitiveList, "www.sexwyt.com") + sensitiveList = append(sensitiveList, "www.sf766.com.cn") + sensitiveList = append(sensitiveList, "www.wang567.com") + sensitiveList = append(sensitiveList, "www.wow366.cn") + sensitiveList = append(sensitiveList, "www.xaoh.cn") + sensitiveList = append(sensitiveList, "www.yhzt.org") + sensitiveList = append(sensitiveList, "www.ylteam.cn") + sensitiveList = append(sensitiveList, "www.yxnpc.com") + sensitiveList = append(sensitiveList, "www.zsyxhd.cn") + sensitiveList = append(sensitiveList, "www.zxkaku.cn") + sensitiveList = append(sensitiveList, "www.zy528.com") + sensitiveList = append(sensitiveList, "wy33.com") + sensitiveList = append(sensitiveList, "wy3868.com") + sensitiveList = append(sensitiveList, "wy724.com") + sensitiveList = append(sensitiveList, "xdns.eu") + sensitiveList = append(sensitiveList, "xiao77") + sensitiveList = append(sensitiveList, "xiao77.1243.net.cn") + sensitiveList = append(sensitiveList, "xiao77luntan.1249.net.cn") + sensitiveList = append(sensitiveList, "xiaoqinzaixian.cn") + sensitiveList = append(sensitiveList, "xiaoquan") + sensitiveList = append(sensitiveList, "xiejiao") + sensitiveList = append(sensitiveList, "xihanuke") + sensitiveList = append(sensitiveList, "xijinping") + sensitiveList = append(sensitiveList, "xilake") + sensitiveList = append(sensitiveList, "xingqingzhongren.1174.net.cn") + sensitiveList = append(sensitiveList, "xinjiangduli") + sensitiveList = append(sensitiveList, "xinsheng") + sensitiveList = append(sensitiveList, "xinwenguanzhi") + sensitiveList = append(sensitiveList, "xitele") + sensitiveList = append(sensitiveList, "xiuau.cn") + sensitiveList = append(sensitiveList, "xizangduli") + sensitiveList = append(sensitiveList, "xl517.com") + sensitiveList = append(sensitiveList, "xq-wl.cn") + sensitiveList = append(sensitiveList, "xtl") + sensitiveList = append(sensitiveList, "xucaihou") + sensitiveList = append(sensitiveList, "xuxiangqian") + sensitiveList = append(sensitiveList, "xya3.cn") + sensitiveList = append(sensitiveList, "xyq2sf.com") + sensitiveList = append(sensitiveList, "xyxgh.com") + sensitiveList = append(sensitiveList, "yalishanda") + sensitiveList = append(sensitiveList, "yaokong7.com") + sensitiveList = append(sensitiveList, "yaowenyuan") + sensitiveList = append(sensitiveList, "yejianying") + sensitiveList = append(sensitiveList, "yeswm.com") + sensitiveList = append(sensitiveList, "yidangzhuanzheng") + sensitiveList = append(sensitiveList, "yisilan") + sensitiveList = append(sensitiveList, "youxiji888.cn") + sensitiveList = append(sensitiveList, "ys168.com") + sensitiveList = append(sensitiveList, "yujiankang") + sensitiveList = append(sensitiveList, "yuming") + sensitiveList = append(sensitiveList, "yumuming") + sensitiveList = append(sensitiveList, "yuyongkang") + sensitiveList = append(sensitiveList, "yuzhengsheng") + sensitiveList = append(sensitiveList, "yz55.cn") + sensitiveList = append(sensitiveList, "zaixu.net") + sensitiveList = append(sensitiveList, "zengpeiyan") + sensitiveList = append(sensitiveList, "zengqinghong") + sensitiveList = append(sensitiveList, "zhangchunqiao") + sensitiveList = append(sensitiveList, "zhangdejiang") + sensitiveList = append(sensitiveList, "zhanggaoli") + sensitiveList = append(sensitiveList, "zhaoziyang") + sensitiveList = append(sensitiveList, "zhengjian") + sensitiveList = append(sensitiveList, "zhengjianwang") + sensitiveList = append(sensitiveList, "zhenshanren") + sensitiveList = append(sensitiveList, "zhenzhu") + sensitiveList = append(sensitiveList, "zhidian8.com") + sensitiveList = append(sensitiveList, "zhonghuaminguo") + sensitiveList = append(sensitiveList, "zhouenlai") + sensitiveList = append(sensitiveList, "zhoujiankang") + sensitiveList = append(sensitiveList, "zhouxiaokang") + sensitiveList = append(sensitiveList, "zhouyongkang") + sensitiveList = append(sensitiveList, "zhuanfalun") + sensitiveList = append(sensitiveList, "zhuanfalunadmin") + sensitiveList = append(sensitiveList, "zhude") + sensitiveList = append(sensitiveList, "zhurongji") + sensitiveList = append(sensitiveList, "zuanshi1000.cn") + sensitiveList = append(sensitiveList, "zx002.com") + sensitiveList = append(sensitiveList, "zxsj188.com") + sensitiveList = append(sensitiveList, "zy666.net") + sensitiveList = append(sensitiveList, "zzmysf.com") + sensitiveList = append(sensitiveList, "zzz.xaoh.cn") + sensitiveList = append(sensitiveList, "銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銊?銏?銏?銏?銏?銏?銏?銏?銏?銏?銑?銕?銕?銕?涓€涓竴鍙?涓€鍏氫笓鍒?涓€鍏氫笓鏀?涓€鍥戒袱鍒?涓€澶滄€х恫") + sensitiveList = append(sensitiveList, "涓€澶滄€х綉") + sensitiveList = append(sensitiveList, "涓€澶滄儏") + sensitiveList = append(sensitiveList, "涓€澶滄") + sensitiveList = append(sensitiveList, "涓€澶滄") + sensitiveList = append(sensitiveList, "涓€鏈亾") + sensitiveList = append(sensitiveList, "涓€璨亾") + sensitiveList = append(sensitiveList, "涓€璐亾") + sensitiveList = append(sensitiveList, "涓€杈逛竴鍥?涓€閭婁竴鍦?涓€闄€绯?涓€榛ㄥ皥鏀?涓佸叧鏍?涓佸瓙闇?涓佸瓧瑁ょ繕鑷€") + sensitiveList = append(sensitiveList, "涓佸瓧瑜茬抗鑷€") + sensitiveList = append(sensitiveList, "涓侀棞鏍?涓侀绀惧尯") + sensitiveList = append(sensitiveList, "涓侀绀惧崁") + sensitiveList = append(sensitiveList, "涓囨帆鍫?涓囩◣") + sensitiveList = append(sensitiveList, "涓囩淮鍗氬") + sensitiveList = append(sensitiveList, "涓囩淮璇昏€呯綉") + sensitiveList = append(sensitiveList, "涓塳榛?涓塸") + sensitiveList = append(sensitiveList, "涓変釜浠e") + sensitiveList = append(sensitiveList, "涓変釜鍛嗗") + sensitiveList = append(sensitiveList, "涓夊€嬩唬琛?涓夊叓娣?涓夊幓杌婁緰宸ュ姏") + sensitiveList = append(sensitiveList, "涓夊幓杞︿粦") + sensitiveList = append(sensitiveList, "涓夊攽浠?涓夋皯涓讳箟") + sensitiveList = append(sensitiveList, "涓夌礆鐗?涓夌骇鐗?涓夐櫔") + sensitiveList = append(sensitiveList, "涓夐櫔濂?涓婁綘") + sensitiveList = append(sensitiveList, "涓婂Τ") + sensitiveList = append(sensitiveList, "涓婃捣瀛ゅ厭闄?涓婃捣骞?涓婅í") + sensitiveList = append(sensitiveList, "涓嬫敞") + sensitiveList = append(sensitiveList, "涓嬫祦鍦板甫") + sensitiveList = append(sensitiveList, "涓嬫祦鍦板付") + sensitiveList = append(sensitiveList, "涓嬭长") + sensitiveList = append(sensitiveList, "涓嬭幢") + sensitiveList = append(sensitiveList, "涓嬮珨") + sensitiveList = append(sensitiveList, "涓?涓嶆粎甯濈帇") + sensitiveList = append(sensitiveList, "涓嶇埥涓嶈閷?涓嶇帺浜?涓嶈壇") + sensitiveList = append(sensitiveList, "涓撳埗") + sensitiveList = append(sensitiveList, "涓撳疇") + sensitiveList = append(sensitiveList, "涓撴斂") + sensitiveList = append(sensitiveList, "涓栫晫鎶ョ焊") + sensitiveList = append(sensitiveList, "涓栫晫鏂伴椈濯掍綋缃?涓栫晫鏂伴椈缃?涓栫晫鏃ユ姤") + sensitiveList = append(sensitiveList, "涓栫晫鐢靛奖(鍙版咕)") + sensitiveList = append(sensitiveList, "涓栫晫缍撴繜灏庡牨") + sensitiveList = append(sensitiveList, "涓栫淮浼?涓樺瀭璨?涓滀簹鏃ユ姤") + sensitiveList = append(sensitiveList, "涓滀簹鐥呭か") + sensitiveList = append(sensitiveList, "涓滀含鐑?涓滀紛杩?涓滃寳xx缃?涓滃寳鐙珛") + sensitiveList = append(sensitiveList, "涓滃崌") + sensitiveList = append(sensitiveList, "涓滄柟鏃ユ姤") + sensitiveList = append(sensitiveList, "涓滄潯鑻辨満") + sensitiveList = append(sensitiveList, "涓滄鏁?涓滅儹绌哄") + sensitiveList = append(sensitiveList, "涓滅獊") + sensitiveList = append(sensitiveList, "涓滅獊鏆村姩鍜岀嫭绔?涓滅獊缁勭粐") + sensitiveList = append(sensitiveList, "涓滅綉") + sensitiveList = append(sensitiveList, "涓濊") + sensitiveList = append(sensitiveList, "涓濊鍐欑湡") + sensitiveList = append(sensitiveList, "涓濊娣") + sensitiveList = append(sensitiveList, "涓濊瓒充氦") + sensitiveList = append(sensitiveList, "涓濊楂樿窡") + sensitiveList = append(sensitiveList, "涓濊") + sensitiveList = append(sensitiveList, "涓や釜涓浗") + sensitiveList = append(sensitiveList, "涓ゅ浗璁?涓よ吙涔嬮棿") + sensitiveList = append(sensitiveList, "涓ユ柟鍐?涓?涓縿閭婄晫") + sensitiveList = append(sensitiveList, "涓叡") + sensitiveList = append(sensitiveList, "涓崕鏃ユ姤") + sensitiveList = append(sensitiveList, "涓崕姘戝浗") + sensitiveList = append(sensitiveList, "涓崕鐢佃鍏徃") + sensitiveList = append(sensitiveList, "涓崡娴?涓浗涔嬫槬") + sensitiveList = append(sensitiveList, "涓浗浜烘潈鍙屽懆鍒?涓浗瀵嗘姤") + sensitiveList = append(sensitiveList, "涓浗鎬х埍鍩?涓浗鎭愭€栫粍缁?涓浗鎴愪汉璁哄潧") + sensitiveList = append(sensitiveList, "涓浗鏃舵姤") + sensitiveList = append(sensitiveList, "涓浗鏃舵姤鏂扮鍒嗙ぞ") + sensitiveList = append(sensitiveList, "涓浗鐚?涓浗鑼夎帀鑺遍潻鍛?涓湅涔嬫槬") + sensitiveList = append(sensitiveList, "涓湅鍏辩敚榛?涓湅鍜屽钩") + sensitiveList = append(sensitiveList, "涓湅鍟忛璜栧") + sensitiveList = append(sensitiveList, "涓湅濞佽剠璜?涓湅瀛ゅ厭闄?涓湅寰╄垐璜栧") + sensitiveList = append(sensitiveList, "涓湅鎬ф剾鍩?涓湅鎴愪汉璜栧") + sensitiveList = append(sensitiveList, "涓湅鐚?涓湅鐪熷鍏у") + sensitiveList = append(sensitiveList, "涓湅绀炬渻璜栧") + sensitiveList = append(sensitiveList, "涓湅绀炬渻閫叉榛?涓湅绉诲嫊閫氫俊") + sensitiveList = append(sensitiveList, "涓湅璜栧") + sensitiveList = append(sensitiveList, "涓湅璞?涓ぎ鏃ユ姤缃戠粶鎶?涓ぎ鐢佃鍙?涓ぎ绀?涓ぎ绀炬柊闂荤綉") + sensitiveList = append(sensitiveList, "涓ぎ闆昏鑷?涓閮?涓勾缇庡") + sensitiveList = append(sensitiveList, "涓勾缇庡│") + sensitiveList = append(sensitiveList, "涓枃鎼滄€х恫") + sensitiveList = append(sensitiveList, "涓枃鎼滄€х綉") + sensitiveList = append(sensitiveList, "涓椂鐢靛瓙鎶?涓鏂伴椈缃?涓瘨鍦栭ò") + sensitiveList = append(sensitiveList, "涓瘨鐨勫湒楱?涓彲浜烘皯瀵﹁┍瀵﹁") + sensitiveList = append(sensitiveList, "涓彲浜烘皯姝i偑") + sensitiveList = append(sensitiveList, "涓彲澶у湴") + sensitiveList = append(sensitiveList, "涓彲澶ц") + sensitiveList = append(sensitiveList, "涓彲鏅備簨") + sensitiveList = append(sensitiveList, "涓彲姘戝湅") + sensitiveList = append(sensitiveList, "涓彲鐪熷鍫遍亾") + sensitiveList = append(sensitiveList, "涓彲璎涙竻") + sensitiveList = append(sensitiveList, "涓彲椁婄敓鐩婃櫤鍔?涓颁钩") + sensitiveList = append(sensitiveList, "涓板攪鑹冲К") + sensitiveList = append(sensitiveList, "涓讳經") + sensitiveList = append(sensitiveList, "涓诲満鏂伴椈") + sensitiveList = append(sensitiveList, "涓诲腑") + sensitiveList = append(sensitiveList, "涓绘敾鎸囨彯瀹?涓荤京") + sensitiveList = append(sensitiveList, "涓芥槬鑻?涔旂煶") + sensitiveList = append(sensitiveList, "涔犱富甯?涔犲ぇ澶?涔犺繎骞?涔犺繙骞?涔″反浣?涔?涔板崠鏋敮") + sensitiveList = append(sensitiveList, "涔版槬") + sensitiveList = append(sensitiveList, "涔版槬鍫?涔拌储瀵?涔变鸡") + sensitiveList = append(sensitiveList, "涔变鸡鐔熷コ缃?涔?涔充氦") + sensitiveList = append(sensitiveList, "涔冲ご") + sensitiveList = append(sensitiveList, "涔冲皠") + sensitiveList = append(sensitiveList, "涔冲盎") + sensitiveList = append(sensitiveList, "涔虫埧") + sensitiveList = append(sensitiveList, "涔虫殘") + sensitiveList = append(sensitiveList, "涔虫涓濊") + sensitiveList = append(sensitiveList, "涔虫涓版弧") + sensitiveList = append(sensitiveList, "涔虫鍔ㄤ汉") + sensitiveList = append(sensitiveList, "涔虫鍕曚汉") + sensitiveList = append(sensitiveList, "涔虫绲茶オ") + sensitiveList = append(sensitiveList, "涔虫璞愭豢") + sensitiveList = append(sensitiveList, "涔虫矡") + sensitiveList = append(sensitiveList, "涔虫尝") + sensitiveList = append(sensitiveList, "涔虫尝鑷€娴?涔虫簼") + sensitiveList = append(sensitiveList, "涔崇垎") + sensitiveList = append(sensitiveList, "涔抽湼") + sensitiveList = append(sensitiveList, "浜備氦") + sensitiveList = append(sensitiveList, "浜傚€?浜傚€啛濂崇恫") + sensitiveList = append(sensitiveList, "浜傝吉") + sensitiveList = append(sensitiveList, "浜夐福鏉傚織") + sensitiveList = append(sensitiveList, "浜嬪鐛ㄧ珛") + sensitiveList = append(sensitiveList, "浜宐") + sensitiveList = append(sensitiveList, "浜屼箼鍩洪叞鑳哄彂鎶?浜屽眲") + sensitiveList = append(sensitiveList, "浜岀┐涓嚭") + sensitiveList = append(sensitiveList, "浜岄€?浜庡辜杌?浜掓帆") + sensitiveList = append(sensitiveList, "浜掕垟娣┐") + sensitiveList = append(sensitiveList, "浜旀槦绾㈡棗") + sensitiveList = append(sensitiveList, "浜旀湀澶?浜氬巻灞卞ぇ") + sensitiveList = append(sensitiveList, "浜氭儏") + sensitiveList = append(sensitiveList, "浜氭床鍛ㄥ垔") + sensitiveList = append(sensitiveList, "浜氭床鎬ц檺") + sensitiveList = append(sensitiveList, "浜氭床鎯呰壊缃?浜氭床鏈夌爜") + sensitiveList = append(sensitiveList, "浜氭床娣▋") + sensitiveList = append(sensitiveList, "浜氭床鑹?浜炴儏") + sensitiveList = append(sensitiveList, "浜炴灞卞ぇ") + sensitiveList = append(sensitiveList, "浜炴床鎬ц檺") + sensitiveList = append(sensitiveList, "浜炴床鎯呰壊缍?浜炴床鏈夌⒓") + sensitiveList = append(sensitiveList, "浜炴床娣▋") + sensitiveList = append(sensitiveList, "浜炴床鑹?浜ゅ") + sensitiveList = append(sensitiveList, "浜ゆ崲澶") + sensitiveList = append(sensitiveList, "浜ゆ彌澶") + sensitiveList = append(sensitiveList, "浜ゆ竻鐢靛瓙鎶?浜ら€氶儴") + sensitiveList = append(sensitiveList, "浜ら厤") + sensitiveList = append(sensitiveList, "浜ф潈灞€") + sensitiveList = append(sensitiveList, "浜眲") + sensitiveList = append(sensitiveList, "浜┐") + sensitiveList = append(sensitiveList, "浜叉皯鍏?浜轰唬浼?浜轰綋鎽勫奖") + sensitiveList = append(sensitiveList, "浜轰綋鐐稿脊") + sensitiveList = append(sensitiveList, "浜哄ぇ") + sensitiveList = append(sensitiveList, "浜哄ぇ浠h〃") + sensitiveList = append(sensitiveList, "浜哄") + sensitiveList = append(sensitiveList, "浜哄浜ゆ崲") + sensitiveList = append(sensitiveList, "浜哄浜ゆ彌") + sensitiveList = append(sensitiveList, "浜哄鍋氭剾") + sensitiveList = append(sensitiveList, "浜哄鍋氱埍") + sensitiveList = append(sensitiveList, "浜哄姒ㄤ钩") + sensitiveList = append(sensitiveList, "浜哄鐔熷コ") + sensitiveList = append(sensitiveList, "浜哄鑷媿") + sensitiveList = append(sensitiveList, "浜哄鑹茶獦") + sensitiveList = append(sensitiveList, "浜哄鑹茶") + sensitiveList = append(sensitiveList, "浜哄睜") + sensitiveList = append(sensitiveList, "浜哄脊") + sensitiveList = append(sensitiveList, "浜烘€ф湰鑹?浜烘瑠") + sensitiveList = append(sensitiveList, "浜烘皯") + sensitiveList = append(sensitiveList, "浜烘皯涔嬭伈璜栧") + sensitiveList = append(sensitiveList, "浜烘皯鍏ф儏鐪熺浉") + sensitiveList = append(sensitiveList, "浜烘皯鍫?浜烘皯澶т細鍫?浜烘皯澶ф渻鍫?浜烘皯骞垮満") + sensitiveList = append(sensitiveList, "浜烘皯鎶?浜烘皯鏃ユ姤") + sensitiveList = append(sensitiveList, "浜烘皯鐪熷") + sensitiveList = append(sensitiveList, "浜烘皯閾惰") + sensitiveList = append(sensitiveList, "浜洪棿绂忔姤") + sensitiveList = append(sensitiveList, "浜洪珨鏀濆奖") + sensitiveList = append(sensitiveList, "浠?浠嗚") + sensitiveList = append(sensitiveList, "浠婃棩鎮夊凹") + sensitiveList = append(sensitiveList, "浠栧棊鐨?浠栧") + sensitiveList = append(sensitiveList, "浠栧鐨?浠栧") + sensitiveList = append(sensitiveList, "浠栧鐨?浠栨瘝浜?浠栨瘝瑕?浠栫埞") + sensitiveList = append(sensitiveList, "浠栫瀹?浠栭┈鐨?浠よ▓鐣?浠よ鍒?浠ヨ寧鑷虫礊") + sensitiveList = append(sensitiveList, "浠?浼婃媺鍏?浼婃柉鍏?浼婃柉铇?浼婃柉铇簽鏍兼灄灏兼柉") + sensitiveList = append(sensitiveList, "浼婃湕") + sensitiveList = append(sensitiveList, "浼婅帋璨濈埦") + sensitiveList = append(sensitiveList, "浼卞") + sensitiveList = append(sensitiveList, "浼烘湇鍣?浣撳ジ") + sensitiveList = append(sensitiveList, "浣旈牁涓挵") + sensitiveList = append(sensitiveList, "浣曞€欏崕") + sensitiveList = append(sensitiveList, "浣欒嫳鏅?浣涘睍鍗冩墜娉?浣涙暀") + sensitiveList = append(sensitiveList, "浣涚") + sensitiveList = append(sensitiveList, "浣涜槶鍏嬫灄") + sensitiveList = append(sensitiveList, "浣滄剾") + sensitiveList = append(sensitiveList, "浣滅埍") + sensitiveList = append(sensitiveList, "浣滅") + sensitiveList = append(sensitiveList, "浣犱簩澶х埛") + sensitiveList = append(sensitiveList, "浣犲ぇ鐖?浣犲ぇ鐖?浣犲") + sensitiveList = append(sensitiveList, "浣犲鐨?浣犲閫?浣犲Д") + sensitiveList = append(sensitiveList, "浣犲Д濮ョ殑") + sensitiveList = append(sensitiveList, "浣犲") + sensitiveList = append(sensitiveList, "浣犲") + sensitiveList = append(sensitiveList, "浣犲浜嗗") + sensitiveList = append(sensitiveList, "浣犲姣?浣犲鐨?浣犲閫?浣犵埛鐖风殑") + sensitiveList = append(sensitiveList, "浣犵埜") + sensitiveList = append(sensitiveList, "浣犵埡") + sensitiveList = append(sensitiveList, "浣犺€佸懗") + sensitiveList = append(sensitiveList, "浣犺€佹瘝") + sensitiveList = append(sensitiveList, "浣犺壊鍚?浣犺壊鍡?浣犺鎴戣璜栧") + sensitiveList = append(sensitiveList, "浣?渚嶅緸鍊柉闊?渚嶅緸璨濊但鐖剧壒") + sensitiveList = append(sensitiveList, "渚︽帰璁惧") + sensitiveList = append(sensitiveList, "渚ㄥ姙") + sensitiveList = append(sensitiveList, "淇勫湅") + sensitiveList = append(sensitiveList, "淇忚噣鎽勯瓌") + sensitiveList = append(sensitiveList, "淇忚噣鏀濋瓌") + sensitiveList = append(sensitiveList, "淇濆瘑灞€") + sensitiveList = append(sensitiveList, "淇濈洃浼?淇濋嚕") + sensitiveList = append(sensitiveList, "淇濋挀") + sensitiveList = append(sensitiveList, "淇炴澹?淇炴鑱?淇″緬") + sensitiveList = append(sensitiveList, "淇℃姤鏈堝垔") + sensitiveList = append(sensitiveList, "淇℃姤璐㈢粡鏂伴椈") + sensitiveList = append(sensitiveList, "淇$敤鍗辨") + sensitiveList = append(sensitiveList, "淇厜") + sensitiveList = append(sensitiveList, "鍊嶅彲浜?鍊偛璩?鍊姛") + sensitiveList = append(sensitiveList, "鍊?鍊浗") + sensitiveList = append(sensitiveList, "鍊瘒") + sensitiveList = append(sensitiveList, "鍋囧簡鏋?鍋囨叾鏋?鍋囬槼鍏锋彃绌?鍋囬櫧鍏锋彃绌?鍋囬潚鏋?鍋氭剾") + sensitiveList = append(sensitiveList, "鍋氭剾鍦栫墖") + sensitiveList = append(sensitiveList, "鍋氭剾鑷媿") + sensitiveList = append(sensitiveList, "鍋氭剾闆诲奖") + sensitiveList = append(sensitiveList, "鍋氱埍") + sensitiveList = append(sensitiveList, "鍋氱埍鍥剧墖") + sensitiveList = append(sensitiveList, "鍋氱埍濮垮娍") + sensitiveList = append(sensitiveList, "鍋氱埍鐢靛奖") + sensitiveList = append(sensitiveList, "鍋氱埍鑷媿") + sensitiveList = append(sensitiveList, "鍋?鍋锋媿") + sensitiveList = append(sensitiveList, "鍋锋媿缇庣┐") + sensitiveList = append(sensitiveList, "鍋锋") + sensitiveList = append(sensitiveList, "鍋锋") + sensitiveList = append(sensitiveList, "鍋风鍥剧墖") + sensitiveList = append(sensitiveList, "鍋风瑙嗛") + sensitiveList = append(sensitiveList, "鍋风鍦栫墖") + sensitiveList = append(sensitiveList, "鍋风瑕栭牷") + sensitiveList = append(sensitiveList, "鍋风簿") + sensitiveList = append(sensitiveList, "鍌呬綔涔?鍌呬綔缇?鍌呴箯") + sensitiveList = append(sensitiveList, "鍌儏鑽?鍌虫煋鎬х梾") + sensitiveList = append(sensitiveList, "鍌?鍌?鍌籦") + sensitiveList = append(sensitiveList, "鍌诲嵉") + sensitiveList = append(sensitiveList, "鍌诲悐") + sensitiveList = append(sensitiveList, "鍌诲瓙") + sensitiveList = append(sensitiveList, "鍌诲眲") + sensitiveList = append(sensitiveList, "鍌绘瘮") + sensitiveList = append(sensitiveList, "鍌婚€?鍌婚偿") + sensitiveList = append(sensitiveList, "鍍?鍍佃硦") + sensitiveList = append(sensitiveList, "鍍佃硦姘?鍍佃醇姘?鍎傝憲鍗垫媼") + sensitiveList = append(sensitiveList, "鍎傝憲宀″窉") + sensitiveList = append(sensitiveList, "鍎?鍎垮瓙") + sensitiveList = append(sensitiveList, "鍏冭€佽槶鎻?娌冨痉锛?鍏堝ジ鍚庢潃") + sensitiveList = append(sensitiveList, "鍏堝ジ寰屾") + sensitiveList = append(sensitiveList, "鍏嬪嫗妫?鍏嬫灄闋?鍏嬫灄椤?鍏嬭悐鎭?鍏嬭悐鐗?鍏嶈不a鐗?鍏嶈不鍋风缍?鍏嶈不鎴愪汉缍茬珯") + sensitiveList = append(sensitiveList, "鍏嶈垂a鐗?鍏嶈垂鍋风缃?鍏嶈垂鎴愪汉缃戠珯") + sensitiveList = append(sensitiveList, "鍏掑瓙") + sensitiveList = append(sensitiveList, "鍏氫富甯?鍏ョ┐涓€娓?鍏у皠") + sensitiveList = append(sensitiveList, "鍏у皠缇庡│") + sensitiveList = append(sensitiveList, "鍏ц。") + sensitiveList = append(sensitiveList, "鍏цげ") + sensitiveList = append(sensitiveList, "鍏ㄥ浗浜哄ぇ") + sensitiveList = append(sensitiveList, "鍏ㄥ湅浜哄ぇ") + sensitiveList = append(sensitiveList, "鍏ㄥ湅鍏╂渻") + sensitiveList = append(sensitiveList, "鍏ㄨ兘绁?鍏ㄨ8") + sensitiveList = append(sensitiveList, "鍏╁€嬩腑鍦?鍏╁湅璜?鍏╁哺涓夊湴璜栧") + sensitiveList = append(sensitiveList, "鍏╁哺闂滀總") + sensitiveList = append(sensitiveList, "鍏╂渻") + sensitiveList = append(sensitiveList, "鍏╂渻鍫遍亾") + sensitiveList = append(sensitiveList, "鍏╂渻鏂拌仦") + sensitiveList = append(sensitiveList, "鍏節") + sensitiveList = append(sensitiveList, "鍏節椋庢尝") + sensitiveList = append(sensitiveList, "鍏柟鍥介檯璧勮") + sensitiveList = append(sensitiveList, "鍏涔?鍏浜?鍏畨") + sensitiveList = append(sensitiveList, "鍏畨灞€") + sensitiveList = append(sensitiveList, "鍏畨閮?鍏暀鎶?鍏?鍥?鍏?鍥?鍏?鍥?鍏€傚洓") + sensitiveList = append(sensitiveList, "鍏悎褰?鍏洓") + sensitiveList = append(sensitiveList, "鍏洓浜嬩欢") + sensitiveList = append(sensitiveList, "鍏洓鐪熺浉") + sensitiveList = append(sensitiveList, "鍏洓杩愬姩") + sensitiveList = append(sensitiveList, "鍏洓閬嬪嫊") + sensitiveList = append(sensitiveList, "鍏湀鑱旂洘") + sensitiveList = append(sensitiveList, "鍏湀鑱洘") + sensitiveList = append(sensitiveList, "鍏变骇涓讳箟") + sensitiveList = append(sensitiveList, "鍏变骇鍏?鍏卞厷") + sensitiveList = append(sensitiveList, "鍏卞尓") + sensitiveList = append(sensitiveList, "鍏辨Ξ鍦?鍏辩嫍") + sensitiveList = append(sensitiveList, "鍏辩敘榛?鍏辩敚") + sensitiveList = append(sensitiveList, "鍏辩敚涓荤京") + sensitiveList = append(sensitiveList, "鍏辩敚榛?鍏辫粛") + sensitiveList = append(sensitiveList, "鍏遍花") + sensitiveList = append(sensitiveList, "鍏村鍓?鍏戒氦") + sensitiveList = append(sensitiveList, "鍏藉吔闂?鍏藉ジ") + sensitiveList = append(sensitiveList, "鍏芥") + sensitiveList = append(sensitiveList, "鍐?鍐呭皠") + sensitiveList = append(sensitiveList, "鍐呭皠缇庡") + sensitiveList = append(sensitiveList, "鍐呭箷") + sensitiveList = append(sensitiveList, "鍐呭箷绗?8鏈?鍐堟潙瀹佹") + sensitiveList = append(sensitiveList, "鍐堟潙绉€鏍?鍐岄偅濞橀姣?鍐欑湡") + sensitiveList = append(sensitiveList, "鍐涘浗涓讳箟") + sensitiveList = append(sensitiveList, "鍐涘") + sensitiveList = append(sensitiveList, "鍐涢暱鍙戝▉") + sensitiveList = append(sensitiveList, "鍐滀笟鐢靛瓙鎶?鍐滀笟閮?鍐鐑?鍐鐔?鍐板緦") + sensitiveList = append(sensitiveList, "鍐版瘨") + sensitiveList = append(sensitiveList, "鍐扮矇") + sensitiveList = append(sensitiveList, "鍐?鍑岃颈") + sensitiveList = append(sensitiveList, "鍑卞ェ鍕掑凹浠€") + sensitiveList = append(sensitiveList, "鍑辩埦鏈?鍑辩埦闆锋柉") + sensitiveList = append(sensitiveList, "鍑辩壒鍒囩埦") + sensitiveList = append(sensitiveList, "鍑?鍑镐汉涔?鍑镐粬") + sensitiveList = append(sensitiveList, "鍑镐綘") + sensitiveList = append(sensitiveList, "鍑稿コ涔?鍑稿ス") + sensitiveList = append(sensitiveList, "鍑稿Τ") + sensitiveList = append(sensitiveList, "鍑告垜") + sensitiveList = append(sensitiveList, "鍑歌倝浼?鍑歌倝鍎?鍑哄敭鍋囧竵") + sensitiveList = append(sensitiveList, "鍑哄敭鎵嬫灙") + sensitiveList = append(sensitiveList, "鍑哄敭鏋敮") + sensitiveList = append(sensitiveList, "鍑哄敭姝ゅ彿") + sensitiveList = append(sensitiveList, "鍑哄敭绁炵") + sensitiveList = append(sensitiveList, "鍑哄敭璐﹀彿") + sensitiveList = append(sensitiveList, "鍑鸿建") + sensitiveList = append(sensitiveList, "鍒嗚绁栧浗") + sensitiveList = append(sensitiveList, "鍒嗛殜闀锋柦钂傛枃") + sensitiveList = append(sensitiveList, "鍒囦竷") + sensitiveList = append(sensitiveList, "鍒楀畞") + sensitiveList = append(sensitiveList, "鍒楀") + sensitiveList = append(sensitiveList, "鍒樸€€娣?鍒樹簯灞?鍒樺啗") + sensitiveList = append(sensitiveList, "鍒樺钁?鍒樺皯濂?鍒樺欢涓?鍒樻穱") + sensitiveList = append(sensitiveList, "鍒?鍙?鍒犲彿") + sensitiveList = append(sensitiveList, "鍒舵湇鐙?鍒舵湇缇庡") + sensitiveList = append(sensitiveList, "鍒舵湇缇庡│") + sensitiveList = append(sensitiveList, "鍒舵湇瑾樻儜") + sensitiveList = append(sensitiveList, "鍒?鍓囨皯") + sensitiveList = append(sensitiveList, "鍓嶅嚫鍚庣繕") + sensitiveList = append(sensitiveList, "鍓嶅嚫寰岀抗") + sensitiveList = append(sensitiveList, "鍓涙瘮") + sensitiveList = append(sensitiveList, "鍓涙瘮妯e瓙") + sensitiveList = append(sensitiveList, "鍔変集鎵?鍔変繆鍦?鍔夊嚤涓?鍔夊墰") + sensitiveList = append(sensitiveList, "鍔夊崈鐭?鍔夊湅鍑?鍔夊+璩?鍔夊皯濂?鍔夊北闈?鍔夊欢鏉?鍔夋枃鍕?鍔夋枃闆?鍔夋泬娉?鍔夋泬绔?鍔夋案宸?鍔夋穱") + sensitiveList = append(sensitiveList, "鍔夎彲娓?鍔夎硴娣?鍔夎硴闆?鍔夐洸灞?鍔夐潚") + sensitiveList = append(sensitiveList, "鍔?鍔?鍔炵悊鏂囧嚟") + sensitiveList = append(sensitiveList, "鍔炵悊璇佷欢") + sensitiveList = append(sensitiveList, "鍔犳嬁澶у浗闄呭箍鎾數鍙?鍔ㄥ悜鏉傚織") + sensitiveList = append(sensitiveList, "鍔ㄦ极鑹插浘") + sensitiveList = append(sensitiveList, "鍔╂墜濞佺埦鐗?鍔╃悊") + sensitiveList = append(sensitiveList, "鍔?鍔?鏁?鎵€") + sensitiveList = append(sensitiveList, "鍔冲姩淇濋殰閮?鍔虫媺") + sensitiveList = append(sensitiveList, "鍕曚簜") + sensitiveList = append(sensitiveList, "鍕曟极鑹插湒") + sensitiveList = append(sensitiveList, "鍕炲嫊鏁欓鎵€") + sensitiveList = append(sensitiveList, "鍕炴敼") + sensitiveList = append(sensitiveList, "鍕炴暀") + sensitiveList = append(sensitiveList, "鍕?鍕惧紩") + sensitiveList = append(sensitiveList, "鍕鹃瓊灏戝") + sensitiveList = append(sensitiveList, "鍕鹃瓊灏戝│") + sensitiveList = append(sensitiveList, "鍖呬簩濂?鍖呯毊") + sensitiveList = append(sensitiveList, "鍖椾含xx缍?鍖椾含xx缃?鍖椾含涔嬫槬") + sensitiveList = append(sensitiveList, "鍖椾含鐣跺眬") + sensitiveList = append(sensitiveList, "鍖楀ぇ涓夎鍦拌珫澹?鍖楃編涓枃缃?鍖楃編鑷敱璜栧") + sensitiveList = append(sensitiveList, "鍖楅煋") + sensitiveList = append(sensitiveList, "鍗佸叓鎽?鍗佸勾鍔ㄤ贡鐭宠繘") + sensitiveList = append(sensitiveList, "鍗囧ぉ") + sensitiveList = append(sensitiveList, "鍗庝汉浠婃棩缃?鍗庡浗閿?鍗庡瘜璐㈢粡") + sensitiveList = append(sensitiveList, "鍗庡皵琛楁棩鎶?鍗庡缓鏁?鍗庣洓椤?鍗撲集婧?鍗?鍥?鍗朾") + sensitiveList = append(sensitiveList, "鍗杋d") + sensitiveList = append(sensitiveList, "鍗杚q") + sensitiveList = append(sensitiveList, "鍗栧厷姹傝崳") + sensitiveList = append(sensitiveList, "鍗栧崱") + sensitiveList = append(sensitiveList, "鍗栧彿") + sensitiveList = append(sensitiveList, "鍗栧浗") + sensitiveList = append(sensitiveList, "鍗栧浗姹傝崳") + sensitiveList = append(sensitiveList, "鍗栨灙鏀脊鑽?鍗栨瘮") + sensitiveList = append(sensitiveList, "鍗栨帆") + sensitiveList = append(sensitiveList, "鍗栬储瀵?鍗栬韩") + sensitiveList = append(sensitiveList, "鍗栬蒋浠?鍗栭€?鍗楀崕鏃╂姤") + sensitiveList = append(sensitiveList, "鍗楀ぇ鑷敱璜栧") + sensitiveList = append(sensitiveList, "鍗楁棭涓枃缃?鍗楁磱瑙嗙晫") + sensitiveList = append(sensitiveList, "鍗楄仈鐩?鍗楄彲鏃╁牨") + sensitiveList = append(sensitiveList, "鍗楄洰") + sensitiveList = append(sensitiveList, "鍗楄洰瀛?鍗楄牷") + sensitiveList = append(sensitiveList, "鍗楄牷瀛?鍗氬澶ц禌缃戠珯") + sensitiveList = append(sensitiveList, "鍗氳▕") + sensitiveList = append(sensitiveList, "鍗氳") + sensitiveList = append(sensitiveList, "鍗氳缃?鍗氳皥缃?鍗犻涓幆") + sensitiveList = append(sensitiveList, "鍗℃柉妤?鍗℃柉鐗圭綏") + sensitiveList = append(sensitiveList, "鍗фЫ") + sensitiveList = append(sensitiveList, "鍗敓閮?鍗?鍗靛瓙") + sensitiveList = append(sensitiveList, "鍘曞ゴ") + sensitiveList = append(sensitiveList, "鍘曟墍鍋锋媿") + sensitiveList = append(sensitiveList, "鍘曟墍鐩楁憚") + sensitiveList = append(sensitiveList, "鍘?鍘熶綇姘戞棌鐢佃鍙?鍘熷懗涓濊") + sensitiveList = append(sensitiveList, "鍘熷懗绲茶オ") + sensitiveList = append(sensitiveList, "鍘熷瓙寮?鍘熷瓙褰?鍘熷瓙鑳芥満鏋?鍘讳綘濡堢殑") + sensitiveList = append(sensitiveList, "鍘讳綘濯界殑") + sensitiveList = append(sensitiveList, "鍘绘") + sensitiveList = append(sensitiveList, "鍙備簨瀹?鍙嬪ソ鐨勯寰?鍙屽嘲寰ⅳ") + sensitiveList = append(sensitiveList, "鍙岃瀛︾敓閭姤") + sensitiveList = append(sensitiveList, "鍙岄緳鍏ユ礊") + sensitiveList = append(sensitiveList, "鍙嶄汉椤?鍙嶅厷") + sensitiveList = append(sensitiveList, "鍙嶅叡") + sensitiveList = append(sensitiveList, "鍙嶅姩") + sensitiveList = append(sensitiveList, "鍙嶅崕") + sensitiveList = append(sensitiveList, "鍙嶅皝閹?鍙嶅皝閹栨妧琛?鍙嶆亹濮斿憳浼?鍙嶆斂搴?鍙嶆棩") + sensitiveList = append(sensitiveList, "鍙嶇ぞ鏈?鍙嶈厫鏁楄珫澹?鍙嶉潻鍛?鍙嶉花") + sensitiveList = append(sensitiveList, "鍙戜鸡") + sensitiveList = append(sensitiveList, "鍙戜鸡鍔?鍙戞姟鍔?鍙戞氮") + sensitiveList = append(sensitiveList, "鍙戠エ") + sensitiveList = append(sensitiveList, "鍙戣") + sensitiveList = append(sensitiveList, "鍙戣鍏?鍙戣鍔?鍙戣疆") + sensitiveList = append(sensitiveList, "鍙戦獨") + sensitiveList = append(sensitiveList, "鍙斿珎鑲夋") + sensitiveList = append(sensitiveList, "鍙楄檺鐙?鍙樻€?鍙d氦") + sensitiveList = append(sensitiveList, "鍙d氦鏀惧翱") + sensitiveList = append(sensitiveList, "鍙e収鐖嗗皠") + sensitiveList = append(sensitiveList, "鍙e唴鐖嗗皠") + sensitiveList = append(sensitiveList, "鍙e皠") + sensitiveList = append(sensitiveList, "鍙f椿") + sensitiveList = append(sensitiveList, "鍙f帆") + sensitiveList = append(sensitiveList, "鍙g垎") + sensitiveList = append(sensitiveList, "鍙g垎鍚炵簿") + sensitiveList = append(sensitiveList, "鍙ら緧绁") + sensitiveList = append(sensitiveList, "鍙皬濮?鍙簥") + sensitiveList = append(sensitiveList, "鍙洖") + sensitiveList = append(sensitiveList, "鍙浮") + sensitiveList = append(sensitiveList, "鍙崱鍙?鍙崱鍥?鍙緟鍥?鍙板姙") + sensitiveList = append(sensitiveList, "鍙版咕鍏?鍙版咕鍏变骇鍏?鍙版咕鍏卞拰鍥?鍙版咕鍥?鍙版咕宀涘浗") + sensitiveList = append(sensitiveList, "鍙版咕甯濆浗") + sensitiveList = append(sensitiveList, "鍙版咕鏃烘姤") + sensitiveList = append(sensitiveList, "鍙版咕姘戝浗") + sensitiveList = append(sensitiveList, "鍙版咕鐙?鍙版咕鐙珛") + sensitiveList = append(sensitiveList, "鍙版咕璁拌€呭崗浼?鍙扮嫭") + sensitiveList = append(sensitiveList, "鍙扮嫭涓囧瞾") + sensitiveList = append(sensitiveList, "鍙扮嫭鍒嗗瓙") + sensitiveList = append(sensitiveList, "鍙扮崹") + sensitiveList = append(sensitiveList, "鍙扮崹鍒嗗瓙") + sensitiveList = append(sensitiveList, "鍙扮崹钀") + sensitiveList = append(sensitiveList, "鍙拌仈") + sensitiveList = append(sensitiveList, "鍙拌伅") + sensitiveList = append(sensitiveList, "鍙拌嫳绀?鍙茶悐濮?鍙茶悐濮嗙帇") + sensitiveList = append(sensitiveList, "鍙查仈鏋?鍙崇考") + sensitiveList = append(sensitiveList, "鍙跺墤鑻?鍙稿緬鑿?鍙告硶閮?鍙搁Μ鏅?鍙搁Μ鐠?鍙间綘") + sensitiveList = append(sensitiveList, "鍙间綘濡?鍙间綘濯?鍚冨ぇ渚?鍚冨睅") + sensitiveList = append(sensitiveList, "鍚冪簿") + sensitiveList = append(sensitiveList, "鍚冮洖宸?鍚冮浮宸?鍚屽眳") + sensitiveList = append(sensitiveList, "鍚屽眳涓囧瞾") + sensitiveList = append(sensitiveList, "鍚庡涵") + sensitiveList = append(sensitiveList, "鍚曠鑾?鍚楀暋") + sensitiveList = append(sensitiveList, "鍚楀暋鐗?鍚楀暋纰?鍚楃殑") + sensitiveList = append(sensitiveList, "鍚炵簿") + sensitiveList = append(sensitiveList, "鍚炵簿楱峰") + sensitiveList = append(sensitiveList, "鍚炵簿楠氬") + sensitiveList = append(sensitiveList, "鍚睂") + sensitiveList = append(sensitiveList, "鍚充粊鑿?鍚冲剙") + sensitiveList = append(sensitiveList, "鍚冲鐕?鍚冲鐠?鍚冲畯閬?鍚冲畼姝?鍚冲公鍦?鍚冲紭閬?鍚冲織鑺?鍚虫暒缇?鍚虫柟鍩?鍚虫窇鐝?鍚崇櫨鐩?鍚宠偛鍗?鍚抽偊鍦?鍚淬€€浠?鍚翠华") + sensitiveList = append(sensitiveList, "鍚村畼姝?鍚村府鍥?鍚撮偊鍥?鍚告敹鐨勫湒楱?鍚告瘨") + sensitiveList = append(sensitiveList, "鍚哥簿") + sensitiveList = append(sensitiveList, "鍚哥簿灏戝コ") + sensitiveList = append(sensitiveList, "鍚歌鐛?鍚瑰枃鍙?鍚圭") + sensitiveList = append(sensitiveList, "鍚圭矮") + sensitiveList = append(sensitiveList, "鍚硅惂") + sensitiveList = append(sensitiveList, "鍛備含鑺?鍛傜钃?鍛嗗嵉") + sensitiveList = append(sensitiveList, "鍛?鍛ㄥ仴搴?鍛ㄥ叚鎬у惂") + sensitiveList = append(sensitiveList, "鍛ㄥ畧瑷?鍛ㄥ皬搴?鍛ㄦ€荤悊") + sensitiveList = append(sensitiveList, "鍛ㄦ仼渚?鍛ㄦ仼鏉?鍛ㄦ案搴?鍛ㄩ嫆閹?鍛诲悷") + sensitiveList = append(sensitiveList, "鍜岄崑妲?鍜岄攨鏋?鍜挭") + sensitiveList = append(sensitiveList, "鍜挭鍥剧墖") + sensitiveList = append(sensitiveList, "鍜挭鍦栫墖") + sensitiveList = append(sensitiveList, "鍜潃榫熷ご") + sensitiveList = append(sensitiveList, "鍜憲榫滈牠") + sensitiveList = append(sensitiveList, "鍜?鍝佺┐") + sensitiveList = append(sensitiveList, "鍝佽壊鍫?鍝侀鍫?鍝堢埦缇呭凹") + sensitiveList = append(sensitiveList, "鍝瘝") + sensitiveList = append(sensitiveList, "鍝埜") + sensitiveList = append(sensitiveList, "鍞愬鐠?鍞愬鐠?鍞愭煆姗?鍞?鍞甶d") + sensitiveList = append(sensitiveList, "鍞彿") + sensitiveList = append(sensitiveList, "鍞蒋浠?鍟嗗姟閮?鍟婄劇鍗?鍠勬儭鏈夊牨") + sensitiveList = append(sensitiveList, "鍠?鍠紛") + sensitiveList = append(sensitiveList, "鍠煶") + sensitiveList = append(sensitiveList, "鍠蜂綘") + sensitiveList = append(sensitiveList, "鍠风簿") + sensitiveList = append(sensitiveList, "鍡庡暋") + sensitiveList = append(sensitiveList, "鍡庣殑") + sensitiveList = append(sensitiveList, "鍡戣嵂") + sensitiveList = append(sensitiveList, "鍡戣棩") + sensitiveList = append(sensitiveList, "鍢?鍢?鍢?鍢?鍢绘父涓浗") + sensitiveList = append(sensitiveList, "鍢婚亰涓湅") + sensitiveList = append(sensitiveList, "鍣?鍣翠綘") + sensitiveList = append(sensitiveList, "鍣寸簿") + sensitiveList = append(sensitiveList, "鍤?鍤村鍏?鍤村绁?鍥?鍥涗汉甯?鍥涗汉骞?鍥涘窛鐙珛") + sensitiveList = append(sensitiveList, "鍥涘窛鐛?鍥涘窛鐛ㄧ珛") + sensitiveList = append(sensitiveList, "鍥?鍥炲洖") + sensitiveList = append(sensitiveList, "鍥炴暀") + sensitiveList = append(sensitiveList, "鍥炴棌浜哄悆鐚倝") + sensitiveList = append(sensitiveList, "鍥炴皯鍚冪尓鑲?鍥炴皯鍚冭爆鑲?鍥炴皯鏆村姩") + sensitiveList = append(sensitiveList, "鍥炴皯鏆村嫊") + sensitiveList = append(sensitiveList, "鍥炶壇鐜?鍥戒骇av") + sensitiveList = append(sensitiveList, "鍥戒骇鍋锋媿") + sensitiveList = append(sensitiveList, "鍥戒骇瀚栧") + sensitiveList = append(sensitiveList, "鍥戒骇鏃犵爜") + sensitiveList = append(sensitiveList, "鍥戒骇楠氳揣") + sensitiveList = append(sensitiveList, "鍥藉唴鍔ㄦ€佹竻鏍?鍥藉畨灞€") + sensitiveList = append(sensitiveList, "鍥藉涓诲腑") + sensitiveList = append(sensitiveList, "鍥藉涓昏閮ㄥ") + sensitiveList = append(sensitiveList, "鍥藉姘戝") + sensitiveList = append(sensitiveList, "鍥芥皯鍏?鍥芥皯鍏氫竾宀?鍥界灞€") + sensitiveList = append(sensitiveList, "鍥借鏃ユ姤") + sensitiveList = append(sensitiveList, "鍥介槻绉戝伐濮?鍥介槻閮?鍥介檯骞挎挱鐨勭數瑙嗚妭鐩?鍥介檯娉曢櫌") + sensitiveList = append(sensitiveList, "鍦嗘弧") + sensitiveList = append(sensitiveList, "鍦嬪収鍕曟厠娓呮ǎ") + sensitiveList = append(sensitiveList, "鍦嬪瀹夊叏") + sensitiveList = append(sensitiveList, "鍦嬪姗熷瘑") + sensitiveList = append(sensitiveList, "鍦嬬敘av") + sensitiveList = append(sensitiveList, "鍦嬬敘鍋锋媿") + sensitiveList = append(sensitiveList, "鍦嬬敘瀚栧") + sensitiveList = append(sensitiveList, "鍦嬬敘鐒$⒓") + sensitiveList = append(sensitiveList, "鍦嬬敘楱疯波") + sensitiveList = append(sensitiveList, "鍦嬭硦") + sensitiveList = append(sensitiveList, "鍦嬭粛") + sensitiveList = append(sensitiveList, "鍦撴豢") + sensitiveList = append(sensitiveList, "鍦栨浉绠$悊鍝″崱鐗?鍦樺摗棣埦姹€") + sensitiveList = append(sensitiveList, "鍦橀暦鎴堢櫥") + sensitiveList = append(sensitiveList, "鍦eコ宄?鍦f垬") + sensitiveList = append(sensitiveList, "鍦f瘝") + sensitiveList = append(sensitiveList, "鍦f硥瀛︽帆") + sensitiveList = append(sensitiveList, "鍦鼎") + sensitiveList = append(sensitiveList, "鍦颁笅鏁欐渻") + sensitiveList = append(sensitiveList, "鍦拌棌") + sensitiveList = append(sensitiveList, "鍧愬彴") + sensitiveList = append(sensitiveList, "鍧愬彴鐨?鍧愬簞") + sensitiveList = append(sensitiveList, "鍧愯帄") + sensitiveList = append(sensitiveList, "鍨冨溇娓告垙") + sensitiveList = append(sensitiveList, "鍨?鍩冩柉钀?鍩冭鍏嬭槆鐗瑰嫟") + sensitiveList = append(sensitiveList, "鍩?鍩哄湴绲勭箶") + sensitiveList = append(sensitiveList, "鍩哄湴缁勭粐") + sensitiveList = append(sensitiveList, "鍩虹潱") + sensitiveList = append(sensitiveList, "鍩虹潱鏁?鍫曟帆") + sensitiveList = append(sensitiveList, "濉斿収") + sensitiveList = append(sensitiveList, "濉斿埄鐝?濉旂儚") + sensitiveList = append(sensitiveList, "濉炰綘鍏?濉炰綘濞?濉炰綘姣?濉炰綘鐖?濉炰綘鑰佸笀") + sensitiveList = append(sensitiveList, "濉炰綘鑰佹瘝") + sensitiveList = append(sensitiveList, "濉炵櫧") + sensitiveList = append(sensitiveList, "澧ㄧ储閲屽凹") + sensitiveList = append(sensitiveList, "澧帆") + sensitiveList = append(sensitiveList, "澹叺绠$悊鍝$摝鐖捐嚕") + sensitiveList = append(sensitiveList, "澹瑰懆鍒?澹瑰懆鍒?鍙版咕)") + sensitiveList = append(sensitiveList, "澹圭數瑙") + sensitiveList = append(sensitiveList, "澶栥€€鎸?澶栦笓灞€") + sensitiveList = append(sensitiveList, "澶栦氦鑸囨柟鐣?澶栦氦璜栧") + sensitiveList = append(sensitiveList, "澶栦氦閮?澶栨寕") + sensitiveList = append(sensitiveList, "澶栨帥") + sensitiveList = append(sensitiveList, "澶栨眹灞€") + sensitiveList = append(sensitiveList, "澶栭亣") + sensitiveList = append(sensitiveList, "澶氫汉鎬ф剾") + sensitiveList = append(sensitiveList, "澶氫汉杓?澶氫汉杞?澶氱董") + sensitiveList = append(sensitiveList, "澶氱淮") + sensitiveList = append(sensitiveList, "澶氱淮鏂伴椈") + sensitiveList = append(sensitiveList, "澶氱淮鏂伴椈缃?澶氱淮缃?澶滄儏") + sensitiveList = append(sensitiveList, "澶滆┍绱鍩?澶") + sensitiveList = append(sensitiveList, "澶т腑鍦嬭珫澹?澶т腑鑿珫澹?澶т贡浜?澶т钩") + sensitiveList = append(sensitiveList, "澶т钩闋?澶т簜浜?澶т簨浠?澶т紬鑹叉儏鎴愪汉缃?澶т究") + sensitiveList = append(sensitiveList, "澶у姏鎶介€?澶у崼鏁?澶у弬鑰?澶у弮鑰?澶у彶绱€") + sensitiveList = append(sensitiveList, "澶у彶瑷?澶уザ澶?澶уザ濯?澶уザ缇庨€?澶уザ楱峰コ") + sensitiveList = append(sensitiveList, "澶уザ楠氬コ") + sensitiveList = append(sensitiveList, "澶у楠氫贡") + sensitiveList = append(sensitiveList, "澶у璜栧") + sensitiveList = append(sensitiveList, "澶у笀") + sensitiveList = append(sensitiveList, "澶ф澅浜?澶ф澅浜炲叡姒?澶ф瘮") + sensitiveList = append(sensitiveList, "澶ф硶") + sensitiveList = append(sensitiveList, "澶ф尝") + sensitiveList = append(sensitiveList, "澶ф尝娉?澶ф尝绮塨") + sensitiveList = append(sensitiveList, "澶ф尝楱峰│") + sensitiveList = append(sensitiveList, "澶ф尝楠氬") + sensitiveList = append(sensitiveList, "澶х溇鑹叉儏鎴愪汉缍?澶х磤鍏?澶х磤鍏冩柊鑱炵恫") + sensitiveList = append(sensitiveList, "澶х磤鍦?澶х邯鍏?澶х邯鍏冩柊闂荤綉") + sensitiveList = append(sensitiveList, "澶х邯鍏冩椂鎶?澶х邯鍏冪綉") + sensitiveList = append(sensitiveList, "澶ц儐鍑轰綅") + sensitiveList = append(sensitiveList, "澶ц儐灏戝コ") + sensitiveList = append(sensitiveList, "澶ц喗鍑轰綅") + sensitiveList = append(sensitiveList, "澶ц喗灏戝コ") + sensitiveList = append(sensitiveList, "澶ц姳閫?澶цb") + sensitiveList = append(sensitiveList, "澶ц姣?澶ц閫?澶ц鐪熶汉鐪熶簨") + sensitiveList = append(sensitiveList, "澶ц鏁?澶ч€?澶ч洖宸?澶ч稄宸?澶ч浮宸?澶ч夯") + sensitiveList = append(sensitiveList, "澶ч夯鏍戣剛") + sensitiveList = append(sensitiveList, "澶ч夯娌?澶╀笅鏉傚織") + sensitiveList = append(sensitiveList, "澶╀富鏁?澶╁ぉ骞?澶╁ぉ骞茶布鍦?澶╁ぉ骞茶创鍥?澶╁ぉ鎯呰壊") + sensitiveList = append(sensitiveList, "澶╁畨闁€") + sensitiveList = append(sensitiveList, "澶╁畨闁€涓€浠?澶╁畨闁€浜嬩欢") + sensitiveList = append(sensitiveList, "澶╁畨闁€灞犳") + sensitiveList = append(sensitiveList, "澶╁畨闁€閷勫奖甯?澶╁畨闂ㄤ簨浠?澶╃殗闄涗笅") + sensitiveList = append(sensitiveList, "澶╃溂鏃ユ姤绀?澶╄棌") + sensitiveList = append(sensitiveList, "澶╅柟") + sensitiveList = append(sensitiveList, "澶瓙鍏?澶洃") + sensitiveList = append(sensitiveList, "澶洠") + sensitiveList = append(sensitiveList, "澶槼鎶?澶櫧鍫?澶3p") + sensitiveList = append(sensitiveList, "澶涔变氦") + sensitiveList = append(sensitiveList, "澶浜備氦") + sensitiveList = append(sensitiveList, "澶淇变箰閮?澶淇辨▊閮?澶澶歱") + sensitiveList = append(sensitiveList, "澶鑷媿") + sensitiveList = append(sensitiveList, "澶鍏ч儴鏅氭渻") + sensitiveList = append(sensitiveList, "澶?澶辩") + sensitiveList = append(sensitiveList, "澶辫韩") + sensitiveList = append(sensitiveList, "澶存潯鏃ユ姤") + sensitiveList = append(sensitiveList, "濂?濂囨帆瀹濋壌") + sensitiveList = append(sensitiveList, "濂囨帆瀵堕憭") + sensitiveList = append(sensitiveList, "濂ュ反椹?濂ラ┈灏?濂у€?濂у厠鎷?濂у埄寮?濂у反棣?濂ф媺寰?濂х壒铇?濂чΜ鐖?濂ч濂?濂?濂充富浜虹緟濮Μ鑾?濂充紭") + sensitiveList = append(sensitiveList, "濂冲劒") + sensitiveList = append(sensitiveList, "濂冲盎") + sensitiveList = append(sensitiveList, "濂冲眲") + sensitiveList = append(sensitiveList, "濂冲共") + sensitiveList = append(sensitiveList, "濂冲构") + sensitiveList = append(sensitiveList, "濂抽Μ銊?濂抽Μ鐧藉嫼") + sensitiveList = append(sensitiveList, "濂抽Μ鐨?濂抽┈銊?濂抽┈鐧藉嫼") + sensitiveList = append(sensitiveList, "濂抽┈鐨?濂寸暅鎶?濂撮毞瑾挎暀") + sensitiveList = append(sensitiveList, "濂撮毞璋冩暀") + sensitiveList = append(sensitiveList, "濂撮毟榄旀棌澹叺") + sensitiveList = append(sensitiveList, "濂?濂跺ぇ灞勮偉") + sensitiveList = append(sensitiveList, "濂跺ザ鐨?濂跺瓙") + sensitiveList = append(sensitiveList, "濂舵尯鑷€缈?濂舵尯鑷€缈?濂堕牠") + sensitiveList = append(sensitiveList, "濂?濂镐綘") + sensitiveList = append(sensitiveList, "濂稿か娣") + sensitiveList = append(sensitiveList, "濂稿辜") + sensitiveList = append(sensitiveList, "濂告儏") + sensitiveList = append(sensitiveList, "濂告帆") + sensitiveList = append(sensitiveList, "濂告帆鐢佃溅") + sensitiveList = append(sensitiveList, "濂瑰") + sensitiveList = append(sensitiveList, "濂瑰鐨勯噾鏃ユ垚") + sensitiveList = append(sensitiveList, "濂瑰") + sensitiveList = append(sensitiveList, "濂藉") + sensitiveList = append(sensitiveList, "濂借壊cc") + sensitiveList = append(sensitiveList, "濡備締") + sensitiveList = append(sensitiveList, "濡傛潵") + sensitiveList = append(sensitiveList, "濡?濡坆") + sensitiveList = append(sensitiveList, "濡堝崠濡堝眮") + sensitiveList = append(sensitiveList, "濡堟壒") + sensitiveList = append(sensitiveList, "濡堟瘮") + sensitiveList = append(sensitiveList, "濡堢櫧鍕?濡堢殑") + sensitiveList = append(sensitiveList, "濡堢") + sensitiveList = append(sensitiveList, "濡堥€?濡?濡撳コ") + sensitiveList = append(sensitiveList, "濡撻櫌") + sensitiveList = append(sensitiveList, "濡栧獨鐔熸瘝") + sensitiveList = append(sensitiveList, "濡╁獨鎸戦€?濡冲鐨?濡冲鐨?濡宠€佹瘝鐨?濡抽┈鐨?濡瑰闃存瘺") + sensitiveList = append(sensitiveList, "濡瑰闄版瘺") + sensitiveList = append(sensitiveList, "濡瑰楱峰湒") + sensitiveList = append(sensitiveList, "濡瑰楠氬浘") + sensitiveList = append(sensitiveList, "濮氭枃鍏?濮氭湀璎?濮氱緟") + sensitiveList = append(sensitiveList, "濮hタ") + sensitiveList = append(sensitiveList, "濮︽儏") + sensitiveList = append(sensitiveList, "濮︽煋") + sensitiveList = append(sensitiveList, "濮︽帆") + sensitiveList = append(sensitiveList, "濮︽帆闆昏粖") + sensitiveList = append(sensitiveList, "濮颈") + sensitiveList = append(sensitiveList, "濮嫕寰?濞佽€屾煍") + sensitiveList = append(sensitiveList, "濞佽€岄挗") + sensitiveList = append(sensitiveList, "濞樼殑") + sensitiveList = append(sensitiveList, "濞橀姣?濞?濠?濠婂瓙") + sensitiveList = append(sensitiveList, "濠婂瓙椁婄殑") + sensitiveList = append(sensitiveList, "濠贡鍐涘洟") + sensitiveList = append(sensitiveList, "濠簜杌嶅湗") + sensitiveList = append(sensitiveList, "濯掍綋鍏皯琛屽姩缃?濯氳嵂灏戝勾") + sensitiveList = append(sensitiveList, "濯氳棩灏戝勾") + sensitiveList = append(sensitiveList, "濯?濯絙") + sensitiveList = append(sensitiveList, "濯芥壒") + sensitiveList = append(sensitiveList, "濯芥瘮") + sensitiveList = append(sensitiveList, "濯界櫧鍕?濯界殑") + sensitiveList = append(sensitiveList, "濯介€?瀚愬眲") + sensitiveList = append(sensitiveList, "瀚栧鎸囧崡") + sensitiveList = append(sensitiveList, "瀚栧") + sensitiveList = append(sensitiveList, "瀚?瀚゜") + sensitiveList = append(sensitiveList, "瀚゜b") + sensitiveList = append(sensitiveList, "瀚╁コ") + sensitiveList = append(sensitiveList, "瀚╁ザ") + sensitiveList = append(sensitiveList, "瀚╁眲") + sensitiveList = append(sensitiveList, "瀚╃┐") + sensitiveList = append(sensitiveList, "瀚╃┐鑲夌斧") + sensitiveList = append(sensitiveList, "瀚╃┐鑲夌紳") + sensitiveList = append(sensitiveList, "瀚╃斧") + sensitiveList = append(sensitiveList, "瀚╃紳") + sensitiveList = append(sensitiveList, "瀚╅€?瀚╅畱") + sensitiveList = append(sensitiveList, "瀚╅畱榄?瀚╅矋") + sensitiveList = append(sensitiveList, "瀚╅矋楸?瀚靛獨鎸戦€?瀣€") + sensitiveList = append(sensitiveList, "瀛愬コ浠昏亴鍚嶅崟") + sensitiveList = append(sensitiveList, "瀛愬") + sensitiveList = append(sensitiveList, "瀛愬") + sensitiveList = append(sensitiveList, "瀛欎腑灞?瀛欐斂鎵?瀛欐枃") + sensitiveList = append(sensitiveList, "瀛欐槬鍏?瀛欓€镐粰") + sensitiveList = append(sensitiveList, "瀛熷缓鏌?瀛︽疆") + sensitiveList = append(sensitiveList, "瀛︾敓濡?瀛﹁繍") + sensitiveList = append(sensitiveList, "瀛腑灞?瀛ぇ鍗?瀛枃") + sensitiveList = append(sensitiveList, "瀛€镐粰") + sensitiveList = append(sensitiveList, "瀛?瀛告疆") + sensitiveList = append(sensitiveList, "瀛哥敓濡?瀛歌伅") + sensitiveList = append(sensitiveList, "瀛歌嚜鑱?瀛搁亱") + sensitiveList = append(sensitiveList, "瀹?瀹囨槑缍?瀹夊€嶆檳涓?瀹夊€嶆檵涓?瀹夊叏灞€") + sensitiveList = append(sensitiveList, "瀹夊崡") + sensitiveList = append(sensitiveList, "瀹夋媺") + sensitiveList = append(sensitiveList, "瀹夐潪浠栧懡") + sensitiveList = append(sensitiveList, "瀹嬫浉鍏?瀹嬫鐟?瀹嬬鑻?瀹岃泲鎿?瀹楁暀") + sensitiveList = append(sensitiveList, "瀹樺晢鍕剧粨") + sensitiveList = append(sensitiveList, "瀹樻柟") + sensitiveList = append(sensitiveList, "瀹橀€兼皯鍙?瀹℃煡") + sensitiveList = append(sensitiveList, "瀹㈠鐢佃鍙?瀹㈡埗鏈嶅嫏") + sensitiveList = append(sensitiveList, "瀹㈡埛鏈嶅姟") + sensitiveList = append(sensitiveList, "瀹㈡湇") + sensitiveList = append(sensitiveList, "瀹崇緸") + sensitiveList = append(sensitiveList, "瀹惧懆") + sensitiveList = append(sensitiveList, "瀹鹃濂抽儙") + sensitiveList = append(sensitiveList, "瀵傚癁鑷懜") + sensitiveList = append(sensitiveList, "瀵嗗畻") + sensitiveList = append(sensitiveList, "瀵嗗娣") + sensitiveList = append(sensitiveList, "瀵嗘礊") + sensitiveList = append(sensitiveList, "瀵嗙┐") + sensitiveList = append(sensitiveList, "瀵嗙┐璨煎湒") + sensitiveList = append(sensitiveList, "瀵嗙┐璐村浘") + sensitiveList = append(sensitiveList, "瀵囨檽浼?瀵屽叞鍏嬫灄") + sensitiveList = append(sensitiveList, "瀵╂煡") + sensitiveList = append(sensitiveList, "瀵湡") + sensitiveList = append(sensitiveList, "瀵剁煶鍟嗕汉") + sensitiveList = append(sensitiveList, "瀵绘") + sensitiveList = append(sensitiveList, "瀵煎脊") + sensitiveList = append(sensitiveList, "灏佸嵃鐨勯潏榄傞◣澹?灏佸緸寰?灏佹") + sensitiveList = append(sensitiveList, "灏勪簡閭勮瑕?灏勫ザ") + sensitiveList = append(sensitiveList, "灏勭埥") + sensitiveList = append(sensitiveList, "灏勭簿") + sensitiveList = append(sensitiveList, "灏勯") + sensitiveList = append(sensitiveList, "灏勯") + sensitiveList = append(sensitiveList, "灏囧墖姘?灏堝埗") + sensitiveList = append(sensitiveList, "灏堟斂") + sensitiveList = append(sensitiveList, "灏夊仴琛?灏庡斧") + sensitiveList = append(sensitiveList, "灏庡綀") + sensitiveList = append(sensitiveList, "灏廱") + sensitiveList = append(sensitiveList, "灏廱妯?灏忎钩澶?灏忎究") + sensitiveList = append(sensitiveList, "灏忓弬鑰?灏忓弮鑰?灏忓彈") + sensitiveList = append(sensitiveList, "灏忓彸") + sensitiveList = append(sensitiveList, "灏忓") + sensitiveList = append(sensitiveList, "灏忓鍏艰亴") + sensitiveList = append(sensitiveList, "灏忓鎵撻姗?灏忓鎵撻鏈?灏忓瑁歌亰") + sensitiveList = append(sensitiveList, "灏忓闆?灏忓楦?灏忓紵寮?灏忔敾") + sensitiveList = append(sensitiveList, "灏忔棩鏈?灏忔瘮妯?灏忔硥") + sensitiveList = append(sensitiveList, "灏忔硥绱斾竴閮?灏忔硥绾竴閮?灏忕┐") + sensitiveList = append(sensitiveList, "灏忚倝绮?灏忛€?灏忛洖闆?灏忛潏閫?灏忛稄槎?灏忛浮楦?灏戜慨姝?灏戝コ琚彃") + sensitiveList = append(sensitiveList, "灏戝鍋锋儏") + sensitiveList = append(sensitiveList, "灏戝│鍋锋儏") + sensitiveList = append(sensitiveList, "灏ゆ瘮浜?灏卞幓鏃?灏卞幓鑹茶壊") + sensitiveList = append(sensitiveList, "灏卞幓瑾樻儜") + sensitiveList = append(sensitiveList, "灏卞幓璇辨儜") + sensitiveList = append(sensitiveList, "灏规叾姘?灏?灏煎厠鏉?灏煎厠妫?灏煎ェ澶?灏肩帥") + sensitiveList = append(sensitiveList, "灏肩應") + sensitiveList = append(sensitiveList, "灞佺溂") + sensitiveList = append(sensitiveList, "灞?灞勫眲") + sensitiveList = append(sensitiveList, "灞勫眲鐗瑰啓") + sensitiveList = append(sensitiveList, "灞勫眲鐗瑰") + sensitiveList = append(sensitiveList, "灞勬瘺") + sensitiveList = append(sensitiveList, "灞婁腑澶斂娌诲眬濮斿摗") + sensitiveList = append(sensitiveList, "灞?灞?") + sensitiveList = append(sensitiveList, "灞屼竷") + sensitiveList = append(sensitiveList, "灞屼汉涔?灞屼粬") + sensitiveList = append(sensitiveList, "灞屼綘") + sensitiveList = append(sensitiveList, "灞屽コ涔?灞屽ス") + sensitiveList = append(sensitiveList, "灞屽Τ") + sensitiveList = append(sensitiveList, "灞屾垜") + sensitiveList = append(sensitiveList, "灞屾瘺") + sensitiveList = append(sensitiveList, "灞岃タ") + sensitiveList = append(sensitiveList, "灞岄笭") + sensitiveList = append(sensitiveList, "灞犳潃") + sensitiveList = append(sensitiveList, "灞犳") + sensitiveList = append(sensitiveList, "灞?灞?灞卞彛绲?灞辨湰浜斿崄鍏?灞?宀?宀″窉") + sensitiveList = append(sensitiveList, "宀℃潙瀵ф") + sensitiveList = append(sensitiveList, "宀℃潙绉€妯?宄?宕楀摠澹叺") + sensitiveList = append(sensitiveList, "宥?宥?宸℃煡") + sensitiveList = append(sensitiveList, "宸ュ晢鏃舵姤") + sensitiveList = append(sensitiveList, "宸ヨ嚜鑱?宸ф帆濂告垙") + sensitiveList = append(sensitiveList, "宸ф帆濂告埐") + sensitiveList = append(sensitiveList, "宸ㄤ钩") + sensitiveList = append(sensitiveList, "宸ㄤ钩淇忓コ鍖?宸ㄤ钩淇忓コ閱?宸ㄤ钩绱犱汉") + sensitiveList = append(sensitiveList, "宸ㄥザ") + sensitiveList = append(sensitiveList, "宸ㄥ睂") + sensitiveList = append(sensitiveList, "宸ㄦ楱庡叺") + sensitiveList = append(sensitiveList, "宸ㄧ偖鍏靛洟") + sensitiveList = append(sensitiveList, "宸ㄧ偖鍏靛湗") + sensitiveList = append(sensitiveList, "宸ㄩ惖瑙掑搱鍏?宸ㄩǚ") + sensitiveList = append(sensitiveList, "宸ㄩ獨") + sensitiveList = append(sensitiveList, "宸村€緧寰?宸村€潶") + sensitiveList = append(sensitiveList, "宸?甯冧粈") + sensitiveList = append(sensitiveList, "甯冨笇") + sensitiveList = append(sensitiveList, "甯冭幈灏?甯冭悐鐖?甯冮浄灏?甯冮浄鐖?甯屾媺鍏?甯屾湜涔嬪0") + sensitiveList = append(sensitiveList, "甯屾湜涔嬪0鍥介檯骞挎挱鐢靛彴") + sensitiveList = append(sensitiveList, "甯岀壒鍕?甯濆浗涓讳箟") + sensitiveList = append(sensitiveList, "甯濆湅涓荤京") + sensitiveList = append(sensitiveList, "甯﹀鑲涗氦") + sensitiveList = append(sensitiveList, "甯宠櫉") + sensitiveList = append(sensitiveList, "甯跺鑲涗氦") + sensitiveList = append(sensitiveList, "甯稿媮") + sensitiveList = append(sensitiveList, "骞?骞?8") + sensitiveList = append(sensitiveList, "骞瞘m") + sensitiveList = append(sensitiveList, "骞瞘y") + sensitiveList = append(sensitiveList, "骞蹭竷鍏?骞蹭汉涔?骞蹭粬") + sensitiveList = append(sensitiveList, "骞蹭綘") + sensitiveList = append(sensitiveList, "骞蹭綘濡?骞蹭綘濡坆") + sensitiveList = append(sensitiveList, "骞蹭綘濡堥€?骞蹭綘濞?骞插コ涔?骞插ス") + sensitiveList = append(sensitiveList, "骞插Τ") + sensitiveList = append(sensitiveList, "骞插Τ濡?骞插Τ濞?骞插Τ鑰佹瘝") + sensitiveList = append(sensitiveList, "骞叉嫀濞?骞叉") + sensitiveList = append(sensitiveList, "骞叉浣?骞茬殑鐖?骞茬┐") + sensitiveList = append(sensitiveList, "骞诧紬锛?骞诧絿锝?骞?8") + sensitiveList = append(sensitiveList, "骞筨i") + sensitiveList = append(sensitiveList, "骞筭y") + sensitiveList = append(sensitiveList, "骞逛竴瀹?骞逛竷鍏?骞逛汉涔?骞逛粬") + sensitiveList = append(sensitiveList, "骞逛綘") + sensitiveList = append(sensitiveList, "骞逛綘濞?骞逛綘鑰佹瘝") + sensitiveList = append(sensitiveList, "骞逛綘鑰佹瘮") + sensitiveList = append(sensitiveList, "骞瑰叏瀹?骞瑰コ涔?骞瑰ス") + sensitiveList = append(sensitiveList, "骞瑰Τ") + sensitiveList = append(sensitiveList, "骞瑰构骞?骞规") + sensitiveList = append(sensitiveList, "骞规浣?骞规瘮") + sensitiveList = append(sensitiveList, "骞圭偖") + sensitiveList = append(sensitiveList, "骞圭殑浣?骞圭殑鐖?骞圭牪") + sensitiveList = append(sensitiveList, "骞圭┐") + sensitiveList = append(sensitiveList, "骞归€?骞癸紬锛?骞癸絿锝?骞间氦") + sensitiveList = append(sensitiveList, "骞煎湒") + sensitiveList = append(sensitiveList, "骞煎コ") + sensitiveList = append(sensitiveList, "骞煎") + sensitiveList = append(sensitiveList, "骞肩敺") + sensitiveList = append(sensitiveList, "骞奸€?骞奸闁?骞奸闃?骞奸綊") + sensitiveList = append(sensitiveList, "骞奸娇") + sensitiveList = append(sensitiveList, "骞奸緧") + sensitiveList = append(sensitiveList, "搴€") + sensitiveList = append(sensitiveList, "搴嗙孩") + sensitiveList = append(sensitiveList, "搴?搴斿彫") + sensitiveList = append(sensitiveList, "搴櫌璀﹁鍏?寤佸ゴ") + sensitiveList = append(sensitiveList, "寤佹墍鍋锋媿") + sensitiveList = append(sensitiveList, "寤佹墍鐩滄敐") + sensitiveList = append(sensitiveList, "寤夋斂澶ц珫澹?寤栭尗榫?寤?寤㈠瀹堣鑰?寤h仦") + sensitiveList = append(sensitiveList, "寤哄湅榛?寮€鎴?寮€鏀炬潅蹇?寮€鏀剧綉") + sensitiveList = append(sensitiveList, "寮€鑻?寮傚瀷鍙涜粛") + sensitiveList = append(sensitiveList, "寮傝浜哄+") + sensitiveList = append(sensitiveList, "寮傝浜哄+") + sensitiveList = append(sensitiveList, "寮撹櫧濂冲共") + sensitiveList = append(sensitiveList, "寮撻洊") + sensitiveList = append(sensitiveList, "寮曞皫") + sensitiveList = append(sensitiveList, "寮犲皬骞?寮犲痉姹?寮犳槬妗?寮犳槬璐?寮犳湞闃?寮犳疆闃?寮犵") + sensitiveList = append(sensitiveList, "寮犵珛鏄?寮犵闆?寮犻珮涓?寮典集绗?寮靛亯鍦?寮靛仴") + sensitiveList = append(sensitiveList, "寮靛崥闆?寮靛痉姹?寮靛織娓?寮垫槬姗?寮垫槶瀵?寮垫灄") + sensitiveList = append(sensitiveList, "寮垫竻鑺?寮垫韩榉?寮电闆?寮甸嫾") + sensitiveList = append(sensitiveList, "寮甸珮楹?寮峰埗娴h吀") + sensitiveList = append(sensitiveList, "寮峰Е") + sensitiveList = append(sensitiveList, "寮峰Е铏曞コ") + sensitiveList = append(sensitiveList, "寮锋毚") + sensitiveList = append(sensitiveList, "寮哄埗娴h偁") + sensitiveList = append(sensitiveList, "寮哄崼") + sensitiveList = append(sensitiveList, "寮哄ジ") + sensitiveList = append(sensitiveList, "寮哄ジ澶勫コ") + sensitiveList = append(sensitiveList, "寮哄ジ鐘?寮烘晥澶辨剰鑽?寮烘毚") + sensitiveList = append(sensitiveList, "寮虹‖鍙戣█") + sensitiveList = append(sensitiveList, "褰€") + sensitiveList = append(sensitiveList, "褰堝娋") + sensitiveList = append(sensitiveList, "褰?褰附濯?褰崥") + sensitiveList = append(sensitiveList, "褰崥鍟嗕笟鍛ㄥ垔") + sensitiveList = append(sensitiveList, "褰崥鏂伴椈绀?褰痉鎳?褰湡") + sensitiveList = append(sensitiveList, "褰簵濯?寰堥粍") + sensitiveList = append(sensitiveList, "寰屽涵") + sensitiveList = append(sensitiveList, "寰愬厜鏄?寰愬悜鍓?寰愬湅鑸?寰愭墠鍘?寰°伄浜屼唬鐩?寰峰浗涔嬪0涓枃缃?寰风董鐖?蹇冪伒娉曢棬鈥滅櫧璇濅經娉曗€濈郴鍒楄妭鐩?蹇箰av") + sensitiveList = append(sensitiveList, "蹇▊av") + sensitiveList = append(sensitiveList, "蹇界劧1鍛?鎬?鎬濈缇?鎬т箣绔?鎬т箰") + sensitiveList = append(sensitiveList, "鎬т氦") + sensitiveList = append(sensitiveList, "鎬т氦鍚炵簿") + sensitiveList = append(sensitiveList, "鎬т氦鍥?鎬т氦鍦?鎬т氦鏃犵爜") + sensitiveList = append(sensitiveList, "鎬т氦鏄?鎬т氦鐒$⒓") + sensitiveList = append(sensitiveList, "鎬т氦鑷媿") + sensitiveList = append(sensitiveList, "鎬т氦瑾?鎬т氦璇?鎬т剑") + sensitiveList = append(sensitiveList, "鎬у偔") + sensitiveList = append(sensitiveList, "鎬уゴ") + sensitiveList = append(sensitiveList, "鎬уゴ浼?鎬уゴ鏈?鎬ф伅") + sensitiveList = append(sensitiveList, "鎬ф剾") + sensitiveList = append(sensitiveList, "鎬ф剾鍦栧韩") + sensitiveList = append(sensitiveList, "鎬ф剾鍦栫墖") + sensitiveList = append(sensitiveList, "鎬ф剾鎻掔┐") + sensitiveList = append(sensitiveList, "鎬ф剾鎿傝嚭") + sensitiveList = append(sensitiveList, "鎬ф劅") + sensitiveList = append(sensitiveList, "鎬ф劅涔冲") + sensitiveList = append(sensitiveList, "鎬ф劅濡栧▎") + sensitiveList = append(sensitiveList, "鎬ф劅濡栧瑘") + sensitiveList = append(sensitiveList, "鎬ф劅鑲変笣") + sensitiveList = append(sensitiveList, "鎬ф劅鑲夌挡") + sensitiveList = append(sensitiveList, "鎬ф劅瑾樻儜") + sensitiveList = append(sensitiveList, "鎬ф劅璇辨儜") + sensitiveList = append(sensitiveList, "鎬ф垬鎿傚彴") + sensitiveList = append(sensitiveList, "鎬ф埌鎿傝嚭") + sensitiveList = append(sensitiveList, "鎬ф棤鑳?鎬ф") + sensitiveList = append(sensitiveList, "鎬х劇鑳?鎬х埍") + sensitiveList = append(sensitiveList, "鎬х埍鍥惧簱") + sensitiveList = append(sensitiveList, "鎬х埍鎿傚彴") + sensitiveList = append(sensitiveList, "鎬ц檸") + sensitiveList = append(sensitiveList, "鎬ц檸鑹茬恫") + sensitiveList = append(sensitiveList, "鎬ц檸鑹茬綉") + sensitiveList = append(sensitiveList, "鎬ц檺") + sensitiveList = append(sensitiveList, "鎬ц檺寰?鎬ч娓?鎬чゥ娓?鎬ч獨鎵?鎬诲眬") + sensitiveList = append(sensitiveList, "鎬荤悊") + sensitiveList = append(sensitiveList, "鎭嬪瓡") + sensitiveList = append(sensitiveList, "鎭愭€栦富涔?鎭愭€栦富缇?鎭愭€栦唤瀛?鎭?鎭ヨ颈杓ジ") + sensitiveList = append(sensitiveList, "鎭╂牸鏂?鎮?鎯呭│") + sensitiveList = append(sensitiveList, "鎯呯嵏") + sensitiveList = append(sensitiveList, "鎯呰壊") + sensitiveList = append(sensitiveList, "鎯呰壊浜旀湀") + sensitiveList = append(sensitiveList, "鎯呰壊澶╁礀") + sensitiveList = append(sensitiveList, "鎯呰壊瀵艰埅") + sensitiveList = append(sensitiveList, "鎯呰壊灏庤埅") + sensitiveList = append(sensitiveList, "鎯呰壊鏂囧") + sensitiveList = append(sensitiveList, "鎯呰壊鏂囧") + sensitiveList = append(sensitiveList, "鎯呰壊鑹烘湳澶╃┖") + sensitiveList = append(sensitiveList, "鎯呰壊钘濊澶╃┖") + sensitiveList = append(sensitiveList, "鎯充笂浣?鎯圭伀鑷媿") + sensitiveList = append(sensitiveList, "鎯圭伀韬潗") + sensitiveList = append(sensitiveList, "鎰?鎰忓織涓嶅爡鐨勫湒楱?鎰忔帆") + sensitiveList = append(sensitiveList, "鎰涘湒鍏湌") + sensitiveList = append(sensitiveList, "鎰涘コ浜?鎰涘娣┐") + sensitiveList = append(sensitiveList, "鎰涘辜闁?鎰涙恫") + sensitiveList = append(sensitiveList, "鎰涙恫姗祦") + sensitiveList = append(sensitiveList, "鎰涙粙") + sensitiveList = append(sensitiveList, "鎰涙粙鐥?鎰涜壊cc") + sensitiveList = append(sensitiveList, "鎰?鎱板畨濡?鎱板畨濠?鎱版槬鎯?鎱剁磪") + sensitiveList = append(sensitiveList, "鎲傞鐨勮壘鎷?鎳傛枃鍗?鎳夊彫") + sensitiveList = append(sensitiveList, "鎳掑彨") + sensitiveList = append(sensitiveList, "鎳掓暀") + sensitiveList = append(sensitiveList, "鎳跺彨") + sensitiveList = append(sensitiveList, "鎳舵暀") + sensitiveList = append(sensitiveList, "鎳风壒") + sensitiveList = append(sensitiveList, "鎴堟彋") + sensitiveList = append(sensitiveList, "鎴堢憺鐖惧痉") + sensitiveList = append(sensitiveList, "鎴愪汉a鐗?鎴愪汉bt") + sensitiveList = append(sensitiveList, "鎴愪汉鍗堝鍦?鎴愪汉鍗堝鍫?鎴愪汉鍥剧墖") + sensitiveList = append(sensitiveList, "鎴愪汉鍦栫墖") + sensitiveList = append(sensitiveList, "鎴愪汉瀵艰埅") + sensitiveList = append(sensitiveList, "鎴愪汉灏庤埅") + sensitiveList = append(sensitiveList, "鎴愪汉灏忚") + sensitiveList = append(sensitiveList, "鎴愪汉灏忚") + sensitiveList = append(sensitiveList, "鎴愪汉鏂囧") + sensitiveList = append(sensitiveList, "鎴愪汉鏂囧") + sensitiveList = append(sensitiveList, "鎴愪汉鐗?鎴愪汉鐢靛奖") + sensitiveList = append(sensitiveList, "鎴愪汉鐧惧挤") + sensitiveList = append(sensitiveList, "鎴愪汉鐧惧己") + sensitiveList = append(sensitiveList, "鎴愪汉绀惧尯") + sensitiveList = append(sensitiveList, "鎴愪汉绀惧崁") + sensitiveList = append(sensitiveList, "鎴愪汉缍茬珯") + sensitiveList = append(sensitiveList, "鎴愪汉缃戠珯") + sensitiveList = append(sensitiveList, "鎴愪汉鑷媿") + sensitiveList = append(sensitiveList, "鎴愪汉璜栧") + sensitiveList = append(sensitiveList, "鎴愪汉璁哄潧") + sensitiveList = append(sensitiveList, "鎴愪汉杞欢") + sensitiveList = append(sensitiveList, "鎴戝Τ鑰佺埜") + sensitiveList = append(sensitiveList, "鎴戝氨鍘昏壊") + sensitiveList = append(sensitiveList, "鎴戝氨鑹?鎴戝共") + sensitiveList = append(sensitiveList, "鎴戞搷") + sensitiveList = append(sensitiveList, "鎴戞搷浣?鎴戞棩") + sensitiveList = append(sensitiveList, "鎴戞棩浣?鎴戣鎬т氦") + sensitiveList = append(sensitiveList, "鎴?鎴?鎴樼墝") + sensitiveList = append(sensitiveList, "鎴充綘") + sensitiveList = append(sensitiveList, "鎴抽偅鍡巄") + sensitiveList = append(sensitiveList, "鎴抽偅鍡庢瘮") + sensitiveList = append(sensitiveList, "鎴抽偅鍡庨€?鎴存櫠") + sensitiveList = append(sensitiveList, "鎴存捣闈?鎴寸浉榫?鎴寸董鏁?鎴寸孩") + sensitiveList = append(sensitiveList, "鎴寸淮鏁?鎴?鎵変箣闃?鎵変箣闄?鎵嬫帆") + sensitiveList = append(sensitiveList, "鎵庡崱缍槸鑻遍泟") + sensitiveList = append(sensitiveList, "鎵庡崱缁?鎵庡崱缁存槸鑻遍泟") + sensitiveList = append(sensitiveList, "鎵掑眲") + sensitiveList = append(sensitiveList, "鎵掔┐") + sensitiveList = append(sensitiveList, "鎵撳€掑叡浜у厷") + sensitiveList = append(sensitiveList, "鎵撳€掑叡鐢㈤花") + sensitiveList = append(sensitiveList, "鎵撶偖") + sensitiveList = append(sensitiveList, "鎵撻噹鐐?鎵撻姗?鎵撻鏈?鎵惧皬濮?鎶戝埗鍓?鎶曟瘨鏉€浜?鎶犵┐") + sensitiveList = append(sensitiveList, "鎶㈠姭") + sensitiveList = append(sensitiveList, "鎶ゅ+璇辨儜") + sensitiveList = append(sensitiveList, "鎶煎ぇ") + sensitiveList = append(sensitiveList, "鎶煎皬") + sensitiveList = append(sensitiveList, "鎶戒綘涓殑") + sensitiveList = append(sensitiveList, "鎶藉ご") + sensitiveList = append(sensitiveList, "鎶芥彃") + sensitiveList = append(sensitiveList, "鎶介牠") + sensitiveList = append(sensitiveList, "鎷夊鏂彶灏斿痉") + sensitiveList = append(sensitiveList, "鎷夊鏂彶鐖惧痉") + sensitiveList = append(sensitiveList, "鎷夌櫥") + sensitiveList = append(sensitiveList, "鎷夌毊姊?鎷嶈偐绁炶嵂") + sensitiveList = append(sensitiveList, "鎷愬崠") + sensitiveList = append(sensitiveList, "鎷愯常") + sensitiveList = append(sensitiveList, "鎷斿嚭渚?鎷斿嚭鏉?鎷斿眲") + sensitiveList = append(sensitiveList, "鎷斿眲鑷媿") + sensitiveList = append(sensitiveList, "鎷涘") + sensitiveList = append(sensitiveList, "鎷涢稄") + sensitiveList = append(sensitiveList, "鎷充氦") + sensitiveList = append(sensitiveList, "鎷跨牬浠?鎷跨牬宕?鎸佷笉鍚屾斂瑕?鎸囬粸姹熷北璜栧") + sensitiveList = append(sensitiveList, "鎸夋懇妫?鎸ㄧ悆") + sensitiveList = append(sensitiveList, "鎹忎綘槎忓反") + sensitiveList = append(sensitiveList, "鎹忓紕") + sensitiveList = append(sensitiveList, "鎹㈠") + sensitiveList = append(sensitiveList, "鎹㈠澶т細") + sensitiveList = append(sensitiveList, "鎹㈠鏉備氦") + sensitiveList = append(sensitiveList, "鎺勫姛") + sensitiveList = append(sensitiveList, "鎺?鎺掓硠") + sensitiveList = append(sensitiveList, "鎺¤姳鍫?鎺ㄥコ閮?鎺ㄦ补") + sensitiveList = append(sensitiveList, "鎺?鎺扮┐") + sensitiveList = append(sensitiveList, "鎺扮┐鎵撴礊") + sensitiveList = append(sensitiveList, "鎺扮┐鐨崱涓?鎻抌") + sensitiveList = append(sensitiveList, "鎻抔m") + sensitiveList = append(sensitiveList, "鎻掍粬") + sensitiveList = append(sensitiveList, "鎻掍綘") + sensitiveList = append(sensitiveList, "鎻掍綘濡?鎻掍綘濯?鎻掍綘鐖虹埡") + sensitiveList = append(sensitiveList, "鎻掑叆鍏у皠") + sensitiveList = append(sensitiveList, "鎻掑叆鍐呭皠") + sensitiveList = append(sensitiveList, "鎻掑悗搴?鎻掑ス") + sensitiveList = append(sensitiveList, "鎻掑Τ") + sensitiveList = append(sensitiveList, "鎻掑緦搴?鎻掓垜") + sensitiveList = append(sensitiveList, "鎻掓毚") + sensitiveList = append(sensitiveList, "鎻掓瘮") + sensitiveList = append(sensitiveList, "鎻掓繁浜?鎻掔┐") + sensitiveList = append(sensitiveList, "鎻掔┐鎵嬫帆") + sensitiveList = append(sensitiveList, "鎻掔┐姝㈢棐") + sensitiveList = append(sensitiveList, "鎻掔┐姝㈢櫌") + sensitiveList = append(sensitiveList, "鎻掕繘") + sensitiveList = append(sensitiveList, "鎻掗€?鎻掗€?鎻掗偅鍡巄") + sensitiveList = append(sensitiveList, "鎻掗偅鍡庢瘮") + sensitiveList = append(sensitiveList, "鎻掗偅鍡庨€?鎻掗槾") + sensitiveList = append(sensitiveList, "鎻掗槾鑼?鎻掗櫚") + sensitiveList = append(sensitiveList, "鎻掗櫚鑾?鎻涘澶ф渻") + sensitiveList = append(sensitiveList, "鎻涘闆滀氦") + sensitiveList = append(sensitiveList, "鎻壒鏇?鎻翠氦") + sensitiveList = append(sensitiveList, "鎻翠氦濡?鎻翠氦鑷媿") + sensitiveList = append(sensitiveList, "鎻翠氦钖勭爜") + sensitiveList = append(sensitiveList, "鎻翠氦钖勭⒓") + sensitiveList = append(sensitiveList, "鎻村姪浜ゆ槗") + sensitiveList = append(sensitiveList, "鎼?鎼栭牠涓?鎼?鎼?鎼跺姭") + sensitiveList = append(sensitiveList, "鎽囧ご涓?鎽囧ご鐜?鎽╂礇瀹?鎽╅杸鏁?鎽╅棬鏁?鎽崇┐") + sensitiveList = append(sensitiveList, "鎽?鎽镐綘槎忓反") + sensitiveList = append(sensitiveList, "鎽搁槾钂?鎽搁櫚钂?鎾呰捣澶х櫧鑵?鎾掑垏灏?鎾掑翱") + sensitiveList = append(sensitiveList, "鎾?鎾?鎾╀汉") + sensitiveList = append(sensitiveList, "鎾?鎿囨皯") + sensitiveList = append(sensitiveList, "鎿婂€掑湒楱?鎿婂偡鐨勫湒楱?鎿?鎿?8") + sensitiveList = append(sensitiveList, "鎿峛") + sensitiveList = append(sensitiveList, "鎿峛鎸囧崡") + sensitiveList = append(sensitiveList, "鎿峠m") + sensitiveList = append(sensitiveList, "鎿峠y") + sensitiveList = append(sensitiveList, "鎿峹x") + sensitiveList = append(sensitiveList, "鎿嶄竷鍏?鎿嶄汉涔?鎿嶄粬") + sensitiveList = append(sensitiveList, "鎿嶄綘") + sensitiveList = append(sensitiveList, "鎿嶄綘鍏緢绁栧畻") + sensitiveList = append(sensitiveList, "鎿嶄綘濡?鎿嶄綘濡堝眲") + sensitiveList = append(sensitiveList, "鎿嶄綘濯?鎿嶄綘鐖虹埡") + sensitiveList = append(sensitiveList, "鎿嶅コ涔?鎿嶅ス") + sensitiveList = append(sensitiveList, "鎿嶅Τ") + sensitiveList = append(sensitiveList, "鎿嶅") + sensitiveList = append(sensitiveList, "鎿嶅皠") + sensitiveList = append(sensitiveList, "鎿嶅凹鐜?鎿嶅眲") + sensitiveList = append(sensitiveList, "鎿嶆垜") + sensitiveList = append(sensitiveList, "鎿嶆") + sensitiveList = append(sensitiveList, "鎿嶆瘝鐙?鎿嶆瘮") + sensitiveList = append(sensitiveList, "鎿嶇儌") + sensitiveList = append(sensitiveList, "鎿嶇垱") + sensitiveList = append(sensitiveList, "鎿嶇埥") + sensitiveList = append(sensitiveList, "鎿嶇┐") + sensitiveList = append(sensitiveList, "鎿嶇┐鍠锋按") + sensitiveList = append(sensitiveList, "鎿嶇┐鍣存按") + sensitiveList = append(sensitiveList, "鎿嶈偪") + sensitiveList = append(sensitiveList, "鎿嶈叓") + sensitiveList = append(sensitiveList, "鎿嶈泲") + sensitiveList = append(sensitiveList, "鎿嶉€?鎿嶉€兼瘺") + sensitiveList = append(sensitiveList, "鎿嶉偅鍡巄") + sensitiveList = append(sensitiveList, "鎿嶉偅鍡庢瘮") + sensitiveList = append(sensitiveList, "鎿嶉偅鍡庨€?鎿嶉稄") + sensitiveList = append(sensitiveList, "鎿嶉粦") + sensitiveList = append(sensitiveList, "鎿嶏紬锛?鎿嶏絿锝?鏀?鏀偅") + sensitiveList = append(sensitiveList, "鏀惧翱") + sensitiveList = append(sensitiveList, "鏀捐崱灏戝") + sensitiveList = append(sensitiveList, "鏀捐崱灏戝瀹鹃") + sensitiveList = append(sensitiveList, "鏀捐崱鐔熷コ") + sensitiveList = append(sensitiveList, "鏀捐暕") + sensitiveList = append(sensitiveList, "鏀捐暕灏戝│") + sensitiveList = append(sensitiveList, "鏀捐暕灏戝│璩撻え") + sensitiveList = append(sensitiveList, "鏀捐暕鐔熷コ") + sensitiveList = append(sensitiveList, "鏀垮彉") + sensitiveList = append(sensitiveList, "鏀垮簻") + sensitiveList = append(sensitiveList, "鏀挎瑠") + sensitiveList = append(sensitiveList, "鏀挎不") + sensitiveList = append(sensitiveList, "鏀挎不鍙嶅娲?鏀挎不鍙嶅皪娲?鏀挎不鐘?鏀胯畩") + sensitiveList = append(sensitiveList, "鏁欒偛閮?鏁欓闄?鏁浗绁炵ぞ") + sensitiveList = append(sensitiveList, "鏁告摎涓湅") + sensitiveList = append(sensitiveList, "鏂囧寲閮?鏂囨畩") + sensitiveList = append(sensitiveList, "鏂囩墿灞€") + sensitiveList = append(sensitiveList, "鏂уご闀板垁") + sensitiveList = append(sensitiveList, "鏂?鏂ぇ鏋?鏂毊鐖惧痉") + sensitiveList = append(sensitiveList, "鏂") + sensitiveList = append(sensitiveList, "鏂颁笁鎵?鏂板厷") + sensitiveList = append(sensitiveList, "鏂板彴婀炬柊闂?鏂板彶璁?鏂板攼浜虹數瑙嗗彴") + sensitiveList = append(sensitiveList, "鏂板攼浜虹數瑙嗗彴缃?鏂板ご澹?鏂版墜瑷撶反鐕?鏂版姤缃戠珯") + sensitiveList = append(sensitiveList, "鏂扮敓缍?鏂扮枂") + sensitiveList = append(sensitiveList, "鏂扮枂7.5浜嬩欢") + sensitiveList = append(sensitiveList, "鏂扮枂鍒嗚") + sensitiveList = append(sensitiveList, "鏂扮枂鍥?鏂扮枂鍦?鏂扮枂鐙珛") + sensitiveList = append(sensitiveList, "鏂扮枂鐛?鏂扮枂鐛ㄧ珛") + sensitiveList = append(sensitiveList, "鏂扮邯鍏?鏂扮邯鍏冨懆鍒?87鏈?鏂扮京瀹?鏂拌仦鍑虹増绺界讲") + sensitiveList = append(sensitiveList, "鏂拌仦灏侀帠") + sensitiveList = append(sensitiveList, "鏂拌仦绠″埗") + sensitiveList = append(sensitiveList, "鏂拌彲鍏ф儏") + sensitiveList = append(sensitiveList, "鏂拌彲鑸夊牨") + sensitiveList = append(sensitiveList, "鏂拌彲閫氳珫澹?鏂拌瀵熻珫澹?鏂拌獮绲?鏂伴椈绠″埗") + sensitiveList = append(sensitiveList, "鏂伴花") + sensitiveList = append(sensitiveList, "鏂瑰嫷涔?鏃呮父灞€") + sensitiveList = append(sensitiveList, "鏃呴え鑷媿") + sensitiveList = append(sensitiveList, "鏃呴鑷媿") + sensitiveList = append(sensitiveList, "鏃犱慨姝?鏃犲鍐呭皠") + sensitiveList = append(sensitiveList, "鏃犲鑷媿") + sensitiveList = append(sensitiveList, "鏃犲府鍥?鏃犲府杩?鏃犳瘺缇庡皯濂?鏃犵晫娴忚鍣?鏃犵爜") + sensitiveList = append(sensitiveList, "鏃犵爜涓濊") + sensitiveList = append(sensitiveList, "鏃犵爜浣撻獙") + sensitiveList = append(sensitiveList, "鏃犵爜鍋氱埍") + sensitiveList = append(sensitiveList, "鏃犵爜褰╁浘") + sensitiveList = append(sensitiveList, "鏃犵爜鏃犲") + sensitiveList = append(sensitiveList, "鏃犵爜娣コ") + sensitiveList = append(sensitiveList, "鏃犵爜娣极") + sensitiveList = append(sensitiveList, "鏃犵爜鐐浘") + sensitiveList = append(sensitiveList, "鏃犵爜绮鹃€?鏃犵爜闀跨墖") + sensitiveList = append(sensitiveList, "鏃犻偊鍥?鏃m") + sensitiveList = append(sensitiveList, "鏃濡?鏃ヤ粬濞?鏃ヤ綘") + sensitiveList = append(sensitiveList, "鏃ヤ綘濡?鏃ヤ綘濞?鏃ヤ綘濯?鏃ヤ綘鐖虹埡") + sensitiveList = append(sensitiveList, "鏃ュ収鐡﹂噾铻?鏃ュ眲") + sensitiveList = append(sensitiveList, "鏃ユ湰av濂充紭") + sensitiveList = append(sensitiveList, "鏃ユ湰av濂冲劒") + sensitiveList = append(sensitiveList, "鏃ユ湰ring") + sensitiveList = append(sensitiveList, "鏃ユ湰甯濆湅") + sensitiveList = append(sensitiveList, "鏃ユ湰鏈夌爜") + sensitiveList = append(sensitiveList, "鏃ユ湰鏈夌⒓") + sensitiveList = append(sensitiveList, "鏃ユ湰鐏岃偁") + sensitiveList = append(sensitiveList, "鏃ユ湰鐏岃吀") + sensitiveList = append(sensitiveList, "鏃ユ湰鐔熸瘝") + sensitiveList = append(sensitiveList, "鏃ユ湰绱犱汉") + sensitiveList = append(sensitiveList, "鏃ユ湰楱疯波") + sensitiveList = append(sensitiveList, "鏃ユ湰楠氳揣") + sensitiveList = append(sensitiveList, "鏃ユ湰楝煎瓙") + sensitiveList = append(sensitiveList, "鏃ユ湵椐?鏃ユ浣?鏃ヨ粛") + sensitiveList = append(sensitiveList, "鏃ラ€?鏃烘姤") + sensitiveList = append(sensitiveList, "鏄嗗湒") + sensitiveList = append(sensitiveList, "鏄庢収缍?鏄庢収缃?鏄庢姤") + sensitiveList = append(sensitiveList, "鏄庢姤鏈堝垔") + sensitiveList = append(sensitiveList, "鏄庢槦鏂拌仦缍?鏄庢槦娣浘") + sensitiveList = append(sensitiveList, "鏄庢槦娣湒") + sensitiveList = append(sensitiveList, "鏄庨暅鍛ㄥ垔") + sensitiveList = append(sensitiveList, "鏄庨暅鏂伴椈") + sensitiveList = append(sensitiveList, "鏄庨暅缃?鏄忚嵂") + sensitiveList = append(sensitiveList, "鏄忚糠鍦栭ò") + sensitiveList = append(sensitiveList, "鏄撲腹杌?鏄熷矝鏃ユ姤") + sensitiveList = append(sensitiveList, "鏄熷矝鏃ユ姤娑堟伅") + sensitiveList = append(sensitiveList, "鏄熷矝鐜悆缃?鏄ュ厜澶栨郴") + sensitiveList = append(sensitiveList, "鏄ュ厜澶栫€?鏄ュ鑷敱璜栧") + sensitiveList = append(sensitiveList, "鏄ヨ嵂") + sensitiveList = append(sensitiveList, "鏄ヨ棩") + sensitiveList = append(sensitiveList, "鏄浮") + sensitiveList = append(sensitiveList, "鏅備簨璜栧") + sensitiveList = append(sensitiveList, "鏅備唬璜栧") + sensitiveList = append(sensitiveList, "鏅氬勾鍛ㄦ仼渚?鏅氬勾鍛ㄦ仼鏉?鏅含") + sensitiveList = append(sensitiveList, "鏅尝") + sensitiveList = append(sensitiveList, "鏅搐") + sensitiveList = append(sensitiveList, "鏆楅粦娉曞斧") + sensitiveList = append(sensitiveList, "鏆翠贡") + sensitiveList = append(sensitiveList, "鏆翠钩") + sensitiveList = append(sensitiveList, "鏆翠簜") + sensitiveList = append(sensitiveList, "鏆村姏铏愬緟") + sensitiveList = append(sensitiveList, "鏆村姩") + sensitiveList = append(sensitiveList, "鏆村悰") + sensitiveList = append(sensitiveList, "鏆村ジ") + sensitiveList = append(sensitiveList, "鏆村共") + sensitiveList = append(sensitiveList, "鏆村构") + sensitiveList = append(sensitiveList, "鏆村緬") + sensitiveList = append(sensitiveList, "鏆存帆") + sensitiveList = append(sensitiveList, "鏆寸啽鐨勬埌澹?鏆磋簛鐨勫煄濉旈噹鐛?鏆磋簛鐨勮琛涘叺闈堥瓊") + sensitiveList = append(sensitiveList, "鏆磋簛鐨勯Μ鏉滃厠") + sensitiveList = append(sensitiveList, "鏆撮ⅷ浜¢潏") + sensitiveList = append(sensitiveList, "鏇癵m") + sensitiveList = append(sensitiveList, "鏇颁綘") + sensitiveList = append(sensitiveList, "鏇茬窔娑堥瓊") + sensitiveList = append(sensitiveList, "鏇茬嚎娑堥瓊") + sensitiveList = append(sensitiveList, "鏇寸敓鏃ユ姤") + sensitiveList = append(sensitiveList, "鏇?鏇瑰垰宸?鏇瑰墰宸?鏇规斂") + sensitiveList = append(sensitiveList, "鏇归暦闈?鏇煎痉鎷?鏇惧煿鐐?鏇惧簡绾?鏇炬叾绱?鏈堢稉") + sensitiveList = append(sensitiveList, "鏈堢粡") + sensitiveList = append(sensitiveList, "鏈堢粡涓嶈皟") + sensitiveList = append(sensitiveList, "鏈夊濂跺ぇ") + sensitiveList = append(sensitiveList, "鏈嶅姟鍣?鏈嶅姟澶╀娇") + sensitiveList = append(sensitiveList, "鏈嶅姟绠$悊") + sensitiveList = append(sensitiveList, "鏈嶅嫏澶╀娇") + sensitiveList = append(sensitiveList, "鏈嶅嫏绠$悊") + sensitiveList = append(sensitiveList, "鏈?鏈濆ぉ绌?鏈濋") + sensitiveList = append(sensitiveList, "鏈濋矞") + sensitiveList = append(sensitiveList, "鏈ㄥ瓙璜栧") + sensitiveList = append(sensitiveList, "鏈ㄨ€?鏈?鎷夌櫥") + sensitiveList = append(sensitiveList, "鏈湡鏃犵爜") + sensitiveList = append(sensitiveList, "鏈湡鐒$⒓") + sensitiveList = append(sensitiveList, "鏈媺鐧?鏈卞鍩?鏈卞楦?鏈卞痉") + sensitiveList = append(sensitiveList, "鏈辨€荤悊") + sensitiveList = append(sensitiveList, "鏈辨憾鍓?鏈辨憾鍔?鏈辩啍鍩?鏈辩嫧鍩?鏈辩珛鍊?鏈遍帞鍩?鏈遍晻鍩?鏈遍Э") + sensitiveList = append(sensitiveList, "鏈遍吵鑺?鏈?") + sensitiveList = append(sensitiveList, "鏈簓") + sensitiveList = append(sensitiveList, "鏈哄叓") + sensitiveList = append(sensitiveList, "鏈哄彮") + sensitiveList = append(sensitiveList, "鏈哄反") + sensitiveList = append(sensitiveList, "鏈烘幇") + sensitiveList = append(sensitiveList, "鏈鸿姯") + sensitiveList = append(sensitiveList, "鏈猴綑") + sensitiveList = append(sensitiveList, "鏉€浜?鏉€浜虹姱") + sensitiveList = append(sensitiveList, "鏉傜") + sensitiveList = append(sensitiveList, "鏉庡厛蹇?鏉庡厠寮?鏉庡厠寮?鏉庡叴骞?鏉庡ぇ甯?鏉庡ぇ甯?鏉庡ぇ閲?鏉庡畯蹇?鏉庡畯鏃?鏉庡皬榈?鏉庡北") + sensitiveList = append(sensitiveList, "鏉庡矚娓?鏉庡祼娓?鏉庡缓鍥?鏉庡紭鏃?鏉庡織缍?鏉庢叾瀹?鏉庢叾鑿?鏉庢椇闄?鏉庢湀鏈堥偿") + sensitiveList = append(sensitiveList, "鏉庢椽瀵?鏉庢椽蹇?鏉庢窇瀚?鏉庢簮娼?鏉庣憺鐜?鏉庣憺鐠?鏉庣櫥杓?鏉庣櫥杈?鏉庣澧?鏉庣鐗?鏉庣タ") + sensitiveList = append(sensitiveList, "鏉庣磪鐥?鏉庣附鐞?鏉庣附绲?鏉庣辜鑰?鏉庣孩蹇?鏉庤€佸斧") + sensitiveList = append(sensitiveList, "鏉庤垐骞?鏉庤槶鑿?鏉庨寗") + sensitiveList = append(sensitiveList, "鏉庨惖鏄?鏉庨搧鏄?鏉庨暦鏄?鏉庨暱鏄?鏉庨惮") + sensitiveList = append(sensitiveList, "鏉庨箯") + sensitiveList = append(sensitiveList, "鏉庨箯*") + sensitiveList = append(sensitiveList, "鏉滃喎涓?鏉滈闁€") + sensitiveList = append(sensitiveList, "鏉滈瞾闂?鏉ㄦ€濇晱") + sensitiveList = append(sensitiveList, "鏉变簽") + sensitiveList = append(sensitiveList, "鏉变簽鐥呭か") + sensitiveList = append(sensitiveList, "鏉变含鐔?鏉卞寳xx缍?鏉卞寳鐛ㄧ珛") + sensitiveList = append(sensitiveList, "鏉卞崌") + sensitiveList = append(sensitiveList, "鏉卞崡瑗垮寳璜栬珖") + sensitiveList = append(sensitiveList, "鏉卞湡鑰冲叾鏂潶") + sensitiveList = append(sensitiveList, "鏉辨柟鏅傜┖") + sensitiveList = append(sensitiveList, "鏉辨柟绱呮檪绌?鏉辨") + sensitiveList = append(sensitiveList, "鏉辨鑻辨") + sensitiveList = append(sensitiveList, "鏉辨鏁?鏉辨磱灞?鏉辩啽绌哄") + sensitiveList = append(sensitiveList, "鏉辩ぞ") + sensitiveList = append(sensitiveList, "鏉辩獊") + sensitiveList = append(sensitiveList, "鏉辩獊鏆村嫊") + sensitiveList = append(sensitiveList, "鏉辩獊鐛ㄧ珛") + sensitiveList = append(sensitiveList, "鏉辫タ鍗楀寳璜栧") + sensitiveList = append(sensitiveList, "鏉遍儴鍦颁笅姘磋矾") + sensitiveList = append(sensitiveList, "鏉遍櫌鐪嬪畧") + sensitiveList = append(sensitiveList, "鏋佸搧濂跺") + sensitiveList = append(sensitiveList, "鏋佸搧娉㈢") + sensitiveList = append(sensitiveList, "鏋佸搧娉㈤湼") + sensitiveList = append(sensitiveList, "鏋佸搧鐧借檸") + sensitiveList = append(sensitiveList, "鏋佸搧榛戜笣") + sensitiveList = append(sensitiveList, "鏋椾笟灞€") + sensitiveList = append(sensitiveList, "鏋椾匠榫?鏋椾繚鑿?鏋椾俊缇?鏋楀姜") + sensitiveList = append(sensitiveList, "鏋楁鍕?鏋楄偗") + sensitiveList = append(sensitiveList, "鏋楅噸璎?鏋楅暦鐩?鏋喅濂崇姱") + sensitiveList = append(sensitiveList, "鏋喅鐜板満") + sensitiveList = append(sensitiveList, "鏋敮寮硅嵂") + sensitiveList = append(sensitiveList, "鏌旈槾鏈?鏌旈櫚琛?鏌缓閵?鏌硿娴?鏌村鐖?鏌寸幉") + sensitiveList = append(sensitiveList, "鏍楁垬涔?鏍稿伐涓氬熀鍦?鏍稿伐妤熀鍦?鏍告鍣?鏍告經鑹?鏍告綔鑹?鏍规鑻楃磪") + sensitiveList = append(sensitiveList, "鏍规鑻楃孩") + sensitiveList = append(sensitiveList, "鏍奸浄(闂滃崱鎺掑悕绠$悊鑰?") + sensitiveList = append(sensitiveList, "鏍奸") + sensitiveList = append(sensitiveList, "鏍奸(鍩庨幃绉诲嫊)") + sensitiveList = append(sensitiveList, "妗冨洯铚滄礊") + sensitiveList = append(sensitiveList, "妗冨湌铚滄礊") + sensitiveList = append(sensitiveList, "妗?妗f灞€") + sensitiveList = append(sensitiveList, "姊呭痉闊嬪倯澶?姊呭痉闊︽澃澶?姊呮瘨") + sensitiveList = append(sensitiveList, "姊呰姳灞?姊呰姳缃?姊佃拏鍐堜簹娲叉柊闂婚€氳绀?姊佃拏鍐堝箍鎾數鍙?妫?妞?妤婂懆") + sensitiveList = append(sensitiveList, "妤婂穽") + sensitiveList = append(sensitiveList, "妤婂缓鍒?妤婃€濇晱") + sensitiveList = append(sensitiveList, "妤婃嚪瀹?妤婃湀娓?妤靛搧濂跺") + sensitiveList = append(sensitiveList, "妤靛搧娉㈢") + sensitiveList = append(sensitiveList, "妤靛搧娉㈤湼") + sensitiveList = append(sensitiveList, "妤靛搧鐧借檸") + sensitiveList = append(sensitiveList, "妤靛搧榛戠挡") + sensitiveList = append(sensitiveList, "姒?妲嶆敮褰堣棩") + sensitiveList = append(sensitiveList, "妲?姗嬩镜瑗插叺") + sensitiveList = append(sensitiveList, "姗?") + sensitiveList = append(sensitiveList, "姗焬") + sensitiveList = append(sensitiveList, "姗熷叓") + sensitiveList = append(sensitiveList, "姗熷彮") + sensitiveList = append(sensitiveList, "姗熷反") + sensitiveList = append(sensitiveList, "姗熸幇") + sensitiveList = append(sensitiveList, "姗熻姯") + sensitiveList = append(sensitiveList, "姗燂綑") + sensitiveList = append(sensitiveList, "妾?妾?娅?娆犲共") + sensitiveList = append(sensitiveList, "娆犲构") + sensitiveList = append(sensitiveList, "娆犳搷") + sensitiveList = append(sensitiveList, "娆犳棩") + sensitiveList = append(sensitiveList, "娆箰鎬т粖瀹?娆㈡濞变箰鏃剁┖") + sensitiveList = append(sensitiveList, "娆х編澶т钩") + sensitiveList = append(sensitiveList, "娆х編鏃犲") + sensitiveList = append(sensitiveList, "娆蹭粰娆叉") + sensitiveList = append(sensitiveList, "娆蹭粰娆叉氮") + sensitiveList = append(sensitiveList, "娆茬伀") + sensitiveList = append(sensitiveList, "娆茬伀鐒氳韩") + sensitiveList = append(sensitiveList, "娆芥湰绔?姝囬€?姝屽姛闋屽痉") + sensitiveList = append(sensitiveList, "姝愮編澶т钩") + sensitiveList = append(sensitiveList, "姝愮編鐒″") + sensitiveList = append(sensitiveList, "姝℃▊鎬т粖瀹?姝℃濞涙▊鏅傜┖") + sensitiveList = append(sensitiveList, "姝g京榛ㄨ珫澹?姝h缍?姝﹁棨") + sensitiveList = append(sensitiveList, "姝籫d") + sensitiveList = append(sensitiveList, "姝籫m") + sensitiveList = append(sensitiveList, "姝诲叏瀹?娈?娈?娈轰汉") + sensitiveList = append(sensitiveList, "娈轰汉鐘?娈轰綘涓€瀹?娈轰綘鍏ㄥ") + sensitiveList = append(sensitiveList, "姣€婊呭皠鎵?姣€婊呮鍏?姣€婊呴◣澹?姣?姣嶅コ鍙岄") + sensitiveList = append(sensitiveList, "姣嶅コ闆欓") + sensitiveList = append(sensitiveList, "姣嶅ジ") + sensitiveList = append(sensitiveList, "姣嶅瓙浜ゆ") + sensitiveList = append(sensitiveList, "姣嶅瓙浜ゆ") + sensitiveList = append(sensitiveList, "姣嶅瓙濂告儏") + sensitiveList = append(sensitiveList, "姣嶅瓙濮︽儏") + sensitiveList = append(sensitiveList, "姣掑搧") + sensitiveList = append(sensitiveList, "姣掗緧鑸旇叧") + sensitiveList = append(sensitiveList, "姣掗緳鑸旇剼") + sensitiveList = append(sensitiveList, "姣旀ǎ") + sensitiveList = append(sensitiveList, "姣旀瘺") + sensitiveList = append(sensitiveList, "姣泋x") + sensitiveList = append(sensitiveList, "姣涗竴椴?姣涗富甯?姣涗富搴?姣涘帟娲?姣涘帬娲?姣涙辰涓?姣涙兢鏉?姣涚墖") + sensitiveList = append(sensitiveList, "姣涜硦") + sensitiveList = append(sensitiveList, "姣涜硦鏉?姣涜醇涓?姣涢畱") + sensitiveList = append(sensitiveList, "姣涢矋") + sensitiveList = append(sensitiveList, "姣?姣?姘戜富") + sensitiveList = append(sensitiveList, "姘戜富涓浗") + sensitiveList = append(sensitiveList, "姘戜富鍏?姘戜富澧?姘戜富娼?姘戜富榛?姘戜紬鐢靛瓙鎶?姘戝浗") + sensitiveList = append(sensitiveList, "姘戝湅") + sensitiveList = append(sensitiveList, "姘戞剰璜栧") + sensitiveList = append(sensitiveList, "姘戞斂閮?姘戣伅") + sensitiveList = append(sensitiveList, "姘戣埅灞€") + sensitiveList = append(sensitiveList, "姘戣繍") + sensitiveList = append(sensitiveList, "姘戣繘鍏?姘戦€查花") + sensitiveList = append(sensitiveList, "姘戦亱") + sensitiveList = append(sensitiveList, "姘戦棿鐢靛彴") + sensitiveList = append(sensitiveList, "姘戦櫍") + sensitiveList = append(sensitiveList, "姘旇薄灞€") + sensitiveList = append(sensitiveList, "姘㈠脊") + sensitiveList = append(sensitiveList, "姘綀") + sensitiveList = append(sensitiveList, "姘村埄閮?姘村幓杞︿粦") + sensitiveList = append(sensitiveList, "姘靛幓") + sensitiveList = append(sensitiveList, "姘靛幓杌婁緰宸ュ姏") + sensitiveList = append(sensitiveList, "姘靛幓杌婁緰宸ュ姏?") + sensitiveList = append(sensitiveList, "姘?姹?姹?姹焎ore") + sensitiveList = append(sensitiveList, "姹熶富甯?姹熷叓") + sensitiveList = append(sensitiveList, "姹熷叓鐐?姹熷叓榛?姹熷墖姘?姹熸垙瀛?姹熸埐瀛?姹熸姌姘?姹熸嫨姘?姹熸牳蹇?姹熸辰姘?姹熸兢鎱?姹熸兢姘?姹熺嫭瑁?姹熺尓") + sensitiveList = append(sensitiveList, "姹熺尓濯?姹熺犊鎭?姹熺緟") + sensitiveList = append(sensitiveList, "姹熻爆濯?姹熻硦") + sensitiveList = append(sensitiveList, "姹熻硦姘?姹熻醇姘?姹熼潚") + sensitiveList = append(sensitiveList, "姹?姹笢椋?姹澅棰?姹磱") + sensitiveList = append(sensitiveList, "娌冨皵寮€瑗?娌冪埦闁嬭タ") + sensitiveList = append(sensitiveList, "娌夌潯鍦栭ò") + sensitiveList = append(sensitiveList, "娌虫畤") + sensitiveList = append(sensitiveList, "娌虫") + sensitiveList = append(sensitiveList, "娉?娉?杞?鍔?娉?杞?鍔?娉晘鍊?娉晘娣?娉晘缍?娉晘璜?娉晘杓?娉曚粦") + sensitiveList = append(sensitiveList, "娉曚緰") + sensitiveList = append(sensitiveList, "娉曞埗鍔?娉曞姛") + sensitiveList = append(sensitiveList, "娉曞崄杓崄鍔?娉曞浗鍥介檯骞挎挱鐢靛彴") + sensitiveList = append(sensitiveList, "娉曞箍涓枃缃?娉曞箍鏂伴椈缃?娉曞箍缃?娉曟劊") + sensitiveList = append(sensitiveList, "娉曟巹") + sensitiveList = append(sensitiveList, "娉曟巹鍔?娉曟藩") + sensitiveList = append(sensitiveList, "娉曠埦鍗?娉曠陡") + sensitiveList = append(sensitiveList, "娉曡タ鏂?娉曡") + sensitiveList = append(sensitiveList, "娉曡鍔?娉曡吉") + sensitiveList = append(sensitiveList, "娉曡吉鍔?娉曡吉澶ф硶") + sensitiveList = append(sensitiveList, "娉曡疆") + sensitiveList = append(sensitiveList, "娉曡疆鍔?娉曡疆澶ф硶") + sensitiveList = append(sensitiveList, "娉℃搏缍撴繜") + sensitiveList = append(sensitiveList, "娉㈤湼") + sensitiveList = append(sensitiveList, "娉板ゴ姗嬫帬濂€?娉板ゴ姗嬭琛涘叺") + sensitiveList = append(sensitiveList, "娲楄偁灏勫翱") + sensitiveList = append(sensitiveList, "娲楄剳鐝?娲楄叇") + sensitiveList = append(sensitiveList, "娲楄吀灏勫翱") + sensitiveList = append(sensitiveList, "娲涘厠鑿茬埦鐗?娲偝") + sensitiveList = append(sensitiveList, "娲叴") + sensitiveList = append(sensitiveList, "娲摬鍕?娲織") + sensitiveList = append(sensitiveList, "娲垐") + sensitiveList = append(sensitiveList, "娲诲姩绠$悊鍛?娲诲嫊") + sensitiveList = append(sensitiveList, "娲诲嫊绠$悊鍝?娲绘憳") + sensitiveList = append(sensitiveList, "娴佹皳") + sensitiveList = append(sensitiveList, "娴佹帆") + sensitiveList = append(sensitiveList, "娴佽湝姹?娴嬬粯灞€") + sensitiveList = append(sensitiveList, "娴嬭瘯") + sensitiveList = append(sensitiveList, "娴彨") + sensitiveList = append(sensitiveList, "娴コ") + sensitiveList = append(sensitiveList, "娴") + sensitiveList = append(sensitiveList, "娴│") + sensitiveList = append(sensitiveList, "娴┐") + sensitiveList = append(sensitiveList, "娴噣") + sensitiveList = append(sensitiveList, "娴村涔变鸡") + sensitiveList = append(sensitiveList, "娴村浜傚€?娴村鑷媿") + sensitiveList = append(sensitiveList, "娴村翱") + sensitiveList = append(sensitiveList, "娴锋磱灞€") + sensitiveList = append(sensitiveList, "娴锋礇鍥?娣嬬梾") + sensitiveList = append(sensitiveList, "娣樺疂") + sensitiveList = append(sensitiveList, "娣╄颈") + sensitiveList = append(sensitiveList, "娣╅嫆") + sensitiveList = append(sensitiveList, "娣?娣玝") + sensitiveList = append(sensitiveList, "娣伄鏂圭▼寮?娣笢鏂?娣笣鑽¤") + sensitiveList = append(sensitiveList, "娣功") + sensitiveList = append(sensitiveList, "习近平") +} + +func TestIsMatch(t *testing.T) { + sensitiveList := []string{"明月", "血", "毒", "金鳞岂是池中物", "岂是"} + input := "明天血" + + util := NewDFAUtil(sensitiveList) + if util.IsMatch(input) == false { + t.Errorf("Expected true, but got false") + } + + input = "血明" + if util.IsMatch(input) == false { + t.Errorf("Expected true, but got false") + } + + input = "明血" + if util.IsMatch(input) == false { + t.Errorf("Expected true, but got false") + } + + input = "金鳞岂是" + if util.IsMatch(input) == false { + t.Errorf("Expected true, but got false") + } +} + +func TestHandleWord(t *testing.T) { + sensitiveList := []string{"明月", "血", "毒", "金鳞岂是池中物", "池中物", "金鳞"} + util := NewDFAUtil(sensitiveList) + + input := "血明天血" + newInput := util.HandleWord(input, '*') + expected := "*明天*" + if newInput != expected { + t.Errorf("Expected %s, but got %s", expected, newInput) + } + + input = "明天血" + newInput = util.HandleWord(input, '*') + expected = "明天*" + if newInput != expected { + t.Errorf("Expected %s, but got %s", expected, newInput) + } + + input = "血明" + newInput = util.HandleWord(input, '*') + expected = "*明" + if newInput != expected { + t.Errorf("Expected %s, but got %s", expected, newInput) + } + + input = "明血" + newInput = util.HandleWord(input, '*') + expected = "明*" + if newInput != expected { + t.Errorf("Expected %s, but got %s", expected, newInput) + } + + input = "金鳞不是池中物" + newInput = util.HandleWord(input, '*') + expected = "**不是***" + if newInput != expected { + t.Errorf("Expected %s, but got %s", expected, newInput) + } +} + +func BenchmarkIsMatch(b *testing.B) { + input := "椰林摇曳,沙滩延绵。印度西海岸,阿拉伯海之畔。10月15日至16日,国家主席习近平出席在印度果阿举行的金砖国家领导人第八次会晤。从南非德班到巴西福塔莱萨,从俄罗斯乌法到印度果阿,这是习近平主席第4次出席金砖国家领导人会晤。今年恰逢金砖国家合作10周年。十年磨一剑。金砖国家合作正面临拓展深化的重要任务。习近平主席出席峰会,发表重要讲话,同各方深入交流,高瞻远瞩、切中肯綮,为金砖发展把脉开方。习近平" + + util := NewDFAUtil(sensitiveList) + b.ResetTimer() + for i := 0; i < b.N; i++ { + util.IsMatch(input) + } +} diff --git a/trunk/goutil/dfaUtil/trieNode.go b/trunk/goutil/dfaUtil/trieNode.go new file mode 100644 index 0000000..5151cc3 --- /dev/null +++ b/trunk/goutil/dfaUtil/trieNode.go @@ -0,0 +1,23 @@ +package dfaUtil + +const ( + INIT_TRIE_CHILDREN_NUM = 0 +) + +// trieNode data structure +// trieNode itself doesn't have any value. The value is represented on the path +type trieNode struct { + // if this node is the end of a word + isEndOfWord bool + + // the collection of children of this node + children map[rune]*trieNode +} + +// Create new trieNode +func newtrieNode() *trieNode { + return &trieNode{ + isEndOfWord: false, + children: make(map[rune]*trieNode, INIT_TRIE_CHILDREN_NUM), + } +} diff --git a/trunk/goutil/doc/Code Specification.txt b/trunk/goutil/doc/Code Specification.txt new file mode 100644 index 0000000..ad2af9e --- /dev/null +++ b/trunk/goutil/doc/Code Specification.txt @@ -0,0 +1,9 @@ +#go语言项目约定 +1、如果在函数内部出现错误,需记录该错误,然后抛出错误,直至回退到函数最外层进行处理。(对于项目初始化的函数,还需调用panic结束协程) +2、对于包内逻辑独立完整的类型,才需建立子包单独存放;对于其他情况,请在同一级包内进行处理。(注意:包与文件夹概念不同,轻易建立子包会造成数据与接口的暴露) +3、对于变量,一律以小写开头;如果该变量需要对包外提供,提供Get,Set接口进行数据访问。 +4、对于常量,如果该常量仅包内可见,前缀小写(con_);如果包外可见,前缀大写(Con_)。 +5、对于自定义类型,如果仅包内可见,小写开头;如果包外可见,大写开头。类型内部字段名默认小写开头,需要序列化的字段大写。 +6、对于通道(chan),如果仅包内可见,小写开头;如果包外可见,大写开头,此处不提供接口访问。 +7、在函数参数中出现的变量若与包内数据同名,统一以_开头加以区分。 +8、对于需要向包外提供的数据,如果不需要修改,统一返回副本,如果需要在包外修改,请返回指针。 \ No newline at end of file diff --git a/trunk/goutil/ensureSendUtil/.gitignore b/trunk/goutil/ensureSendUtil/.gitignore new file mode 100644 index 0000000..54b794f --- /dev/null +++ b/trunk/goutil/ensureSendUtil/.gitignore @@ -0,0 +1,3 @@ +DefaultLogPath/ + +/test_*/ diff --git a/trunk/goutil/ensureSendUtil/baseSender.go b/trunk/goutil/ensureSendUtil/baseSender.go new file mode 100644 index 0000000..09808dd --- /dev/null +++ b/trunk/goutil/ensureSendUtil/baseSender.go @@ -0,0 +1,54 @@ +package ensureSendUtil + +import ( + "fmt" +) + +/* +实现sender接口 +*/ + +type baseSender struct { + // 待发送的数据channel + waitingDataChan chan dataItem + + // 失败数据缓存 + cachedDataChan chan dataItem + + // 用于停止协程 + done chan struct{} +} + +func newBaseSender() *baseSender { + return &baseSender{ + waitingDataChan: make(chan dataItem, 1024), + cachedDataChan: make(chan dataItem, 1024000), + done: make(chan struct{}), + } +} + +// Sender接口 +// Send: +func (this *baseSender) Send() error { + // baseSender不实现发送 + // 由tcpSender和httpSender实现发送 + return fmt.Errorf("baseSender dose not have Send Method") +} + +// Sender接口 +// Data: 返回待发送的数据channel +func (this *baseSender) Data() <-chan dataItem { + return this.waitingDataChan +} + +// Sender接口 +// Cache:返回失败数据缓存channel +func (this *baseSender) Cache() chan dataItem { + return this.cachedDataChan +} + +// Sender接口 +// Done:返回channel用于判断是否关闭 +func (this *baseSender) Done() <-chan struct{} { + return this.done +} diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/baseSender.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/baseSender.go new file mode 100644 index 0000000..87c95ad --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/baseSender.go @@ -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 +} diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/dataItem.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/dataItem.go new file mode 100644 index 0000000..82ce3b9 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/dataItem.go @@ -0,0 +1,101 @@ +package bytesSendUtil + +import ( + "goutil/zlibUtil" +) + +type dataItem interface { + // 返回原始数据 + OriginData() []byte + + // 返回发送字节流 + Bytes() []byte + + // 设置发送次数 + SetCount(uint) + + // 返回发送次数 + Count() uint +} + +///////////////////////////////////////////////// +// httpDataItem + +type httpDataItem struct { + // 数据 + data []byte + + // 发送次数 + count uint +} + +func newHTTPData(_data []byte) dataItem { + return &httpDataItem{ + data: _data, + count: 0, + } +} + +// 返回原始数据 +func (this *httpDataItem) OriginData() []byte { + return this.data +} + +// 返回原始数据用于发送 +func (this *httpDataItem) Bytes() []byte { + return this.data +} + +func (this *httpDataItem) SetCount(cnt uint) { + this.count = cnt +} + +func (this *httpDataItem) Count() uint { + return this.count +} + +///////////////////////////////////////////////// +// tcpDataItem + +type tcpDataItem struct { + // 原始数据 + origin []byte + + // 压缩后数据 + data []byte + + // 重试次数 + count uint +} + +func newTCPDataItem(_data []byte) (dataItem, error) { + compressed, err := zlibUtil.Compress([]byte(_data), 5) + if err != nil { + return nil, err + } + + item := &tcpDataItem{ + origin: _data, + data: compressed, + count: 0, + } + return item, nil +} + +// 返回原始数据 +func (this *tcpDataItem) OriginData() []byte { + return this.origin +} + +// 返回压缩数据用于发送 +func (this *tcpDataItem) Bytes() []byte { + return this.data +} + +func (this *tcpDataItem) SetCount(cnt uint) { + this.count = cnt +} + +func (this *tcpDataItem) Count() uint { + return this.count +} diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSendUtil.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSendUtil.go new file mode 100644 index 0000000..8f2ecf4 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSendUtil.go @@ -0,0 +1,29 @@ +package bytesSendUtil + +/* +ensureSendUtil 用于推送数据 +支持TCP和HTTP两种形式,在发送失败时会缓存数据,并在一定时间间隔后重试 + +通过NewTCPSender和NewHTTPSender两个接口分别创建TCP和HTTP模式的EnsureSender + +type EnsureSender interface { + // 用于发送数据 + Write([]byte) error + + // 用于停止发送,此时会自动保存未发送数据 + Close() error +} + +// 创建一个tcp数据发送器 +// 参数: +// _dataFolder 数据存放目录 +// _address 连接地址 +func NewTCPSender(_dataFolder, _address string) (EnsureSender, error) { + + +// 创建一个http数据发送器 +// 参数: +// _dataFolder 数据存放目录 +// _url 发送地址 +func NewHTTPSender(_dataFolder, _url string) (EnsureSender, error) { +*/ diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSender.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSender.go new file mode 100644 index 0000000..ee85c47 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/ensureSender.go @@ -0,0 +1,24 @@ +package bytesSendUtil + +type EnsureSender interface { + // use Write to send data + Write([]byte) error + + // stop sender + Close() error +} + +// resend和dataSaver通过此接口调用tcpSender与httpSender +type sender interface { + // 发送数据 + Send(dataItem) error + + // 返回待发送的数据channel + Data() <-chan dataItem + + // 返回失败数据缓存channel + Cache() chan dataItem + + // 用于判断是否关闭 + Done() <-chan struct{} +} diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender.go new file mode 100644 index 0000000..cab989b --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender.go @@ -0,0 +1,96 @@ +package bytesSendUtil + +import ( + "fmt" + + "goutil/webUtil" +) + +// 实现 EnsureSender和sender接口 +type httpSender struct { + // 需要实现的接口 + EnsureSender + + // 包含sender接口部分实现 + *baseSender + + // 数据目录 + dataFolder string + + // 发送地址 + url string + + // 用于sendLoop和resendLoop发送退出信号 + closeSignal chan struct{} +} + +// 创建一个http数据发送器 +// 参数: +// +// _dataFolder 数据存放目录 +// _url 发送地址 +func NewHTTPSender(_dataFolder, _url string) (EnsureSender, error) { + this := &httpSender{ + dataFolder: _dataFolder, + url: _url, + baseSender: newBaseSender(), + closeSignal: make(chan struct{}), + } + + // 新开协程发送数据 + go sendLoop(this, this.closeSignal) + + // 定时重发 + go resendLoop(this, _dataFolder, this.closeSignal) + + return this, nil +} + +// EnsureSender接口 +// Write:写入数据 +func (this *httpSender) Write(data []byte) error { + item := newHTTPData(data) + + this.waitingDataChan <- item + + return nil +} + +// EnsureSender接口 +// Close:关闭 +func (this *httpSender) Close() error { + close(this.done) + + // 等待sendLoop和resendLoop退出 + <-this.closeSignal + <-this.closeSignal + + // 保存数据 + _, e1 := saveData(this.Cache(), this.dataFolder) + _, e2 := saveData(this.Data(), this.dataFolder) + + if e2 != nil { + if e1 != nil { + return fmt.Errorf("%s %s", e1, e2) + } + return e2 + } else { + return e1 + } +} + +// sender接口 +// Send:发送数据 +func (this *httpSender) Send(item dataItem) error { + statusCode, _, err := webUtil.PostByteData2(this.url, item.Bytes(), nil, nil) + if err != nil || statusCode != 200 { + if err == nil { + err = fmt.Errorf("StatusCode is not 200") + } + + // 发送失败时发送次数+1 + item.SetCount(item.Count() + 1) + } + + return err +} diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender_test.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender_test.go new file mode 100644 index 0000000..8a257ea --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/httpSender_test.go @@ -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") + } +} diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/saveData.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/saveData.go new file mode 100644 index 0000000..acb66cf --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/saveData.go @@ -0,0 +1,58 @@ +package bytesSendUtil + +import ( + "fmt" + + "goutil/fileUtil" + "goutil/logUtil" + "goutil/stringUtil" +) + +// 从目录加载缓存数据并发送 +func loadData(s EnsureSender, folder string) error { + if fileList, err := fileUtil.GetFileList(folder); err != nil { + return err + } else { + for _, filename := range fileList { + // 读取发送内容 + if fileContent, err := fileUtil.ReadFileBytes(filename); err != nil { + // 打印错误 + log := fmt.Sprintf("ensureSendUtil.loadData: Failed To Read File: %s %s\n", err, filename) + logUtil.NormalLog(log, logUtil.Error) + } else if err = fileUtil.DeleteFile(filename); err != nil { + // 删除文件,如果成功则将内容添加到通道中,否则不处理 + log := fmt.Sprintf("ensureSendUtil.loadData: Failed To Delete File: %s %s", err, filename) + logUtil.NormalLog(log, logUtil.Error) + } else { + // 发送数据 + s.Write(fileContent) + } + } + } + + return nil +} + +// 保存数据到文件中(通常在退出时调用) +func saveData(datas <-chan dataItem, folder string) (failed []dataItem, err error) { + defer func() { + if len(failed) > 0 { + err = fmt.Errorf("保存数据时有%d个失败数据", len(failed)) + } + }() + + for { + select { + case v := <-datas: + filename := stringUtil.GetNewGUID() + if e := fileUtil.WriteFile4Byte(folder, filename, false, v.OriginData()); e != nil { + failed = append(failed, v) + log := fmt.Sprintf("ensureSendUtil.saveData: 写入错误\n目录:%s,文件:%s,错误信息为:%s, Data Len:%v", + folder, filename, err, len(v.OriginData())) + logUtil.NormalLog(log, logUtil.Error) + } + default: + return + } + } +} diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/send.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/send.go new file mode 100644 index 0000000..ee17417 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/send.go @@ -0,0 +1,111 @@ +package bytesSendUtil + +import ( + "fmt" + "time" + + "goutil/debugUtil" + "goutil/logUtil" +) + +// 负责发送数据的协程 +func sendLoop(s sender, closeSignal chan struct{}) { + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + }() + + for { + select { + case <-s.Done(): + closeSignal <- struct{}{} + return + case v := <-s.Data(): + if err := s.Send(v); err != nil { + // 发送失败存入缓存 + s.Cache() <- v + } + } + } +} + +// 定时重发失败的数据 +func resendLoop(s sender, folder string, closeSignal chan struct{}) { + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + }() + + // debug模式每秒重试1次 + var delay time.Duration + if debugUtil.IsDebug() { + delay = time.Second + } else { + delay = time.Minute * 5 + } + + // 定时重发失败数据 + for { + select { + case <-s.Done(): + closeSignal <- struct{}{} + return + case <-time.After(delay): + sendCacheData(s, folder) + loadData(s.(EnsureSender), folder) + } + } +} + +// 从sender获取失败数据重发 +func sendCacheData(s sender, folder string) { + failed := make([]dataItem, 0) + length := len(s.Cache()) + + defer func() { + // 用于记录多次失败后放弃发送的数据 + giveUpItems := make(chan dataItem, len(failed)) + + for _, v := range failed { + if v.Count() >= 3 { + // 失败次数太多的数据准备存放到磁盘中 + giveUpItems <- v + } else { + s.Cache() <- v + } + } + + giveUpLen := len(giveUpItems) + if giveUpLen > 0 { + // 将多次失败的数据保存到文件中 + if folder[len(folder)-1] == '/' { + folder = folder[:len(folder)-1] + } + saveData(giveUpItems, folder+"_giveup") + + if giveUpLen >= 5 { + log := fmt.Sprintf("ensureSendUtil: 有%d条数据多次发送失败", giveUpLen) + logUtil.NormalLog(log, logUtil.Error) + } + } + + // 输出信息 + log := fmt.Sprintf("ensureSendUtil: 重发%d条数据,失败%d条,存盘%d条\n", length, len(failed), giveUpLen) + logUtil.NormalLog(log, logUtil.Info) + }() + + for { + select { + case v := <-s.Cache(): + // 重发数据 + if e := s.Send(v); e != nil { + // 记录失败的数据 + failed = append(failed, v) + } + default: + return + } + } +} diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender.go new file mode 100644 index 0000000..fa17bbf --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender.go @@ -0,0 +1,208 @@ +package bytesSendUtil + +import ( + "encoding/binary" + "fmt" + "net" + "sync" + "time" + + "goutil/intAndBytesUtil" + "goutil/logUtil" +) + +var ( + errConnectEmpty = fmt.Errorf("scoket reconnecting...") + byterOrder = binary.LittleEndian +) + +// 实现 EnsureSender和sender接口 +type tcpSender struct { + // 需要实现的接口 + EnsureSender + + // 包含sender接口部分实现 + *baseSender + + // 数据目录 + dataFolder string + + // 服务器地址 + address string + + // 连接 + conn net.Conn + + // 用于重连时互斥 + mutex sync.Mutex + + // 用于sendLoop和resendLoop发送退出信号 + closeSignal chan struct{} +} + +// 创建一个tcp数据发送器 +// 参数: +// +// _dataFolder 数据存放目录 +// _address 连接地址 +func NewTCPSender(_dataFolder, _address string) (EnsureSender, error) { + // 连接服务器 + conn, err := net.DialTimeout("tcp", _address, 5*time.Second) + if err != nil { + return nil, err + } + + this := &tcpSender{ + dataFolder: _dataFolder, + baseSender: newBaseSender(), + address: _address, + conn: conn, + closeSignal: make(chan struct{}), + } + + // 新开协程发送数据 + go sendLoop(this, this.closeSignal) + + // 定时重发 + go resendLoop(this, _dataFolder, this.closeSignal) + + // 发送心跳包 + go this.heartBeat() + + return this, nil +} + +// 每隔15秒发送心跳包 +func (this *tcpSender) heartBeat() { + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + }() + + tick := time.Tick(time.Second * 15) + + for { + select { + case <-this.Done(): + return + case <-tick: + this.sendBytes([]byte{}) + } + } +} + +// EnsureSender接口 +// Write:写入数据 +func (this *tcpSender) Write(data []byte) error { + item, err := newTCPDataItem(data) + if err != nil { + return err + } + + this.waitingDataChan <- item + + return nil +} + +// EnsureSender接口 +// Close:关闭 +func (this *tcpSender) Close() error { + close(this.done) + + // 关闭socket连接 + conn := this.conn + if conn != nil { + conn.Close() + } + + // 等待sendLoop和resendLoop退出 + <-this.closeSignal + <-this.closeSignal + + // 保存数据 + _, e1 := saveData(this.Cache(), this.dataFolder) + _, e2 := saveData(this.Data(), this.dataFolder) + + if e2 != nil { + if e1 != nil { + return fmt.Errorf("%s %s", e1, e2) + } + return e2 + } else { + return e1 + } +} + +// Sender接口 +// Send:发送dataItem +func (this *tcpSender) Send(item dataItem) error { + err := this.sendBytes(item.Bytes()) + if err != nil && err != errConnectEmpty { + // 发送失败时发送次数+1 + item.SetCount(item.Count() + 1) + } + + return err +} + +// 发送字节数据 +// 发送格式:[lenght+data] +func (this *tcpSender) sendBytes(data []byte) error { + conn := this.conn + if conn == nil { + return errConnectEmpty + } + + // 将长度转化为字节数组 + header := intAndBytesUtil.Int32ToBytes(int32(len(data)), byterOrder) + + if len(data) > 0 { + data = append(header, data...) + } else { + data = header + } + + _, err := conn.Write(data) + if err != nil { + this.mutex.Lock() + // 发送失败 + // 检查失败的conn是否this.conn(避免多个线程失败后均调用reconnect) + // 是则关闭并重连 + if conn == this.conn { + this.conn.Close() + this.conn = nil + this.mutex.Unlock() + + // 重连 + go this.reconnect() + } else { + this.mutex.Unlock() + } + } + + return err +} + +// 重连服务器 +func (this *tcpSender) reconnect() error { + // lock-it + this.mutex.Lock() + defer this.mutex.Unlock() + + for { + // 检查是否已经重连 + if this.conn != nil { + return nil + } + + conn, err := net.DialTimeout("tcp", this.address, 5*time.Second) + if err != nil { + // 连接失败,5秒后重试 + <-time.After(time.Second * 5) + continue + } + + this.conn = conn + } +} diff --git a/trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender_test.go b/trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender_test.go new file mode 100644 index 0000000..67fd076 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/bytesSendUtil/tcpSender_test.go @@ -0,0 +1,95 @@ +package bytesSendUtil + +import ( + "fmt" + "net" + "testing" + "time" + + "goutil/debugUtil" + "goutil/zlibUtil" +) + +// 保存接收的数据用于校验 +var tcp_recv_msg = make([]byte, 0) + +func init() { + debugUtil.SetDebug(true) +} + +// 创建socket服务器,保存收到的数据 +func server(addr string) net.Listener { + listener, err := net.Listen("tcp", addr) + if err != nil { + panic(err) + } + + go func() { + for { + conn, err := listener.Accept() + if err != nil { + return + } + + for { + buff := make([]byte, 512) + _, err := conn.Read(buff) + if err != nil { + break + } else { + decompressed, err := zlibUtil.Decompress(buff[4:]) + if err != nil { + panic(err) + } else { + tcp_recv_msg = append(tcp_recv_msg, decompressed...) + } + } + } + } + }() + + return listener +} + +func Test_tcp(t *testing.T) { + // 开启服务器 + l := server("127.0.0.1:9559") + + tcp, err := NewTCPSender("./test_tcp", "127.0.0.1:9559") + if err != nil { + t.Error(err) + } + + // 发送消息 + tcp.Write([]byte("tcp-msg-1")) + time.Sleep(time.Millisecond * 50) // 等待协程发送数据 + + // 关闭连接和服务器 + l.Close() + (tcp.(*tcpSender)).conn.Close() + + // 发送消息,此数据会失败 + tcp.Write([]byte("tcp-msg-2")) + // time.Sleep(time.Millisecond * 50) + + // 保存数据 + tcp.Close() + + // 重启,检查是否重发tcp-msg-2 + l = server("127.0.0.1:9559") + tcp, err = NewTCPSender("./test_tcp", "127.0.0.1:9559") + if err != nil { + t.Error(err) + } + + time.Sleep(time.Second * 2) + + if string(tcp_recv_msg) != "tcp-msg-1tcp-msg-2" { + t.Error("message error. got " + string(tcp_recv_msg)) + } else { + fmt.Println("TCP OK") + } + + tcp.Close() + l.Close() +} diff --git a/trunk/goutil/ensureSendUtil/dataItem.go b/trunk/goutil/ensureSendUtil/dataItem.go new file mode 100644 index 0000000..09e1119 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/dataItem.go @@ -0,0 +1,101 @@ +package ensureSendUtil + +import ( + "goutil/zlibUtil" +) + +type dataItem interface { + // 返回原始数据 + String() string + + // 返回发送字节流 + Bytes() []byte + + // 设置发送次数 + SetCount(uint) + + // 返回发送次数 + Count() uint +} + +///////////////////////////////////////////////// +// httpDataItem + +type httpDataItem struct { + // 数据 + data string + + // 发送次数 + count uint +} + +func newHTTPData(_data string) dataItem { + return &httpDataItem{ + data: _data, + count: 0, + } +} + +// 返回原始数据 +func (this *httpDataItem) String() string { + return this.data +} + +// 返回原始数据用于发送 +func (this *httpDataItem) Bytes() []byte { + return []byte(this.data) +} + +func (this *httpDataItem) SetCount(cnt uint) { + this.count = cnt +} + +func (this *httpDataItem) Count() uint { + return this.count +} + +///////////////////////////////////////////////// +// tcpDataItem + +type tcpDataItem struct { + // 原始数据 + origin string + + // 压缩后数据 + data []byte + + // 重试次数 + count uint +} + +func newTCPDataItem(_data string) (dataItem, error) { + compressed, err := zlibUtil.Compress([]byte(_data), 5) + if err != nil { + return nil, err + } + + item := &tcpDataItem{ + origin: _data, + data: compressed, + count: 0, + } + return item, nil +} + +// 返回原始数据 +func (this *tcpDataItem) String() string { + return this.origin +} + +// 返回压缩数据用于发送 +func (this *tcpDataItem) Bytes() []byte { + return this.data +} + +func (this *tcpDataItem) SetCount(cnt uint) { + this.count = cnt +} + +func (this *tcpDataItem) Count() uint { + return this.count +} diff --git a/trunk/goutil/ensureSendUtil/ensureSendUtil.go b/trunk/goutil/ensureSendUtil/ensureSendUtil.go new file mode 100644 index 0000000..b5425d1 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/ensureSendUtil.go @@ -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) { +*/ diff --git a/trunk/goutil/ensureSendUtil/ensureSender.go b/trunk/goutil/ensureSendUtil/ensureSender.go new file mode 100644 index 0000000..5fb3387 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/ensureSender.go @@ -0,0 +1,24 @@ +package ensureSendUtil + +type EnsureSender interface { + // use Write to send data + Write(string) error + + // stop sender + Close() error +} + +// resend和dataSaver通过此接口调用tcpSender与httpSender +type sender interface { + // 发送数据 + Send(dataItem) error + + // 返回待发送的数据channel + Data() <-chan dataItem + + // 返回失败数据缓存channel + Cache() chan dataItem + + // 用于判断是否关闭 + Done() <-chan struct{} +} diff --git a/trunk/goutil/ensureSendUtil/httpSender.go b/trunk/goutil/ensureSendUtil/httpSender.go new file mode 100644 index 0000000..6505cc5 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/httpSender.go @@ -0,0 +1,96 @@ +package ensureSendUtil + +import ( + "fmt" + + "goutil/webUtil" +) + +// 实现 EnsureSender和sender接口 +type httpSender struct { + // 需要实现的接口 + EnsureSender + + // 包含sender接口部分实现 + *baseSender + + // 数据目录 + dataFolder string + + // 发送地址 + url string + + // 用于sendLoop和resendLoop发送退出信号 + closeSignal chan struct{} +} + +// 创建一个http数据发送器 +// 参数: +// +// _dataFolder 数据存放目录 +// _url 发送地址 +func NewHTTPSender(_dataFolder, _url string) (EnsureSender, error) { + this := &httpSender{ + dataFolder: _dataFolder, + url: _url, + baseSender: newBaseSender(), + closeSignal: make(chan struct{}), + } + + // 新开协程发送数据 + go sendLoop(this, this.closeSignal) + + // 定时重发 + go resendLoop(this, _dataFolder, this.closeSignal) + + return this, nil +} + +// EnsureSender接口 +// Write:写入数据 +func (this *httpSender) Write(data string) error { + item := newHTTPData(data) + + this.waitingDataChan <- item + + return nil +} + +// EnsureSender接口 +// Close:关闭 +func (this *httpSender) Close() error { + close(this.done) + + // 等待sendLoop和resendLoop退出 + <-this.closeSignal + <-this.closeSignal + + // 保存数据 + _, e1 := saveData(this.Cache(), this.dataFolder) + _, e2 := saveData(this.Data(), this.dataFolder) + + if e2 != nil { + if e1 != nil { + return fmt.Errorf("%s %s", e1, e2) + } + return e2 + } else { + return e1 + } +} + +// sender接口 +// Send:发送数据 +func (this *httpSender) Send(item dataItem) error { + statusCode, _, err := webUtil.PostByteData2(this.url, item.Bytes(), nil, nil) + if err != nil || statusCode != 200 { + if err == nil { + err = fmt.Errorf("StatusCode is not 200") + } + + // 发送失败时发送次数+1 + item.SetCount(item.Count() + 1) + } + + return err +} diff --git a/trunk/goutil/ensureSendUtil/httpSender_test.go b/trunk/goutil/ensureSendUtil/httpSender_test.go new file mode 100644 index 0000000..a6b9172 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/httpSender_test.go @@ -0,0 +1,86 @@ +package ensureSendUtil + +import ( + "fmt" + "io/ioutil" + "net/http" + "testing" + "time" + + "goutil/debugUtil" +) + +// 保存接收的数据用于校验 +var http_recv_msg = make([]byte, 0) + +func init() { + debugUtil.SetDebug(true) +} + +type httpHandler struct { + cnt int +} + +func (ctx *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + + defer r.Body.Close() + result, _ := ioutil.ReadAll(r.Body) + + if string(result) == "http-msg-failed" { + http.NotFound(w, r) + } else { + ctx.cnt++ + // 模拟一次失败 + if ctx.cnt == 2 { + http.NotFound(w, r) + } else { + http_recv_msg = append(http_recv_msg, result...) + } + } + +} + +func Test_http(t *testing.T) { + http.Handle("/test", new(httpHandler)) + go http.ListenAndServe("127.0.0.1:9560", nil) + + httpSender, err := NewHTTPSender("./test_http", "http://127.0.0.1:9560/test") + if err != nil { + t.Error(err) + } + + time.Sleep(time.Millisecond * 50) + + // 第一次应该成功 + httpSender.Write("http-msg-1") + + time.Sleep(time.Millisecond) + + // 发送消息,此数据会多次失败,被丢弃到giveup目录 + httpSender.Write("http-msg-failed") + + time.Sleep(time.Second * 4) + + // 第二次应该失败 + httpSender.Write("http-msg-2") + + time.Sleep(time.Millisecond) + + // 保存数据 + httpSender.Close() + + // 重启之后应该会重发数据 + httpSender, err = NewHTTPSender("./test_http", "http://127.0.0.1:9560/test") + if err != nil { + t.Error(err) + } + time.Sleep(time.Second * 2) + + httpSender.Close() + + if string(http_recv_msg) != "http-msg-1http-msg-2" { + t.Error("message error. got " + string(http_recv_msg)) + } else { + fmt.Println("HTTP OK") + } +} diff --git a/trunk/goutil/ensureSendUtil/saveData.go b/trunk/goutil/ensureSendUtil/saveData.go new file mode 100644 index 0000000..d53356b --- /dev/null +++ b/trunk/goutil/ensureSendUtil/saveData.go @@ -0,0 +1,58 @@ +package ensureSendUtil + +import ( + "fmt" + + "goutil/fileUtil" + "goutil/logUtil" + "goutil/stringUtil" +) + +// 从目录加载缓存数据并发送 +func loadData(s EnsureSender, folder string) error { + if fileList, err := fileUtil.GetFileList(folder); err != nil { + return err + } else { + for _, filename := range fileList { + // 读取发送内容 + if fileContent, err := fileUtil.ReadFileContent(filename); err != nil { + // 打印错误 + log := fmt.Sprintf("ensureSendUtil.loadData: Failed To Read File: %s %s\n", err, filename) + logUtil.NormalLog(log, logUtil.Error) + } else if err = fileUtil.DeleteFile(filename); err != nil { + // 删除文件,如果成功则将内容添加到通道中,否则不处理 + log := fmt.Sprintf("ensureSendUtil.loadData: Failed To Delete File: %s %s", err, filename) + logUtil.NormalLog(log, logUtil.Error) + } else { + // 发送数据 + s.Write(fileContent) + } + } + } + + return nil +} + +// 保存数据到文件中(通常在退出时调用) +func saveData(datas <-chan dataItem, folder string) (failed []dataItem, err error) { + defer func() { + if len(failed) > 0 { + err = fmt.Errorf("保存数据时有%d个失败数据", len(failed)) + } + }() + + for { + select { + case v := <-datas: + filename := stringUtil.GetNewGUID() + if e := fileUtil.WriteFile(folder, filename, false, v.String()); e != nil { + failed = append(failed, v) + log := fmt.Sprintf("ensureSendUtil.saveData: 写入错误\n目录:%s,文件:%s,错误信息为:%s, Data:%s", + folder, filename, err, v.String()) + logUtil.NormalLog(log, logUtil.Error) + } + default: + return + } + } +} diff --git a/trunk/goutil/ensureSendUtil/send.go b/trunk/goutil/ensureSendUtil/send.go new file mode 100644 index 0000000..3561842 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/send.go @@ -0,0 +1,111 @@ +package ensureSendUtil + +import ( + "fmt" + "time" + + "goutil/debugUtil" + "goutil/logUtil" +) + +// 负责发送数据的协程 +func sendLoop(s sender, closeSignal chan struct{}) { + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + }() + + for { + select { + case <-s.Done(): + closeSignal <- struct{}{} + return + case v := <-s.Data(): + if err := s.Send(v); err != nil { + // 发送失败存入缓存 + s.Cache() <- v + } + } + } +} + +// 定时重发失败的数据 +func resendLoop(s sender, folder string, closeSignal chan struct{}) { + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + }() + + // debug模式每秒重试1次 + var delay time.Duration + if debugUtil.IsDebug() { + delay = time.Second + } else { + delay = time.Minute * 5 + } + + // 定时重发失败数据 + for { + select { + case <-s.Done(): + closeSignal <- struct{}{} + return + case <-time.After(delay): + sendCacheData(s, folder) + loadData(s.(EnsureSender), folder) + } + } +} + +// 从sender获取失败数据重发 +func sendCacheData(s sender, folder string) { + failed := make([]dataItem, 0) + length := len(s.Cache()) + + defer func() { + // 用于记录多次失败后放弃发送的数据 + giveUpItems := make(chan dataItem, len(failed)) + + for _, v := range failed { + if v.Count() >= 3 { + // 失败次数太多的数据准备存放到磁盘中 + giveUpItems <- v + } else { + s.Cache() <- v + } + } + + giveUpLen := len(giveUpItems) + if giveUpLen > 0 { + // 将多次失败的数据保存到文件中 + if folder[len(folder)-1] == '/' { + folder = folder[:len(folder)-1] + } + saveData(giveUpItems, folder+"_giveup") + + if giveUpLen >= 5 { + log := fmt.Sprintf("ensureSendUtil: 有%d条数据多次发送失败", giveUpLen) + logUtil.NormalLog(log, logUtil.Error) + } + } + + // 输出信息 + log := fmt.Sprintf("ensureSendUtil: 重发%d条数据,失败%d条,存盘%d条\n", length, len(failed), giveUpLen) + logUtil.NormalLog(log, logUtil.Info) + }() + + for { + select { + case v := <-s.Cache(): + // 重发数据 + if e := s.Send(v); e != nil { + // 记录失败的数据 + failed = append(failed, v) + } + default: + return + } + } +} diff --git a/trunk/goutil/ensureSendUtil/tcpSender.go b/trunk/goutil/ensureSendUtil/tcpSender.go new file mode 100644 index 0000000..9b8a118 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/tcpSender.go @@ -0,0 +1,208 @@ +package ensureSendUtil + +import ( + "encoding/binary" + "fmt" + "net" + "sync" + "time" + + "goutil/intAndBytesUtil" + "goutil/logUtil" +) + +var ( + errConnectEmpty = fmt.Errorf("scoket reconnecting...") + byterOrder = binary.LittleEndian +) + +// 实现 EnsureSender和sender接口 +type tcpSender struct { + // 需要实现的接口 + EnsureSender + + // 包含sender接口部分实现 + *baseSender + + // 数据目录 + dataFolder string + + // 服务器地址 + address string + + // 连接 + conn net.Conn + + // 用于重连时互斥 + mutex sync.Mutex + + // 用于sendLoop和resendLoop发送退出信号 + closeSignal chan struct{} +} + +// 创建一个tcp数据发送器 +// 参数: +// +// _dataFolder 数据存放目录 +// _address 连接地址 +func NewTCPSender(_dataFolder, _address string) (EnsureSender, error) { + // 连接服务器 + conn, err := net.DialTimeout("tcp", _address, 5*time.Second) + if err != nil { + return nil, err + } + + this := &tcpSender{ + dataFolder: _dataFolder, + baseSender: newBaseSender(), + address: _address, + conn: conn, + closeSignal: make(chan struct{}), + } + + // 新开协程发送数据 + go sendLoop(this, this.closeSignal) + + // 定时重发 + go resendLoop(this, _dataFolder, this.closeSignal) + + // 发送心跳包 + go this.heartBeat() + + return this, nil +} + +// 每隔15秒发送心跳包 +func (this *tcpSender) heartBeat() { + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + } + }() + + tick := time.Tick(time.Second * 15) + + for { + select { + case <-this.Done(): + return + case <-tick: + this.sendBytes([]byte{}) + } + } +} + +// EnsureSender接口 +// Write:写入数据 +func (this *tcpSender) Write(data string) error { + item, err := newTCPDataItem(data) + if err != nil { + return err + } + + this.waitingDataChan <- item + + return nil +} + +// EnsureSender接口 +// Close:关闭 +func (this *tcpSender) Close() error { + close(this.done) + + // 关闭socket连接 + conn := this.conn + if conn != nil { + conn.Close() + } + + // 等待sendLoop和resendLoop退出 + <-this.closeSignal + <-this.closeSignal + + // 保存数据 + _, e1 := saveData(this.Cache(), this.dataFolder) + _, e2 := saveData(this.Data(), this.dataFolder) + + if e2 != nil { + if e1 != nil { + return fmt.Errorf("%s %s", e1, e2) + } + return e2 + } else { + return e1 + } +} + +// Sender接口 +// Send:发送dataItem +func (this *tcpSender) Send(item dataItem) error { + err := this.sendBytes(item.Bytes()) + if err != nil && err != errConnectEmpty { + // 发送失败时发送次数+1 + item.SetCount(item.Count() + 1) + } + + return err +} + +// 发送字节数据 +// 发送格式:[lenght+data] +func (this *tcpSender) sendBytes(data []byte) error { + conn := this.conn + if conn == nil { + return errConnectEmpty + } + + // 将长度转化为字节数组 + header := intAndBytesUtil.Int32ToBytes(int32(len(data)), byterOrder) + + if len(data) > 0 { + data = append(header, data...) + } else { + data = header + } + + _, err := conn.Write(data) + if err != nil { + this.mutex.Lock() + // 发送失败 + // 检查失败的conn是否this.conn(避免多个线程失败后均调用reconnect) + // 是则关闭并重连 + if conn == this.conn { + this.conn.Close() + this.conn = nil + this.mutex.Unlock() + + // 重连 + go this.reconnect() + } else { + this.mutex.Unlock() + } + } + + return err +} + +// 重连服务器 +func (this *tcpSender) reconnect() error { + // lock-it + this.mutex.Lock() + defer this.mutex.Unlock() + + for { + // 检查是否已经重连 + if this.conn != nil { + return nil + } + + conn, err := net.DialTimeout("tcp", this.address, 5*time.Second) + if err != nil { + // 连接失败,5秒后重试 + <-time.After(time.Second * 5) + continue + } + + this.conn = conn + } +} diff --git a/trunk/goutil/ensureSendUtil/tcpSender_test.go b/trunk/goutil/ensureSendUtil/tcpSender_test.go new file mode 100644 index 0000000..93600d2 --- /dev/null +++ b/trunk/goutil/ensureSendUtil/tcpSender_test.go @@ -0,0 +1,95 @@ +package ensureSendUtil + +import ( + "fmt" + "net" + "testing" + "time" + + "goutil/debugUtil" + "goutil/zlibUtil" +) + +// 保存接收的数据用于校验 +var tcp_recv_msg = make([]byte, 0) + +func init() { + debugUtil.SetDebug(true) +} + +// 创建socket服务器,保存收到的数据 +func server(addr string) net.Listener { + listener, err := net.Listen("tcp", addr) + if err != nil { + panic(err) + } + + go func() { + for { + conn, err := listener.Accept() + if err != nil { + return + } + + for { + buff := make([]byte, 512) + _, err := conn.Read(buff) + if err != nil { + break + } else { + decompressed, err := zlibUtil.Decompress(buff[4:]) + if err != nil { + panic(err) + } else { + tcp_recv_msg = append(tcp_recv_msg, decompressed...) + } + } + } + } + }() + + return listener +} + +func Test_tcp(t *testing.T) { + // 开启服务器 + l := server("127.0.0.1:9559") + + tcp, err := NewTCPSender("./test_tcp", "127.0.0.1:9559") + if err != nil { + t.Error(err) + } + + // 发送消息 + tcp.Write("tcp-msg-1") + time.Sleep(time.Millisecond * 50) // 等待协程发送数据 + + // 关闭连接和服务器 + l.Close() + (tcp.(*tcpSender)).conn.Close() + + // 发送消息,此数据会失败 + tcp.Write("tcp-msg-2") + // time.Sleep(time.Millisecond * 50) + + // 保存数据 + tcp.Close() + + // 重启,检查是否重发tcp-msg-2 + l = server("127.0.0.1:9559") + tcp, err = NewTCPSender("./test_tcp", "127.0.0.1:9559") + if err != nil { + t.Error(err) + } + + time.Sleep(time.Second * 2) + + if string(tcp_recv_msg) != "tcp-msg-1tcp-msg-2" { + t.Error("message error. got " + string(tcp_recv_msg)) + } else { + fmt.Println("TCP OK") + } + + tcp.Close() + l.Close() +} diff --git a/trunk/goutil/esLogUtil/esIndexHandler.go b/trunk/goutil/esLogUtil/esIndexHandler.go new file mode 100644 index 0000000..6ff7d3a --- /dev/null +++ b/trunk/goutil/esLogUtil/esIndexHandler.go @@ -0,0 +1,113 @@ +package esLogUtil + +import ( + "bytes" + "encoding/json" + "sync" + "time" + + "github.com/elastic/go-elasticsearch/v8/esutil" + "golang.org/x/net/context" + "goutil/logUtil" +) + +var ( + indexMutex sync.Mutex + indexer esutil.BulkIndexer +) + +func timedReindex() { + go func() { + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + + timedReindex() + time.Sleep(time.Second * 1) + } + }() + + currentIndexName := getIndexName() + for { + //设置休眠 + time.Sleep(time.Second * 1) + + newIndexName := getIndexName() + if currentIndexName != getIndexName() { + newIndexer(newIndexName) + currentIndexName = newIndexName + } + } + }() +} + +func newIndexer(newIndexName string) { + if indexer != nil { + closeIndex() + } + + //wait until get the lock + indexMutex.Lock() + defer indexMutex.Unlock() + + var err error + indexer, err = esutil.NewBulkIndexer(esutil.BulkIndexerConfig{ + Index: getIndexName(), // The default index name + Client: esClient, // The Elasticsearch client + FlushInterval: time.Second, // The periodic flush interval + }) + if err != nil { + logUtil.ErrorLog("[%s]: Creating the indexer err: %s", serverModuleName, err) + } +} + +func closeIndex() { + //wait until get the lock + indexMutex.Lock() + defer indexMutex.Unlock() + + if indexer == nil { + return + } + + err := indexer.Close(context.Background()) + if err != nil { + logUtil.ErrorLog("[%s]:Close err:%s", serverModuleName, err.Error()) + } + + indexer = nil +} + +// 批量保存到在线日志系统 +// 参数: +// +// 数量 +// +// 返回值: +// +// 日志列表对象 +func bulkSendHandler(logObj EsLog) { + if esClient == nil || indexer == nil { + return + } + + //try to get the lock in 10000 milliseconds,if cant obtain it,return false + indexMutex.Lock() + defer indexMutex.Unlock() + + message, err := json.Marshal(logObj) + if err != nil { + logUtil.ErrorLog("[%s]: Marshal failed. Err:%s", serverModuleName, err) + return + } + + err = indexer.Add( + context.Background(), + esutil.BulkIndexerItem{ + Action: "index", + Body: bytes.NewReader(message), + }) + if err != nil { + logUtil.ErrorLog("[%s]: Add data err:%s", serverModuleName, err.Error()) + } +} diff --git a/trunk/goutil/esLogUtil/esLogUtil.go b/trunk/goutil/esLogUtil/esLogUtil.go new file mode 100644 index 0000000..5de8b5a --- /dev/null +++ b/trunk/goutil/esLogUtil/esLogUtil.go @@ -0,0 +1,269 @@ +package esLogUtil + +import ( + "fmt" + "sync" + "time" + + "github.com/elastic/go-elasticsearch/v8" + "goutil/logUtil" + "goutil/stringUtil" +) + +var ( + serverModuleName = "esLog" + esClient *elasticsearch.Client + indexName string //Index名 + strServerGroupId string //区服 + isStop bool + logChan = make(chan EsLog, 2048) + warnCount = 2000 + closedChan = make(chan struct{}) + logPool = sync.Pool{ + New: func() interface{} { + return &EsLog{} + }, + } +) + +// 启动ES日志系统 +// 参数: +// +// esUrls:ES地址(多个地址使用,分割) +// name:IndexName +// serverGroupId:服务器组Id +// +// 返回值: +// +// 结果状态 +func Start(esUrls string, name string, serverGroupId int32) { + if stringUtil.IsEmpty(esUrls) { + return + } + + //构造Es客户端 + var err error + esClient, err = elasticsearch.NewClient(elasticsearch.Config{ + Addresses: stringUtil.Split(esUrls, []string{","}), + // Retry on 429 TooManyRequests statuses + // + RetryOnStatus: []int{502, 503, 504, 429}, + + // A simple incremental backoff function + // + RetryBackoff: func(i int) time.Duration { return time.Duration(i) * 100 * time.Millisecond }, + + // Retry up to 5 attempts + // + MaxRetries: 5, + }) + if err != nil { + panic(fmt.Sprintf("构造es对象出错,err:%s", err.Error())) + } + + indexName = name + strServerGroupId = fmt.Sprintf("%d", serverGroupId) + + //初始化ES + newIndexer(getIndexName()) + + timedReindex() + + startSendProcessor() + + guardProcessor() + return +} + +// 停止服务 +func Stop() { + //停止接受日志 + isStop = true + + if indexer == nil { + return + } + + if len(logChan) == 0 { + close(logChan) + } + + <-closedChan +} + +//#region 内部方法 + +func guardProcessor() { + go func() { + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + + time.Sleep(1 * time.Second) + guardProcessor() + } + }() + + for { + time.Sleep(5 * time.Second) + + count := len(logChan) + if count < warnCount { + continue + } + + logUtil.NormalLog(fmt.Sprintf("ES日志通道中当前有%d条消息待消费。", count), logUtil.Warn) + } + }() +} + +func startSendProcessor() { + go func() { + defer func() { + if r := recover(); r != nil { + logUtil.LogUnknownError(r) + + time.Sleep(1 * time.Second) + startSendProcessor() + } + }() + + for { + select { + case logObj, ok := <-logChan: + if ok { + bulkSendHandler(logObj) // 执行刷新 + } + + if len(logChan) == 0 && isStop { + // is closed + closeIndex() + + closedChan <- struct{}{} + return + } + } + } + }() +} + +func getIndexName() string { + //获取当天日期 + return fmt.Sprintf("%s_%s", indexName, time.Now().Format("20060102")) +} + +// 写入在线日志 +// 参数: +// +// 日志信息对象 +// +// 返回值: +// +// 无 +func writeLog(logObj *EsLog) { + if isStop || indexer == nil { + return + } + + logChan <- *logObj +} + +// 组装ES日志对象 +// 参数: +// +// logType 日志类型 +// format 日志格式 +// args 参数列表 +// +// 返回值: +// +// 结果状态 +func buildLog(logType, format string, args ...interface{}) (newLogObj *EsLog) { + msg := format + if len(args) > 0 { + msg = fmt.Sprintf(format, args...) + } + + //构造新的日志对象 + newLogObj = new(logType, msg, strServerGroupId) + return +} + +//#endregion + +//#region 外部方法 + +// 日志记录 +// +// format:日志格式 +// logType:日志类型 +// args:参数列表 +// +// 返回值 +// +// 无 +func NormalLog(format string, logType logUtil.LogType, args ...interface{}) { + writeLog(buildLog(logType.String(), format, args...)) +} + +// 消息日志记录 +// +// format:日志格式 +// args:参数列表 +// +// 返回值 +// +// 无 +func InfoLog(format string, args ...interface{}) { + writeLog(buildLog("Info", format, args...)) +} + +// 警告日志记录 +// +// format:日志格式 +// args:参数列表 +// +// 返回值 +// +// 无 +func WarnLog(format string, args ...interface{}) { + writeLog(buildLog("Warn", format, args...)) +} + +// 调试日志记录 +// +// format:日志格式 +// args:参数列表 +// +// 返回值 +// +// 无 +func DebugLog(format string, args ...interface{}) { + writeLog(buildLog("Debug", format, args...)) +} + +// 错误日志记录 +// +// format:日志格式 +// args:参数列表 +// +// 返回值 +// +// 无 +func ErrorLog(format string, args ...interface{}) { + writeLog(buildLog("Error", format, args...)) +} + +// 致命错误日志记录 +// +// format:日志格式 +// args:参数列表 +// +// 返回值 +// +// 无 +func FatalLog(format string, args ...interface{}) { + writeLog(buildLog("Fatal", format, args...)) +} + +//#endregion diff --git a/trunk/goutil/esLogUtil/esLogUtil_test.go b/trunk/goutil/esLogUtil/esLogUtil_test.go new file mode 100644 index 0000000..c119522 --- /dev/null +++ b/trunk/goutil/esLogUtil/esLogUtil_test.go @@ -0,0 +1,35 @@ +package esLogUtil + +import ( + "testing" + "time" +) + +func TestWrite(t *testing.T) { + Start("http://10.1.0.86:9200", "dzg_gs_log_gmc2", 20008) + for i := 0; i < 10000; i++ { + InfoLog("ES在线日志测试") + WarnLog("ES在线日志测试") + DebugLog("ES在线日志测试") + ErrorLog("ES在线日志测试") + FatalLog("ES在线日志测试") + } + Stop() +} + +func BenchmarkWrite(b *testing.B) { + Start("http://106.52.100.147:14001", "20008_gs_log", 20008) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + InfoLog("ES在线日志测试%d", i) + WarnLog("ES在线日志测试%d", i) + DebugLog("ES在线日志测试%d", i) + ErrorLog("ES在线日志测试%d", i) + FatalLog("ES在线日志测试%d", i) + } + b.StopTimer() + + time.Sleep(30 * time.Second) + Stop() +} diff --git a/trunk/goutil/esLogUtil/model.go b/trunk/goutil/esLogUtil/model.go new file mode 100644 index 0000000..4aa5542 --- /dev/null +++ b/trunk/goutil/esLogUtil/model.go @@ -0,0 +1,39 @@ +package esLogUtil + +import ( + "time" +) + +// 日志对象 +type EsLog struct { + // 日志类型 + LogType string + + // 日志消息 + Message string + + // 程序标识 + InnerId string + + // 日志时间 + CrTime time.Time +} + +func new(logType string, msg string, innerId string) *EsLog { + logObj := logPool.Get().(*EsLog) + + logObj.CrTime = time.Now() + logObj.LogType = logType + logObj.Message = msg + logObj.InnerId = innerId + + return logObj + + // + //return EsLog{ + // LogType: logType, + // Message: msg, + // InnerId: innerId, + // CrTime: time.Now(), + //} +} diff --git a/trunk/goutil/fileUtil/bigFile.go b/trunk/goutil/fileUtil/bigFile.go new file mode 100644 index 0000000..0afc5f8 --- /dev/null +++ b/trunk/goutil/fileUtil/bigFile.go @@ -0,0 +1,200 @@ +package fileUtil + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "goutil/timeUtil" +) + +// 大文件对象,可用于连续写入内容而不关闭文件,直到达到指定的大小 +type BigFile struct { + // 文件夹名称 + path string + + // 当前文件名称 + fileName string + + // 文件名称前缀 + fileNamePrefix string + + // 当前文件大小(单位:Byte) + fileSize int + + // 最大的文件大小(单位:Byte) + maxFileSize int + + // 文件对象 + file *os.File + + // 获得新文件名称的方法 + newFileNameFunc func(string, string) string +} + +// 获取文件的完整路径 +func (this *BigFile) getFullPath() string { + return filepath.Join(this.path, this.fileName) +} + +// 初始化文件对象 +func (this *BigFile) initFile() error { + // 初始化文件名称 + this.fileName = this.newFileNameFunc(this.fileNamePrefix, this.fileName) + + // 初始化文件大小 + this.fileSize = 0 + + // 打开文件 + file, err := os.OpenFile(this.getFullPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm|os.ModeTemporary) + if err != nil { + return fmt.Errorf("打开文件%s错误,错误信息为:%s", this.getFullPath(), err) + } else { + this.file = file + } + + return nil +} + +// 返回当前文件名称 +// 返回值: +// 当前文件名称 +func (this *BigFile) FileName() string { + return this.fileName +} + +// 保存消息 +// message:消息内容 +// 返回值:错误对象 +func (this *BigFile) SaveMessage(message string) error { + if this.file == nil { + return fmt.Errorf("文件对象为空,path:%s", this.getFullPath()) + } + + // 增加文件大小 + this.fileSize += len([]byte(message)) + + // 写入消息(在结尾处增加一个换行符\n) + message = fmt.Sprintf("%s\n", message) + if _, err := this.file.WriteString(message); err != nil { + return fmt.Errorf("向文件%s写入信息错误,错误信息为:%s", this.getFullPath(), err) + } + + // 如果达到了文件的上限,则关闭文件并重新打开一个新文件 + if this.fileSize >= this.maxFileSize { + this.Close() + this.initFile() + } + + return nil +} + +// 写入字节消息 +// message:消息内容 +// 返回值:错误对象 +func (this *BigFile) WriteMessage(message []byte) error { + if this.file == nil { + return fmt.Errorf("文件对象为空,path:%s", this.getFullPath()) + } + + // 增加文件大小 + this.fileSize += len(message) + + // 写入消息 + if _, err := this.file.Write(message); err != nil { + return fmt.Errorf("向文件%s写入信息错误,错误信息为:%s", this.getFullPath(), err) + } + + // 如果达到了文件的上限,则关闭文件并重新打开一个新文件 + if this.fileSize >= this.maxFileSize { + this.Close() + this.initFile() + } + + return nil +} + +// 关闭对象 +// 返回值:无 +func (this *BigFile) Close() { + if this.file != nil { + this.file.Close() + this.file = nil + } +} + +// 创建新的大文件对象(obsolete) +// _path:文件夹路径 +// _maxFileSize:单个文件大小的最大值(单位:Byte) +// 返回值: +// 大文件对象 +// 错误对象 +func NewBigFile(_path string, _maxFileSize int) (*BigFile, error) { + return NewBigFileWithNewFileNameFunc(_path, "default", _maxFileSize, newFileName) +} + +// 创建新的大文件对象 +// _path:文件夹路径 +// _fileNamePrefix:文件名称前缀 +// _maxFileSize:单个文件大小的最大值(单位:Byte) +// 返回值: +// 大文件对象 +// 错误对象 +func NewBigFile2(_path, _fileNamePrefix string, _maxFileSize int) (*BigFile, error) { + return NewBigFileWithNewFileNameFunc(_path, _fileNamePrefix, _maxFileSize, newFileName) +} + +// 创建新的大文件对象 +// _path:文件夹路径 +// _fileNamePrefix:文件名称前缀 +// _maxFileSize:单个文件大小的最大值(单位:Byte) +// _newFileNameFunc:创建新文件名称的方法 +// 返回值: +// 大文件对象 +// 错误对象 +func NewBigFileWithNewFileNameFunc(_path, _fileNamePrefix string, _maxFileSize int, _newFileNameFunc func(string, string) string) (*BigFile, error) { + return NewBigFileWithNewFileNameFunc2(_path, _fileNamePrefix, "default", _maxFileSize, _newFileNameFunc) +} + +// 创建新的大文件对象 +// _path:文件夹路径 +// _fileNamePrefix:文件名称前缀 +// _fileName:文件名称 +// _maxFileSize:单个文件大小的最大值(单位:Byte) +// _newFileNameFunc:创建新文件名称的方法 +// 返回值: +// 大文件对象 +// 错误对象 +func NewBigFileWithNewFileNameFunc2(_path, _fileNamePrefix, _fileName string, _maxFileSize int, _newFileNameFunc func(string, string) string) (*BigFile, error) { + // 判断文件夹是否存在,如果不存在则创建 + if !IsDirExists(_path) { + os.MkdirAll(_path, os.ModePerm|os.ModeTemporary) + } + + // 初始化对象 + obj := &BigFile{ + path: _path, + fileNamePrefix: _fileNamePrefix, + fileName: _fileName, + maxFileSize: _maxFileSize, + newFileNameFunc: _newFileNameFunc, + } + + // 初始化文件对象 + if err := obj.initFile(); err != nil { + obj.Close() + return nil, err + } + + return obj, nil +} + +// 创建新的文件名称 +// prefix:前缀 +// currFileName:当前文件名称 +// 返回值: +// 新的文件名称 +func newFileName(prefix, currFileName string) string { + return fmt.Sprintf("%s_%s.data", prefix, timeUtil.Format(time.Now(), "yyyyMMddHHmmss")) +} diff --git a/trunk/goutil/fileUtil/bigFile_test.go b/trunk/goutil/fileUtil/bigFile_test.go new file mode 100644 index 0000000..806e514 --- /dev/null +++ b/trunk/goutil/fileUtil/bigFile_test.go @@ -0,0 +1,37 @@ +package fileUtil + +import ( + "fmt" + "testing" +) + +func BenchmarkSaveMessage(b *testing.B) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + bigFileObj, err := NewBigFile(path, 1024*1024*1024) + if err != nil { + b.Errorf("there should no err, but not there is:%s", err) + } + + for i := 0; i < b.N; i++ { + bigFileObj.SaveMessage(fmt.Sprintf("line %d", i)) + } +} + +func TestSaveMessage(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + bigFileObj, err := NewBigFile(path, 1024) + if err != nil { + t.Errorf("there should no err, but not there is:%s", err) + } + + for i := 0; i < 100000; i++ { + bigFileObj.SaveMessage(fmt.Sprintf("line %d", i)) + } + + fileList, err := GetFileList(path) + for _, item := range fileList { + fmt.Printf("file:%s\n", item) + } +} diff --git a/trunk/goutil/fileUtil/doc.go b/trunk/goutil/fileUtil/doc.go new file mode 100644 index 0000000..f539c3f --- /dev/null +++ b/trunk/goutil/fileUtil/doc.go @@ -0,0 +1,4 @@ +/* +文件助手类 +*/ +package fileUtil diff --git a/trunk/goutil/fileUtil/file.go b/trunk/goutil/fileUtil/file.go new file mode 100644 index 0000000..b371b52 --- /dev/null +++ b/trunk/goutil/fileUtil/file.go @@ -0,0 +1,298 @@ +package fileUtil + +import ( + "bufio" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" +) + +var ( + mutex sync.Mutex +) + +// 文件是否存在 +// 文件路径 +// 返回值: +// 是否存在 +// 错误对象 +func IsFileExists(path string) (bool, error) { + file, err := os.Stat(path) + if err == nil { + return file.IsDir() == false, nil + } else { + if os.IsNotExist(err) { + return false, nil + } + } + + return true, err +} + +// 文件夹是否存在 +// 文件夹路径 +// 返回值: +// 是否存在 +// 错误对象 +func IsDirectoryExists(path string) (bool, error) { + file, err := os.Stat(path) + if err == nil { + return file.IsDir(), nil + } else { + if os.IsNotExist(err) { + return false, nil + } + } + + return true, err +} + +// 文件夹是否存在(obsolete) +// 文件夹路径 +// 返回值: +// 是否存在 +func IsDirExists(path string) bool { + file, err := os.Stat(path) + if err != nil { + return false + } else { + return file.IsDir() + } +} + +// 获取当前路径 +// 返回值: +// 当前路径 +func GetCurrentPath() string { + file, _ := exec.LookPath(os.Args[0]) + fileAbsPath, _ := filepath.Abs(file) + + return filepath.Dir(fileAbsPath) +} + +// 获取目标文件列表(完整路径) +// path:文件夹路径 +// 返回值:文件列表(完整路径) +func GetFileList(path string) (fileList []string, err error) { + if exists, err1 := IsDirectoryExists(path); err1 != nil { + err = err1 + return + } else if !exists { + return + } + + // 遍历目录,获取所有文件列表 + err = filepath.Walk(path, func(fileName string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + // 忽略目录 + if fi.IsDir() { + return nil + } + + // 添加到列表 + fileList = append(fileList, fileName) + + return nil + }) + + return +} + +// 获取目标文件列表(完整路径) +// path:文件夹路径 +// prefix:文件前缀 +// suffix:文件后缀 +// 返回值:文件列表(完整路径) +func GetFileList2(path, prefix, suffix string) (fileList []string, err error) { + if exists, err1 := IsDirectoryExists(path); err1 != nil { + err = err1 + return + } else if !exists { + return + } + + // 遍历目录,获取所有文件列表 + err = filepath.Walk(path, func(fileName string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + // 忽略目录 + if fi.IsDir() { + return nil + } + + // 添加到列表 + baseName := filepath.Base(fileName) + if prefix != "" && strings.HasPrefix(baseName, prefix) == false { + return nil + } + + if suffix != "" && strings.HasSuffix(baseName, suffix) == false { + return nil + } + + fileList = append(fileList, fileName) + + return nil + }) + + return +} + +// 按行读取每一个文件的内容 +// fileName:文件的绝对路径 +// 返回值: +// 行内容列表 +// 错误信息 +func ReadFileLineByLine(fileName string) (lineList []string, err error) { + // 打开文件 + file, err1 := os.Open(fileName) + if err1 != nil { + err = err1 + return + } + defer file.Close() + + // 读取文件 + buf := bufio.NewReader(file) + for { + // 按行读取 + line, _, err2 := buf.ReadLine() + if err2 == io.EOF { + break + } + + //将byte[]转换为string,并添加到列表中 + lineList = append(lineList, string(line)) + } + + return +} + +// 读取文件内容(字符串) +// fileName:文件的绝对路径 +// 返回值: +// 文件内容 +// 错误信息 +func ReadFileContent(fileName string) (content string, err error) { + bytes, err1 := ioutil.ReadFile(fileName) + if err1 != nil { + err = err1 + return + } + + content = string(bytes) + return +} + +// 读取文件内容(字符数组) +// fileName:文件的绝对路径 +// 返回值: +// 文件内容 +// 错误信息 +func ReadFileBytes(fileName string) (content []byte, err error) { + content, err = ioutil.ReadFile(fileName) + return +} + +// 写入文件 +// filePath:文件夹路径 +// fileName:文件名称 +// ifAppend:是否追加内容 +// args:可变参数 +// 返回值: +// error:错误信息 +func WriteFile(filePath, fileName string, ifAppend bool, args ...string) error { + // 得到最终的fileName + fileName = filepath.Join(filePath, fileName) + + // 判断文件夹是否存在,如果不存在则创建 + mutex.Lock() + if !IsDirExists(filePath) { + os.MkdirAll(filePath, os.ModePerm|os.ModeTemporary) + } + mutex.Unlock() + + // 打开文件(如果文件存在就以写模式打开,并追加写入;如果文件不存在就创建,然后以写模式打开。) + var f *os.File + var err error + if ifAppend == false { + f, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm|os.ModeTemporary) + } else { + f, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm|os.ModeTemporary) + } + + if err != nil { + return err + } + defer f.Close() + + // 写入内容 + for _, arg := range args { + _, err = f.WriteString(arg) + if err != nil { + return err + } + } + + return nil +} + +// 写入文件 +// filePath:文件夹路径 +// fileName:文件名称 +// ifAppend:是否追加内容 +// args:可变参数 +// 返回值: +// error:错误信息 +func WriteFile4Byte(filePath, fileName string, ifAppend bool, args ...[]byte) error { + // 得到最终的fileName + fileName = filepath.Join(filePath, fileName) + + // 判断文件夹是否存在,如果不存在则创建 + mutex.Lock() + if !IsDirExists(filePath) { + os.MkdirAll(filePath, os.ModePerm|os.ModeTemporary) + } + mutex.Unlock() + + // 打开文件(如果文件存在就以写模式打开,并追加写入;如果文件不存在就创建,然后以写模式打开。) + var f *os.File + var err error + if ifAppend == false { + f, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm|os.ModeTemporary) + } else { + f, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm|os.ModeTemporary) + } + + if err != nil { + return err + } + defer f.Close() + + // 写入内容 + for _, arg := range args { + _, err = f.Write(arg) + if err != nil { + return err + } + } + + return nil +} + +// 删除文件 +// fileName:文件的绝对路径 +// 返回值: +// 错误对象 +func DeleteFile(fileName string) error { + return os.Remove(fileName) +} diff --git a/trunk/goutil/fileUtil/file_test.go b/trunk/goutil/fileUtil/file_test.go new file mode 100644 index 0000000..97f0d81 --- /dev/null +++ b/trunk/goutil/fileUtil/file_test.go @@ -0,0 +1,345 @@ +package fileUtil + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "testing" + "time" +) + +func BenchmarkWriteFile(b *testing.B) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + + for i := 0; i < b.N; i++ { + WriteFile(path, "test.txt", true, fmt.Sprintf("line %d", i)) + } +} + +func TestIsFileExists(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + fileName := fmt.Sprintf("%s/%s", path, "test.txt") + fmt.Printf("FileName:%s\n", fileName) + if exists, err := IsFileExists(fileName); err != nil || exists { + t.Errorf("the file %s should not be exists, but now it's exists", fileName) + } + + if err := WriteFile(path, "test.txt", true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + + if exists, err := IsFileExists(fileName); err != nil || !exists { + t.Errorf("the file %s should be exists, but now it's not exists", fileName) + } + + if content, err := ReadFileContent(fileName); err != nil { + t.Errorf("there should be no error, but now err:%s", err) + } else { + fmt.Printf("Content:%s\n", content) + } + + DeleteFile(fileName) +} + +func TestIsDirectoryExists(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + filePath := filepath.Join(path, "Parent") + if exists, err := IsDirectoryExists(filePath); err != nil || exists { + t.Errorf("the file %s should not be exists, but now it's exists", filePath) + } + + fileName := fmt.Sprintf("%s/%s", filePath, "test.txt") + + if err := WriteFile(filePath, "test.txt", true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + + if exists, err := IsDirectoryExists(filePath); err != nil || !exists { + t.Errorf("the file %s should be exists, but now it's not exists", filePath) + } + + if content, err := ReadFileContent(fileName); err != nil { + t.Errorf("there should be no error, but now err:%s", err) + } else { + fmt.Printf("Content:%s\n", content) + } + + DeleteFile(fileName) +} + +func TestIsDirExists(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + filePath := filepath.Join(path, "Parent2") + if IsDirExists(filePath) { + t.Errorf("the file %s should not be exists, but now it's exists", filePath) + } + + fileName := fmt.Sprintf("%s/%s", filePath, "test.txt") + if err := WriteFile(filePath, "test.txt", true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + + if IsDirExists(filePath) == false { + t.Errorf("the file %s should be exists, but now it's not exists", filePath) + } + + if content, err := ReadFileContent(fmt.Sprintf("%s/%s", filePath, "test.txt")); err != nil { + t.Errorf("there should be no error, but now err:%s", err) + } else { + fmt.Printf("Content:%s\n", content) + } + + DeleteFile(fileName) +} + +func TestGetFileList(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + + fileName1 := "2017-09-12-12.txt" + fileName2 := "2017-09-12-13.txt" + fileName3 := "2017-09-12-14.txt" + fileName4 := "2017-09-12.tar.bz2" + + seperator := "\\" + if runtime.GOOS != "windows" { + seperator = "/" + } + + filePath1 := fmt.Sprintf("%s%s%s", path, seperator, fileName1) + filePath2 := fmt.Sprintf("%s%s%s", path, seperator, fileName2) + filePath3 := fmt.Sprintf("%s%s%s", path, seperator, fileName3) + filePath4 := fmt.Sprintf("%s%s%s", path, seperator, fileName4) + + if err := WriteFile(path, fileName1, true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if err := WriteFile(path, fileName2, true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if err := WriteFile(path, fileName3, true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if err := WriteFile(path, fileName4, true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + + fileList, err := GetFileList(path) + if err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if fileList[0] != filePath1 { + t.Errorf("Expected:%s, now got:%s", filePath1, fileList[0]) + } + if fileList[1] != filePath2 { + t.Errorf("Expected:%s, now got:%s", filePath2, fileList[1]) + } + if fileList[2] != filePath3 { + t.Errorf("Expected:%s, now got:%s", filePath3, fileList[2]) + } + if fileList[3] != filePath4 { + t.Errorf("Expected:%s, now got:%s", filePath4, fileList[3]) + } + + DeleteFile(filePath1) + DeleteFile(filePath2) + DeleteFile(filePath3) + DeleteFile(filePath4) +} + +func TestGetFileList2(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + + fileName1 := "2017-09-12-12.txt" + fileName2 := "2017-09-12-13.txt" + fileName3 := "2017-09-12-14.txt" + fileName4 := "2017-09-12.tar.bz2" + + seperator := "\\" + if runtime.GOOS != "windows" { + seperator = "/" + } + + filePath1 := fmt.Sprintf("%s%s%s", path, seperator, fileName1) + filePath2 := fmt.Sprintf("%s%s%s", path, seperator, fileName2) + filePath3 := fmt.Sprintf("%s%s%s", path, seperator, fileName3) + filePath4 := fmt.Sprintf("%s%s%s", path, seperator, fileName4) + + if err := WriteFile(path, fileName1, true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if err := WriteFile(path, fileName2, true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if err := WriteFile(path, fileName3, true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if err := WriteFile(path, fileName4, true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + + fileList, err := GetFileList2(path, "2017-09-12", "txt") + if err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + fmt.Printf("fileList:%v\n", fileList) + if fileList[0] != filePath1 { + t.Errorf("Expected:%s, now got:%s", filePath1, fileList[0]) + } + if fileList[1] != filePath2 { + t.Errorf("Expected:%s, now got:%s", filePath2, fileList[1]) + } + if fileList[2] != filePath3 { + t.Errorf("Expected:%s, now got:%s", filePath3, fileList[2]) + } + + fileList2, err := GetFileList2(path, "2017-09-12", "tar.bz2") + if err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + fmt.Printf("fileList2:%v\n", fileList2) + if fileList2[0] != filePath4 { + t.Errorf("Expected:%s, now got:%s", filePath4, fileList2[0]) + } + + DeleteFile(filePath1) + DeleteFile(filePath2) + DeleteFile(filePath3) + DeleteFile(filePath4) +} + +func TestReadFileLineByLine(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + + fileName := fmt.Sprintf("%s/%s", path, "test.txt") + if err := WriteFile(path, "test.txt", true, "first line\n"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if err := WriteFile(path, "test.txt", true, "second line\n"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + + expectedFirstLine := "first line" + expectedSecondLine := "second line" + lineList, err := ReadFileLineByLine(fileName) + if err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if lineList[0] != expectedFirstLine { + t.Errorf("Expected:%s, but now got:%s", expectedFirstLine, lineList[0]) + } + if lineList[1] != expectedSecondLine { + t.Errorf("Expected:%s, but now got:%s", expectedSecondLine, lineList[1]) + } + + if err := DeleteFile(fileName); err != nil { + t.Errorf("There should be no error, but now it has:%s", err) + } +} + +func TestReadFileContent(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + + fileName := fmt.Sprintf("%s/%s", path, "test.txt") + if err := WriteFile(path, "test.txt", true, "first line\n"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + if err := WriteFile(path, "test.txt", true, "second line\n"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + + expectedContent := "first line\nsecond line\n" + if content, err := ReadFileContent(fileName); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } else if content != expectedContent { + t.Errorf("Expected:%s, but now got:%s", expectedContent, content) + } + + if err := DeleteFile(fileName); err != nil { + t.Errorf("There should be no error, but now it has:%s", err) + } +} + +func TestDeleteFile(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + + fileName := fmt.Sprintf("%s/%s", path, "test.txt") + if err := WriteFile(path, "test.txt", true, "first line"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + + if err := DeleteFile(fileName); err != nil { + t.Errorf("There should be no error, but now it has:%s", err) + } +} + +func TestReadWriteSimultaneously(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + + fileName := fmt.Sprintf("%s/%s", path, "test.txt") + + file1, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm|os.ModeTemporary) + if err != nil { + t.Errorf("1:there should be no err, but now err:%s", err) + } + + // for i := 0; i < 10; i++ { + // file1.WriteString(fmt.Sprintf("line %d\n", i)) + // } + + go func() { + for i := 0; i < 10; i++ { + file1.WriteString(fmt.Sprintf("line %d\n", i)) + time.Sleep(time.Second) + } + }() + + file2, err := os.OpenFile(fileName, os.O_RDONLY, os.ModePerm|os.ModeTemporary) + if err != nil { + t.Errorf("2:there should be no err, but now err:%s", err) + } + + go func() { + offset := 0 + + // 读取文件 + buf := bufio.NewReader(file2) + + for { + // 按行读取 + line, _, err2 := buf.ReadLine() + if err2 == io.EOF { + time.Sleep(500 * time.Millisecond) + continue + } + + if len(line) == 0 { + continue + } + + //将byte[]转换为string,并添加到列表中 + fmt.Printf("line %d:%s\n", offset, string(line)) + + offset += 1 + if offset >= 10 { + break + } + } + }() + + time.Sleep(30 * time.Second) + + fmt.Println("end") +} diff --git a/trunk/goutil/fileUtil/gzip.go b/trunk/goutil/fileUtil/gzip.go new file mode 100644 index 0000000..3cb94b4 --- /dev/null +++ b/trunk/goutil/fileUtil/gzip.go @@ -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 +} diff --git a/trunk/goutil/fileUtil/gzip_test.go b/trunk/goutil/fileUtil/gzip_test.go new file mode 100644 index 0000000..5b6a313 --- /dev/null +++ b/trunk/goutil/fileUtil/gzip_test.go @@ -0,0 +1,54 @@ +package fileUtil + +import ( + "fmt" + "testing" +) + +func TestGzip(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + + fileName := fmt.Sprintf("%s/%s", path, "test.txt") + if err := WriteFile(path, "test.txt", true, "first line\nHello world"); err != nil { + t.Errorf("there should be no error, but now it is:%s", err) + } + + if err := Gzip(fileName, ""); err != nil { + // if err := Gzip(fileName, path); err != nil { + t.Errorf("There should be no error, but now it has:%s", err) + } + + if fileList, err := GetFileList(path); err != nil { + t.Errorf("There should be no error, but now it has:%s", err) + } else { + for _, item := range fileList { + fmt.Printf("item:%s\n", item) + } + } + + DeleteFile(fileName) +} + +func TestUnGzip(t *testing.T) { + path := GetCurrentPath() + fmt.Printf("CurrPath:%s\n", path) + + fileName := fmt.Sprintf("%s/%s", path, "test.txt.gz") + if err := UnGzip(fileName, ""); err != nil { + // if err := UnGzip(fileName, path); err != nil { + t.Errorf("There should be no error, but now it has:%s", err) + } + + content, err := ReadFileContent(fmt.Sprintf("%s/%s", path, "test.txt")) + if err != nil { + t.Errorf("There should be no error, but now it has:%s", err) + } else { + fmt.Printf("content:%s\n", content) + } + + DeleteFile(fileName) + + fileName = fmt.Sprintf("%s/%s", path, "test.txt") + DeleteFile(fileName) +} diff --git a/trunk/goutil/fileUtil/netFile.go b/trunk/goutil/fileUtil/netFile.go new file mode 100644 index 0000000..270ad06 --- /dev/null +++ b/trunk/goutil/fileUtil/netFile.go @@ -0,0 +1,55 @@ +package fileUtil + +import ( + "io" + "net/http" + "os" + "path" +) + +// 下载网络文件 +// netUrl:网络文件地址 +// saveDir:存储位置 +// saveFileName:存储的文件名 +// ifTruncate:如果文件存在了,是否覆盖此文件 +// 返回值: +// err:错误对象 +func DownLoadNetFile(netUrl string, saveDir string, saveFileName string, ifTruncate bool) (err error) { + resp, err := http.Get(netUrl) + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + if err != nil { + return + } + + // 创建文件夹 + if IsDirExists(saveDir) == false { + os.MkdirAll(saveDir, os.ModePerm|os.ModeTemporary) + } + + // 创建文件 + filePath := path.Join(saveDir, saveFileName) + var fileObj *os.File + if ifTruncate { + fileObj, err = os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm|os.ModeTemporary) + } else { + // 如果文件已经存在,则不能打开 + fileObj, err = os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, os.ModePerm|os.ModeTemporary) + } + defer func() { + if fileObj != nil { + fileObj.Close() + } + }() + if err != nil { + return + } + + // 写入文件数据 + _, err = io.Copy(fileObj, resp.Body) + + return +} diff --git a/trunk/goutil/fileUtil/netFile_test.go b/trunk/goutil/fileUtil/netFile_test.go new file mode 100644 index 0000000..1bfd4da --- /dev/null +++ b/trunk/goutil/fileUtil/netFile_test.go @@ -0,0 +1,13 @@ +package fileUtil + +import "testing" + +func TestDownLoadNetFile(t *testing.T) { + err := DownLoadNetFile("https://www.baidu.com/img/bd_logo1.png", "./", "baidu.png", false) + if err != nil { + t.Error(err) + return + } + + t.Log("成功了") +} diff --git a/trunk/goutil/fileUtil/tar.go b/trunk/goutil/fileUtil/tar.go new file mode 100644 index 0000000..e707517 --- /dev/null +++ b/trunk/goutil/fileUtil/tar.go @@ -0,0 +1,104 @@ +package fileUtil + +import ( + "archive/tar" + "io" + "os" + "path/filepath" +) + +// 对一组文件进行tar打包 +// sourceList:源文件完整路径列表 +// target:目标文件名称 +// 返回值 +// 错误对象 +func Tar(sourceList []string, target string) error { + tarFile, err := os.Create(target) + if err != nil { + return err + } + defer tarFile.Close() + + tarball := tar.NewWriter(tarFile) + defer tarball.Close() + + // 对源文件遍历处理 + for _, item := range sourceList { + info, err := os.Stat(item) + if err != nil || info.IsDir() { + continue + } + + header, err := tar.FileInfoHeader(info, info.Name()) + if err != nil { + return err + } + + header.Name = filepath.Base(item) + + if err := tarball.WriteHeader(header); err != nil { + return err + } + + file, err := os.Open(item) + if err != nil { + return err + } + defer file.Close() + + if _, err = io.Copy(tarball, file); err != nil { + return err + } + } + + return nil +} + +// 对一组文件进行tar解包 +// source:源文件完整路径 +// target:目标文件名称 +// 返回值 +// 错误对象 +func Untar(source, target string) error { + reader, err := os.Open(source) + if err != nil { + return err + } + defer reader.Close() + tarReader := tar.NewReader(reader) + + // 给目标文件夹赋值,如果传空,则默认为当前文件夹 + if target == "" { + target = filepath.Dir(source) + } + + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } else if err != nil { + return err + } + + targetFilePath := filepath.Join(target, header.Name) + info := header.FileInfo() + if info.IsDir() { + if err = os.MkdirAll(targetFilePath, info.Mode()); err != nil { + return err + } + continue + } + + file, err := os.OpenFile(targetFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode()) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(file, tarReader) + if err != nil { + return err + } + } + + return nil +} diff --git a/trunk/goutil/fileUtil/tar_test.go b/trunk/goutil/fileUtil/tar_test.go new file mode 100644 index 0000000..aa66436 --- /dev/null +++ b/trunk/goutil/fileUtil/tar_test.go @@ -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) + } +} diff --git a/trunk/goutil/go.mod b/trunk/goutil/go.mod new file mode 100644 index 0000000..881f141 --- /dev/null +++ b/trunk/goutil/go.mod @@ -0,0 +1,25 @@ +module goutil + +go 1.22.10 + +require ( + github.com/bkaradzic/go-lz4 v1.0.0 + github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 + github.com/fatih/color v1.15.0 + github.com/go-sql-driver/mysql v1.5.0 + github.com/gomodule/redigo v1.8.9 + github.com/gorilla/websocket v1.4.2 + golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 + google.golang.org/grpc v1.45.0 + google.golang.org/protobuf v1.26.0 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c +) + +require ( + github.com/golang/protobuf v1.5.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect +) diff --git a/trunk/goutil/go.sum b/trunk/goutil/go.sum new file mode 100644 index 0000000..a633c1a --- /dev/null +++ b/trunk/goutil/go.sum @@ -0,0 +1,147 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= +github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 h1:OoL469zqSNrTLSz5zeVF/I6VOO7fiw2bzSzQe4J557c= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4/go.mod h1:xe9a/L2aeOgFKKgrO3ibQTnMdpAeL0GC+5/HpGScSa4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= +github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/trunk/goutil/grpc-util/.gitignore b/trunk/goutil/grpc-util/.gitignore new file mode 100644 index 0000000..1a1566e --- /dev/null +++ b/trunk/goutil/grpc-util/.gitignore @@ -0,0 +1,2 @@ +Log/* +logs/* \ No newline at end of file diff --git a/trunk/goutil/grpc-util/client/client.go b/trunk/goutil/grpc-util/client/client.go new file mode 100644 index 0000000..ed64581 --- /dev/null +++ b/trunk/goutil/grpc-util/client/client.go @@ -0,0 +1,63 @@ +package client + +import ( + "context" + "sync" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +var ( + // 缓存连接 key:rpc地址 value:连接指针对象 + rpcConMap sync.Map + + // 连接超时时间 + defaultTimeOut = time.Second * 1 +) + +// GetClientConn +// @description: 获取grpc连接对象 +// parameter: +// @rpcAddres:连接地址 +// return: +// @*grpc.ClientConn:连接对象 +// @error:错误对象 +func GetClientConn(rpcAddres string) (*grpc.ClientConn, error) { + // 从缓存获取 + if con1, isok := rpcConMap.Load(rpcAddres); isok { + return con1.(*grpc.ClientConn), nil + } + + // 构造新对象 + //ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(time.Second*1)) + var ctx, _ = context.WithTimeout(context.Background(), defaultTimeOut) + con, err := grpc.DialContext(ctx, rpcAddres, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + + // 加入缓存 + rpcConMap.Store(rpcAddres, con) + + return con, nil +} + +// ClearClientConn +// @description: 根据连接地址,删除缓存的连接对象 +// parameter: +// @rpcAddres:连接地址 +// return: +func ClearClientConn(rpcAddres string) { + rpcConMap.Delete(rpcAddres) +} + +// SetDefaultTimeOut +// @description: 设置连接的默认超时 +// parameter: +// @outTime:超时时间 +// return: +func SetDefaultTimeOut(outTime time.Duration) { + defaultTimeOut = outTime +} diff --git a/trunk/goutil/grpc-util/client/doc.go b/trunk/goutil/grpc-util/client/doc.go new file mode 100644 index 0000000..84fe65e --- /dev/null +++ b/trunk/goutil/grpc-util/client/doc.go @@ -0,0 +1,8 @@ +// ************************************ +// @package: client +// @description: grpc客户端辅助包 +// @author: byron +// @revision history: +// @create date: 2022-01-19 16:50:24 +// ************************************ +package client diff --git a/trunk/goutil/grpc-util/client/readme.md b/trunk/goutil/grpc-util/client/readme.md new file mode 100644 index 0000000..cceb881 --- /dev/null +++ b/trunk/goutil/grpc-util/client/readme.md @@ -0,0 +1,29 @@ +client简单封装了grpc相关调用方法,缓存了connection连接 +整体文档入口请参考:[传送门](../readme.md) + +## 使用方式如下: + +### 1.导入包 +```go +import ( + "vast.com/goutil/grpc-util/client" +) +``` + +### 2.获取连接 +```go +con, err := client.GetClientConn(host) +``` + +### 3.清理某个连接 +```go +client.ClearClientConn("ip:port") +``` + +### 4.设置连接的默认超时时间 +```go +client.SetDefaultTimeOut(time.Second * 3) +``` +注意: +1.设置后对后续创建的连接生效,已经创建的不生效 +2.默认的超时 时间为1s \ No newline at end of file diff --git a/trunk/goutil/grpc-util/readme.md b/trunk/goutil/grpc-util/readme.md new file mode 100644 index 0000000..116144c --- /dev/null +++ b/trunk/goutil/grpc-util/readme.md @@ -0,0 +1,20 @@ +grpc-util做了如下工作: + +### server(废弃): + +1.实现服务的启动和相关方法的注册 +2.提供了公共对象和接口的实现 +3.提供公共req对象到res对象build辅助方法 +具体参考server文档 [传送门](server/readme.md) + +### client: + +1.缓存了grpc连接对象,使其可复用(可单独使用) +2.基于提供的公共对象和接口,提供了grpc的4种基本用法的调用(建议需要搭配server使用) +具体参考client文档 [传送门](client/readme.md) + +### util: + +1.提供pb相关辅助方法 +具体请直接看代码 + diff --git a/trunk/goutil/grpc-util/util/pbUtil.go b/trunk/goutil/grpc-util/util/pbUtil.go new file mode 100644 index 0000000..7de2f6d --- /dev/null +++ b/trunk/goutil/grpc-util/util/pbUtil.go @@ -0,0 +1,135 @@ +package util + +import ( + "context" + "fmt" + "net" + "strings" + + "google.golang.org/grpc/peer" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +var ( + moOpt protojson.MarshalOptions + uoOpt protojson.UnmarshalOptions +) + +func init() { + moOpt = protojson.MarshalOptions{ + // Multiline: true, + AllowPartial: true, + UseProtoNames: true, + UseEnumNumbers: true, + EmitUnpopulated: true, + } + uoOpt = protojson.UnmarshalOptions{ + DiscardUnknown: true, + } +} + +// PbCopy +// @description: 将from对象的内容copy给to对象 +// parameter: +// @from:proto.Message +// @to:proto.Message +// return: +// @error:如果失败,则返回错误,否则返回nil +func PbCopy(from, to proto.Message) error { + data, err := proto.Marshal(from) + if err != nil { + return err + } + + return proto.Unmarshal(data, to) +} + +// Pb2Json +// @description: 将pb对象转换为json字符串 +// parameter: +// @m: +// return: +// @string:pb对象的json标识形式 +// @error: +func Pb2Json(m proto.Message) (string, error) { + str, err := moOpt.Marshal(m) + if err != nil { + return "", err + } + + return string(str), nil +} + +// Json2Pb +// @description: 将json字符串转换成对应pb对象 +// parameter: +// @js: +// @m: +// return: +// @error: +func Json2Pb(js string, m proto.Message) error { + return uoOpt.Unmarshal([]byte(js), m) +} + +// Marshal +// @description: 序列化pb对象 +// parameter: +// @m: +// return: +// @[]byte: +// @error: +func Marshal(m proto.Message) ([]byte, error) { + data, err := proto.Marshal(m) + if err != nil { + return nil, err + } + + return data, nil +} + +// Marshal_Panic +// @description: 序列化pb对象 +// parameter: +// @m: +// return: +// @[]byte: +func Marshal_Panic(m proto.Message) []byte { + data, err := Marshal(m) + if err != nil { + panic(fmt.Sprintf("pbUtil Marshal pb错误,err:%s", err)) + } + + return data +} + +// Unmarshal +// @description: 将数据转换为pb对象 +// parameter: +// @data: +// @m: +// return: +func Unmarshal(data []byte, m proto.Message) error { + err := proto.Unmarshal(data, m) + return err +} + +// GetClientIP +// @description: 获取客户端Ip +// parameter: +// @ctx:grpc底层传递过来的上下文对象 +// return: +// @string:客户端ip +// @error:错误对象 +func GetClientIP(ctx context.Context) (string, error) { + pr, ok := peer.FromContext(ctx) + if !ok { + return "", fmt.Errorf("GetClietIP未获取到客户端ip") + } + if pr.Addr == net.Addr(nil) { + return "", fmt.Errorf("GetClientIP 获取到的peer.Addr=nil") + } + addSlice := strings.Split(pr.Addr.String(), ":") + + return addSlice[0], nil +} diff --git a/trunk/goutil/idUtil/idUtil_test.go b/trunk/goutil/idUtil/idUtil_test.go new file mode 100644 index 0000000..b00c481 --- /dev/null +++ b/trunk/goutil/idUtil/idUtil_test.go @@ -0,0 +1,136 @@ +package idUtil + +import ( + "testing" +) + +func TestNewTimeIdentifierSeedGenerator(t *testing.T) { + generator, err := NewTimeIdentifierSeedGenerator(40, 1, 7, 20) + if err == nil { + t.Errorf("there should be err, but now not.") + } + + generator, err = NewTimeIdentifierSeedGenerator(10, int64(1), 7, 20) + if err != nil { + t.Errorf("there should be no err, but now there is.") + } + _, err = generator.GenerateNewId() + if err == nil { + t.Errorf("there should be err, but now not.") + } + + generator, err = NewTimeIdentifierSeedGenerator(40, int64(127), 3, 20) + if err == nil { + t.Errorf("there should be err, but now not.") + } + + count := 1048575 + idMap := make(map[int64]struct{}, count) + for identifier := 0; identifier < 8; identifier++ { + generator, err = NewTimeIdentifierSeedGenerator(40, int64(identifier), 3, 20) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + for i := 0; i < count; i++ { + id, err := generator.GenerateNewId() + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + if _, exist := idMap[id]; exist { + t.Errorf("Id:%d is duplicated.", id) + return + } + + idMap[id] = struct{}{} + } + } +} + +func TestNewIdentifierTimeSeedGenerator(t *testing.T) { + generator, err := NewIdentifierTimeSeedGenerator(int64(1), 7, 40, 20) + if err == nil { + t.Errorf("there should be err, but now not.") + } + + generator, err = NewIdentifierTimeSeedGenerator(int64(1), 7, 10, 20) + if err != nil { + t.Errorf("there should be no err, but now there is.") + } + _, err = generator.GenerateNewId() + if err == nil { + t.Errorf("there should be err, but now not.") + } + + generator, err = NewIdentifierTimeSeedGenerator(int64(127), 3, 40, 20) + if err == nil { + t.Errorf("there should be err, but now not.") + } + + count := 1048575 + idMap := make(map[int64]struct{}, count) + for identifier := 0; identifier < 8; identifier++ { + generator, err = NewIdentifierTimeSeedGenerator(int64(identifier), 3, 40, 20) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + for i := 0; i < count; i++ { + id, err := generator.GenerateNewId() + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + if _, exist := idMap[id]; exist { + t.Errorf("Id:%d is duplicated.", id) + return + } + + idMap[id] = struct{}{} + } + } +} + +func TestNewIdentifierConstructCountSeedGenerator(t *testing.T) { + generator, err := NewIdentifierConstructCountSeedGenerator(int64(1), 27, int64(1), 20, 23) + if err == nil { + t.Errorf("there should be err, but now not.") + } + + generator, err = NewIdentifierConstructCountSeedGenerator(int64(32768), 15, int64(1), 18, 30) + if err == nil { + t.Errorf("there should be err, but now not.") + } + + count := 1048575 + idMap := make(map[int64]struct{}, count) + for identifier := 0; identifier < 8; identifier++ { + for constructCount := 0; constructCount < 5; constructCount++ { + generator, err = NewIdentifierConstructCountSeedGenerator(int64(identifier), 15, int64(constructCount), 18, 30) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + for i := 0; i < count; i++ { + id, err := generator.GenerateNewId() + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + if _, exist := idMap[id]; exist { + t.Errorf("Id:%d is duplicated.", id) + return + } + + idMap[id] = struct{}{} + } + } + } +} diff --git a/trunk/goutil/idUtil/identifierConstructorSeed.go b/trunk/goutil/idUtil/identifierConstructorSeed.go new file mode 100644 index 0000000..836e891 --- /dev/null +++ b/trunk/goutil/idUtil/identifierConstructorSeed.go @@ -0,0 +1,108 @@ +/* +用于生成唯一的、递增的Id。生成的规则如下: +1、生成的Id包含一个固定前缀值 +2、为了生成尽可能多的不重复数字,所以使用int64来表示一个数字,其中: +0 000000000000000 0000000000000000000000000000 00000000000000000000 +第一部分:1位,固定为0 +第二部分:共IdentifierBit位,表示固定唯一标识(机器号或者服务器Id等)。范围为[0, math.Pow(2, IdentifierBit)) +第三部分:共ConstructCountBit位,表示对象被构造的次数。范围为[0, math.Pow(2, ConstructCountBit)),以2020-1-1 00:00:00为基准 +第四部分:共SeedBit位,表示自增种子。范围为[0, math.Pow(2, SeedBit)) +3、总体而言,此规则支持总共创建math.Pow(2, ConstructCountBit)次对象,并且每次对象构造期间生成math.Pow(2, SeedBit)个不同的数字 +*/ + +/* +修改记录: +2020-03-04 14:30:00 调整了时间和唯一标识在Id中的位置,以便生成递增的Id +2020-04-20 21:10:00 同步了C#版本的逻辑 +*/ + +package idUtil + +import ( + "fmt" + "math" + "sync" +) + +type IdentifierConstructCountSeedGenerator struct { + identifier int64 // 唯一标识 + identifierBit int32 // 唯一标识(机器号或者服务器Id等)所占的位数 + identifierBitOffset int32 // 唯一标识的偏移位数 + + constructCount int64 // 对象构造的次数 + constructCountBit int32 // 对象构造的次数所占的位数 + constructCountBitOffset int32 // 时间戳对象构造的次数的偏移位数的偏移位数 + + seed int64 // 当前种子值 + seedBit int32 // 自增种子所占的位数 + seedBitOffset int32 // 自增种子的偏移位数 + maxSeed int64 // 最大的种子值 + + mutex sync.Mutex // 锁对象 +} + +func (this *IdentifierConstructCountSeedGenerator) getNewSeed() (int64, error) { + this.mutex.Lock() + defer this.mutex.Unlock() + + if this.seed >= this.maxSeed { + return 0, fmt.Errorf("Seed's value is out of scope") + } + + this.seed += 1 + + return this.seed, nil +} + +// 生成新的Id +// 返回值: +// 新的Id +// 错误对象 +func (this *IdentifierConstructCountSeedGenerator) GenerateNewId() (int64, error) { + seed, err := this.getNewSeed() + if err != nil { + return 0, err + } + id := (this.identifier << this.identifierBitOffset) | (this.constructCount << this.constructCountBitOffset) | (seed << this.seedBitOffset) + + return id, nil +} + +// 创建新的Id生成器对象(为了保证Id的唯一,需要保证生成的对象全局唯一) +// identifierBit + constructCountBit + seedBit <= 63 +// identifier:id唯一标识 +// identifierBit:id唯一标识(机器号或者服务器Id等)的位数 +// constructCount:对象构造次数 +// constructCountBit:对象构造次数的位数 +// seedBit:自增种子的位数 +// 返回值: +// 新的Id生成器对象 +// 错误对象 +func NewIdentifierConstructCountSeedGenerator(identifier int64, identifierBit int32, constructCount int64, constructCountBit, seedBit int32) (*IdentifierConstructCountSeedGenerator, error) { + // 之所以使用63位而不是64,是为了保证值为正数 + if identifierBit+constructCountBit+seedBit > 63 { + return nil, fmt.Errorf("总位数%d超过63位,请调整所有值的合理范围。", identifierBit+constructCountBit+seedBit) + } + if identifier < 0 || identifier > int64(math.Pow(2, float64(identifierBit)))-1 { + return nil, fmt.Errorf("唯一标识值溢出,有效范围为【0,%d】", int64(math.Pow(2, float64(identifierBit)))-1) + } + if constructCount < 0 || constructCount > int64(math.Pow(2, float64(constructCountBit)))-1 { + return nil, fmt.Errorf("对象构造次数的值溢出,有效范围为【0,%d】", int64(math.Pow(2, float64(constructCountBit)))-1) + } + + obj := &IdentifierConstructCountSeedGenerator{ + identifier: identifier, + identifierBit: identifierBit, + constructCount: constructCount, + constructCountBit: constructCountBit, + seed: 0, + seedBit: seedBit, + maxSeed: int64(math.Pow(2, float64(seedBit)) - 1), + } + + obj.seedBitOffset = 0 + obj.constructCountBitOffset = obj.seedBitOffset + obj.seedBit + obj.identifierBitOffset = obj.constructCountBitOffset + obj.constructCountBit + + return obj, nil +} diff --git a/trunk/goutil/idUtil/identifierTimeSeed.go b/trunk/goutil/idUtil/identifierTimeSeed.go new file mode 100644 index 0000000..6c8f571 --- /dev/null +++ b/trunk/goutil/idUtil/identifierTimeSeed.go @@ -0,0 +1,121 @@ +/* +用于生成唯一的、递增的Id。生成的规则如下: +1、生成的Id包含一个固定前缀值 +2、为了生成尽可能多的不重复数字,所以使用int64来表示一个数字,其中: +0 000000000000000 0000000000000000000000000000 00000000000000000000 +第一部分:1位,固定为0 +第二部分:共IdentifierBit位,表示固定唯一标识(机器号或者服务器Id等)。范围为[0, math.Pow(2, IdentifierBit)) +第三部分:共TimeBit位,表示当前时间距离基础时间的秒数。范围为[0, math.Pow(2, TimeBit)),以2020-1-1 00:00:00为基准 +第四部分:共SeedBit位,表示自增种子。范围为[0, math.Pow(2, SeedBit)) +3、总体而言,此规则支持每秒生成math.Pow(2, SeedBit)个不同的数字,并且在math.Pow(2, TimeBit)/60/60/24/365年的时间范围内有效 +*/ + +/* +修改记录: +2020-03-04 14:30:00 调整了时间和唯一标识在Id中的位置,以便生成递增的Id +2020-04-20 21:10:00 同步了C#版本的逻辑 +*/ + +package idUtil + +import ( + "fmt" + "math" + "sync" + "time" +) + +type IdentifierTimeSeedGenerator struct { + identifier int64 // 唯一标识 + identifierBit int32 // 唯一标识(机器号或者服务器Id等)所占的位数 + identifierBitOffset int32 // 唯一标识的偏移位数 + + timeBit int32 // 时间戳所占的位数 + timeBitOffset int32 // 时间戳的偏移位数 + startTimeStamp int64 // 起始时间戳 + endTimeStamp int64 // 结束时间戳 + + seedBit int32 // 自增种子所占的位数 + seedBitOffset int32 // 自增种子的偏移位数 + minSeed int64 // 最小的种子值 + maxSeed int64 // 最大的种子值 + currSeed int64 // 当前种子值 + + mutex sync.Mutex // 锁对象 +} + +func (this *IdentifierTimeSeedGenerator) getTimeStamp() (int64, error) { + if time.Now().Unix() > this.endTimeStamp { + return 0, fmt.Errorf("Time's value is out of scope") + } + + return time.Now().Unix() - this.startTimeStamp, nil +} + +func (this *IdentifierTimeSeedGenerator) getNewSeed() int64 { + this.mutex.Lock() + defer this.mutex.Unlock() + + if this.currSeed >= this.maxSeed { + this.currSeed = this.minSeed + } else { + this.currSeed = this.currSeed + 1 + } + + return this.currSeed +} + +// 生成新的Id +// identifier:Id的唯一标识值。取值范围必须可以用创建对象时指定的唯一标识值的位数来表示,否则会返回参数超出范围的错误 +// 返回值: +// 新的Id +// 错误对象 +func (this *IdentifierTimeSeedGenerator) GenerateNewId() (int64, error) { + timestamp, err := this.getTimeStamp() + if err != nil { + return 0, err + } + seed := this.getNewSeed() + id := (this.identifier << this.identifierBitOffset) | (timestamp << this.timeBitOffset) | (seed << this.seedBitOffset) + + return id, nil +} + +// 创建新的Id生成器对象(为了保证Id的唯一,需要保证生成的对象全局唯一) +// identifierBit + timeBit + seedBit <= 63 +// identifier:id唯一标识 +// identifierBit:id唯一标识(机器号或者服务器Id等)的位数 +// timeBit:时间的位数 +// seedBit:自增种子的位数 +// 返回值: +// 新的Id生成器对象 +// 错误对象 +func NewIdentifierTimeSeedGenerator(identifier int64, identifierBit, timeBit, seedBit int32) (*IdentifierTimeSeedGenerator, error) { + // 之所以使用63位而不是64,是为了保证值为正数 + if identifierBit+timeBit+seedBit > 63 { + return nil, fmt.Errorf("总位数%d超过63位,请调整所有值的合理范围。", identifierBit+timeBit+seedBit) + } + + if identifier < 0 || identifier > int64(math.Pow(2, float64(identifierBit)))-1 { + return nil, fmt.Errorf("唯一标识值溢出,有效范围为【0,%d】", int64(math.Pow(2, float64(identifierBit)))-1) + } + + startTimeStamp := time.Date(2019, time.January, 1, 0, 0, 0, 0, time.Local).Unix() + obj := &IdentifierTimeSeedGenerator{ + identifier: identifier, + identifierBit: identifierBit, + timeBit: timeBit, + startTimeStamp: startTimeStamp, + endTimeStamp: startTimeStamp + int64(math.Pow(2, float64(timeBit))) - 1, + seedBit: seedBit, + minSeed: 0, + maxSeed: int64(math.Pow(2, float64(seedBit))) - 1, + currSeed: 0, + } + + obj.seedBitOffset = 0 + obj.timeBitOffset = obj.seedBitOffset + obj.seedBit + obj.identifierBitOffset = obj.timeBitOffset + obj.timeBit + + return obj, nil +} diff --git a/trunk/goutil/idUtil/timeIdentifierSeed.go b/trunk/goutil/idUtil/timeIdentifierSeed.go new file mode 100644 index 0000000..885d145 --- /dev/null +++ b/trunk/goutil/idUtil/timeIdentifierSeed.go @@ -0,0 +1,121 @@ +/* +用于生成唯一的、递增的Id。生成的规则如下: +1、生成的Id包含一个固定前缀值 +2、为了生成尽可能多的不重复数字,所以使用int64来表示一个数字,其中: +0 000000000000000 0000000000000000000000000000 00000000000000000000 +第一部分:1位,固定为0 +第二部分:共TimeBit位,表示当前时间距离基础时间的秒数。范围为[0, math.Pow(2, TimeBit)),以2020-1-1 00:00:00为基准 +第三部分:共IdentifierBit位,表示固定唯一标识(机器号或者服务器Id等)。范围为[0, math.Pow(2, IdentifierBit)) +第四部分:共SeedBit位,表示自增种子。范围为[0, math.Pow(2, SeedBit)) +3、总体而言,此规则支持每秒生成math.Pow(2, SeedBit)个不同的数字,并且在math.Pow(2, TimeBit)/60/60/24/365年的时间范围内有效 +*/ + +/* +修改记录: +2020-03-04 14:30:00 调整了时间和唯一标识在Id中的位置,以便生成递增的Id +2020-04-20 21:10:00 同步了C#版本的逻辑 +*/ + +package idUtil + +import ( + "fmt" + "math" + "sync" + "time" +) + +type TimeIdentifierSeedGenerator struct { + timeBit int32 // 时间戳所占的位数 + timeBitOffset int32 // 时间戳的偏移位数 + startTimeStamp int64 // 起始时间戳 + endTimeStamp int64 // 结束时间戳 + + identifier int64 // 唯一标识 + identifierBit int32 // 唯一标识(机器号或者服务器Id等)所占的位数 + identifierBitOffset int32 // 唯一标识的偏移位数 + + seedBit int32 // 自增种子所占的位数 + seedBitOffset int32 // 自增种子的偏移位数 + minSeed int64 // 最小的种子值 + maxSeed int64 // 最大的种子值 + currSeed int64 // 当前种子值 + + mutex sync.Mutex // 锁对象 +} + +func (this *TimeIdentifierSeedGenerator) getTimeStamp() (int64, error) { + if time.Now().Unix() > this.endTimeStamp { + return 0, fmt.Errorf("Time's value is out of scope") + } + + return time.Now().Unix() - this.startTimeStamp, nil +} + +func (this *TimeIdentifierSeedGenerator) getNewSeed() int64 { + this.mutex.Lock() + defer this.mutex.Unlock() + + if this.currSeed >= this.maxSeed { + this.currSeed = this.minSeed + } else { + this.currSeed = this.currSeed + 1 + } + + return this.currSeed +} + +// 生成新的Id +// identifier:Id的唯一标识值。取值范围必须可以用创建对象时指定的唯一标识值的位数来表示,否则会返回参数超出范围的错误 +// 返回值: +// 新的Id +// 错误对象 +func (this *TimeIdentifierSeedGenerator) GenerateNewId() (int64, error) { + timestamp, err := this.getTimeStamp() + if err != nil { + return 0, err + } + seed := this.getNewSeed() + id := (timestamp << this.timeBitOffset) | (this.identifier << this.identifierBitOffset) | (seed << this.seedBitOffset) + + return id, nil +} + +// 创建新的Id生成器对象(为了保证Id的唯一,需要保证生成的对象全局唯一) +// timeBit + identifierBit + seedBit <= 63 +// timeBit:时间的位数 +// identifier:id唯一标识 +// identifierBit:id唯一标识(机器号或者服务器Id等)的位数 +// seedBit:自增种子的位数 +// 返回值: +// 新的Id生成器对象 +// 错误对象 +func NewTimeIdentifierSeedGenerator(timeBit int32, identifier int64, identifierBit, seedBit int32) (*TimeIdentifierSeedGenerator, error) { + // 之所以使用63位而不是64,是为了保证值为正数 + if timeBit+identifierBit+seedBit > 63 { + return nil, fmt.Errorf("总位数%d超过63位,请调整所有值的合理范围。", timeBit+identifierBit+seedBit) + } + + if identifier < 0 || identifier > int64(math.Pow(2, float64(identifierBit)))-1 { + return nil, fmt.Errorf("唯一标识值溢出,有效范围为【0,%d】", int64(math.Pow(2, float64(identifierBit)))-1) + } + + startTimeStamp := time.Date(2019, time.January, 1, 0, 0, 0, 0, time.Local).Unix() + obj := &TimeIdentifierSeedGenerator{ + timeBit: timeBit, + startTimeStamp: startTimeStamp, + endTimeStamp: startTimeStamp + int64(math.Pow(2, float64(timeBit))) - 1, + identifier: identifier, + identifierBit: identifierBit, + seedBit: seedBit, + minSeed: 0, + maxSeed: int64(math.Pow(2, float64(seedBit))) - 1, + currSeed: 0, + } + + obj.seedBitOffset = 0 + obj.identifierBitOffset = obj.seedBitOffset + obj.seedBit + obj.timeBitOffset = obj.identifierBitOffset + obj.identifierBit + + return obj, nil +} diff --git a/trunk/goutil/ini-config/ini.conf b/trunk/goutil/ini-config/ini.conf new file mode 100644 index 0000000..a947fe3 --- /dev/null +++ b/trunk/goutil/ini-config/ini.conf @@ -0,0 +1,16 @@ +#配置项说明 +log.es.enable=false #(false,默认值,关闭es日志记录,后续配置可不填写; true 打开日志记录) +log.es.url= #(es服务地址) +log.es.indexName=1 #(es服务中Index名) +log.es.level=info #(debug|info|warn|error|fatal等级,等于或高于配置项则记录) + +log.file.enable=false #(默认false) +log.file.path=log #(运行目录下log目录,默认logs) +log.file.pre=log #(文件名前缀,默认log) +log.file.enableHour=true #(文件以小时划分,格式:yyyyMMddHH,默认true,false 一天一个文件,格式:yyyyMMdd) +log.file.level=info + +log.console.enable=false #(默认false) +log.console.level=info + +modelcenter.modelDBConnStr=root:moqikaka3306@tcp(10.255.0.10:3306)/xj_model_mr?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=10||MaxIdleConns=5 #(model数据库信息) \ No newline at end of file diff --git a/trunk/goutil/ini-config/ini.go b/trunk/goutil/ini-config/ini.go new file mode 100644 index 0000000..503ebf7 --- /dev/null +++ b/trunk/goutil/ini-config/ini.go @@ -0,0 +1,103 @@ +package ini_config + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "strings" +) + +// ParseFile +// @description: 从文件读取配置文件,并转化为map对象 +// parameter: +// @filePath:文件路径 +// return: +// @map[string]string: 配置信息 +// @error:错误信息 +func ParseFile(filePath string) (map[string]string, error) { + fn, err := os.Open(filePath) + if err != nil { + panic(err) + } + defer fn.Close() + + kvMap := make(map[string]string) + rd := bufio.NewReader(fn) + for { + data, _, err := rd.ReadLine() + if err != nil || io.EOF == err { + break + } + + line := strings.TrimSpace(string(data)) + if line == "" || line[0:1] == "#" { + continue + } + + var k string + var v string + k, v, err = Parse(line) + if err != nil { + return nil, err + } + + kvMap[k] = v + } + + return kvMap, nil +} + +// Parse +// @description: 转换单行字符串为配置对象 +// parameter: +// @line:单行内容 +// return: +// @string:key +// @string:value +// @error:错误信息 +func Parse(line string) (string, string, error) { + ls := strings.SplitN(line, "=", 2) + if len(ls) != 2 { + return "", "", errors.New(fmt.Sprintf("配置%s中未找到=分隔符", line)) + } + + var key = strings.TrimSpace(ls[0]) + var value string + + i := strings.LastIndex(ls[1], "#") + if i < 0 { + value = strings.TrimSpace(ls[1]) + } else { + s := ls[1][:i] + value = strings.TrimSpace(s) + } + + return key, value, nil +} + +// ParseMultipleLines +// @description: 转换多行字符串为配置对象 +// parameter: +// @content:多行内容(通过\n换行) +// return: +// @map[string]string:配置信息 +// @error:错误信息 +func ParseMultipleLines(content string) (map[string]string, error) { + lines := strings.Split(content, "\n") + kvMap := make(map[string]string) + for _, line := range lines { + if line == "" || line[0:1] == "#" { + continue + } + + k, v, err := Parse(line) + if err != nil { + return nil, err + } + kvMap[k] = v + } + + return kvMap, nil +} diff --git a/trunk/goutil/ini-config/ini_test.go b/trunk/goutil/ini-config/ini_test.go new file mode 100644 index 0000000..ca200f1 --- /dev/null +++ b/trunk/goutil/ini-config/ini_test.go @@ -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读取的值不正确") + } +} diff --git a/trunk/goutil/intAndBytesUtil/bytes.go b/trunk/goutil/intAndBytesUtil/bytes.go new file mode 100644 index 0000000..37c012a --- /dev/null +++ b/trunk/goutil/intAndBytesUtil/bytes.go @@ -0,0 +1,58 @@ +package intAndBytesUtil + +import ( + "bytes" + "encoding/binary" +) + +// 字节数组转换成整形 +// b:字节数组 +// order:大、小端的枚举 +// 返回值:对应的int值 +func BytesToInt(b []byte, order binary.ByteOrder) int { + bytesBuffer := bytes.NewBuffer(b) + + var result int + binary.Read(bytesBuffer, order, &result) + + return result +} + +// 字节数组转换成整形 +// b:字节数组 +// order:大、小端的枚举 +// 返回值:对应的int16值 +func BytesToInt16(b []byte, order binary.ByteOrder) int16 { + bytesBuffer := bytes.NewBuffer(b) + + var result int16 + binary.Read(bytesBuffer, order, &result) + + return result +} + +// 字节数组转换成整形 +// b:字节数组 +// order:大、小端的枚举 +// 返回值:对应的int32值 +func BytesToInt32(b []byte, order binary.ByteOrder) int32 { + bytesBuffer := bytes.NewBuffer(b) + + var result int32 + binary.Read(bytesBuffer, order, &result) + + return result +} + +// 字节数组转换成整形 +// b:字节数组 +// order:大、小端的枚举 +// 返回值:对应的int64值 +func BytesToInt64(b []byte, order binary.ByteOrder) int64 { + bytesBuffer := bytes.NewBuffer(b) + + var result int64 + binary.Read(bytesBuffer, order, &result) + + return result +} diff --git a/trunk/goutil/intAndBytesUtil/bytes_test.go b/trunk/goutil/intAndBytesUtil/bytes_test.go new file mode 100644 index 0000000..33f6b30 --- /dev/null +++ b/trunk/goutil/intAndBytesUtil/bytes_test.go @@ -0,0 +1,70 @@ +package intAndBytesUtil + +import ( + "encoding/binary" + "testing" +) + +func TestBytesToInt(t *testing.T) { + var givenBigEndian []byte = []byte{0, 0, 1, 0} + var givenLittleEndian []byte = []byte{0, 1, 0, 0} + var expectedInt int32 = 256 + + result := BytesToInt32(givenBigEndian, binary.BigEndian) + if result != expectedInt { + t.Errorf("BytesToInt(%v) failed.Got %v, expected %v", givenBigEndian, result, expectedInt) + } + + result = BytesToInt32(givenLittleEndian, binary.LittleEndian) + if result != expectedInt { + t.Errorf("BytesToInt(%v) failed.Got %v, expected %v", givenLittleEndian, result, expectedInt) + } +} + +func TestBytesToInt16(t *testing.T) { + var givenBigEndian []byte = []byte{1, 0} + var givenLittleEndian []byte = []byte{0, 1} + var expectedInt int16 = 256 + + result := BytesToInt16(givenBigEndian, binary.BigEndian) + if result != expectedInt { + t.Errorf("BytesToInt(%v) failed.Got %v, expected %v", givenBigEndian, result, expectedInt) + } + + result = BytesToInt16(givenLittleEndian, binary.LittleEndian) + if result != expectedInt { + t.Errorf("BytesToInt(%v) failed.Got %v, expected %v", givenLittleEndian, result, expectedInt) + } +} + +func TestBytesToInt32(t *testing.T) { + var givenBigEndian []byte = []byte{0, 0, 1, 0} + var givenLittleEndian []byte = []byte{0, 1, 0, 0} + var expectedInt int32 = 256 + + result := BytesToInt32(givenBigEndian, binary.BigEndian) + if result != expectedInt { + t.Errorf("BytesToInt(%v) failed.Got %v, expected %v", givenBigEndian, result, expectedInt) + } + + result = BytesToInt32(givenLittleEndian, binary.LittleEndian) + if result != expectedInt { + t.Errorf("BytesToInt(%v) failed.Got %v, expected %v", givenLittleEndian, result, expectedInt) + } +} + +func TestBytesToInt64(t *testing.T) { + var givenBigEndian []byte = []byte{0, 0, 0, 0, 0, 0, 1, 0} + var givenLittleEndian []byte = []byte{0, 1, 0, 0, 0, 0, 0, 0} + var expectedInt int64 = 256 + + result := BytesToInt64(givenBigEndian, binary.BigEndian) + if result != expectedInt { + t.Errorf("BytesToInt(%v) failed.Got %v, expected %v", givenBigEndian, result, expectedInt) + } + + result = BytesToInt64(givenLittleEndian, binary.LittleEndian) + if result != expectedInt { + t.Errorf("BytesToInt(%v) failed.Got %v, expected %v", givenLittleEndian, result, expectedInt) + } +} diff --git a/trunk/goutil/intAndBytesUtil/doc.go b/trunk/goutil/intAndBytesUtil/doc.go new file mode 100644 index 0000000..1638da7 --- /dev/null +++ b/trunk/goutil/intAndBytesUtil/doc.go @@ -0,0 +1,4 @@ +/* +提供int和[]Bytes互相转化的助手方法,其中需要注意的是在不同的平台上大、小端是不同的 +*/ +package intAndBytesUtil diff --git a/trunk/goutil/intAndBytesUtil/int.go b/trunk/goutil/intAndBytesUtil/int.go new file mode 100644 index 0000000..bbef68a --- /dev/null +++ b/trunk/goutil/intAndBytesUtil/int.go @@ -0,0 +1,50 @@ +package intAndBytesUtil + +import ( + "bytes" + "encoding/binary" +) + +// 整形转换成字节(无效,因为系统无法判断读取的字节数) +// n:int型数字 +// order:大、小端的枚举 +// 返回值:对应的字节数组 +// func IntToBytes(n int, order binary.ByteOrder) []byte { +// bytesBuffer := bytes.NewBuffer([]byte{}) +// binary.Write(bytesBuffer, order, n) + +// return bytesBuffer.Bytes() +// } + +// 整形转换成字节 +// n:int16型数字 +// order:大、小端的枚举 +// 返回值:对应的字节数组 +func Int16ToBytes(n int16, order binary.ByteOrder) []byte { + bytesBuffer := bytes.NewBuffer([]byte{}) + binary.Write(bytesBuffer, order, n) + + return bytesBuffer.Bytes() +} + +// 整形转换成字节 +// n:int32型数字 +// order:大、小端的枚举 +// 返回值:对应的字节数组 +func Int32ToBytes(n int32, order binary.ByteOrder) []byte { + bytesBuffer := bytes.NewBuffer([]byte{}) + binary.Write(bytesBuffer, order, n) + + return bytesBuffer.Bytes() +} + +// 整形转换成字节 +// n:int64型数字 +// order:大、小端的枚举 +// 返回值:对应的字节数组 +func Int64ToBytes(n int64, order binary.ByteOrder) []byte { + bytesBuffer := bytes.NewBuffer([]byte{}) + binary.Write(bytesBuffer, order, n) + + return bytesBuffer.Bytes() +} diff --git a/trunk/goutil/intAndBytesUtil/int_test.go b/trunk/goutil/intAndBytesUtil/int_test.go new file mode 100644 index 0000000..4e5137f --- /dev/null +++ b/trunk/goutil/intAndBytesUtil/int_test.go @@ -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 +} diff --git a/trunk/goutil/intUtil/int.go b/trunk/goutil/intUtil/int.go new file mode 100644 index 0000000..f505d70 --- /dev/null +++ b/trunk/goutil/intUtil/int.go @@ -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 +} diff --git a/trunk/goutil/intUtil/int_test.go b/trunk/goutil/intUtil/int_test.go new file mode 100644 index 0000000..a50f921 --- /dev/null +++ b/trunk/goutil/intUtil/int_test.go @@ -0,0 +1,16 @@ +package intUtil + +import ( + "fmt" + "testing" +) + +func Test1(t *testing.T) { + var originalNum int64 = 1234567891011121314 + shuffledNum, err := ShuffleIntDigits(int64(originalNum)) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Printf("Original number: %d\nShuffled number: %d\n", originalNum, shuffledNum) +} diff --git a/trunk/goutil/jsonUtil/json.go b/trunk/goutil/jsonUtil/json.go new file mode 100644 index 0000000..f03bf19 --- /dev/null +++ b/trunk/goutil/jsonUtil/json.go @@ -0,0 +1,78 @@ +package jsonUtil + +import ( + "encoding/json" + "strings" +) + +// 使用Number类型来反序列化字符串 +// 当被序列化为interface{}类型时,如果int型的长度大于7,则会被使用科学计数法进行表示 +// 当反序列化时,会无法转换为int类型,会导致错误 +// 所以需要使用Number类型 +// s:输入字符串 +// 返回值: +// 反序列化后的数据 +// 错误对象 +func UnMarshalWithNumberType(s string) (interface{}, error) { + // 构造decode对象 + var decode = json.NewDecoder(strings.NewReader(s)) + decode.UseNumber() + + // decode + var result interface{} + if err := decode.Decode(&result); err != nil { + return nil, err + } + + return result, nil +} + +// 深拷贝对象 +// src:源数据 +// 返回值: +// 新对象 +// 错误对象 +func DeepClone(src interface{}) (interface{}, error) { + var byteSlice []byte + var err error + + // 先序列化为[]byte + if byteSlice, err = json.Marshal(src); err != nil { + return nil, err + } + + // 再反序列化成对象 + var result interface{} + if err := json.Unmarshal(byteSlice, &result); err != nil { + return nil, err + } + + return result, nil +} + +// 使用Number类型来深拷贝对象 +// src:源数据 +// 返回值: +// 新对象 +// 错误对象 +func DeepCloneWithNumberType(src interface{}) (interface{}, error) { + var byteSlice []byte + var err error + + // 先序列化为[]byte + if byteSlice, err = json.Marshal(src); err != nil { + return nil, err + } + + // 构造decode对象 + var decode = json.NewDecoder(strings.NewReader(string(byteSlice))) + decode.UseNumber() + + // decode + var result interface{} + if err := decode.Decode(&result); err != nil { + return nil, err + } + + return result, nil +} diff --git a/trunk/goutil/jsonUtil/json_test.go b/trunk/goutil/jsonUtil/json_test.go new file mode 100644 index 0000000..99730be --- /dev/null +++ b/trunk/goutil/jsonUtil/json_test.go @@ -0,0 +1,131 @@ +package jsonUtil + +import ( + "encoding/json" + "fmt" + "testing" +) + +func TestUnMarshalWithNumberType(t *testing.T) { + src := make(map[string]int) + src["Name"] = 123 + src["Money"] = 100000000 + + var byteSlice []byte + var err error + if byteSlice, err = json.Marshal(src); err != nil { + t.Errorf("Marshal src failed\n") + } + + if target_interface, err := UnMarshalWithNumberType(string(byteSlice)); err != nil { + t.Errorf("Expected got nil, but got err:%s\n", err) + } else { + if target_map, ok := target_interface.(map[string]interface{}); !ok { + t.Errorf("Expected got nil, but got err:%s\n", err) + } else { + money, ok := target_map["Money"].(json.Number) + money_int, err := money.Int64() + if !ok || err != nil || money_int != 100000000 { + t.Errorf("Expected got 100000000, but got %v, ok:%v, err:%s\n", money_int, ok, err) + } + + fmt.Printf("target_map:%v\n", target_map) + } + } + + intSlice1 := []int{1, 2, 3, 5} + + if byteSlice, err = json.Marshal(intSlice1); err != nil { + t.Errorf("Marshal src failed\n") + } + + if target_interface, err := UnMarshalWithNumberType(string(byteSlice)); err != nil { + t.Errorf("Expected got nil, but got err:%s\n", err) + } else { + fmt.Printf("target_interface:%v\n", target_interface) + if target_slice, ok := target_interface.([]interface{}); !ok { + t.Errorf("Expected got []int, but failed.\n") + } else { + fmt.Printf("target_slice:%v\n", target_slice) + } + } +} + +func TestDeepClone(t *testing.T) { + p1 := NewPlayer(100000000, "Jordan") + if p1_map, err := DeepClone(p1); err != nil { + t.Errorf("Expected nil, but got err:%s", err) + } else { + fmt.Printf("p1:%s\n", p1) + before := fmt.Sprintf("%v", p1_map) + p1.Name = "Jordan Zuo" + fmt.Printf("p1:%s\n", p1) + after := fmt.Sprintf("%v", p1_map) + fmt.Printf("before:%s\n", before) + fmt.Printf("after:%s\n", after) + if before != after { + t.Errorf("Expected before and after same, but got different") + } + } + + intSlice1 := []int{1, 2, 3, 5} + if intSlice2_interface, err := DeepClone(intSlice1); err != nil { + t.Errorf("Expected nil, but got err:%s", err) + } else { + fmt.Printf("intSlice1:%v\n", intSlice1) + before := fmt.Sprintf("%v", intSlice2_interface) + intSlice1 = append(intSlice1, 10) + fmt.Printf("intSlice1:%v\n", intSlice1) + after := fmt.Sprintf("%v", intSlice2_interface) + fmt.Printf("before:%s\n", before) + fmt.Printf("after:%s\n", after) + if before != after { + t.Errorf("Expected before and after same, but got different") + } + } +} + +func TestDeepCloneWithNumberType(t *testing.T) { + p1 := NewPlayer(100000000, "Jordan") + if p1_interface, err := DeepCloneWithNumberType(p1); err != nil { + t.Errorf("Expected nil, but got err:%s", err) + } else { + if p1_map, ok := p1_interface.(map[string]interface{}); !ok { + t.Errorf("Expected nil, but got err:%s", err) + } else { + fmt.Printf("p1:%s\n", p1) + before := fmt.Sprintf("%v", p1_map) + p1.Name = "Jordan Zuo" + fmt.Printf("p1:%s\n", p1) + after := fmt.Sprintf("%v", p1_map) + fmt.Printf("before:%s\n", before) + fmt.Printf("after:%s\n", after) + if before != after { + t.Errorf("Expected before and after same, but got different") + } + + fmt.Printf("p1_interface_map:%v\n", p1_map) + id, ok := p1_map["Id"].(json.Number) + id_int, err := id.Int64() + if !ok || err != nil || id_int != 100000000 { + t.Errorf("Expected got 100000000, but got %v, ok:%v, err:%s\n", id_int, ok, err) + } + } + } +} + +type Player struct { + Id int + Name string +} + +func (player *Player) String() string { + return fmt.Sprintf("{Addr:%v, Id:%v, Name:%s}", &player, player.Id, player.Name) +} + +func NewPlayer(id int, name string) *Player { + return &Player{ + Id: id, + Name: name, + } +} diff --git a/trunk/goutil/logUtil/doc.go b/trunk/goutil/logUtil/doc.go new file mode 100644 index 0000000..702b395 --- /dev/null +++ b/trunk/goutil/logUtil/doc.go @@ -0,0 +1,2 @@ +// Package logUtil +package logUtil diff --git a/trunk/goutil/logUtil/impl-console/doc.go b/trunk/goutil/logUtil/impl-console/doc.go new file mode 100644 index 0000000..aaba376 --- /dev/null +++ b/trunk/goutil/logUtil/impl-console/doc.go @@ -0,0 +1 @@ +package impl_console diff --git a/trunk/goutil/logUtil/impl-console/logger.go b/trunk/goutil/logUtil/impl-console/logger.go new file mode 100644 index 0000000..82aa9d1 --- /dev/null +++ b/trunk/goutil/logUtil/impl-console/logger.go @@ -0,0 +1,84 @@ +package impl_console + +import ( + "fmt" + "github.com/fatih/color" +) + +type Logger struct { +} + +// NewLogger +// @description: 构造控制台日志 +// parameter: +// return: +// @*Logger: +func NewLogger() *Logger { + return &Logger{} +} + +// InfoLog +// @description: 信息日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (cl *Logger) InfoLog(format string, args ...interface{}) { + c := color.New(color.FgGreen) + s := c.Sprint("Info:") + fmt.Println(s, fmt.Sprintf(format, args...)) +} + +// DebugLog +// @description: 调试日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (cl *Logger) DebugLog(format string, args ...interface{}) { + c := color.New(color.FgGreen) + s := c.Sprint("Debug:") + fmt.Println(s, fmt.Sprintf(format, args...)) +} + +// WarnLog +// @description: 警告日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (cl *Logger) WarnLog(format string, args ...interface{}) { + c := color.New(color.FgYellow) + _, _ = c.Println("Warn:", fmt.Sprintf(format, args...)) +} + +// ErrorLog +// @description: 错误日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (cl *Logger) ErrorLog(format string, args ...interface{}) { + c := color.New(color.FgRed) + _, _ = c.Println("Error:", fmt.Sprintf(format, args...)) +} + +// FatalLog +// @description: 致命错误日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (cl *Logger) FatalLog(format string, args ...interface{}) { + c := color.New(color.FgRed) + _, _ = c.Println("Fatal:", fmt.Sprintf(format, args...)) +} + +// CloseLog +// @description: 关闭日志 +// parameter: +// @waitFinish:是否等待日志 +// return: +func (cl *Logger) CloseLog(waitFinish bool) { + +} diff --git a/trunk/goutil/logUtil/impl-console/logger_test.go b/trunk/goutil/logUtil/impl-console/logger_test.go new file mode 100644 index 0000000..41cbcc5 --- /dev/null +++ b/trunk/goutil/logUtil/impl-console/logger_test.go @@ -0,0 +1,14 @@ +package impl_console + +import ( + "testing" +) + +func TestInfoLog(t *testing.T) { + log := NewLogger() + log.DebugLog("Debug test") + log.InfoLog("Info test") + log.WarnLog("Warn test") + log.ErrorLog("Error test") + log.FatalLog("Fatal test") +} diff --git a/trunk/goutil/logUtil/impl-es/api.go b/trunk/goutil/logUtil/impl-es/api.go new file mode 100644 index 0000000..75277bc --- /dev/null +++ b/trunk/goutil/logUtil/impl-es/api.go @@ -0,0 +1,63 @@ +package impl_es + +// InfoLog +// @description: 信息日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (l *Logger) InfoLog(format string, args ...interface{}) { + info := l.buildEsLog("Info", format, args...) + l.cache.addCacheLog(info) +} + +// DebugLog +// @description: 调试日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (l *Logger) DebugLog(format string, args ...interface{}) { + info := l.buildEsLog("Debug", format, args...) + l.cache.addCacheLog(info) +} + +// WarnLog +// @description: 警告日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (l *Logger) WarnLog(format string, args ...interface{}) { + info := l.buildEsLog("Warn", format, args...) + l.cache.addCacheLog(info) +} + +// ErrorLog +// @description: 错误日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (l *Logger) ErrorLog(format string, args ...interface{}) { + info := l.buildEsLog("Error", format, args...) + l.cache.addCacheLog(info) +} + +// FatalLog +// @description: 致命错误日志记录 +// parameter: +// @format:日志格式 +// @args:参数列表 +// return: +func (l *Logger) FatalLog(format string, args ...interface{}) { + info := l.buildEsLog("Fatal", format, args...) + l.cache.addCacheLog(info) +} + +// CloseLog +// @description: 关闭日志 +// parameter: +// @waitFinish:是否等待日志 +// return: +func (l *Logger) CloseLog(waitFinish bool) {} diff --git a/trunk/goutil/logUtil/impl-es/cache.go b/trunk/goutil/logUtil/impl-es/cache.go new file mode 100644 index 0000000..79550ad --- /dev/null +++ b/trunk/goutil/logUtil/impl-es/cache.go @@ -0,0 +1,72 @@ +package impl_es + +import "sync" + +const ( + // 批量保存的消息数量 + con_MAX_NUMBER_OF_MESSAGE = 20 +) + +// logCache +// @description: 日志缓存 +type logCache struct { + logCacheList []*EsLogModel // logCacheList 日志缓存对象 + logCacheMutex sync.RWMutex // logCacheMutex 锁 +} + +// newlogCache +// @description: 构造日志缓存 +// parameter: +// return: +// @*logCache: +func newlogCache() *logCache { + m := &logCache{ + logCacheList: make([]*EsLogModel, 0, 256), + logCacheMutex: sync.RWMutex{}, + } + + return m +} + +// 写入在线日志 +// 参数: +// 日志信息对象 +// 返回值: +// 无 +func (c *logCache) addCacheLog(logObj *EsLogModel) { + c.logCacheMutex.Lock() + defer c.logCacheMutex.Unlock() + + c.logCacheList = append(c.logCacheList, logObj) +} + +// 获取日志数量 +// 参数: +// 无 +// 返回值: +// 缓存中的日志数量 +func (c *logCache) getCacheLogCount() int { + c.logCacheMutex.RLock() + defer c.logCacheMutex.RUnlock() + + return len(c.logCacheList) +} + +// 获取缓存中的日志 +// 参数: +// 数量 +// 返回值: +// 日志列表对象 +func (c *logCache) getCacheLog(logCount int) (logList []*EsLogModel) { + if logCount > con_MAX_NUMBER_OF_MESSAGE { + logCount = con_MAX_NUMBER_OF_MESSAGE + } + + c.logCacheMutex.Lock() + defer c.logCacheMutex.Unlock() + + logList = c.logCacheList[:logCount] + c.logCacheList = c.logCacheList[logCount:] + + return +} diff --git a/trunk/goutil/logUtil/impl-es/doc.go b/trunk/goutil/logUtil/impl-es/doc.go new file mode 100644 index 0000000..edeb851 --- /dev/null +++ b/trunk/goutil/logUtil/impl-es/doc.go @@ -0,0 +1 @@ +package impl_es diff --git a/trunk/goutil/logUtil/impl-es/logger.go b/trunk/goutil/logUtil/impl-es/logger.go new file mode 100644 index 0000000..dd4223d --- /dev/null +++ b/trunk/goutil/logUtil/impl-es/logger.go @@ -0,0 +1,236 @@ +package impl_es + +import ( + "bytes" + "encoding/json" + "fmt" + "time" + + "github.com/elastic/go-elasticsearch/v8" + "github.com/elastic/go-elasticsearch/v8/esutil" + "golang.org/x/net/context" +) + +const ( + // 如果缓存中的日志数量超过阈值,则记录日志 + con_WRITE_LOG_THRESHOLD_NUMBER = 1000 +) + +// Logger +// @description: es日志处理对象 +type Logger struct { + esClient *elasticsearch.Client // esClient es客户端 + blukIndexer esutil.BulkIndexer // blukIndexer es批量索引 + indexNamePrefix string // indexNamePrefix 索引前缀 + curIndexName string // curIndexName 当前索引名字 + innerId string // innerId 系统唯一标识 + extendCb func() string // extendCb 扩展信息获取方法 + cache *logCache // cache 日志缓存 +} + +// NewLogger +// @description: 构造es日志对象 +// parameter: +// @urls: +// @username: +// @pwd: +// @esIndexName: +// @_innerId: +// @_extendCb: +// return: +// @*Logger: +// @error: +func NewLogger(urls []string, username, pwd string, esIndexName, _innerId string, _extendCb func() string) (*Logger, error) { + if _extendCb == nil { + _extendCb = func() string { + return "" + } + } + + esClient, err := elasticsearch.NewClient(elasticsearch.Config{ + Addresses: urls, + Username: username, + Password: pwd, + }) + if err != nil { + return nil, err + } + + l := &Logger{ + esClient: esClient, + blukIndexer: nil, + indexNamePrefix: esIndexName, + curIndexName: "", + innerId: _innerId, + extendCb: _extendCb, + cache: newlogCache(), + } + + l.curIndexName = l.getCurIndexName() + l.blukIndexer, err = l.newBlukIndexer() + if err != nil { + return nil, err + } + + // 启动日志处理 + l.start() + + return l, nil +} + +// start +// @description: 启动日志处理 +// parameter: +// @receiver l: +// return: +func (l *Logger) start() { + go l.logHandlerStart() + go l.indexNameCheck() +} + +// getCurIndexName +// @description: 获取索引名字 +// parameter: +// @receiver l: +// return: +// @string: +func (l *Logger) getCurIndexName() string { + //获取当天日期 + return fmt.Sprintf("%s_%s", l.indexNamePrefix, time.Now().Format("20060102")) +} + +// newBlukIndexer +// @description: 创建新的BlukIndexer +// parameter: +// @receiver l: +// return: +// @bulkIndexer: +// @err: +func (l *Logger) newBlukIndexer() (bulkIndexer esutil.BulkIndexer, err error) { + bulkIndexer, err = esutil.NewBulkIndexer(esutil.BulkIndexerConfig{ + Index: l.getCurIndexName(), // The default index name + Client: l.esClient, // The Elasticsearch client + FlushInterval: time.Second, // The periodic flush interval + }) + if err != nil { + fmt.Println("[es log]: Creating the indexer err: ", err) + } + + return +} + +// 日志处理 +func (l *Logger) logHandlerStart() { + defer func() { + if r := recover(); r != nil { + fmt.Println("[es log]: Creating the indexer err: ", r) + go l.logHandlerStart() + } + }() + + time.Sleep(1 * time.Second) + for { + // 达到指定的时间或者指定的日志数量,则保存到消息队列中去 + logCount := l.cache.getCacheLogCount() + if logCount == 0 || l.esClient == nil { + time.Sleep(time.Second * 1) + continue + } + + // 记录在线日志待发送数量 + if logCount > con_WRITE_LOG_THRESHOLD_NUMBER { + fmt.Printf("[es log]: 当前缓存中共有%d条未发送到在线日志系统的日志", logCount) + } + + // 执行刷新 + l.bulkPushToOnlineLogSystem(l.cache.getCacheLog(logCount)) + } +} + +// indexNameCheck +// @description: index名字校验 +// parameter: +// @receiver l: +// return: +func (l *Logger) indexNameCheck() { + defer func() { + if r := recover(); r != nil { + fmt.Println("[es log]: indexNameCheck err: ", r) + go l.indexNameCheck() + } + }() + + time.Sleep(1 * time.Second) + for { + time.Sleep(time.Second * 1) + + tempIndexName := l.getCurIndexName() + if tempIndexName == l.curIndexName { + continue + } + + // 关闭老的blukIndexer+建立新的blukIndexer + newBlukIndexer, err := l.newBlukIndexer() + if err != nil { + continue + } + + tempBlukIndexer := l.blukIndexer + l.blukIndexer = newBlukIndexer + l.curIndexName = tempIndexName + + // 暂停3s,等待正在写入的数据写入完成 + time.Sleep(3 * time.Second) + err = tempBlukIndexer.Close(context.Background()) + if err != nil { + fmt.Println("[es log]:BlukIndexer close err:", err.Error()) + } + } +} + +// bulkPushToOnlineLogSystem +// @description: 批量保存到在线日志系统 +// parameter: +// @receiver l: +// @logList: +// return: +func (l *Logger) bulkPushToOnlineLogSystem(logList []*EsLogModel) { + for _, logObj := range logList { + message, err := json.Marshal(logObj) + if err != nil { + fmt.Println("[es log]: Marshal failed. Err:", err) + continue + } + + err = l.blukIndexer.Add( + context.Background(), + esutil.BulkIndexerItem{ + Action: "index", + Body: bytes.NewReader(message), + }) + if err != nil { + fmt.Println("[es log]: Add data err:", err.Error()) + } + } +} + +// buildOnlineLog +// @description: 组装es日志对象 +// parameter: +// @receiver l: +// @logType: +// @format: +// @args: +// return: +// @newLogObj: +func (l *Logger) buildEsLog(logType, format string, args ...interface{}) (newLogObj *EsLogModel) { + msg := format + if len(args) > 0 { + msg = fmt.Sprintf(format, args...) + } + + // 构造新的日志对象 + newLogObj = newEsLogModel(logType, msg, l.innerId, l.extendCb) + + return +} diff --git a/trunk/goutil/logUtil/impl-es/logger_test.go b/trunk/goutil/logUtil/impl-es/logger_test.go new file mode 100644 index 0000000..6b8fd2c --- /dev/null +++ b/trunk/goutil/logUtil/impl-es/logger_test.go @@ -0,0 +1,25 @@ +package impl_es + +import ( + "testing" + "time" +) + +func TestLog(t *testing.T) { + urls := []string{"http://10.1.0.71:9101/"} + log, err := NewLogger(urls, "", "", "es_log_test", "les_log_test", nil) + if err != nil { + t.Error("esLog 创建失败") + } + + for i := 1; i <= 1000; i++ { + log.DebugLog("Debug test %v", i) + log.InfoLog("Info test %v", i) + log.WarnLog("Warn test %v", i) + log.ErrorLog("Error test %v", i) + log.FatalLog("Fatal test %v", i) + time.Sleep(time.Millisecond * 100) + } + + time.Sleep(time.Second * 5) +} diff --git a/trunk/goutil/logUtil/impl-es/model.go b/trunk/goutil/logUtil/impl-es/model.go new file mode 100644 index 0000000..3b7c48a --- /dev/null +++ b/trunk/goutil/logUtil/impl-es/model.go @@ -0,0 +1,53 @@ +package impl_es + +import ( + "sync" + "time" +) + +var ( + logPool = sync.Pool{ + New: func() interface{} { + return &EsLogModel{} + }, + } +) + +// 日志对象 +type EsLogModel struct { + // 日志类型 + LogType string + + // 日志消息 + Message string + + // 程序标识 + InnerId string + + // Extendstr 扩展信息 + Extendstr string + + // 日志时间 + CrTime time.Time +} + +// newEsLogModel +// @description: 构造esLog对象 +// parameter: +// @logType:日志类型 +// @msg:日志内容 +// @innerId:内部唯一id +// @extendCb:扩展信息回调方法 +// return: +// @*EsLogModel: +func newEsLogModel(logType string, msg string, innerId string, extendCb func() string) *EsLogModel { + logObj := logPool.Get().(*EsLogModel) + + logObj.CrTime = time.Now() + logObj.LogType = logType + logObj.Message = msg + logObj.InnerId = innerId + logObj.Extendstr = extendCb() + + return logObj +} diff --git a/trunk/goutil/logUtil/impl-localfile/api.go b/trunk/goutil/logUtil/impl-localfile/api.go new file mode 100644 index 0000000..fc28df4 --- /dev/null +++ b/trunk/goutil/logUtil/impl-localfile/api.go @@ -0,0 +1,35 @@ +package impl_localfile + +import "sync" + +func (l *Logger) InfoLog(format string, args ...interface{}) { + l.log(getLog(format, args...), info, true) +} + +func (l *Logger) WarnLog(format string, args ...interface{}) { + l.log(getLog(format, args...), warn, true) +} + +func (l *Logger) DebugLog(format string, args ...interface{}) { + l.log(getLog(format, args...), debug, true) +} + +func (l *Logger) ErrorLog(format string, args ...interface{}) { + l.log(getLog(format, args...), _error, true) +} + +func (l *Logger) FatalLog(format string, args ...interface{}) { + l.log(getLog(format, args...), fatal, true) +} + +func (l *Logger) CloseLog(waitFinish bool) { + wg := sync.WaitGroup{} + + // 关闭所有的file + for _, logger := range l.loggerMap { + wg.Add(1) + go logger.Close(&wg, waitFinish) + } + + wg.Wait() +} diff --git a/trunk/goutil/logUtil/impl-localfile/doc.go b/trunk/goutil/logUtil/impl-localfile/doc.go new file mode 100644 index 0000000..908b48b --- /dev/null +++ b/trunk/goutil/logUtil/impl-localfile/doc.go @@ -0,0 +1,3 @@ +package impl_localfile + +// 包名不能采用中分线,而且前面的包已经说明了是log包,这个子文件夹不需要在加上log diff --git a/trunk/goutil/logUtil/impl-localfile/log_file.go b/trunk/goutil/logUtil/impl-localfile/log_file.go new file mode 100644 index 0000000..a3da298 --- /dev/null +++ b/trunk/goutil/logUtil/impl-localfile/log_file.go @@ -0,0 +1,203 @@ +package impl_localfile + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strconv" + "sync" + "time" +) + +const ( + con_FILE_SUFFIX = "txt" + + defaultPanicNum = 10 // 默认支持的连续panic次数 + defaultPanicIntervals = 5 // 秒 + + fileMaxSize = 1 << 27 // 128M +) + +type fileLog struct { + logPath string + + // fileDir + fileName 用于判断是否需要切换日志文件 + fileDir string + fileName string + lv levelType + + msgChan chan logObject + + f *os.File + + // 如果出现未知panic并一直发生,那么应该要真正的panic + panicNum int + panicTime time.Time + + // 当前已经写入的大小 + curWriteSize int64 +} + +func newLog(logPath string, lv levelType) *fileLog { + f := &fileLog{ + logPath: logPath, + fileDir: "", + fileName: "", + msgChan: make(chan logObject, 10240), + lv: lv, + f: nil, + } + + go f.loop() + + return f +} + +func (f *fileLog) loop() { + defer func() { + if r := recover(); r != nil { + // 将错误输出,而不是记录到文件,是因为可能导致死循环 + fmt.Println("log file lv:", f.lv, " err:", r) + + // 超过间隔时间重置 + if time.Since(f.panicTime)/time.Second > defaultPanicIntervals { + f.panicNum = 0 + f.panicTime = time.Now() + } + + // 这里处理连续panic 也是防止循环调用形成死循环 + f.panicNum++ + if f.panicNum >= defaultPanicNum { + panic(r) + } + } + go f.loop() + }() + + for logItem := range f.msgChan { + f.writeLog(logItem) + } +} + +func (f *fileLog) writeLog(logObj logObject) { + // 由于不使用直接 logUtil 调用,logUtilPlus 存在终端打印的控制,所以 logUtil 只作为纯文件日志组件 + // if PrintImportantLog && (logObj.level == warn || logObj.level == _error || logObj.level == fatal) { + // fmt.Println(logObj.logInfo) + // } + + // 检查是否需要去切换文件或者创建文件 + f.checkFileAndLoop(logObj.level, logObj.ifIncludeHour) + + f.curWriteSize += int64(len(logObj.logInfo)) + _, _ = f.f.WriteString(logObj.logInfo) +} + +// 检查文件是否需要切换 +func (f *fileLog) checkFileAndLoop(level levelType, ifIncludeHour bool) { + // 获取当前时间 + now := time.Now() + fileAbsoluteDirectory := filepath.Join(f.logPath, strconv.Itoa(now.Year()), strconv.Itoa(int(now.Month()))) + fileName := "" + + if ifIncludeHour { + fileName = fmt.Sprintf("%s.%s.%s", time.Now().Format("2006-01-02-15"), level, con_FILE_SUFFIX) + } else { + fileName = fmt.Sprintf("%s.%s.%s", time.Now().Format("2006-01-02"), level, con_FILE_SUFFIX) + } + + // 说明已经存在, 检查文件大小,并切换 + if f.f != nil && f.fileName == fileName && f.fileDir == fileAbsoluteDirectory { + if f.curWriteSize < fileMaxSize { + return + } + + // 大小超额切换文件 + f.switchFile() + return + } + + // 创建文件夹 + if f.fileDir != fileAbsoluteDirectory { + if err := os.MkdirAll(fileAbsoluteDirectory, os.ModePerm|os.ModeTemporary); err != nil { + log.Println("make dir all is err :", err) + } + f.fileDir = fileAbsoluteDirectory + } + + // 创建文件 + if f.fileName != fileName { + f.fileName = fileName + } + + // 到这里说明要切换了,关闭之前的file + if f.f != nil { + _ = f.f.Close() + } + + // 正常切换文件 + f.createFile() +} + +func (f *fileLog) switchFile() { + // 关闭文件 + _ = f.f.Close() + + // 重命名 + curFileName := filepath.Join(f.fileDir, f.fileName) + newFileName := filepath.Join(f.fileDir, fmt.Sprintf("%s.%s.%s", f.fileName, time.Now().Format("15-04-05.999"), con_FILE_SUFFIX)) + err := os.Rename(curFileName, newFileName) + if err != nil { + log.Println(err) + } + + // 再次创建文件 + f.createFile() +} + +func (f *fileLog) createFile() { + // 得到最终的文件绝对路径 + fileAbsolutePath := filepath.Join(f.fileDir, f.fileName) + // 打开文件(如果文件存在就以读写模式打开,并追加写入;如果文件不存在就创建,然后以写模式打开。) + file, err := os.OpenFile(fileAbsolutePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm|os.ModeTemporary) + if err != nil { + log.Println("open file is err :", err) + return + } + f.f = file + stat, _ := file.Stat() + f.curWriteSize = stat.Size() +} + +func (f *fileLog) SetLogPath(logPath string) { + f.logPath = logPath +} + +// Close 最后等待 3s 钟 +func (f *fileLog) Close(wg *sync.WaitGroup, waitFinish bool) { + ticker := time.NewTicker(3 * time.Second) + defer func() { + ticker.Stop() + + if f.f != nil { + _ = f.f.Close() + } + + wg.Done() + }() + + for { + if !waitFinish || len(f.msgChan) == 0 { + break + } + + select { + case <-ticker.C: + fmt.Println("wait close log file timeout:3s") + return + default: + // 1ms 写入文件500条,最多等待 15ms 检查一次就行 + time.Sleep(15 * time.Millisecond) + } + } +} diff --git a/trunk/goutil/logUtil/impl-localfile/logger.go b/trunk/goutil/logUtil/impl-localfile/logger.go new file mode 100644 index 0000000..a0bca06 --- /dev/null +++ b/trunk/goutil/logUtil/impl-localfile/logger.go @@ -0,0 +1,223 @@ +package impl_localfile + +import ( + "fmt" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + + "goutil/fileUtil" + "goutil/timeUtil" +) + +const ( + con_SEPERATOR = "\n------------------------------------------------------\n" +) + +// Logger +// @description: 日志对象 +type Logger struct { + logPath string + loggerMap map[levelType]*fileLog + bufPool *sync.Pool + + // 压缩属性 + // 上一次日志压缩的日期 时间戳 + preCompressDate int64 + // 压缩锁对象 + compressLock sync.Mutex +} + +// NewLogger +// @description: 构造日志对象 +// parameter: +// +// @*Logger: +func NewLogger() *Logger { + log := &Logger{ + logPath: "Log", + loggerMap: make(map[levelType]*fileLog, 4), + bufPool: &sync.Pool{ + New: func() interface{} { + return &strings.Builder{} + }, + }, + } + + // 初始化基础日志 + for lv := range levels { + log.loggerMap[levelType(lv)] = newLog(log.logPath, levelType(lv)) + } + return log +} + +// log +// @description: 记录日志 +// parameter: +// +// @receiver l: +// @logInfo: +// @lv: +// @ifIncludeHour: +// +// return: +func (l *Logger) log(logInfo string, lv levelType, ifIncludeHour bool) { + buf := l.bufPool.Get().(*strings.Builder) + defer l.bufPool.Put(buf) + buf.Reset() + + // 组装所有需要写入的内容 + // 获取当前时间 + buf.WriteString(time.Now().Format("2006-01-02 15:04:05")) + buf.WriteString("---->\n") + buf.WriteString(logInfo) + // 加上最后的分隔符 + buf.WriteString(con_SEPERATOR) + + // 写入之前检查 + l.checkCompress() + + // 构造对象并添加到队列中 + l.loggerMap[lv].msgChan <- newLogObject(buf.String(), lv, ifIncludeHour) +} + +// SetLogPath +// @description: 设置文件路径 +// parameter: +// +// @receiver l: +// @_logPath: +// +// return: +func (l *Logger) SetLogPath(_logPath string) { + l.logPath = _logPath + for _, lf := range l.loggerMap { + lf.SetLogPath(_logPath) + } +} + +// checkCompress +// @description: 压缩 +// parameter: +// +// @receiver l: +// +// return: +func (l *Logger) checkCompress() { + // 不用压缩,直接返回 + if !l.preCompress() { + return + } + + // 日志压缩 + go l.doCompress() +} + +// closeLogFile +// @description: 关闭日志文件 +// parameter: +// +// @receiver l: +// +// return: +func (l *Logger) closeLogFile() { + // 需要压缩文件,关闭已经打开的文件 + for _, lf := range l.loggerMap { + _ = lf.f.Close() + } +} + +// preCompress 确定是否要压缩 +func (l *Logger) preCompress() bool { + // 检查是否需要进行数据压缩 + nowDate := timeUtil.GetDate(time.Now()).Unix() + if nowDate == l.preCompressDate { + return false + } + + l.compressLock.Lock() + defer l.compressLock.Unlock() + + // 上一次压缩的时间 + if nowDate == l.preCompressDate { + return false + } + + l.closeLogFile() + + l.preCompressDate = nowDate + + return true +} + +// 日志压缩 +func (l *Logger) doCompress() { + defer func() { + if r := recover(); r != nil { + // 将错误输出,而不是记录到文件,是因为可能导致死循环 + fmt.Println(r) + } + }() + + // 获取昨天的日期,并获取昨天对应的文件夹 + yesterday := time.Now().AddDate(0, 0, -1) + dateString := timeUtil.Format(yesterday, "yyyy-MM-dd") + fileAbsoluteDirectory := filepath.Join(l.logPath, strconv.Itoa(yesterday.Year()), strconv.Itoa(int(yesterday.Month()))) + + // 判断是否已经存在压缩文件 + compressFileName := fmt.Sprintf("%s.tar.gz", dateString) + compressAbsolutePath := filepath.Join(fileAbsoluteDirectory, compressFileName) + if exists, err := fileUtil.IsFileExists(compressAbsolutePath); err == nil && exists { + return + } + + // 获取昨天的文件列表 + fileList, err := fileUtil.GetFileList2(fileAbsoluteDirectory, dateString, con_FILE_SUFFIX) + if err != nil { + fmt.Printf("logUtil.compress.fileUtil.GetFileList2 err:%s\n", err) + return + } + if len(fileList) == 0 { + return + } + + // 进行tar操作,得到yyyy-MM-dd.tar + tarFileName := fmt.Sprintf("%s.tar", dateString) + tarAbsolutePath := filepath.Join(fileAbsoluteDirectory, tarFileName) + if err := fileUtil.Tar(fileList, tarAbsolutePath); err != nil { + fmt.Printf("logUtil.compress.fileUtil.Tar err:%s\n", err) + } + + // 进行gzip操作,得到yyyy-MM-dd.tar.gz + if err := fileUtil.Gzip(tarAbsolutePath, ""); err != nil { + fmt.Printf("logUtil.compress.fileUtil.Gzip err:%s\n", err) + } + + // 删除原始文件 + for _, item := range fileList { + _ = fileUtil.DeleteFile(item) + } + + // 删除tar文件 + _ = fileUtil.DeleteFile(tarAbsolutePath) +} + +// getLog +// @description: 组装日志 +// parameter: +// +// @format: +// @args: +// +// return: +// +// @string: +func getLog(format string, args ...interface{}) string { + if len(args) == 0 { + return format + } else { + return fmt.Sprintf(format, args...) + } +} diff --git a/trunk/goutil/logUtil/impl-localfile/logtype.go b/trunk/goutil/logUtil/impl-localfile/logtype.go new file mode 100644 index 0000000..0002341 --- /dev/null +++ b/trunk/goutil/logUtil/impl-localfile/logtype.go @@ -0,0 +1,33 @@ +package impl_localfile + +type levelType int + +// 日志等级 +const ( + // info 信息 + info levelType = iota + + // warn 警告 + warn + + // debug 调试 + debug + + // _error 错误 + _error + + // fatal 致命 + fatal +) + +var levels = [...]string{ + "info", + "warn", + "debug", + "error", + "fatal", +} + +func (t levelType) String() string { + return levels[t] +} diff --git a/trunk/goutil/logUtil/impl-localfile/model.go b/trunk/goutil/logUtil/impl-localfile/model.go new file mode 100644 index 0000000..db70bec --- /dev/null +++ b/trunk/goutil/logUtil/impl-localfile/model.go @@ -0,0 +1,20 @@ +package impl_localfile + +type logObject struct { + // 日志信息 + logInfo string + + // 日志等级 + level levelType + + // 日志文件名称是否包含小时 + ifIncludeHour bool +} + +func newLogObject(logInfo string, level levelType, ifIncludeHour bool) logObject { + return logObject{ + logInfo: logInfo, + level: level, + ifIncludeHour: ifIncludeHour, + } +} diff --git a/trunk/goutil/logUtil/interface.go b/trunk/goutil/logUtil/interface.go new file mode 100644 index 0000000..2ace17a --- /dev/null +++ b/trunk/goutil/logUtil/interface.go @@ -0,0 +1,52 @@ +package logUtil + +// ILog +// @description: 日志接口 +type ILog interface { + // InfoLog + // @description: 信息日志记录 + // parameter: + // @format:日志格式 + // @args:参数列表 + // return: + InfoLog(format string, args ...interface{}) + + // DebugLog + // @description: 调试日志记录 + // parameter: + // @format:日志格式 + // @args:参数列表 + // return: + DebugLog(format string, args ...interface{}) + + // WarnLog + // @description: 警告日志记录 + // parameter: + // @format:日志格式 + // @args:参数列表 + // return: + WarnLog(format string, args ...interface{}) + + // ErrorLog + // @description: 错误日志记录 + // parameter: + // @format:日志格式 + // @args:参数列表 + // return: + ErrorLog(format string, args ...interface{}) + + // FatalLog + // @description: 致命错误日志记录 + // parameter: + // @format:日志格式 + // @args:参数列表 + // return: + FatalLog(format string, args ...interface{}) + + // CloseLog + // @description: 关闭日志 + // parameter: + // @waitFinish:是否等待日志 + // return: + CloseLog(waitFinish bool) +} diff --git a/trunk/goutil/logUtil/log.go b/trunk/goutil/logUtil/log.go new file mode 100644 index 0000000..8410351 --- /dev/null +++ b/trunk/goutil/logUtil/log.go @@ -0,0 +1,57 @@ +package logUtil + +import ( + "goutil/logUtil/impl-localfile" +) + +// 定义一个全局的日志对象 +var ( + // 日志列表 + logs []ILog + + // 文件log + fileLog *impl_localfile.Logger +) + +func init() { + // 提供默认的日志对象 + fileLog = impl_localfile.NewLogger() + logs = append(logs, fileLog) +} + +// AddLogger +// @description: 添加日志对象 +// parameter: +// +// @l:日志实现 +// +// return: +func SettingLogs(_logs []ILog) { + if _logs == nil || len(_logs) == 0 { + panic("_logs不能为nil或者len(_logs)==0") + } + + logs = _logs +} + +// GetLocalFileLog +// @description: 获取文件日志对象 +// parameter: +// return: +// +// @*log_localfile.Logger: +func GetLocalFileLog() *impl_localfile.Logger { + return fileLog +} + +// SetLogPath +// @description: 设置文件日志路径 +// parameter: +// +// @_logPath:路径 +// +// return: +// Deprecated: use GetLocalFileLog().SetLogPath(_logPath) api instead +func SetLogPath(_logPath string) { + fileLog.SetLogPath(_logPath) +} diff --git a/trunk/goutil/logUtil/log_api.go b/trunk/goutil/logUtil/log_api.go new file mode 100644 index 0000000..92f898c --- /dev/null +++ b/trunk/goutil/logUtil/log_api.go @@ -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---------------------------- diff --git a/trunk/goutil/logUtil/log_test.go b/trunk/goutil/logUtil/log_test.go new file mode 100644 index 0000000..c35cf5e --- /dev/null +++ b/trunk/goutil/logUtil/log_test.go @@ -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) +} diff --git a/trunk/goutil/logUtil/log_type.go b/trunk/goutil/logUtil/log_type.go new file mode 100644 index 0000000..5d618f3 --- /dev/null +++ b/trunk/goutil/logUtil/log_type.go @@ -0,0 +1,44 @@ +package logUtil + +type LogType int + +// 日志等级 +const ( + // Info 信息 + Info LogType = iota + + // Warn 警告 + Warn + + // Debug 调试 + Debug + + // Error 错误 + Error + + // Fatal 致命 + Fatal +) + +// String +// @description: 类型转化为字符串 +// parameter: +// @receiver t: +// return: +// @string: +func (t LogType) String() string { + switch t { + case Info: + return "Info" + case Warn: + return "Warn" + case Debug: + return "Debug" + case Error: + return "Error" + case Fatal: + return "Fatal" + } + + return "LogTypeDefault" +} diff --git a/trunk/goutil/logUtil/readme.md b/trunk/goutil/logUtil/readme.md new file mode 100644 index 0000000..034133b --- /dev/null +++ b/trunk/goutil/logUtil/readme.md @@ -0,0 +1,50 @@ +### 日志帮助类,默认实现 文本文件,控制台,elasticsearch(简写es) 三种记录方式。 + + 1.在调用设置参数方法(SettingLogs)之前,会开启文本文件记录方式. + 2.在调用设置参数方法(SettingLogs)之后,默认记录方式关闭,根据参数配置决定开启哪些日志记录方式. + 3.注意:由于日志采用了缓存,关闭程序可能会导致未写入的日志丢失. + 4.日志帮助类内部错误日志默认路径为程序执行目录下Log子目录下,可以帮助排除日志帮助类内部异常. + +#### =======================>注意事项<========================= + + 1.生产环境禁止开始控制台输出,频繁的控制台输出造成性能瓶颈。 + +#### =======================>使用方法说明<========================= + +1.引入包 +2.配置日志输出,如果采用默认输出,可以跳过这一步 +3.调用方法输出日志 + +```go +import ( + "goutil/logUtil" + impl_console "goutil/logUtil/impl-console" + impl_es "goutil/logUtil/impl-es" + impl_localfile "goutil/logUtil/impl-localfile" +) + +func init(){ + //添加控制台日志 + 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 { + panic("esLog 创建失败") + } + + // 设置程序日志 + logUtil.SettingLogs([]ILog{consoleLog, eslog, impl_localfile.NewLogger()}) +} +``` + +3.调用输出方法输出 + +```go +logUtil.DebugLog("加载游戏配置start"); +logUtil.InfoLog("加载游戏配置start"); +logUtil.WarnLog("加载游戏配置start"); +logUtil.ErrorLog("加载游戏配置start"); +logUtil.FatalLog("加载游戏配置start"); +``` \ No newline at end of file diff --git a/trunk/goutil/logUtilPlus/logUtilPlus.go b/trunk/goutil/logUtilPlus/logUtilPlus.go new file mode 100644 index 0000000..afbcfec --- /dev/null +++ b/trunk/goutil/logUtilPlus/logUtilPlus.go @@ -0,0 +1,146 @@ +package logUtilPlus + +import ( + "fmt" + "goutil/debugUtil" + "goutil/esLogUtil" + "goutil/logUtil" + "log" + "strings" +) + +var ( + enableDebugLog = true + enableInfoLog = true + enableWarnLog = true +) + +// 设置日志级别 +// debug / all 时全部开启 +// info 记录 info/warn/error/fatal +// warn 记录 warn/error/fatal +// error 记录 error/fatal +func SetLevel(level string) { + // 大写不敏感 + level = strings.ToLower(level) + + //全部开启 + if level == "debug" || level == "all" { + // debug / all 时全部开启 + enableInfoLog = true + enableDebugLog = true + enableWarnLog = true + } else if level == "info" { + // info 记录 info/warn/error/fatal + enableDebugLog = false + enableInfoLog = true + enableWarnLog = true + } else if level == "warn" { + // warn 记录 warn/error/fatal + enableDebugLog = false + enableInfoLog = false + enableWarnLog = true + } else if level == "error" { + // error 记录 error/fatal + enableInfoLog = false + enableDebugLog = false + enableWarnLog = false + } +} + +// 启动ES日志系统(不调用则不启动ES日志系统,默认记录本地) +// 参数: +// +// esUrls:ES地址(多个地址使用,分割) +// name:IndexName +// serverGroupId:服务器组Id +// +// 返回值: +// +// 结果状态 +func Start(esUrls string, name string, serverGroupId int32) { + esLogUtil.Start(esUrls, name, serverGroupId) +} + +// 停止服务 +func Stop() { + esLogUtil.Stop() +} + +// 调试日志 +func InfoLog(format string, args ...interface{}) { + if !enableInfoLog { + return + } + PrintAndWriteLog(logUtil.Info, format, args...) +} + +// 警告日志 +func WarnLog(format string, args ...interface{}) { + if !enableWarnLog { + return + } + PrintAndWriteLog(logUtil.Warn, format, args...) +} + +// 调试日志 +func DebugLog(format string, args ...interface{}) { + if !enableDebugLog { + return + } + PrintAndWriteLog(logUtil.Debug, format, args...) +} + +// 错误日志 +func ErrorLog(format string, args ...interface{}) { + PrintAndWriteLog(logUtil.Error, format, args...) +} + +// 致命错误日志 +func FatalLog(format string, args ...interface{}) { + PrintAndWriteLog(logUtil.Fatal, format, args...) +} + +// 打印到控制台并写日志 +func PrintAndWriteLog(logType logUtil.LogType, format string, args ...interface{}) { + //控制台打印一行 + PrintLog(format, args...) + + if len(args) <= 0 { + logUtil.NormalLog(format, logType) + esLogUtil.NormalLog(format, logType) + } else { + logUtil.NormalLog(fmt.Sprintf(format, args...), logType) + esLogUtil.NormalLog(fmt.Sprintf(format, args...), logType) + } + + if debugUtil.IsDebug() && (logType == logUtil.Warn || logType == logUtil.Error || logType == logUtil.Fatal) { + StdLog(format, args...) + } +} + +// 控制台打印 +func PrintLog(format string, args ...interface{}) { + //避免非DEBUG模式下组装字符串,提前判断一次 + if debugUtil.IsDebug() { + debugUtil.Println(fmt.Sprintf(format, args...)) + } +} + +// 标准库控制台输出打印 +func StdLog(format string, args ...interface{}) { + if len(args) <= 0 { + log.Print(format) + } else { + log.Print(fmt.Sprintf(format, args...)) + } +} + +// 记录未知错误日志 +// 参数: +// +// err:recover获取到的错误对象 +// args:附加参数 +func LogUnknownError(err interface{}, args ...string) { + logUtil.LogUnknownError(err, args...) +} diff --git a/trunk/goutil/logUtilPlus/logUtilPlus_test.go b/trunk/goutil/logUtilPlus/logUtilPlus_test.go new file mode 100644 index 0000000..5586a8f --- /dev/null +++ b/trunk/goutil/logUtilPlus/logUtilPlus_test.go @@ -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() +} diff --git a/trunk/goutil/lz4Util/doc.go b/trunk/goutil/lz4Util/doc.go new file mode 100644 index 0000000..0a3c4f8 --- /dev/null +++ b/trunk/goutil/lz4Util/doc.go @@ -0,0 +1,4 @@ +/* +lz4压缩、解压缩相关的助手包 +*/ +package lz4Util diff --git a/trunk/goutil/lz4Util/lz4.go b/trunk/goutil/lz4Util/lz4.go new file mode 100644 index 0000000..f466ecc --- /dev/null +++ b/trunk/goutil/lz4Util/lz4.go @@ -0,0 +1,36 @@ +package lz4Util + +import ( + "fmt" + "github.com/bkaradzic/go-lz4" +) + +// 压缩 +// data:待压缩数据 +// 返回值: +// 压缩后数据 +// 对应的错误 +func Compress(data []byte)([]byte, error) { + compressed, err := lz4.Encode(nil, data) + if err != nil { + fmt.Println("Failed to encode:", err) + return nil,err + } + + return compressed,nil +} + +// 解压缩 +// data:待解压缩数据 +// 返回值: +// 解压缩后数据 +// 对应的错误 +func Decompress(data []byte) ([]byte, error){ + decompressed, err := lz4.Decode(nil, data) + if err != nil { + fmt.Println("Failed to decode:", err) + return nil,err + } + + return decompressed,nil +} \ No newline at end of file diff --git a/trunk/goutil/lz4Util/lz4_test.go b/trunk/goutil/lz4Util/lz4_test.go new file mode 100644 index 0000000..39b08e6 --- /dev/null +++ b/trunk/goutil/lz4Util/lz4_test.go @@ -0,0 +1,65 @@ +package lz4Util + +import ( + "fmt" + "strings" + "testing" +) + +var ( + InitString = `{"Code":4,"Message":"IPNotAuthorized","Data":null}` + CompressBytes []byte +) + +//正确性测试 +func TestCompress(t *testing.T){ + s := "hello world" + repeatCount:= 1000 + toCompress := []byte(strings.Repeat(s, repeatCount)) + compressed,err := Compress(toCompress) + if err!=nil{ + fmt.Println(err) + } + fmt.Println("compressed Data:", string(compressed)) + + //decompress + decompressed,err := Decompress(compressed) + if err!=nil{ + fmt.Println(err) + } + + if strings.Repeat(s, repeatCount)!=string(decompressed){ + fmt.Println("有问题") + }else{ + fmt.Println("没问题") + } + + fmt.Println("\ndecompressed Data:", string(decompressed)) +} + +//BenchmarkCompress-12 28647 37948 ns/op +func BenchmarkCompress(b *testing.B) { + toCompress := []byte(strings.Repeat(InitString, 100)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _,err := Compress(toCompress) + if err!=nil{ + b.Log(err) + } + } + b.StopTimer() +} + +//BenchmarkDeCompress-12 267382 4127 ns/op +func BenchmarkDeCompress(b *testing.B) { + toCompress := []byte(strings.Repeat(InitString, 100)) + compressedData,_ := Compress(toCompress) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _,err := Decompress(compressedData) + if err!=nil{ + b.Log(err) + } + } + b.StopTimer() +} \ No newline at end of file diff --git a/trunk/goutil/mailUtil/doc.go b/trunk/goutil/mailUtil/doc.go new file mode 100644 index 0000000..9df80d3 --- /dev/null +++ b/trunk/goutil/mailUtil/doc.go @@ -0,0 +1,5 @@ +package mailUtil + +/* +邮件助手(用于发送邮件) +*/ diff --git a/trunk/goutil/mailUtil/simpleClient.go b/trunk/goutil/mailUtil/simpleClient.go new file mode 100644 index 0000000..bd36802 --- /dev/null +++ b/trunk/goutil/mailUtil/simpleClient.go @@ -0,0 +1,193 @@ +package mailUtil + +import ( + "bytes" + "crypto/tls" + "encoding/base64" + "fmt" + "io/ioutil" + "net" + "net/mail" + "net/smtp" + "path/filepath" + "strings" +) + +// SMTPClient实现 +type simpleClient struct { + host string + port int + isSSL bool + + senderName string + senderAddr string + senderPwd string +} + +// 返回一个simpleClient作为SMTPClient接口 +func SimpleSMTPClient(_host string, _port int, _isSSL bool, + name, address, password string) SMTPClient { + + return &simpleClient{ + host: _host, + port: _port, + isSSL: _isSSL, + + senderName: name, + senderAddr: address, + senderPwd: password, + } +} + +func (this *simpleClient) SetServer(_host string, _port int, _isSSL bool) { + this.host = _host + this.port = _port + this.isSSL = _isSSL +} + +func (this *simpleClient) SetSender(name, address, password string) { + this.senderName = name + this.senderAddr = address + this.senderPwd = password +} + +//发送邮件: +// mailTo 接收方列表 +// subject 主题 +// body 正文 +// isHtmlBody 正文是否html格式 +// attachFiles 附件 +func (this *simpleClient) SendMail( + mailTo []string, + subject, body string, isHtmlBody bool, + attachFiles []string) (err error) { + + defer func() { + if r := recover(); r != nil { + err = r.(error) + } + }() + + // 创建连接 + var conn net.Conn + + if this.isSSL { + // TLS config + tlsconfig := &tls.Config{ + InsecureSkipVerify: true, + ServerName: this.host, + } + conn, err = tls.Dial("tcp", fmt.Sprintf("%s:%d", this.host, this.port), tlsconfig) + } else { + conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", this.host, this.port)) + } + if err != nil { + return err + } + defer conn.Close() + + // 创建smtp.Client + c, err := smtp.NewClient(conn, this.host) + if err != nil { + return err + } + + // 验证信息 + auth := smtp.PlainAuth("", this.senderAddr, this.senderPwd, this.host) + if err = c.Auth(auth); err != nil { + return err + } + + // 发送方 + from := mail.Address{this.senderName, this.senderAddr} + // 接收方 + to := make([]string, 0, len(mailTo)) + for _, v := range mailTo { + to = append(to, "<"+v+">") + } + + // To && From + if err = c.Mail(from.Address); err != nil { + return err + } + + for _, v := range mailTo { + if err = c.Rcpt(v); err != nil { + return err + } + } + + // 边界 + boundary := "a40acf3c8b7200fc6b04c2f1b3da" + + buff := bytes.NewBuffer(nil) + + // 写入基本信息 + buff.WriteString(fmt.Sprintf("From: %s\r\n", from.String())) + buff.WriteString(fmt.Sprintf("To: %s\r\n", strings.Join(to, ", "))) + buff.WriteString(fmt.Sprintf("Subject: %s\r\n", subject)) + + // 写入邮件头部信息 + if len(attachFiles) > 0 { + buff.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\r\n", boundary)) + + // 写入正文的边界信息 + buff.WriteString(fmt.Sprintf("\r\n--%s\r\n", boundary)) + } + + // 写入正文头部 + if isHtmlBody { + buff.WriteString(fmt.Sprintf("Content-Type: text/html; charset=\"utf-8\"\r\n")) + } else { + buff.WriteString(fmt.Sprintf("Content-Type: text/plain; charset=\"utf-8\"\r\n")) + } + buff.WriteString("\r\n") + // 写入正文内容 + buff.WriteString(body) + + if len(attachFiles) > 0 { + for _, file := range attachFiles { + fileBytes, err := ioutil.ReadFile(file) + if err != nil { + return err + } + + _, fileName := filepath.Split(file) + + // 写入文件信息 + buff.WriteString(fmt.Sprintf("\r\n\r\n--%s\r\n", boundary)) + buff.WriteString("Content-Type: application/octet-stream\r\n") + buff.WriteString("Content-Transfer-Encoding: base64\r\n") + buff.WriteString(fmt.Sprintf("Content-Disposition: attachment; filename=\"%s\"\r\n\r\n", fileName)) + + // 写入文件数据 + b := make([]byte, base64.StdEncoding.EncodedLen(len(fileBytes))) + base64.StdEncoding.Encode(b, fileBytes) + buff.Write(b) + } + + // 写入结束标识 + buff.WriteString(fmt.Sprintf("\r\n--%s--", boundary)) + } + + // Data + w, err := c.Data() + if err != nil { + return err + } + + // 写入邮件数据 + _, err = w.Write(buff.Bytes()) + if err != nil { + return err + } + + err = w.Close() + if err != nil { + return err + } + + c.Quit() + + return nil +} diff --git a/trunk/goutil/mailUtil/simpleClient_test.go b/trunk/goutil/mailUtil/simpleClient_test.go new file mode 100644 index 0000000..567adf9 --- /dev/null +++ b/trunk/goutil/mailUtil/simpleClient_test.go @@ -0,0 +1,20 @@ +package mailUtil + +import ( + "testing" +) + +func TestSendMail(t *testing.T) { + svr := SimpleSMTPClient("smtp.exmail.qq.com", 465, true, "name", "service@public.com", "Sv123456") + err := svr.SendMail([]string{"164760769@qq.com"}, + "邮件发送测试", + "

这是邮件正文

", + true, + []string{ + "./doc.go", + "./simpleClient_test.go", + }) + if err != nil { + t.Error(err) + } +} diff --git a/trunk/goutil/mailUtil/smtpClient.go b/trunk/goutil/mailUtil/smtpClient.go new file mode 100644 index 0000000..0510b41 --- /dev/null +++ b/trunk/goutil/mailUtil/smtpClient.go @@ -0,0 +1,18 @@ +package mailUtil + +// 定义SMTPClient接口 +type SMTPClient interface { + // 设置服务器 + SetServer(host string, port int, isSSL bool) + + // 设置发件箱 + SetSender(name, address, password string) + + //发送邮件: + // mailTo 接收方列表 + // subject 主题 + // body 正文 + // isHtmlBody 正文是否html格式 + // attachFiles 附件 + SendMail(mailTo []string, subject, body string, isHtmlBody bool, attachFiles []string) error +} diff --git a/trunk/goutil/mathUtil/doc.go b/trunk/goutil/mathUtil/doc.go new file mode 100644 index 0000000..2233230 --- /dev/null +++ b/trunk/goutil/mathUtil/doc.go @@ -0,0 +1,4 @@ +/* +数学助手包,提供一些与数学相关的助手方法 +*/ +package mathUtil diff --git a/trunk/goutil/mathUtil/iWeight.go b/trunk/goutil/mathUtil/iWeight.go new file mode 100644 index 0000000..c263dd1 --- /dev/null +++ b/trunk/goutil/mathUtil/iWeight.go @@ -0,0 +1,5 @@ +package mathUtil + +type IWeight interface { + GetWeight() int +} diff --git a/trunk/goutil/mathUtil/intRegion.go b/trunk/goutil/mathUtil/intRegion.go new file mode 100644 index 0000000..cb087e6 --- /dev/null +++ b/trunk/goutil/mathUtil/intRegion.go @@ -0,0 +1,33 @@ +package mathUtil + +import ( + "fmt" +) + +// int类型区间对象,表示连续的int类型区间 +type IntRegion struct { + Lower int + Upper int +} + +func (this *IntRegion) String() string { + return fmt.Sprintf("%d-%d", this.Lower, this.Upper) +} + +// 是否包含指定的值 +func (this *IntRegion) Contains(value int) bool { + return this.Lower <= value && value <= this.Upper +} + +// 是否是有序的 +func (this *IntRegion) IsSorted() bool { + return this.Lower < this.Upper +} + +// 创建int类型区间对象 +func NewIntRegion(lower, upper int) *IntRegion { + return &IntRegion{ + Lower: lower, + Upper: upper, + } +} diff --git a/trunk/goutil/mathUtil/math.go b/trunk/goutil/mathUtil/math.go new file mode 100644 index 0000000..c1e47c4 --- /dev/null +++ b/trunk/goutil/mathUtil/math.go @@ -0,0 +1,186 @@ +package mathUtil + +import ( + "sort" +) + +// 判断传入的byte型数组是否连续 +func IsContinuous_byte(list []byte) bool { + if len(list) == 0 || len(list) == 1 { + return true + } + + list_int64 := make([]int64, len(list), len(list)) + for i, v := range list { + list_int64[i] = int64(v) + } + + return IsContinuous_int64(list_int64) +} + +// 判断传入的int型数组是否连续 +func IsContinuous_int(list []int) bool { + if len(list) == 0 || len(list) == 1 { + return true + } + + list_int64 := make([]int64, len(list), len(list)) + for i, v := range list { + list_int64[i] = int64(v) + } + + return IsContinuous_int64(list_int64) +} + +// 判断传入的int型数组是否连续 +func IsContinuous_int32(list []int32) bool { + if len(list) == 0 || len(list) == 1 { + return true + } + + list_int64 := make([]int64, len(list), len(list)) + for i, v := range list { + list_int64[i] = int64(v) + } + + return IsContinuous_int64(list_int64) +} + +// 判断传入的int型数组是否连续 +func IsContinuous_int64(list []int64) bool { + if len(list) == 0 || len(list) == 1 { + return true + } + + if int64(len(list)) != list[len(list)-1]-list[0]+1 { + return false + } + + for i := 0; i < len(list)-1; i++ { + if list[i]+1 != list[i+1] { + return false + } + } + + return true +} + +// 判断区间是否连续 +func IsContinuous_Region(list []*IntRegion) bool { + if len(list) == 0 || len(list) == 1 { + return true + } + + sort.Slice(list, func(i, j int) bool { return list[i].Lower < list[j].Lower }) + + for i := 0; i < len(list)-1; i++ { + if list[i].IsSorted() == false || list[i+1].IsSorted() == false { + return false + } + + if list[i].Upper+1 != list[i+1].Lower { + return false + } + } + + return true +} + +// 判断传入的概率是否全覆盖 +func IsOddFullConvered(list []*IntRegion, min, max int) bool { + if len(list) == 0 { + return false + } + + if list[0].Lower != min || list[len(list)-1].Upper != max { + return false + } + + sort.Slice(list, func(i, j int) bool { return list[i].Lower < list[j].Lower }) + + for i := 0; i < len(list)-1; i++ { + if list[i].IsSorted() == false || list[i+1].IsSorted() == false { + return false + } + + if list[i].Upper+1 != list[i+1].Lower { + return false + } + } + + return true +} + +func IsDistinct_byte(list []byte) (result bool) { + if len(list) == 0 || len(list) == 1 { + result = true + return + } + + list_int64 := make([]int64, len(list), len(list)) + for i, v := range list { + list_int64[i] = int64(v) + } + + return IsDistinct_int64(list_int64) +} + +func IsDistinct_int(list []int) (result bool) { + if len(list) == 0 || len(list) == 1 { + result = true + return + } + + list_int64 := make([]int64, len(list), len(list)) + for i, v := range list { + list_int64[i] = int64(v) + } + + return IsDistinct_int64(list_int64) +} + +func IsDistinct_int32(list []int32) (result bool) { + if len(list) == 0 || len(list) == 1 { + result = true + return + } + + list_int64 := make([]int64, len(list), len(list)) + for i, v := range list { + list_int64[i] = int64(v) + } + + return IsDistinct_int64(list_int64) +} + +// 判断int64数组是否内容唯一 +func IsDistinct_int64(list []int64) (result bool) { + if len(list) == 0 || len(list) == 1 { + result = true + return + } + + sort.Slice(list, func(i, j int) bool { return list[i] < list[j] }) + + for i := 0; i < len(list)-1; i++ { + if list[i] == list[i+1] { + result = false + return + } + } + + result = true + return +} + +//// 将int转成uint类型(因goutil指定go版本过低,不支持泛型,未实际加入;要使用的人取消注释,并指定允许泛型的go版本) +//// 应用场景:atomic.AddUint64 想减1时 +//// +//// x := uint64(1000) +//// y := int64(-1) +//// atomic.AddUint64(&x, intToUint[int64, uint64](y)) +//func intToUint[T1 int8 | int16 | int32 | int64 | int, T2 uint8 | uint16 | uint32 | uint64 | uint](x T1) T2 { +// // 写到函数内,两种方式都可以;若直接使用,只能使用^T2(-x - 1);若使用T2(x)方式,x为负值时转为uint类语法检查无法通过 +// return ^T2(-x - 1) +// // return T2(x) +//} diff --git a/trunk/goutil/mathUtil/math_test.go b/trunk/goutil/mathUtil/math_test.go new file mode 100644 index 0000000..8ff61f4 --- /dev/null +++ b/trunk/goutil/mathUtil/math_test.go @@ -0,0 +1,267 @@ +package mathUtil + +import ( + "fmt" + "testing" +) + +func TestIsContinuous_byte(t *testing.T) { + list := make([]byte, 0, 8) + if IsContinuous_byte(list) == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, 1) + if IsContinuous_byte(list) == false { + t.Errorf("it's should be true, but now false-------2") + } + + list = append(list, 2) + list = append(list, 3) + list = append(list, 4) + list = append(list, 5) + if IsContinuous_byte(list) == false { + t.Errorf("it's should be true, but now false-------3") + } + + list = append(list, 10) + if IsContinuous_byte(list) == true { + t.Errorf("it's should be false, but now true-------3") + } +} + +func TestIsContinuous_int(t *testing.T) { + list := make([]int, 0, 8) + if IsContinuous_int(list) == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, 11) + if IsContinuous_int(list) == false { + t.Errorf("it's should be true, but now false-------2") + } + + list = append(list, 12) + list = append(list, 13) + list = append(list, 14) + list = append(list, 15) + if IsContinuous_int(list) == false { + t.Errorf("it's should be true, but now false-------3") + } + + list = append(list, 10) + if IsContinuous_int(list) == true { + t.Errorf("it's should be false, but now true-------3") + } +} + +func TestIsContinuous_int32(t *testing.T) { + list := make([]int32, 0, 8) + if IsContinuous_int32(list) == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, 1) + if IsContinuous_int32(list) == false { + t.Errorf("it's should be true, but now false-------2") + } + + list = append(list, 2) + list = append(list, 3) + list = append(list, 4) + list = append(list, 5) + if IsContinuous_int32(list) == false { + t.Errorf("it's should be true, but now false-------3") + } + + list = append(list, 10) + if IsContinuous_int32(list) == true { + t.Errorf("it's should be false, but now true-------3") + } +} + +func TestIsContinuous_int64(t *testing.T) { + list := make([]int64, 0, 8) + if IsContinuous_int64(list) == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, 1) + if IsContinuous_int64(list) == false { + t.Errorf("it's should be true, but now false-------2") + } + + list = append(list, 2) + list = append(list, 3) + list = append(list, 4) + list = append(list, 5) + if IsContinuous_int64(list) == false { + t.Errorf("it's should be true, but now false-------3") + } + + list = append(list, 10) + if IsContinuous_int64(list) == true { + t.Errorf("it's should be false, but now true-------3") + } +} + +func TestIsContinuous_Region(t *testing.T) { + list := make([]*IntRegion, 0, 8) + if IsContinuous_Region(list) == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, NewIntRegion(101, 110)) + if IsContinuous_Region(list) == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, NewIntRegion(1, 10)) + if IsContinuous_Region(list) == true { + t.Errorf("it's should be false, but now true-------2") + } + + list = append(list, NewIntRegion(11, 100)) + if IsContinuous_Region(list) == false { + t.Errorf("it's should be true, but now false-------3") + } + +} + +func TestIsOddFullConvered(t *testing.T) { + list := make([]*IntRegion, 0, 8) + min, max := 1, 100 + if IsOddFullConvered(list, min, max) { + t.Errorf("it's should be false, but now true-------1") + } + + list = append(list, NewIntRegion(1, 10)) + if IsOddFullConvered(list, min, max) == true { + t.Errorf("it's should be false, but now true-------2") + } + + list = append(list, NewIntRegion(11, 100)) + if IsOddFullConvered(list, min, max) == false { + t.Errorf("it's should be true, but now false-------1") + } +} + +func TestIsDistinct_byte(t *testing.T) { + list := make([]byte, 0, 8) + result := IsDistinct_byte(list) + fmt.Printf("list:%v,result:%v-------1\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, 10) + result = IsDistinct_byte(list) + fmt.Printf("list:%v,result:%v-------2\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------2") + } + + list = append(list, 10) + result = IsDistinct_byte(list) + fmt.Printf("list:%v,result:%v-------3\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------3") + } + + list = append(list, 0) + result = IsDistinct_byte(list) + fmt.Printf("list:%v,result:%v-------4\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------4") + } +} + +func TestIsDistinct_int(t *testing.T) { + list := make([]int, 0, 8) + result := IsDistinct_int(list) + fmt.Printf("list:%v,result:%v-------1\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, 10) + result = IsDistinct_int(list) + fmt.Printf("list:%v,result:%v-------2\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------2") + } + + list = append(list, 10) + result = IsDistinct_int(list) + fmt.Printf("list:%v,result:%v-------3\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------3") + } + + list = append(list, 0) + result = IsDistinct_int(list) + fmt.Printf("list:%v,result:%v-------4\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------4") + } +} + +func TestIsDistinct_int32(t *testing.T) { + list := make([]int32, 0, 8) + result := IsDistinct_int32(list) + fmt.Printf("list:%v,result:%v-------1\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, 10) + result = IsDistinct_int32(list) + fmt.Printf("list:%v,result:%v-------2\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------2") + } + + list = append(list, 10) + result = IsDistinct_int32(list) + fmt.Printf("list:%v,result:%v-------3\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------3") + } + + list = append(list, 0) + result = IsDistinct_int32(list) + fmt.Printf("list:%v,result:%v-------4\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------4") + } +} + +func TestIsDistinct_int64(t *testing.T) { + list := make([]int64, 0, 8) + result := IsDistinct_int64(list) + fmt.Printf("list:%v,result:%v-------1\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, 10) + result = IsDistinct_int64(list) + fmt.Printf("list:%v,result:%v-------2\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------2") + } + + list = append(list, 10) + result = IsDistinct_int64(list) + fmt.Printf("list:%v,result:%v-------3\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------3") + } + + list = append(list, 0) + result = IsDistinct_int64(list) + fmt.Printf("list:%v,result:%v-------4\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------4") + } +} diff --git a/trunk/goutil/mathUtil/quartile.go b/trunk/goutil/mathUtil/quartile.go new file mode 100644 index 0000000..1c98cee --- /dev/null +++ b/trunk/goutil/mathUtil/quartile.go @@ -0,0 +1,130 @@ +package mathUtil + +import ( + "errors" + "math" + "sort" +) + +//同时计算上中下四分位数 +func Quartile_Int(intList []int) (lowerquartile, midquartile, upperquartile float64, err error) { + n := len(intList) + + if n == 0 { + err = errors.New("List is empty") + return + } + + //长度大于等于2的情况 + //排序 升序 + sort.Ints(intList) + lowerquartile, err = Quartile_Int_Lower(intList, true) + if err != nil { + return + } + midquartile, err = Quartile_Int_Mid(intList, true) + if err != nil { + return + } + upperquartile, err = Quartile_Int_Upper(intList, true) + if err != nil { + return + } + return +} + +// 计算整数数组下四分为数据 +func Quartile_Int_Lower(intList []int, isSorted bool) (lowerquartile float64, err error) { + n := len(intList) + + if n == 0 { + err = errors.New("List is empty") + return + } + //如果长度为1,则上、中、下四分为数都等于该值 + if n == 1 { + lowerquartile = float64(intList[0]) + return + } + //长度大于等于2的情况 + //排序 升序 ,如果数据没有排序,则排序 + if !isSorted { + sort.Ints(intList) + } + //计算下四分位数位置 + lowerPosition := 1 + float64(n-1)*0.25 + intPart, deciPart := math.Modf(lowerPosition) + //将整数部分转换为int类型,成为取数据的索引 + index := int(intPart) + if index < 1 || index > n-1 { + err = errors.New("Quartile_Int_Lower Index Out of Range") + return + } + lowerquartile = float64(intList[index-1])*(1-deciPart) + float64(intList[index])*deciPart + + return +} + +// 计算整数数组中四分为数据 +func Quartile_Int_Mid(intList []int, isSorted bool) (midquartile float64, err error) { + n := len(intList) + + if n == 0 { + err = errors.New("List is empty") + return + } + //如果长度为1,则上、中、下四分为数都等于该值 + if n == 1 { + midquartile = float64(intList[0]) + return + } + //长度大于等于2的情况 + //排序 升序 ,如果数据没有排序,则排序 + if !isSorted { + sort.Ints(intList) + } + //计算下四分位数位置 + lowerPosition := 1 + float64(n-1)*0.5 + intPart, deciPart := math.Modf(lowerPosition) + //将整数部分转换为int类型,成为取数据的索引 + index := int(intPart) + if index < 1 || index > n-1 { + err = errors.New("Quartile_Int_Mid Index Out of Range") + return + } + midquartile = float64(intList[index-1])*(1-deciPart) + float64(intList[index])*deciPart + + return +} + +// 计算整数数组上四分为数据 +func Quartile_Int_Upper(intList []int, isSorted bool) (upperquartile float64, err error) { + n := len(intList) + + if n == 0 { + err = errors.New("List is empty") + return + } + //如果长度为1,则上、中、下四分为数都等于该值 + if n == 1 { + upperquartile = float64(intList[0]) + return + } + //长度大于等于2的情况 + //排序 升序 ,如果数据没有排序,则排序 + if !isSorted { + sort.Ints(intList) + } + //计算下四分位数位置 + lowerPosition := 1 + float64(n-1)*0.75 + intPart, deciPart := math.Modf(lowerPosition) + //将整数部分转换为int类型,成为取数据的索引 + index := int(intPart) + if index < 1 || index > n-1 { + err = errors.New("Quartile_Int_Upper Index Out of Range") + return + } + upperquartile = float64(intList[index-1])*(1-deciPart) + float64(intList[index])*deciPart + + return +} diff --git a/trunk/goutil/mathUtil/quartile_test.go b/trunk/goutil/mathUtil/quartile_test.go new file mode 100644 index 0000000..e44e785 --- /dev/null +++ b/trunk/goutil/mathUtil/quartile_test.go @@ -0,0 +1,53 @@ +package mathUtil + +import ( + "fmt" + "testing" +) + +func TestQuartile(t *testing.T) { + + intList := []int{} + var lower, mid, upper float64 + var err error + lower, mid, upper, err = Quartile_Int(intList) + if err != nil { + fmt.Printf("Error:%s\n", err) + } + + intList = []int{4} + lower, mid, upper, err = Quartile_Int(intList) + if err != nil { + fmt.Printf("Error:%s\n", err) + } + fmt.Printf("LowerQuartile:%f\n", lower) + fmt.Printf("MidQuartile:%f\n", mid) + fmt.Printf("UpperQuartile:%f\n", upper) + + intList = []int{4, 93} + lower, mid, upper, err = Quartile_Int(intList) + if err != nil { + fmt.Printf("Error:%s\n", err) + } + fmt.Printf("LowerQuartile:%f\n", lower) + fmt.Printf("MidQuartile:%f\n", mid) + fmt.Printf("UpperQuartile:%f\n", upper) + + intList = []int{4, 93, 84} + lower, mid, upper, err = Quartile_Int(intList) + if err != nil { + fmt.Printf("Error:%s\n", err) + } + fmt.Printf("LowerQuartile:%f\n", lower) + fmt.Printf("MidQuartile:%f\n", mid) + fmt.Printf("UpperQuartile:%f\n", upper) + + intList = []int{4, 93, 84, 85, 80, 37, 81, 93, 27, 12} + lower, mid, upper, err = Quartile_Int(intList) + if err != nil { + fmt.Printf("Error:%s\n", err) + } + fmt.Printf("LowerQuartile:%f\n", lower) + fmt.Printf("MidQuartile:%f\n", mid) + fmt.Printf("UpperQuartile:%f\n", upper) +} diff --git a/trunk/goutil/mathUtil/rand.go b/trunk/goutil/mathUtil/rand.go new file mode 100644 index 0000000..4e512cc --- /dev/null +++ b/trunk/goutil/mathUtil/rand.go @@ -0,0 +1,479 @@ +package mathUtil + +import ( + "errors" + "math/rand" + "time" +) + +type Rand struct { + *rand.Rand +} + +type weightRand struct { + index int + weight int +} + +// 获得Rand对象(如果是循环生成多个数据,则只需要调用本方法一次,而不能调用多次,否则会得到相同的随机值) +func GetRand() *Rand { + return &Rand{ + Rand: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +func GetRandInSeed(seed int64) *Rand { + return &Rand{ + Rand: rand.New(rand.NewSource(seed)), + } +} + +// 获取指定区间的随机数[lower, upper) +// lower:区间下限 +// upper:区间上限 +// 返回值:随机数 +func (this *Rand) GetRandRangeInt(lower, upper int) int { + return lower + this.Intn(upper-lower) +} + +// 获取指定区间的随机数[lower, upper) +// lower:区间下限 +// upper:区间上限 +// 返回值:随机数 +func (this *Rand) GetRandRangeInt32(lower, upper int32) int32 { + return lower + this.Int31n(upper-lower) +} + +// 获取指定区间的随机数[lower, upper) +// lower:区间下限 +// upper:区间上限 +// 返回值:随机数 +func (this *Rand) GetRandRangeInt64(lower, upper int64) int64 { + return lower + this.Int63n(upper-lower) +} + +// 获取随机数[0, n) +// randObj:随机对象 +// n:范围上限 +// 返回值:随机数 +func (this *Rand) GetRandInt(n int) int { + return this.Intn(n) +} + +// 获取随机数[0, n) +// n:范围上限 +// 返回值:随机数 +func (this *Rand) GetRandInt32(n int32) int32 { + return this.Int31n(n) +} + +// 获取随机数[0, n) +// randObj:随机对象 +// n:范围上限 +// 返回值:随机数 +func (this *Rand) GetRandInt64(n int64) int64 { + return this.Int63n(n) +} + +// 获取随机数[0, 1) +// randObj:随机对象 +// 返回值:随机数 +func (this *Rand) GetRandFloat32() float32 { + return this.Float32() +} + +// 获取随机数[0, 1) +// 返回值:随机数 +func (this *Rand) GetRandFloat64() float64 { + return this.Float64() +} + +// 获取随机数列表(取值范围:[minValue, maxValue],注意:maxValue-minValue < 10000,区间大小超过10000会抛出异常) +// minValue:获取随机数的区间下限值 +// maxValue:获取随机数的区间上限值 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *Rand) GetRandNumList(minValue, maxValue, count int, ifAllowDuplicate bool) ([]int, error) { + if minValue > maxValue { + return nil, errors.New("minValue can't be bigger than maxValue.") + } + + if !ifAllowDuplicate && (maxValue-minValue+1) < count { + return nil, errors.New("随机的数量超过区间的元素数量") + } + + if (maxValue - minValue + 1) > 10000 { + return nil, errors.New("随机数的区间不能大于10000") + } + + // 定义原始数据 + sourceCount := maxValue - minValue + 1 + source := make([]int, sourceCount, sourceCount) + for index := 0; index < sourceCount; index++ { + source[index] = minValue + index + } + + // 定义返回值 + resultList := make([]int, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的int列表 +// source:源列表 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *Rand) GetRandIntList(source []int, count int, ifAllowDuplicate bool) ([]int, error) { + // 在不允许重复的情况下,需要产生的随机数不能超过范围限制 + if ifAllowDuplicate == false && len(source) < count { + return nil, errors.New("随机的数量超过列表的元素数量") + } + + // 定义返回值 + resultList := make([]int, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的int32列表 +// source:源列表 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *Rand) GetRandInt32List(source []int32, count int, ifAllowDuplicate bool) ([]int32, error) { + // 在不允许重复的情况下,需要产生的随机数不能超过范围限制 + if ifAllowDuplicate == false && len(source) < count { + return nil, errors.New("随机的数量超过列表的元素数量") + } + + // 定义返回值 + resultList := make([]int32, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的int64列表 +// source:源列表 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *Rand) GetRandInt64List(source []int64, count int, ifAllowDuplicate bool) ([]int64, error) { + // 在不允许重复的情况下,需要产生的随机数不能超过范围限制 + if ifAllowDuplicate == false && len(source) < count { + return nil, errors.New("随机的数量超过列表的元素数量") + } + + // 定义返回值 + resultList := make([]int64, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的int64列表 +// source:源列表 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *Rand) GetRandStringList(source []string, count int, ifAllowDuplicate bool) ([]string, error) { + // 在不允许重复的情况下,需要产生的随机数不能超过范围限制 + if ifAllowDuplicate == false && len(source) < count { + return nil, errors.New("随机的数量超过列表的元素数量") + } + + // 定义返回值 + resultList := make([]string, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的interface{}列表 +// source:源列表 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *Rand) GetRandInterfaceList(source []interface{}, count int, ifAllowDuplicate bool) ([]interface{}, error) { + // 在不允许重复的情况下,需要产生的随机数不能超过范围限制 + if ifAllowDuplicate == false && len(source) < count { + return nil, errors.New("随机的数量超过列表的元素数量") + } + + // 定义返回值 + resultList := make([]interface{}, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的索引列表 +// count:数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机索引值列表 +func (this *Rand) getRandIndexList(maxNum, count int, ifAllowDuplicate bool) []int { + // 定义返回值 + randIndextList := make([]int, 0, count) + + // 使用源列表的数据量来初始化一个仅存放索引值的数组 + indexList := make([]int, maxNum, maxNum) + for index := 0; index < maxNum; index++ { + indexList[index] = index + } + + // 遍历列表并获取随机对象(通过不断缩小随机的范围来实现) + maxIndex := len(indexList) - 1 + for { + if len(randIndextList) < count { + // 获取随机索引(由于Next方法不取上限值,所以需要maxIndex+1) + randIndex := this.Intn(maxIndex + 1) + + // 将数据添加到列表,并增加findCount + randIndextList = append(randIndextList, indexList[randIndex]) + + // 如果不允许重复,则需要特殊处理 + if !ifAllowDuplicate { + // 将该位置的数据和最大位置的数据进行交换 + indexList[randIndex], indexList[maxIndex] = indexList[maxIndex], indexList[randIndex] + + // 将随机的范围缩小 + maxIndex -= 1 + } + } else { + break + } + } + + return randIndextList +} + +// 获取带权重的随机数据 +// source:源数据 +// 返回值 +// 数据项 +// 错误对象 +func (this *Rand) GetRandWeight(source []IWeight) (result IWeight, err error) { + if source == nil || len(source) == 0 { + err = errors.New("待随机的列表为空") + return + } + + // 计算出总的数据量,并随机一个[0, total)的值 + total := 0 + for _, item := range source { + total += item.GetWeight() + } + + randNum := this.GetRandInt(total) + + // 根据随机出来的值,判断位于哪个区间 + total = 0 + for _, item := range source { + total += item.GetWeight() + if randNum < total { + result = item + return + } + } + + err = errors.New("未找到有效的数据") + return +} + +// 获取带权重的随机数据 +// source:源数据 +// count:返回数量 +// ifAllowDuplicate:返回索引中是否允许包含重复项 +// 返回值 +// 数据项的索引 +// 错误对象 +func (this *Rand) GetRandItemsByWeight(source []IWeight, count int, ifAllowDuplicate bool) ([]int, error) { + var err error + if source == nil || len(source) == 0 { + err = errors.New("待随机的列表为空") + return nil, err + } + if count <= 0 { + err = errors.New("count 必须大于0") + return nil, err + } + var lenCount int = len(source) + if lenCount < count && ifAllowDuplicate == false { + err = errors.New("source长度小于count,并且设定为不可重复,请修复") + return nil, err + } + + // 准备数据 + total := 0 + weightList := make([]*weightRand, 0, lenCount) + for i, item := range source { + tempWeight := item.GetWeight() + weightList = append(weightList, &weightRand{index: i, weight: tempWeight}) + total = total + tempWeight + } + + if lenCount <= count { + return this.countLessThanRand(count, total, weightList), nil + } + + return this.countIsGreaterThanRand(count, total, weightList), nil +} + +// 获取带权重的随机数据(列表元素数量<=需要的数量) +// count:需要数量 +// total:总权重 +// weightList:权重列表 +// 返回值 +// 数据项的索引 +func (this *Rand) countLessThanRand(count, total int, weightList []*weightRand) []int { + // 先所有的加入进去 + result := make([]int, 0, count) + for k := range weightList { + result = append(result, k) + } + + // 不删除随机 + tempCount := len(result) + if tempCount >= count { + return result + } + + for i := 0; i < count-tempCount; i++ { + tempk := -1 + tempTotal := 0 + tempRandNum := this.GetRandInt(total + 1) + for _, w := range weightList { + tempTotal = tempTotal + w.weight + if tempTotal > tempRandNum { + break + } + + tempk = w.index + } + + // 如果未找到,则取第一个 + if tempk == -1 { + tempk = weightList[0].index + } + + result = append(result, tempk) + } + + return result +} + +// 获取带权重的随机数据(列表元素数量>需要的数量) +// count:需要数量 +// total:总权重 +// weightList:权重列表 +// 返回值 +// 数据项的索引 +func (this *Rand) countIsGreaterThanRand(count, total int, weightList []*weightRand) []int { + // 真随机+删除 + result := make([]int, 0, count) + for j := 0; j < count; j++ { + tempTotal := 0 + ri := -1 + var tw *weightRand + tempRandNum := this.GetRandInt(total) + for i, v := range weightList { + tempTotal = tempTotal + v.weight + if tempTotal > tempRandNum { + break + } + tw = v + ri = i + } + + // 如果未找到,则取第一个 + if tw == nil { + tw = weightList[0] + ri = 0 + } + + result = append(result, tw.index) + total = total - tw.weight + + // 删除已经随机过的item + weightList = append(weightList[:ri], weightList[ri+1:]...) + } + + return result +} diff --git a/trunk/goutil/mathUtil/rand_test.go b/trunk/goutil/mathUtil/rand_test.go new file mode 100644 index 0000000..f8fdf8d --- /dev/null +++ b/trunk/goutil/mathUtil/rand_test.go @@ -0,0 +1,325 @@ +package mathUtil + +import ( + "fmt" + "testing" +) + +func TestGetRandRangeInt(t *testing.T) { + lower, upper := 10, 100 + rand := GetRand().GetRandRangeInt(lower, upper) + if rand < lower || rand >= upper { + t.Errorf("Expected a num between %d and %d, but got %d", lower, upper, rand) + } +} + +func TestGetRandInt(t *testing.T) { + var n int = 100 + var rand int = GetRand().GetRandInt(n) + if rand >= n { + t.Errorf("Expected a num < %d, but got %d", n, rand) + } +} + +func TestGetRandInt32(t *testing.T) { + var n int32 = 100 + var rand int32 = GetRand().GetRandInt32(n) + if rand >= n { + t.Errorf("Expected a num < %d, but got %d", n, rand) + } +} + +func TestGetRandInt64(t *testing.T) { + var n int64 = 100 + var rand int64 = GetRand().GetRandInt64(n) + if rand >= n { + t.Errorf("Expected a num < %d, but got %d", n, rand) + } +} + +func TestGetRandFloat32(t *testing.T) { + var rand float32 = GetRand().GetRandFloat32() + if rand >= 1 { + t.Errorf("Expected a num < 1, but got %f", rand) + } +} + +func TestGetRandFloat64(t *testing.T) { + var rand float64 = GetRand().GetRandFloat64() + if rand >= 1 { + t.Errorf("Expected a num < 1, but got %f", rand) + } +} + +func TestGetRandNumList(t *testing.T) { + if _, err := GetRand().GetRandNumList(11, 10, 11, false); err.Error() != "minValue can't be bigger than maxValue." { + t.Error("Expected err, but got nil") + } + + if _, err := GetRand().GetRandNumList(1, 10, 11, false); err.Error() != "随机的数量超过区间的元素数量" { + t.Error("Expected err, but got nil") + } + + if _, err := GetRand().GetRandNumList(1, 10001, 10, false); err.Error() != "随机数的区间不能大于10000" { + t.Error("Expected err, but got nil") + } + + randNumList, _ := GetRand().GetRandNumList(1, 10, 1, false) + fmt.Printf("randNumList:%v\n", randNumList) + randNumList, _ = GetRand().GetRandNumList(1, 10, 3, false) + fmt.Printf("randNumList:%v\n", randNumList) + randNumList, _ = GetRand().GetRandNumList(1, 10, 5, false) + fmt.Printf("randNumList:%v\n", randNumList) + randNumList, _ = GetRand().GetRandNumList(1, 10, 7, false) + fmt.Printf("randNumList:%v\n", randNumList) + randNumList, _ = GetRand().GetRandNumList(1, 10, 9, false) + fmt.Printf("randNumList:%v\n", randNumList) + randNumList, _ = GetRand().GetRandNumList(1, 10, 11, true) + fmt.Printf("randNumList:%v\n", randNumList) +} + +func TestGetRandIntList(t *testing.T) { + source := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + + if _, err := GetRand().GetRandIntList(source, 11, false); err.Error() != "随机的数量超过列表的元素数量" { + t.Error("Expected err, but got nil") + } + + randIntList, _ := GetRand().GetRandIntList(source, 1, false) + fmt.Printf("randIntList:%v\n", randIntList) + randIntList, _ = GetRand().GetRandIntList(source, 3, false) + fmt.Printf("randIntList:%v\n", randIntList) + randIntList, _ = GetRand().GetRandIntList(source, 5, false) + fmt.Printf("randIntList:%v\n", randIntList) + randIntList, _ = GetRand().GetRandIntList(source, 7, false) + fmt.Printf("randIntList:%v\n", randIntList) + randIntList, _ = GetRand().GetRandIntList(source, 9, false) + fmt.Printf("randIntList:%v\n", randIntList) + randIntList, _ = GetRand().GetRandIntList(source, 10, true) + fmt.Printf("randIntList:%v\n", randIntList) +} + +func TestGetRandInterfaceList(t *testing.T) { + item1 := NewItem(1, "name1", 1) + item2 := NewItem(2, "name1", 1) + item3 := NewItem(3, "name1", 1) + item4 := NewItem(4, "name1", 1) + item5 := NewItem(5, "name1", 1) + item6 := NewItem(6, "name1", 1) + item7 := NewItem(7, "name1", 1) + item8 := NewItem(8, "name1", 1) + item9 := NewItem(9, "name1", 1) + item10 := NewItem(10, "name1", 1) + + source := make([]interface{}, 0, 10) + source = append(source, item1) + source = append(source, item2) + source = append(source, item3) + source = append(source, item4) + source = append(source, item5) + source = append(source, item6) + source = append(source, item7) + source = append(source, item8) + source = append(source, item9) + source = append(source, item10) + + if _, err := GetRand().GetRandInterfaceList(source, 11, false); err.Error() != "随机的数量超过列表的元素数量" { + t.Error("Expected err, but got nil") + } + + randInterfaceList, _ := GetRand().GetRandInterfaceList(source, 1, false) + fmt.Printf("randInterfaceList:%v\n", randInterfaceList) + randInterfaceList, _ = GetRand().GetRandInterfaceList(source, 3, false) + fmt.Printf("randInterfaceList:%v\n", randInterfaceList) + randInterfaceList, _ = GetRand().GetRandInterfaceList(source, 5, false) + fmt.Printf("randInterfaceList:%v\n", randInterfaceList) + randInterfaceList, _ = GetRand().GetRandInterfaceList(source, 7, false) + fmt.Printf("randInterfaceList:%v\n", randInterfaceList) + randInterfaceList, _ = GetRand().GetRandInterfaceList(source, 9, false) + fmt.Printf("randInterfaceList:%v\n", randInterfaceList) + randInterfaceList, _ = GetRand().GetRandInterfaceList(source, 10, true) + fmt.Printf("randInterfaceList:%v\n", randInterfaceList) +} + +func TestGetRandWeight(t *testing.T) { + source := make([]IWeight, 0, 10) + if _, err := GetRand().GetRandWeight(source); err == nil { + t.Errorf("err should not be nil, but it's nil") + } + + item1 := NewItem(1, "name1", 1) + item2 := NewItem(2, "name2", 2) + item3 := NewItem(3, "name3", 3) + item4 := NewItem(4, "name4", 4) + item5 := NewItem(5, "name5", 5) + item6 := NewItem(6, "name6", 60) + item7 := NewItem(7, "name7", 70) + item8 := NewItem(8, "name8", 80) + item9 := NewItem(9, "name9", 90) + item10 := NewItem(10, "name10", 100) + + source = append(source, item1) + source = append(source, item2) + source = append(source, item3) + source = append(source, item4) + source = append(source, item5) + source = append(source, item6) + source = append(source, item7) + source = append(source, item8) + source = append(source, item9) + source = append(source, item10) + + data := make(map[int]int) + for i := 0; i < 10000; i++ { + if result, err := GetRand().GetRandWeight(source); err != nil { + t.Errorf("err should be nil, but it's not:%s", err) + } else { + if item, ok := result.(*Item); !ok { + t.Errorf("convert to Item failed") + } else { + if count, ok := data[item.Id]; ok { + data[item.Id] = count + 1 + } else { + data[item.Id] = 1 + } + } + } + } + + total := 0 + for _, v := range data { + total += v + } + + k := 1 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } + k = 2 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } + k = 3 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } + k = 4 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } + k = 5 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } + k = 6 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } + k = 7 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } + k = 8 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } + k = 9 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } + k = 10 + if v, ok := data[k]; ok { + fmt.Printf("%d:%d, ratio:%d\n", k, v, v*100/total) + } +} + +func TestGetRandWeight2(t *testing.T) { + source := make([]IWeight, 0, 10) + if _, err := GetRand().GetRandWeight(source); err == nil { + t.Errorf("err should not be nil, but it's nil") + } + + item1 := NewItem(1, "name1", 1) + item2 := NewItem(2, "name2", 2) + item3 := NewItem(3, "name3", 3) + item4 := NewItem(4, "name4", 4) + item5 := NewItem(5, "name5", 5) + item6 := NewItem(6, "name6", 60) + item7 := NewItem(7, "name7", 70) + item8 := NewItem(8, "name8", 80) + item9 := NewItem(9, "name9", 90) + item10 := NewItem(10, "name10", 100) + + source = append(source, item1) + source = append(source, item2) + source = append(source, item3) + source = append(source, item4) + source = append(source, item5) + source = append(source, item6) + source = append(source, item7) + source = append(source, item8) + source = append(source, item9) + source = append(source, item10) + + rindexs, err := GetRand().GetRandItemsByWeight(source, 2, false) + if err != nil { + t.Errorf("GetRandItemsByWeight出错:%s", err) + } + if len(rindexs) != 2 || rindexs[0] == rindexs[1] { + t.Errorf("GetRandItemsByWeight随机内容出错") + } + + rindexs, err = GetRand().GetRandItemsByWeight(source, 10, false) + if err != nil { + t.Errorf("GetRandItemsByWeight出错:%s", err) + } + + if len(rindexs) != 10 { + t.Errorf("GetRandItemsByWeight随机内容出错") + } + + _, err = GetRand().GetRandItemsByWeight(source, 12, false) + if err == nil { + t.Errorf("GetRandItemsByWeight出错:%s", err) + } + + rindexs, err = GetRand().GetRandItemsByWeight(source, 15, true) + if err != nil { + t.Errorf("GetRandItemsByWeight出错:%s", err) + } + if len(rindexs) != 15 { + t.Errorf("GetRandItemsByWeight随机内容出错") + } + +} + +type Item struct { + Id int + Name string + Weight int +} + +func (item *Item) GetWeight() int { + return item.Weight +} + +func (item *Item) String() string { + return fmt.Sprintf("Id:%d,Name:%s\n", item.Id, item.Name) +} + +func NewItem(id int, name string, weight int) *Item { + return &Item{ + Id: id, + Name: name, + Weight: weight, + } +} + +func Test_GetRandom(t *testing.T) { + rand, err := GetRand().GetRandNumList(1, 10000, 10, true) + if err != nil { + t.Log(fmt.Errorf("获取随机数错误:%s", err)) + } + + t.Log(rand) +} diff --git a/trunk/goutil/mathUtil/safeRand.go b/trunk/goutil/mathUtil/safeRand.go new file mode 100644 index 0000000..8a0f641 --- /dev/null +++ b/trunk/goutil/mathUtil/safeRand.go @@ -0,0 +1,381 @@ +package mathUtil + +import ( + "errors" + "math/rand" + "sync" + "time" +) + +//SafeRand 安全的随机 +type SafeRand struct { + *rand.Rand + mu sync.Mutex +} + +func GetSafeRand() *SafeRand { + return &SafeRand{ + Rand: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +func GetSafeRandInSeed(seed int64) *SafeRand { + return &SafeRand{ + Rand: rand.New(rand.NewSource(seed)), + } +} + +func (this *SafeRand) Int() int { + this.mu.Lock() + defer this.mu.Unlock() + + return this.Rand.Int() +} + +func (this *SafeRand) Intn(n int) int { + this.mu.Lock() + defer this.mu.Unlock() + + return this.Rand.Intn(n) +} + +func (this *SafeRand) Int31() int32 { + this.mu.Lock() + defer this.mu.Unlock() + + return this.Rand.Int31() +} + +func (this *SafeRand) Int31n(n int32) int32 { + this.mu.Lock() + defer this.mu.Unlock() + + return this.Rand.Int31n(n) +} + +func (this *SafeRand) Int63() int64 { + this.mu.Lock() + defer this.mu.Unlock() + + return this.Rand.Int63() +} + +func (this *SafeRand) Int63n(n int64) int64 { + this.mu.Lock() + defer this.mu.Unlock() + + return this.Rand.Int63n(n) +} + +func (this *SafeRand) Float64() float64 { + this.mu.Lock() + defer this.mu.Unlock() + + return this.Rand.Float64() +} + +func (this *SafeRand) Float32() float32 { + this.mu.Lock() + defer this.mu.Unlock() + + return this.Rand.Float32() +} + +// 获取指定区间的随机数[lower, upper) +// lower:区间下限 +// upper:区间上限 +// 返回值:随机数 +func (this *SafeRand) GetRandRangeInt(lower, upper int) int { + return lower + this.Intn(upper-lower) +} + +// 获取指定区间的随机数[lower, upper) +// lower:区间下限 +// upper:区间上限 +// 返回值:随机数 +func (this *SafeRand) GetRandRangeInt32(lower, upper int32) int32 { + return lower + this.Int31n(upper-lower) +} + +// 获取指定区间的随机数[lower, upper) +// lower:区间下限 +// upper:区间上限 +// 返回值:随机数 +func (this *SafeRand) GetRandRangeInt64(lower, upper int64) int64 { + return lower + this.Int63n(upper-lower) +} + +// 获取随机数[0, n) +// randObj:随机对象 +// n:范围上限 +// 返回值:随机数 +func (this *SafeRand) GetRandInt(n int) int { + return this.Intn(n) +} + +// 获取随机数[0, n) +// n:范围上限 +// 返回值:随机数 +func (this *SafeRand) GetRandInt32(n int32) int32 { + return this.Int31n(n) +} + +// 获取随机数[0, n) +// randObj:随机对象 +// n:范围上限 +// 返回值:随机数 +func (this *SafeRand) GetRandInt64(n int64) int64 { + return this.Int63n(n) +} + +// 获取随机数[0, 1) +// randObj:随机对象 +// 返回值:随机数 +func (this *SafeRand) GetRandFloat32() float32 { + return this.Float32() +} + +// 获取随机数[0, 1) +// 返回值:随机数 +func (this *SafeRand) GetRandFloat64() float64 { + return this.Float64() +} + +// 获取随机数列表(1~10000,超过10000会抛出异常) +// minValue:获取随机数的区间下限值 +// maxValue:获取随机数的区间上限值 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *SafeRand) GetRandNumList(minValue, maxValue, count int, ifAllowDuplicate bool) ([]int, error) { + if minValue > maxValue { + return nil, errors.New("minValue can't be bigger than maxValue.") + } + + if !ifAllowDuplicate && (maxValue-minValue+1) < count { + return nil, errors.New("随机的数量超过区间的元素数量") + } + + if (maxValue - minValue + 1) > 10000 { + return nil, errors.New("随机数的区间不能大于10000") + } + + // 定义原始数据 + sourceCount := maxValue - minValue + 1 + source := make([]int, sourceCount, sourceCount) + for index := 0; index < sourceCount; index++ { + source[index] = minValue + index + } + + // 定义返回值 + resultList := make([]int, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的int列表 +// source:源列表 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *SafeRand) GetRandIntList(source []int, count int, ifAllowDuplicate bool) ([]int, error) { + // 在不允许重复的情况下,需要产生的随机数不能超过范围限制 + if ifAllowDuplicate == false && len(source) < count { + return nil, errors.New("随机的数量超过列表的元素数量") + } + + // 定义返回值 + resultList := make([]int, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的int32列表 +// source:源列表 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *SafeRand) GetRandInt32List(source []int32, count int, ifAllowDuplicate bool) ([]int32, error) { + // 在不允许重复的情况下,需要产生的随机数不能超过范围限制 + if ifAllowDuplicate == false && len(source) < count { + return nil, errors.New("随机的数量超过列表的元素数量") + } + + // 定义返回值 + resultList := make([]int32, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的int64列表 +// source:源列表 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *SafeRand) GetRandInt64List(source []int64, count int, ifAllowDuplicate bool) ([]int64, error) { + // 在不允许重复的情况下,需要产生的随机数不能超过范围限制 + if ifAllowDuplicate == false && len(source) < count { + return nil, errors.New("随机的数量超过列表的元素数量") + } + + // 定义返回值 + resultList := make([]int64, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的interface{}列表 +// source:源列表 +// count:随机数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机数列表 +func (this *SafeRand) GetRandInterfaceList(source []interface{}, count int, ifAllowDuplicate bool) ([]interface{}, error) { + // 在不允许重复的情况下,需要产生的随机数不能超过范围限制 + if ifAllowDuplicate == false && len(source) < count { + return nil, errors.New("随机的数量超过列表的元素数量") + } + + // 定义返回值 + resultList := make([]interface{}, 0, count) + + // 获取随机的索引列表 + randIndextList := this.getRandIndexList(len(source), count, ifAllowDuplicate) + for _, index := range randIndextList { + // 判断是否已经取到足够数量的数据? + if count <= 0 { + break + } + + resultList = append(resultList, source[index]) + count -= 1 + } + + return resultList, nil +} + +// 获取随机的索引列表 +// count:数量 +// ifAllowDuplicate:是否允许重复 +// 返回值 +// 随机索引值列表 +func (this *SafeRand) getRandIndexList(maxNum, count int, ifAllowDuplicate bool) []int { + // 定义返回值 + randIndextList := make([]int, 0, count) + + // 使用源列表的数据量来初始化一个仅存放索引值的数组 + indexList := make([]int, maxNum, maxNum) + for index := 0; index < maxNum; index++ { + indexList[index] = index + } + + // 遍历列表并获取随机对象(通过不断缩小随机的范围来实现) + maxIndex := len(indexList) - 1 + for { + if len(randIndextList) < count { + // 获取随机索引(由于Next方法不取上限值,所以需要maxIndex+1) + randIndex := this.Intn(maxIndex + 1) + + // 将数据添加到列表,并增加findCount + randIndextList = append(randIndextList, indexList[randIndex]) + + // 如果不允许重复,则需要特殊处理 + if !ifAllowDuplicate { + // 将该位置的数据和最大位置的数据进行交换 + indexList[randIndex], indexList[maxIndex] = indexList[maxIndex], indexList[randIndex] + + // 将随机的范围缩小 + maxIndex -= 1 + } + } else { + break + } + } + + return randIndextList +} + +// 获取带权重的随机数据 +// source:源数据 +// 返回值 +// 数据项 +// 错误对象 +func (this *SafeRand) GetRandWeight(source []IWeight) (result IWeight, err error) { + if source == nil || len(source) == 0 { + err = errors.New("待随机的列表为空") + return + } + + // 计算出总的数据量,并随机一个[0, total)的值 + total := 0 + for _, item := range source { + total += item.GetWeight() + } + + randNum := this.GetRandInt(total) + + // 根据随机出来的值,判断位于哪个区间 + total = 0 + for _, item := range source { + total += item.GetWeight() + if randNum < total { + result = item + return + } + } + + err = errors.New("未找到有效的数据") + return +} diff --git a/trunk/goutil/mathUtil/saferand_test.go b/trunk/goutil/mathUtil/saferand_test.go new file mode 100644 index 0000000..f70050d --- /dev/null +++ b/trunk/goutil/mathUtil/saferand_test.go @@ -0,0 +1,47 @@ +package mathUtil + +import ( + "testing" +) + +func TestGetSafeRandProccess(t *testing.T) { + // r := GetSafeRand() + // for { + // go r.Int() + // go r.Intn(10) + // go r.Int31() + // go r.Int31n(10) + // go r.Int63() + // go r.Int63n(10) + // go r.Float64() + // go r.Float32() + // } + + // r1 := GetRand() + // for { + // go r1.Int() + // } +} + +func BenchmarkInt(b *testing.B) { + r := GetSafeRand() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + r.Int() + } + }) +} + +func BenchmarkGetRandNumList(b *testing.B) { + r := GetSafeRand() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := r.GetRandNumList(1, 10000, 10, true) + if err != nil { + b.Error(err) + } + } + }) +} diff --git a/trunk/goutil/mathUtil/size.go b/trunk/goutil/mathUtil/size.go new file mode 100644 index 0000000..68d7660 --- /dev/null +++ b/trunk/goutil/mathUtil/size.go @@ -0,0 +1,37 @@ +package mathUtil + +import ( + "fmt" +) + +// 获取字节大小的描述信息 +// size:字节大小 +// 返回值: +// 描述信息 +func GetSizeDesc(size int64) string { + str := "" + + // 判断输入是否超过int64的范围 + if size < 0 || size > (1<<63-1) { + return str + } + + switch { + case size >= 1024*1024*1024*1024*1024*1024: + str = fmt.Sprintf("%.2fEB", float64(size)/1024/1024/1024/1024/1024/1024) + case size >= 1024*1024*1024*1024*1024: + str = fmt.Sprintf("%.2fPB", float64(size)/1024/1024/1024/1024/1024) + case size >= 1024*1024*1024*1024: + str = fmt.Sprintf("%.2fTB", float64(size)/1024/1024/1024/1024) + case size >= 1024*1024*1024: + str = fmt.Sprintf("%.2fGB", float64(size)/1024/1024/1024) + case size >= 1024*1024: + str = fmt.Sprintf("%dMB", size/1024/1024) + case size >= 1024: + str = fmt.Sprintf("%dKB", size/1024) + default: + str = fmt.Sprintf("%dB", size) + } + + return str +} diff --git a/trunk/goutil/mathUtil/size_test.go b/trunk/goutil/mathUtil/size_test.go new file mode 100644 index 0000000..613c5ff --- /dev/null +++ b/trunk/goutil/mathUtil/size_test.go @@ -0,0 +1,61 @@ +package mathUtil + +import ( + "testing" +) + +func TestGetSizeDesc(t *testing.T) { + var size int64 + var expectedStr string + var finalStr string + + size = 1 + expectedStr = "1B" + finalStr = GetSizeDesc(size) + if finalStr != expectedStr { + t.Errorf("Expected %s, but got %s", expectedStr, finalStr) + } + + size *= 1024 + expectedStr = "1KB" + finalStr = GetSizeDesc(size) + if finalStr != expectedStr { + t.Errorf("Expected %s, but got %s", expectedStr, finalStr) + } + + size *= 1024 + expectedStr = "1MB" + finalStr = GetSizeDesc(size) + if finalStr != expectedStr { + t.Errorf("Expected %s, but got %s", expectedStr, finalStr) + } + + size *= 1024 + expectedStr = "1.00GB" + finalStr = GetSizeDesc(size) + if finalStr != expectedStr { + t.Errorf("Expected %s, but got %s", expectedStr, finalStr) + } + + size *= 1024 + expectedStr = "1.00TB" + finalStr = GetSizeDesc(size) + if finalStr != expectedStr { + t.Errorf("Expected %s, but got %s", expectedStr, finalStr) + } + + size *= 1024 + expectedStr = "1.00PB" + finalStr = GetSizeDesc(size) + if finalStr != expectedStr { + t.Errorf("Expected %s, but got %s", expectedStr, finalStr) + } + + size *= 1024 + expectedStr = "1.00EB" + finalStr = GetSizeDesc(size) + if finalStr != expectedStr { + t.Errorf("Expected %s, but got %s", expectedStr, finalStr) + } + +} diff --git a/trunk/goutil/mysqlUtil/convert.go b/trunk/goutil/mysqlUtil/convert.go new file mode 100644 index 0000000..49a42dd --- /dev/null +++ b/trunk/goutil/mysqlUtil/convert.go @@ -0,0 +1,146 @@ +package mysqlUtil + +import ( + "fmt" + "strconv" + "strings" +) + +// 转换数据库连接字符串(从C#->Go) +// connString:C#的数据库连接字符串 +// 返回值: +// Go的数据库连接字符串 +func ConvertConnectionStringFromCSharpToGo(connString string) string { + goConnString := "{userid}:{password}@tcp({datasource}:{port})/{database}?charset={charset}&parseTime=true&loc=Local&timeout={timeout}s||MaxOpenConns={MaxOpenConns}||MaxIdleConns={MaxIdleConns}" + + // 将字符串按;进行切割 + connStringList := strings.Split(connString, ";") + var datasource string // DataSource=10.162.2.205; + var port string // port=3306; + var userid string // UserId=admin; + var password string // Password=MOQIkaka$#@!1234; + var database string // Database=s201_dzz_log; + var charset string // charset=utf8; + var pooling string // pooling=true; + var minimumpoolsize string // MinimumPoolSize=20; or min pool size=20; + var maximumpoolsize string // maximumpoolsize=200; or max pool size=200; + var commandtimeout string // command timeout=60; + + // 遍历处理 + for _, item := range connStringList { + // 将字符串按=进行切割 + subItemList := strings.Split(item, "=") + + // 对每一项进行判断 + if len(subItemList) != 2 { + continue + } + + // 先转换为小写字母 + switch strings.ToLower(subItemList[0]) { + case "datasource": + datasource = subItemList[1] + case "port": + port = subItemList[1] + case "userid": + userid = subItemList[1] + case "password": + password = subItemList[1] + case "database": + database = subItemList[1] + case "charset": + charset = subItemList[1] + case "pooling": + pooling = subItemList[1] + case "minimumpoolsize", "min pool size": + minimumpoolsize = subItemList[1] + case "maximumpoolsize", "max pool size": + maximumpoolsize = subItemList[1] + case "command timeout": + commandtimeout = subItemList[1] + } + } + + // 替换占位符 + goConnString = strings.Replace(goConnString, "{userid}", userid, 1) + goConnString = strings.Replace(goConnString, "{password}", password, 1) + goConnString = strings.Replace(goConnString, "{datasource}", datasource, 1) + goConnString = strings.Replace(goConnString, "{port}", port, 1) + goConnString = strings.Replace(goConnString, "{database}", database, 1) + goConnString = strings.Replace(goConnString, "{charset}", charset, 1) + goConnString = strings.Replace(goConnString, "{timeout}", commandtimeout, 1) + if pooling == "true" { + goConnString = strings.Replace(goConnString, "{MaxOpenConns}", maximumpoolsize, 1) + goConnString = strings.Replace(goConnString, "{MaxIdleConns}", minimumpoolsize, 1) + } else { + goConnString = strings.Replace(goConnString, "{MaxOpenConns}", "0", 1) + goConnString = strings.Replace(goConnString, "{MaxIdleConns}", "0", 1) + } + + return goConnString +} + +// 解析连接字符串(obsolete,建议使用NewDBConfig2) +// connString:数据库连接字符串 +// 返回值 +// 有效的连接字符串 +// 最大开启连接数量 +// 最大空闲连接数量 +// 错误对象 +func ParseConnectionString(connString string) (conn string, maxOpenConns int, maxIdleConns int, err error) { + connSlice := strings.Split(connString, "||") + length := len(connSlice) + if length != 1 && length != 3 { + err = fmt.Errorf("connString:%s格式不正确,length:%d", connString, length) + return + } + + // 获取连接字符串 + conn = connSlice[0] + if conn == "" { + err = fmt.Errorf("connString:%s格式不正确,length:%d", connString, length) + return + } + + // 如果只配置了连接字符串,则MaxOpenConns、MaxIdleConns取默认值 + if length == 1 { + return + } + + // 获取连接池相关 + maxOpenConns_string := strings.Replace(connSlice[1], "MaxOpenConns=", "", 1) + maxOpenConns, err = strconv.Atoi(maxOpenConns_string) + if err != nil { + err = fmt.Errorf("MaxOpenConns必须为int型,连接字符串为:%s", connString) + return + } + + maxIdleConns_string := strings.Replace(connSlice[2], "MaxIdleConns=", "", 1) + maxIdleConns, err = strconv.Atoi(maxIdleConns_string) + if err != nil { + err = fmt.Errorf("MaxIdleConns必须为int型,连接字符串为:%s", connString) + return + } + + return +} + +// 连接字符串是否为C#格式 +func IsCSharpStyle(connString string) bool { + lowerString := strings.ToLower(connString) + if strings.Contains(lowerString, "datasource") && strings.Contains(lowerString, "port") { + return true + } + + return false +} + +// 连接字符串是否为Go格式 +func IsGoStyle(connString string) bool { + lowerString := strings.ToLower(connString) + if strings.Contains(lowerString, "@tcp") { + return true + } + + return false +} diff --git a/trunk/goutil/mysqlUtil/convert_test.go b/trunk/goutil/mysqlUtil/convert_test.go new file mode 100644 index 0000000..1c72fdd --- /dev/null +++ b/trunk/goutil/mysqlUtil/convert_test.go @@ -0,0 +1,45 @@ +package mysqlUtil + +import ( + "testing" +) + +func TestConvertConnectionStringFromCSharpToGo(t *testing.T) { + csharp := "DataSource=10.66.195.134;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=s9501_sd_log;Allow Zero Datetime=true;charset=utf8;pooling=false;command timeout=60;AllowUserVariables=True;" + expected := "admin:MOQIkaka$#@!1234@tcp(10.66.195.134:3306)/s9501_sd_log?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=0||MaxIdleConns=0" + + if goConn := ConvertConnectionStringFromCSharpToGo(csharp); goConn != expected { + t.Errorf("Expected %s, but got %s", expected, goConn) + } + + // csharp = "DataSource=10.162.2.205;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=s201_dzz_log;Allow Zero Datetime=true;charset=utf8;pooling=false;min pool size=20;max pool size=200;command timeout=60;AllowUserVariables=True;" + // expected = "admin:MOQIkaka$#@!1234@tcp(10.162.2.205:3306)/s201_dzz_log?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=0||MaxIdleConns=0" + + // if goConn := ConvertConnectionStringFromCSharpToGo(csharp); goConn != expected { + // t.Errorf("Expected %s, but got %s", expected, goConn) + // } +} + +func TestIsCSharpStyle(t *testing.T) { + connString := "DataSource=10.66.195.134;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=s9501_sd_log;Allow Zero Datetime=true;charset=utf8;pooling=false;command timeout=60;AllowUserVariables=True;" + if !IsCSharpStyle(connString) { + t.Errorf("it's should be C# style, but now not") + } + + connString = "admin:MOQIkaka$#@!1234@tcp(10.66.195.134:3306)/s9501_sd_log?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=0||MaxIdleConns=0" + if IsCSharpStyle(connString) { + t.Errorf("it's should not be C# style, but now it is") + } +} + +func TestIsGoStyle(t *testing.T) { + connString := "admin:MOQIkaka$#@!1234@tcp(10.66.195.134:3306)/s9501_sd_log?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=0||MaxIdleConns=0" + if !IsGoStyle(connString) { + t.Errorf("it's should be Go style, but now not") + } + + connString = "DataSource=10.66.195.134;port=3306;UserId=admin;Password=MOQIkaka$#@!1234;Database=s9501_sd_log;Allow Zero Datetime=true;charset=utf8;pooling=false;command timeout=60;AllowUserVariables=True;" + if IsGoStyle(connString) { + t.Errorf("it's should not be Go style, but now it is") + } +} diff --git a/trunk/goutil/mysqlUtil/dbConfig.go b/trunk/goutil/mysqlUtil/dbConfig.go new file mode 100644 index 0000000..c5058da --- /dev/null +++ b/trunk/goutil/mysqlUtil/dbConfig.go @@ -0,0 +1,85 @@ +package mysqlUtil + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" +) + +// 数据库配置对象 +type DBConfig struct { + // 连接字符串 + ConnectionString string + + // 最大开启连接数量 + MaxOpenConns int + + // 最大空闲连接数量 + MaxIdleConns int +} + +func (this *DBConfig) String() string { + bytes, _ := json.Marshal(this) + return string(bytes) +} + +// 创建数据库配置对象 +// connectionString:连接字符串 +// maxOpenConns:最大开启连接数 +// maxIdleConns:最大空闲连接数 +// 返回值: +// 数据库配置对象 +func NewDBConfig(connectionString string, maxOpenConns, maxIdleConns int) *DBConfig { + return &DBConfig{ + ConnectionString: connectionString, + MaxOpenConns: maxOpenConns, + MaxIdleConns: maxIdleConns, + } +} + +// 创建数据库配置对象 +// dbConfigStr:数据库配置字符串,格式:root:moqikaka3306@tcp(10.1.0.10:3306)/gameserver_data?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=10||MaxIdleConns=5 +// 返回值: +// 数据库配置对象 +// 错误对象 +func NewDBConfig2(dbConfigStr string) (*DBConfig, error) { + connectionString := "" + maxOpenConns := 0 + maxIdleConns := 0 + + // 按照||来进行分割 + connSlice := strings.Split(dbConfigStr, "||") + length := len(connSlice) + if length != 1 && length != 3 { + return nil, fmt.Errorf("dbConfigStr:%s格式不正确,length:%d", dbConfigStr, length) + } + + // 获取连接字符串 + connectionString = connSlice[0] + if connectionString == "" { + return nil, fmt.Errorf("dbConfigStr:%s格式不正确,length:%d", dbConfigStr, length) + } + + var err error + + // 如果配置了MaxOpenConns、MaxIdleConns,则进行解析 + if length == 3 { + // 获取连接池相关 + maxOpenConns_string := strings.Replace(connSlice[1], "MaxOpenConns=", "", 1) + maxOpenConns, err = strconv.Atoi(maxOpenConns_string) + if err != nil { + err = fmt.Errorf("MaxOpenConns必须为int型,连接字符串为:%s", connectionString) + return nil, err + } + + maxIdleConns_string := strings.Replace(connSlice[2], "MaxIdleConns=", "", 1) + maxIdleConns, err = strconv.Atoi(maxIdleConns_string) + if err != nil { + err = fmt.Errorf("MaxIdleConns必须为int型,连接字符串为:%s", connectionString) + return nil, err + } + } + + return NewDBConfig(connectionString, maxOpenConns, maxIdleConns), nil +} diff --git a/trunk/goutil/mysqlUtil/dbConfig_test.go b/trunk/goutil/mysqlUtil/dbConfig_test.go new file mode 100644 index 0000000..0306ee0 --- /dev/null +++ b/trunk/goutil/mysqlUtil/dbConfig_test.go @@ -0,0 +1,54 @@ +package mysqlUtil + +import ( + "testing" +) + +func TestNewDBConfig(t *testing.T) { + connectionString := "root:moqikaka3306@tcp(10.1.0.10:3306)/gameserver_data?charset=utf8&parseTime=true&loc=Local&timeout=60s" + maxOpenConns := 10 + maxIdleConns := 5 + + dbConfigObj := NewDBConfig(connectionString, maxOpenConns, maxIdleConns) + if connectionString != dbConfigObj.ConnectionString { + t.Errorf("Expected %s, but now %s", connectionString, dbConfigObj.ConnectionString) + } + + if maxOpenConns != dbConfigObj.MaxOpenConns { + t.Errorf("Expected %d, but now %d", maxOpenConns, dbConfigObj.MaxOpenConns) + } + + if maxIdleConns != dbConfigObj.MaxIdleConns { + t.Errorf("Expected %d, but now %d", maxIdleConns, dbConfigObj.MaxIdleConns) + } +} + +func TestNewDBConfig2(t *testing.T) { + dbConfigStr := "root:moqikaka3306@tcp(10.1.0.10:3306)/gameserver_data?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=10||MaxIdleConns2=5" + + if _, err := NewDBConfig2(dbConfigStr); err == nil { + t.Errorf("there should be err, but now not.") + } + + dbConfigStr = "root:moqikaka3306@tcp(10.1.0.10:3306)/gameserver_data?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=10||MaxIdleConns=5" + dbConfigObj, err := NewDBConfig2(dbConfigStr) + if err != nil { + t.Errorf("there should be no err, but now has.") + } + + connectionString := "root:moqikaka3306@tcp(10.1.0.10:3306)/gameserver_data?charset=utf8&parseTime=true&loc=Local&timeout=60s" + maxOpenConns := 10 + maxIdleConns := 5 + + if connectionString != dbConfigObj.ConnectionString { + t.Errorf("Expected %s, but now %s", connectionString, dbConfigObj.ConnectionString) + } + + if maxOpenConns != dbConfigObj.MaxOpenConns { + t.Errorf("Expected %d, but now %d", maxOpenConns, dbConfigObj.MaxOpenConns) + } + + if maxIdleConns != dbConfigObj.MaxIdleConns { + t.Errorf("Expected %d, but now %d", maxIdleConns, dbConfigObj.MaxIdleConns) + } +} diff --git a/trunk/goutil/mysqlUtil/dbConn.go b/trunk/goutil/mysqlUtil/dbConn.go new file mode 100644 index 0000000..32dfaf9 --- /dev/null +++ b/trunk/goutil/mysqlUtil/dbConn.go @@ -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失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) +} + +// 记录Query错误 +// command:执行的SQL语句 +// err:错误对象 +func WriteQueryError(command string, err error) { + logUtil.Log(fmt.Sprintf("Query失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) +} + +// 记录Exec错误 +// command:执行的SQL语句 +// err:错误对象 +func WriteExecError(command string, err error) { + logUtil.Log(fmt.Sprintf("Exec失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) +} + +// 记录Scan错误 +// command:执行的SQL语句 +// err:错误对象 +func WriteScanError(command string, err error) { + logUtil.Log(fmt.Sprintf("Scan失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) +} diff --git a/trunk/goutil/mysqlUtil/dbConn_test.go b/trunk/goutil/mysqlUtil/dbConn_test.go new file mode 100644 index 0000000..a31c5dc --- /dev/null +++ b/trunk/goutil/mysqlUtil/dbConn_test.go @@ -0,0 +1,56 @@ +package mysqlUtil + +import ( + "fmt" + "testing" + "time" +) + +func TestOpenMysqlConnection(t *testing.T) { + connectionString := "root:moqikaka3306@tcp(10.1.0.10:3306)/gameserver_data?charset=utf8&parseTime=true&loc=Local&timeout=60s||MaxOpenConns=10||MaxIdleConns=5" + + if _, err := OpenMysqlConnection(connectionString); err != nil { + t.Errorf("there should be no err, but now has:%s", err) + } +} + +func TestOpenMysqlConnection2(t *testing.T) { + connectionString := "root:moqikaka3306@tcp(10.1.0.10:3306)/gameserver_data?charset=utf8&parseTime=true&loc=Local&timeout=60s" + maxOpenConns := 10 + maxIdleConns := 5 + + if _, err := OpenMysqlConnection2(connectionString, maxOpenConns, maxIdleConns); err != nil { + t.Errorf("there should be no err, but now has:%s", err) + } +} + +func TestOpenMysqlConnection3(t *testing.T) { + dbConfigObj := NewDBConfig("root:moqikaka3306@tcp(10.1.0.10:3306)/sdkcenter?charset=utf8&parseTime=true&loc=Local&timeout=10s", 5, 2) + if _, err := OpenMysqlConnection3(dbConfigObj); err != nil { + t.Errorf("there should be no err, but now has:%s", err) + } +} + +func TestTestConnection(t *testing.T) { + dbConfigObj := NewDBConfig("root:moqikaka3306@tcp(10.1.0.10:3306)/sdkcenter?charset=utf8&parseTime=true&loc=Local&timeout=10s", 5, 2) + dbObj, err := OpenMysqlConnection3(dbConfigObj) + if err != nil { + t.Errorf("there should be no err, but now has:%s", err) + } + + succeedCount := 0 + expectedCount := 5 + for i := 0; i < expectedCount; i++ { + if err := TestConnection(dbObj); err != nil { + fmt.Printf("%s:%s\n", time.Now(), err) + } else { + succeedCount += 1 + fmt.Printf("%s:%s\n", time.Now(), "ok") + } + time.Sleep(time.Second * 3) + } + + if succeedCount != expectedCount { + t.Errorf("ExecptedCount:%d, but got %d", expectedCount, succeedCount) + } +} diff --git a/trunk/goutil/netUtil/getLocalIP.go b/trunk/goutil/netUtil/getLocalIP.go new file mode 100644 index 0000000..c090475 --- /dev/null +++ b/trunk/goutil/netUtil/getLocalIP.go @@ -0,0 +1,40 @@ +package netUtil + +import ( + "errors" + "net" +) + +// 需要排除的IP: 169.254.xx.xx (未分配到IP的段) +func excludeIP(ip4 net.IP) bool { + return ip4[0] == 169 && ip4[1] == 254 +} + +// GetLocalIPs +// +// @Description: 获取本机局域网IP(排除环回地址) +// @return ips +// @return err +func GetLocalIPs() (ips []string, err error) { + var addrs []net.Addr + if addrs, err = net.InterfaceAddrs(); err != nil { + return + } + + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() /*&& ipnet.IP.IsPrivate()*/ { + if ip4 := ipnet.IP.To4(); ip4 != nil { + if !excludeIP(ip4) { + ips = append(ips, ipnet.IP.String()) + } + } + } + } + + if len(ips) == 0 { + // 未获取到本机局域网IP + err = errors.New("not found") + } + + return +} diff --git a/trunk/goutil/netUtil/net.go b/trunk/goutil/netUtil/net.go new file mode 100644 index 0000000..9e920f2 --- /dev/null +++ b/trunk/goutil/netUtil/net.go @@ -0,0 +1,115 @@ +package netUtil + +import ( + "net" + "net/http" + "strconv" + "strings" + + "github.com/gorilla/websocket" +) + +// 远程地址解析器 +type RemoteAddrParser struct { + // 主机地址(IP) + Host string + + // 端口 + Port int +} + +// 解析远程地址 +func (this *RemoteAddrParser) parseRemoteAddr(remoteAddr string) { + /* + http中调用JoinHostPort来给RemoteAddr赋值;它的规则如下: + JoinHostPort combines host and port into a network address of the + form "host:port" or, if host contains a colon or a percent sign, + "[host]:port". + net包中是类似的 + + 所以现在要将RemoteAddr解析成host和port,则需要找到最后一个:,前面的部分则是host; + 如果host包含[],则需要去除 + */ + + // 找到分隔host、port的: + index := strings.LastIndex(remoteAddr, ":") + if index == -1 { + return + } + + // 取出host部分 + this.Host = remoteAddr[:index] + this.Port, _ = strconv.Atoi(remoteAddr[index+1:]) + + // 处理host中可能的[] + if strings.Index(this.Host, "[") == -1 { + return + } + this.Host = this.Host[1:] + + if strings.Index(this.Host, "]") == -1 { + return + } + this.Host = this.Host[:len(this.Host)-1] + + return +} + +// 直接读取IP地址 +func GetHttpAddr(request *http.Request) *RemoteAddrParser { + this := &RemoteAddrParser{} + this.parseRemoteAddr(request.RemoteAddr) + return this +} + +// 优先获取header中的代理地址,如果未设置代理地址,则使用request地址 +func GetHttpAddr2(request *http.Request) *RemoteAddrParser { + remoteAddr := request.Header.Get("HTTP_X_FORWARDED_FOR") + if len(remoteAddr) > 0 { + if len(remoteAddr) >= 10 { + strArray := strings.Split(remoteAddr, ",") + if len(strArray) > 0 { + remoteAddr = strArray[0] + } + } + + if len(remoteAddr) > 0 && len(remoteAddr) <= 15 { + this := &RemoteAddrParser{ + Host: remoteAddr, + } + return this + } + } + + remoteAddr = request.Header.Get("X-Real-IP") + if len(remoteAddr) > 0 { + this := &RemoteAddrParser{ + Host: remoteAddr, + } + return this + } + + remoteAddr = request.Header.Get("X-Forwarded-For") + if len(remoteAddr) > 0 { + this := &RemoteAddrParser{ + Host: remoteAddr, + } + return this + } + + this := &RemoteAddrParser{} + this.parseRemoteAddr(request.RemoteAddr) + return this +} + +func GetWebSocketAddr(conn *websocket.Conn) *RemoteAddrParser { + this := &RemoteAddrParser{} + this.parseRemoteAddr(conn.RemoteAddr().String()) + return this +} + +func GetConnAddr(conn net.Conn) *RemoteAddrParser { + this := &RemoteAddrParser{} + this.parseRemoteAddr(conn.RemoteAddr().String()) + return this +} diff --git a/trunk/goutil/rank-util/model.go b/trunk/goutil/rank-util/model.go new file mode 100644 index 0000000..395f152 --- /dev/null +++ b/trunk/goutil/rank-util/model.go @@ -0,0 +1,46 @@ +package rank_util + +import "fmt" + +// Model +// @description: 排行榜对象 +type Model struct { + // rank 排行 + rank int + + // key 唯一key + key string + + // obj 携带的对象 + obj interface{} +} + +// GetObj +// @description: 获取对象附加信息 +// parameter: +// @receiver m: +// return: +// @interface{}: +func (m *Model) GetObj() interface{} { + return m.obj +} + +// GetRank +// @description: 获取对象排行 +// parameter: +// @receiver m: +// return: +// @int: +func (m *Model) GetRank() int { + return m.rank +} + +// String +// @description: 字符串 +// parameter: +// @receiver m: +// return: +// @string: +func (m *Model) String() string { + return fmt.Sprintf("rank:%v, Key:%s, Obj:{%s}", m.rank, m.key, m.obj) +} diff --git a/trunk/goutil/rank-util/rank_test.go b/trunk/goutil/rank-util/rank_test.go new file mode 100644 index 0000000..d3aa28b --- /dev/null +++ b/trunk/goutil/rank-util/rank_test.go @@ -0,0 +1,148 @@ +package rank_util + +import ( + "fmt" + "goutil/mathUtil" + "math" + "strconv" + "sync" + "testing" + "time" +) + +func Test_Rank(t *testing.T) { + r := NewRankUtil(20, compar) + + dc := make(map[string]*rmodel) + for i := 1; i < 1000; i++ { + tk := strconv.Itoa(i) + m := &rmodel{k: tk, Fap: 0} + dc[m.k] = m + } + + goCount := 10 + srand := mathUtil.GetRand() + var wg sync.WaitGroup + wg.Add(goCount) + for i := 0; i < goCount; i++ { + go func() { + for i := 1; i < 1000; i++ { + tk := strconv.Itoa(i) + newf := srand.GetRandRangeInt(1, 1000) + + m, _ := dc[tk] + isUp := m.Fap < newf + m.Fap = newf + r.Refresh(tk, m, isUp) + } + }() + + time.Sleep(time.Second) + wg.Done() + } + + wg.Wait() + items := r.GetAll() + if len(items) != r.maxCount { + t.Error("排行榜长度不为", r.maxCount) + } + + //测试删除 + r.Delete(r.GetItemForRank(1).key) + items = r.GetAll() + if len(items) != r.maxCount-1 { + t.Error("删除后的排行榜长度不为", r.maxCount-1) + } + + //校验标准: 1.key不可以重复 2.fap越来越小 + beforeFap := math.MaxInt + tempKeyDict := make(map[string]bool) + isPrintAll := false + for _, item := range items { + m := item.obj.(*rmodel) + if m.k != item.key { + t.Errorf("错误:item.key!=m.k item.key:%s m.k:%s", item.key, m.k) + isPrintAll = true + } + + // 校验1 + if _, exist := tempKeyDict[item.key]; exist { + t.Errorf("错误:item.key重复 item.key:%s m.k:%s", item.key, m.k) + isPrintAll = true + } else { + tempKeyDict[item.key] = false + } + + // 校验2 + if m.Fap > beforeFap { + t.Errorf("错误:m.Fap大于前面的排行 beforeFap:%v curFap:%v", beforeFap, m.Fap) + isPrintAll = true + } + } + + if isPrintAll { + for _, item := range items { + m := item.obj.(*rmodel) + t.Log(m) + } + } + +} + +func Test_Rank2(t *testing.T) { + r := NewRankUtil(5, compar) + r.Refresh("1", &rmodel{k: "1", Fap: 1}, true) + r.Refresh("2", &rmodel{k: "2", Fap: 2}, true) + r.Refresh("3", &rmodel{k: "3", Fap: 3}, true) + r.Refresh("4", &rmodel{k: "4", Fap: 4}, true) + r.Refresh("5", &rmodel{k: "5", Fap: 5}, true) + + //把1挤出去 + ischange, dm := r.Refresh("6", &rmodel{k: "6", Fap: 6}, true) + if ischange == false { + t.Errorf("把1挤出去错误,排行榜未变动") + return + } + if dm.key != "1" { + t.Errorf("把1挤出去错误,挤出的不是1") + return + } + t.Log("被挤出的对象信息-> ", dm) + + //打印所有 + items := r.GetAll() + for _, item := range items { + t.Log(item) + } +} + +type rmodel struct { + k string + Fap int +} + +func (m *rmodel) String() string { + return fmt.Sprintf("m.k:%s fap:%v", m.k, m.Fap) +} + +// compar +// @description: 判断对象大小,返回含义 -1:ab +// parameter: +// +// @a:对象a +// @b:对象b +// +// return: +// +// @int: +func compar(a, b interface{}) int { + af := a.(*rmodel).Fap + bf := b.(*rmodel).Fap + if af > bf { + return 1 + } else if af == bf { + return 0 + } else { + return -1 + } +} diff --git a/trunk/goutil/rank-util/readme.md b/trunk/goutil/rank-util/readme.md new file mode 100644 index 0000000..74e0625 --- /dev/null +++ b/trunk/goutil/rank-util/readme.md @@ -0,0 +1,54 @@ +提供游戏内的实时排行榜功能,建议最大长度设定<=200 +使用方式: + +```go +package main + +import ( + rank_util "goutil/rank-util" +) + +func main() { + // 构造对象 + r := rank_util.NewRankUtil(20, compar) + + // 刷新排行榜 + m := &rmodel{k: "byrontest", Fap: 110} + ifChangeRank, dm := r.Refresh(m.k, m, true) + + // 获取全部排行榜 + tempList := r.GetAll() + + // 删除某个key + isok := r.Delete("byrontest") +} + +// compar +// @description: 判断对象大小,返回含义 -1:ab +// parameter: +// @a:对象a +// @b:对象b +// return: +// @int: +func compar(a, b interface{}) int { + af := a.(*rmodel).Fap + bf := b.(*rmodel).Fap + if af > bf { + return 1 + } else if af == bf { + return 0 + } else { + return -1 + } +} + +type rmodel struct { + k string + Fap int +} + +``` + + + + diff --git a/trunk/goutil/rank-util/util.go b/trunk/goutil/rank-util/util.go new file mode 100644 index 0000000..5977d2a --- /dev/null +++ b/trunk/goutil/rank-util/util.go @@ -0,0 +1,343 @@ +package rank_util + +import ( + "sync" +) + +// RankUtil +// @description:排行工具类 +type RankUtil struct { + // maxCount 排行榜最大数量 + maxCount int + + // compar 对比函数 返回含义 -1:ab + compar func(a, b interface{}) int + + // dataList 排行数据 + dataList []*Model + + // dataDict 数据字典 + dataDict map[string]*Model + + // dataLock 数据锁 + dataLock sync.RWMutex +} + +// NewRankUtil +// @description: 构造排行对象 +// parameter: +// @mc:最大容量 +// @comp:元素比对方法 +// return: +func NewRankUtil(mc int, comp func(a, b interface{}) int) *RankUtil { + result := &RankUtil{ + maxCount: mc, + compar: comp, + dataList: make([]*Model, 0, mc), + dataDict: make(map[string]*Model, mc), + } + + return result +} + +// GetMaxCount +// @description: 获取排行榜最大数量 +// parameter: +// @receiver r: +// return: +// @int: +func (r *RankUtil) GetMaxCount() int { + return r.maxCount +} + +// GetCount +// @description: 获取排行榜当前数量 +// parameter: +// @receiver r: +// return: +// @int: +func (r *RankUtil) GetCount() int { + r.dataLock.RLock() + defer r.dataLock.RUnlock() + + return len(r.dataList) +} + +// IsFull +// @description: 判断排行榜是否已经满了 +// parameter: +// @receiver r: +// return: +// @bool: +func (r *RankUtil) IsFull() bool { + r.dataLock.RLock() + defer r.dataLock.RUnlock() + + return len(r.dataList) == r.maxCount +} + +// GetAll +// @description: 获取排行榜内容 +// parameter: +// @receiver r: +// return: +// @[]*Model: +func (r *RankUtil) GetAll() []*Model { + r.dataLock.RLock() + defer r.dataLock.RUnlock() + + result := make([]*Model, 0, len(r.dataList)) + for _, item := range r.dataList { + if item == nil || item.obj == nil { + break + } + + result = append(result, item) + } + + return result +} + +// GetRankListBySkip +// @description: 获取排行榜内容 +// parameter: +// @receiver r: +// @skip:跳过的数量 +// @count:跳过后需要返回的数量 +// return: +// @[]*Model: +func (r *RankUtil) GetRankListBySkip(skip, count int) []*Model { + r.dataLock.RLock() + defer r.dataLock.RUnlock() + + result := make([]*Model, 0, count) + cn := len(r.dataList) + if skip >= cn { + return result + } + + for i := 0; i < count; i++ { + item := r.dataList[skip+i] + if item == nil || item.obj == nil { + break + } + + result = append(result, item) + } + + return result +} + +// GetItemForK +// @description: 根据key获取对象 +// parameter: +// @receiver r: +// @k:key +// return: +// @*Model: +func (r *RankUtil) GetItemForK(k string) *Model { + r.dataLock.RLock() + defer r.dataLock.RUnlock() + + item, _ := r.dataDict[k] + + return item +} + +// GetItemForRank +// @description: 根据排名获取对应对象 +// parameter: +// @receiver r: +// @rank:排名 +// return: +// @*Model: +func (r *RankUtil) GetItemForRank(rank int) *Model { + r.dataLock.RLock() + defer r.dataLock.RUnlock() + + if len(r.dataList) < rank { + return nil + } + + return r.dataList[rank-1] +} + +// Refresh +// @description: 刷新排行榜 +// parameter: +// @receiver r: +// @k:key +// @o:排行持有的对象,业务层不应该持有该对象。否则业务层更改内容,会导致排行榜内容被改变 +// @isup:变动是否升高 +// return: +// @changeRank:排行是否变动 +// @dm:如果有对象,因为排行变动导致掉出排行榜,则返回该对象,否则返回nil +func (r *RankUtil) Refresh(k string, o interface{}, isup bool) (changeRank bool, dm *Model) { + r.dataLock.Lock() + defer r.dataLock.Unlock() + + changeRank = false + curItem, exists := r.dataDict[k] + if isup { + if !exists { + newRank := len(r.dataList) + 1 + curItem = &Model{rank: newRank, key: k, obj: o} + // 排行榜未满,必定入榜,则先加入排行榜 + if newRank <= r.maxCount { + r.dataList = append(r.dataList, curItem) + r.dataDict[curItem.key] = curItem + changeRank = true + _, dm = r.upRank(curItem) + } else { + changeRank, dm = r.upRank(curItem) + } + } else { + curItem.obj = o + changeRank, dm = r.upRank(curItem) + } + } else { + // 如果在排行榜内,从之前往下比较 + if exists { + curItem.obj = o + changeRank = r.downRank(curItem) + } else { + // 如果不在排行榜内,直接结束 + } + } + + return +} + +// Delete +// @description: 删除k指定的对象,很耗性能,不建议高频使用 +// parameter: +// @receiver r: +// @k:key +// return: +// @bool:true:代表key存在排行榜中,并且删除成功 false:其他情况 +func (r *RankUtil) Delete(k string) bool { + r.dataLock.Lock() + defer r.dataLock.Unlock() + + m, exists := r.dataDict[k] + if !exists { + return false + } + + //从当前位置向后移动,到最后的时候,删除 + for { + if m.rank >= len(r.dataList) { + break + } + + am := r.dataList[m.rank] + r.exchange(m, am) + m = am + } + + //最后一个应该是要删除的对象 + r.dataList = append(r.dataList[:m.rank-1:r.maxCount], r.dataList[m.rank:]...) + delete(r.dataDict, k) + + return true +} + +// upRank +// @description: 排行升高 +// parameter: +// @receiver r: +// @curItem:当前对象 +// return: +// @changeRank:排行是否变动 +// @dm:因排行变动被移除排行的对象 +func (r *RankUtil) upRank(curItem *Model) (changeRank bool, dm *Model) { + changeRank = false + for { + // rank=1,排在第一位,下标为0,故这里需要-2为上一位的下标 + beforeIndex := curItem.rank - 2 + if beforeIndex < 0 { + break + } + + beforeItem := r.dataList[beforeIndex] + if r.compar(curItem.obj, beforeItem.obj) <= 0 { + break + } + + // 交换操作 + tdm := r.exchange(curItem, beforeItem) + if tdm != nil { + dm = tdm + } + + curItem = beforeItem + changeRank = true + } + + return +} + +// downRank +// @description: 排行降低 +// parameter: +// @receiver r: +// @curItem:当前对象 +// return: +// @bool: +func (r *RankUtil) downRank(curItem *Model) bool { + changeRank := false + + for { + // rank=1,排在第一位,下标为0,需要对比rank=2的,下标为1,故这里直接取rank即可 + afterIndex := curItem.rank + if afterIndex >= len(r.dataList) { + break + } + + afterItem := r.dataList[afterIndex] + if r.compar(afterItem.obj, curItem.obj) <= 0 { + break + } + + // 交换操作 + r.exchange(curItem, afterItem) + + curItem = afterItem + changeRank = true + } + + return changeRank +} + +// exchange +// @description: 排行对象交换 +// parameter: +// @receiver r: +// @a:对象a +// @b:对象b +// return: +// @delRank:因交换被移除排行榜的对象 +func (r *RankUtil) exchange(a, b *Model) (dm *Model) { + tempObj := a.obj + tempKey := a.key + + a.obj = b.obj + a.key = b.key + b.obj = tempObj + b.key = tempKey + + if a.rank > r.maxCount { + delete(r.dataDict, a.key) + dm = a + } else { + r.dataDict[a.key] = a + } + if b.rank > r.maxCount { + delete(r.dataDict, b.key) + dm = b + } else { + r.dataDict[b.key] = b + } + + return +} diff --git a/trunk/goutil/redisUtil/pubsub.go b/trunk/goutil/redisUtil/pubsub.go new file mode 100644 index 0000000..a31dd54 --- /dev/null +++ b/trunk/goutil/redisUtil/pubsub.go @@ -0,0 +1,131 @@ +package redisUtil + +import ( + "context" + "fmt" + "time" + + "github.com/gomodule/redigo/redis" +) + +// 订阅回调函数 +type SubscribeCallback func() error + +// Subscriber +// @description: 订阅者 +type Subscriber struct { + // pool 订阅者连接池 + pool *RedisPool + // callBack 订阅者回调函数 + callBack SubscribeCallback +} + +// NewSubscriber +// @description: 构建一个订阅者 +// parameter: +// @pool: +// @callBack: +// return: +// @*Subscriber: +func NewSubscriber(pool *RedisPool, callBack SubscribeCallback) *Subscriber { + return &Subscriber{pool: pool, callBack: callBack} +} + +// Promulgator +// @description: 发布者 +type Promulgator struct { + // pool 发布者连接池 + pool *RedisPool +} + +// NewPromulgator +// @description: 构建一个发布者 +// parameter: +// @pool: +// return: +// @*Promulgator: +func NewPromulgator(pool *RedisPool) *Promulgator { + return &Promulgator{pool: pool} +} + +// Publish +// @description: 发布消息 +// parameter: +// @receiver s: +// @channel: +// @message: +// return: +// @error: +func (s *Promulgator) Publish(channel, message string) error { + c := s.pool.GetConnection() + defer c.Close() + + _, err := c.Do("PUBLISH", channel, message) + if err != nil { + return fmt.Errorf("redis publish %s %s, err: %v", channel, message, err) + } + //n, err := s.pool.Int(result) + //if err != nil { + // return fmt.Errorf("redis publish %s %s, err: %v", channel, message, err) + //} + + return nil +} + +// Subscribe +// @description: 订阅者订阅消息 +// parameter: +// @receiver s: +// @ctx: +// @channel: 频道 +// return: +// @error: +func (s *Subscriber) Subscribe(ctx context.Context, channel ...string) error { + sub := redis.PubSubConn{Conn: s.pool.GetConnection()} + if err := sub.Subscribe(redis.Args{}.AddFlat(channel)...); err != nil { + return err + } + done := make(chan error, 1) + + // 启动一个新协程去持续订阅消息 + go func() { + defer sub.Close() + for { + switch msg := sub.Receive().(type) { + case error: + done <- fmt.Errorf("redis pubsub receive err: %v", msg) + return + case redis.Message: + if err := s.callBack(); err != nil { + done <- err + return + } + case redis.Subscription: + if msg.Count == 0 { + // 所有的订阅者都退出 + done <- nil + return + } + } + } + }() + + // health check + tick := time.NewTicker(time.Minute) + defer tick.Stop() + for { + select { + case <-ctx.Done(): + if err := sub.Unsubscribe(); err != nil { + return fmt.Errorf("redis pubsub unsubscribe err: %v", err) + } + return nil + case err := <-done: + return err + case <-tick.C: + if err := sub.Ping(""); err != nil { + return err + } + } + } +} diff --git a/trunk/goutil/redisUtil/redisConfig.go b/trunk/goutil/redisUtil/redisConfig.go new file mode 100644 index 0000000..56a5d9b --- /dev/null +++ b/trunk/goutil/redisUtil/redisConfig.go @@ -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; +// redisConfigStr:redis连接字符串 +// 返回值: +// 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, + } +} diff --git a/trunk/goutil/redisUtil/redisConfig_test.go b/trunk/goutil/redisUtil/redisConfig_test.go new file mode 100644 index 0000000..d29c1ba --- /dev/null +++ b/trunk/goutil/redisUtil/redisConfig_test.go @@ -0,0 +1,88 @@ +package redisUtil + +import ( + "testing" + "time" +) + +func TestNewRedisConfig(t *testing.T) { + redisConfigStr := "ConnectionString=10.1.0.21:6379;Password=redis_pwd;Database=3;MaxActive=50;MaxIdle=20;IdleTimeout=300;DialConnectTimeout=10;" + redisConfig, err := NewRedisConfig(redisConfigStr) + if err != nil { + t.Errorf("there should be no err, but now has:%s", err) + } + + connectionString := "10.1.0.21:6379" + password := "redis_pwd" + database := 3 + maxActive := 50 + maxIdle := 20 + idleTimeout := 300 * time.Second + dialConnectTimeout := 10 * time.Second + if redisConfig.ConnectionString != connectionString { + t.Errorf("expected %s, but now got %s", connectionString, redisConfig.ConnectionString) + } + + if redisConfig.Password != password { + t.Errorf("expected %s, but now got %s", password, redisConfig.Password) + } + + if redisConfig.Database != database { + t.Errorf("expected %d, but now got %d", database, redisConfig.Database) + } + + if redisConfig.MaxActive != maxActive { + t.Errorf("expected %d, but now got %d", maxActive, redisConfig.MaxActive) + } + + if redisConfig.MaxIdle != maxIdle { + t.Errorf("expected %d, but now got %d", maxIdle, redisConfig.MaxIdle) + } + + if redisConfig.IdleTimeout != idleTimeout { + t.Errorf("expected %d, but now got %d", idleTimeout, redisConfig.IdleTimeout) + } + + if redisConfig.DialConnectTimeout != dialConnectTimeout { + t.Errorf("expected %d, but now got %d", dialConnectTimeout, redisConfig.DialConnectTimeout) + } +} + +func TestNewRedisConfig2(t *testing.T) { + connectionString := "10.1.0.21:6379" + password := "redis_pwd" + database := 3 + maxActive := 50 + maxIdle := 20 + idleTimeout := 300 * time.Second + dialConnectTimeout := 10 * time.Second + + redisConfig := NewRedisConfig2(connectionString, password, database, maxActive, maxIdle, idleTimeout, dialConnectTimeout) + if redisConfig.ConnectionString != connectionString { + t.Errorf("expected %s, but now got %s", connectionString, redisConfig.ConnectionString) + } + + if redisConfig.Password != password { + t.Errorf("expected %s, but now got %s", password, redisConfig.Password) + } + + if redisConfig.Database != database { + t.Errorf("expected %d, but now got %d", database, redisConfig.Database) + } + + if redisConfig.MaxActive != maxActive { + t.Errorf("expected %d, but now got %d", maxActive, redisConfig.MaxActive) + } + + if redisConfig.MaxIdle != maxIdle { + t.Errorf("expected %d, but now got %d", maxIdle, redisConfig.MaxIdle) + } + + if redisConfig.IdleTimeout != idleTimeout { + t.Errorf("expected %d, but now got %d", idleTimeout, redisConfig.IdleTimeout) + } + + if redisConfig.DialConnectTimeout != dialConnectTimeout { + t.Errorf("expected %d, but now got %d", dialConnectTimeout, redisConfig.DialConnectTimeout) + } +} diff --git a/trunk/goutil/redisUtil/redisPool.go b/trunk/goutil/redisUtil/redisPool.go new file mode 100644 index 0000000..50e5caa --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool.go @@ -0,0 +1,119 @@ +/* +redisUtil对Redis的连接池进行了一定程度的封装 +将常用的方法进行了内部封装,对于不常见的方法,有两种处理方式: +1、向作者提出请求,由作者添加到代码中 +2、调用GetConnection方法,然后自己实现逻辑 +在代码中,统一将conn.Do的结果和redis.Int,redis.String等类型转换合并处理 + +redis的命令请参考:https://redis.readthedocs.io/en/2.6/index.html +*/ +package redisUtil + +import ( + "fmt" + "time" + + "github.com/gomodule/redigo/redis" +) + +// 自定义Redis连接池对象 +type RedisPool struct { + name string + address string + pool *redis.Pool +} + +// 获取自定义Redis连接池对象的名称 +// 返回值: +// 自定义Redis连接池对象的名称 +func (this *RedisPool) GetName() string { + return this.name +} + +// 获取自定义Redis连接池对象的目标地址 +// 返回值: +// 自定义Redis连接池对象的目标地址 +func (this *RedisPool) GetAddress() string { + return this.address +} + +// 从自定义连接池中获取连接,在使用后需要调用Close方法 +// 返回值: +// 连接对象 +func (this *RedisPool) GetConnection() redis.Conn { + return this.pool.Get() +} + +// 关闭自定义连接池 +func (this *RedisPool) Close() { + this.pool.Close() +} + +// 测试连接情况 +// 返回值: +// 错误对象 +func (this *RedisPool) TestConnection() error { + conn := this.GetConnection() + defer conn.Close() + + _, err := conn.Do("PING") + + return err +} + +// 创建新的Redis连接池对象(obsolete,建议使用NewRedisPool2) +// name:连接池对象名称 +// connectionString:Redis服务器连接地址 +// password:Redis服务器连接密码 +// database:Redis服务器选择的数据库 +// maxActive:Redis连接池允许的最大活跃连接数量 +// maxIdle:Redis连接池允许的最大空闲数量 +// idleTimeout:连接被回收前的空闲时间 +// dialConnectTimeout:连接Redis服务器超时时间 +// 返回值: +// Redis连接池对象 +func NewRedisPool(name, connectionString, password string, database, maxActive, maxIdle int, idleTimeout, dialConnectTimeout time.Duration) *RedisPool { + redisConfig := NewRedisConfig2(connectionString, password, database, maxActive, maxIdle, idleTimeout, dialConnectTimeout) + return NewRedisPool2(name, redisConfig) +} + +// 创建新的Redis连接池对象 +// name:连接池对象名称 +// redisConfig:Redis配置对象 +// 返回值: +// Redis连接池对象 +func NewRedisPool2(name string, redisConfig *RedisConfig) *RedisPool { + poolObj := &redis.Pool{ + MaxActive: redisConfig.MaxActive, + MaxIdle: redisConfig.MaxIdle, + IdleTimeout: redisConfig.IdleTimeout, + Dial: func() (redis.Conn, error) { + options := make([]redis.DialOption, 0, 4) + options = append(options, redis.DialConnectTimeout(redisConfig.DialConnectTimeout)) + options = append(options, redis.DialDatabase(redisConfig.Database)) + if redisConfig.Password != "" { + options = append(options, redis.DialPassword(redisConfig.Password)) + } + + conn, err := redis.Dial("tcp", redisConfig.ConnectionString, options...) + if err != nil { + return nil, fmt.Errorf("Dial failed, err:%s", err) + } + + return conn, err + }, + TestOnBorrow: func(conn redis.Conn, t time.Time) error { + if time.Since(t) < time.Minute { + return nil + } + _, err := conn.Do("PING") + return err + }, + } + + return &RedisPool{ + name: name, + address: redisConfig.ConnectionString, + pool: poolObj, + } +} diff --git a/trunk/goutil/redisUtil/redisPool_db.go b/trunk/goutil/redisUtil/redisPool_db.go new file mode 100644 index 0000000..c1ed7b6 --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_db.go @@ -0,0 +1,221 @@ +/* +未实现的哈希表方法: +MOVE、SCAN、SORT、FLUSHDB、FLUSHALL、SELECT、SWAPDB +*/ +package redisUtil + +import ( + "github.com/gomodule/redigo/redis" +) + +/* +EXISTS key +可用版本: >= 1.0.0 +时间复杂度: O(1) +检查给定 key 是否存在。 + +返回值 +若 key 存在,返回 1 ,否则返回 0 。 +*/ +func (this *RedisPool) Exists(key string) (exist bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + result, err = redis.Int(conn.Do("EXISTS", key)) + if err != nil { + return + } + + if result == 1 { + exist = true + } + + return +} + +/* +TYPE key +可用版本: >= 1.0.0 +时间复杂度: O(1) +返回 key 所储存的值的类型。 + +返回值 +none (key不存在) + +string (字符串) + +list (列表) + +set (集合) + +zset (有序集) + +hash (哈希表) + +stream (流) +*/ +func (this *RedisPool) Type(key string) (_type string, err error) { + conn := this.GetConnection() + defer conn.Close() + + _type, err = redis.String(conn.Do("TYPE", key)) + return +} + +/* +RENAME key newkey + +将 key 改名为 newkey 。 + +当 key 和 newkey 相同,或者 key 不存在时,返回一个错误。 + +当 newkey 已经存在时, RENAME 命令将覆盖旧值。 + +可用版本: +>= 1.0.0 +时间复杂度: +O(1) +返回值: +改名成功时提示 OK ,失败时候返回一个错误。 +*/ +func (this *RedisPool) Rename(key, newkey string) (err error) { + conn := this.GetConnection() + defer conn.Close() + + _, err = conn.Do("RENAME", key, newkey) + return +} + +/* +RENAMENX key newkey + +当且仅当 newkey 不存在时,将 key 改名为 newkey 。 + +当 key 不存在时,返回一个错误。 + +可用版本: +>= 1.0.0 +时间复杂度: +O(1) +返回值: +修改成功时,返回 1 。 +如果 newkey 已经存在,返回 0 。 +*/ +func (this *RedisPool) RenameNX(key, newkey string) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + result, err = redis.Int(conn.Do("RENAMENX", key, newkey)) + if err != nil { + return + } + + if result == 1 { + successful = true + } + + return +} + +/* +DEL key [key ...] + +删除给定的一个或多个 key 。 + +不存在的 key 会被忽略。 + +可用版本: +>= 1.0.0 +时间复杂度: +O(N), N 为被删除的 key 的数量。 +删除单个字符串类型的 key ,时间复杂度为O(1)。 +删除单个列表、集合、有序集合或哈希表类型的 key ,时间复杂度为O(M), M 为以上数据结构内的元素数量。 +返回值: +被删除 key 的数量。 +*/ +func (this *RedisPool) Del(keys ...string) (count int, err error) { + conn := this.GetConnection() + defer conn.Close() + + count, err = redis.Int(conn.Do("DEL", redis.Args{}.AddFlat(keys)...)) + return +} + +/* +从当前数据库中随机返回(不删除)一个 key 。 + +可用版本: +>= 1.0.0 +时间复杂度: +O(1) +返回值: +当数据库不为空时,返回一个 key 。 +当数据库为空时,返回 nil 。 +*/ +func (this *RedisPool) RandomKey() (key string, exist bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var reply interface{} + reply, err = conn.Do("RANDOMKEY") + if err != nil { + return + } + if reply == nil { + return + } + + key, err = redis.String(reply, err) + if err != nil { + return + } + + exist = true + return +} + +/* +DBSIZE +可用版本: >= 1.0.0 +时间复杂度: O(1) +返回当前数据库的 key 的数量。 + +返回值 +当前数据库的 key 的数量。 +*/ +func (this *RedisPool) DBSize() (keyCount int, err error) { + conn := this.GetConnection() + defer conn.Close() + + keyCount, err = redis.Int(conn.Do("DBSIZE")) + return +} + +/* +KEYS pattern + +查找所有符合给定模式 pattern 的 key 。 + +KEYS * 匹配数据库中所有 key 。 +KEYS h?llo 匹配 hello , hallo 和 hxllo 等。 +KEYS h*llo 匹配 hllo 和 heeeeello 等。 +KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。 +特殊符号用 \ 隔开 + +KEYS 的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 key ,你最好还是用 Redis 的集合结构(set)来代替。 +可用版本: +>= 1.0.0 +时间复杂度: +O(N), N 为数据库中 key 的数量。 +返回值: +符合给定模式的 key 列表。 +*/ +func (this *RedisPool) Keys(pattern string) (keyList []string, err error) { + conn := this.GetConnection() + defer conn.Close() + + keyList, err = redis.Strings(conn.Do("KEYS", pattern)) + return +} diff --git a/trunk/goutil/redisUtil/redisPool_db_test.go b/trunk/goutil/redisUtil/redisPool_db_test.go new file mode 100644 index 0000000..e9637bc --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_db_test.go @@ -0,0 +1,860 @@ +package redisUtil + +import ( + "testing" + "time" +) + +var ( + redisPoolObj_db *RedisPool +) + +func init() { + redisPoolObj_db = NewRedisPool("testPool", "10.1.0.21:6379", "redis_pwd", 5, 500, 200, 10*time.Second, 5*time.Second) +} + +func TestExists(t *testing.T) { + /* + redis> SET db "redis" + OK + */ + key := "db" + value := "redis" + successful, err := redisPoolObj_db.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set the key:%s should be successful, but now it's not.", key) + return + } + + /* + redis> EXISTS db + (integer) 1 + */ + exist, err := redisPoolObj_db.Exists(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("Set the key:%s should exist, but now it doesn't.", key) + return + } + + /* + redis> DEL db + (integer) 1 + */ + expected := 1 + got, err := redisPoolObj_db.Del(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> EXISTS db + (integer) 0 + */ + exist, err = redisPoolObj_db.Exists(key) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("Set the key:%s should not exist, but now it does.", key) + return + } +} + +func TestType(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 字符串 + + redis> SET weather "sunny" + OK + + redis> TYPE weather + string + */ + key := "weather" + value := "sunny" + + successful, err := redisPoolObj_db.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set the key:%s should be successful, but now it's not.", key) + return + } + + expected := "string" + got, err := redisPoolObj_db.Type(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %s, but now got %s", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + # 列表 + + redis> LPUSH book_list "programming in scala" + (integer) 1 + + redis> TYPE book_list + list + */ + key = "book_list" + value = "programming in scala" + expected2 := 1 + got2, err := redisPoolObj_db.LPush(key, value) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but now got %d", expected2, got2) + return + } + + expected = "list" + got, err = redisPoolObj_db.Type(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %s, but now got %s", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + # 集合 + + redis> SADD pat "dog" + (integer) 1 + + redis> TYPE pat + set + */ + + key = "pat" + value = "dog" + expected3 := 1 + got3, err := redisPoolObj_db.SAdd(key, value) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %d, but now got %d", expected3, got3) + return + } + + expected = "set" + got, err = redisPoolObj_db.Type(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %s, but now got %s", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) +} + +func TestRename(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # key 存在且 newkey 不存在 + + redis> SET message "hello world" + OK + + redis> RENAME message greeting + OK + + redis> EXISTS message # message 不复存在 + (integer) 0 + + redis> EXISTS greeting # greeting 取而代之 + (integer) 1 + */ + key := "message" + value := "hello world" + expected := true + got, err := redisPoolObj_db.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + newkey := "greeting" + err = redisPoolObj_db.Rename(key, newkey) + if err != nil { + t.Fail() + } + + expected = false + got, err = redisPoolObj_db.Exists(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + expected = true + got, err = redisPoolObj_db.Exists(newkey) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + deleteKeys = append(deleteKeys, newkey) + + /* + # 当 key 不存在时,返回错误 + + redis> RENAME fake_key never_exists + (error) ERR no such key + */ + key = "fake_key" + newkey = "never_exists" + err = redisPoolObj_db.Rename(key, newkey) + if err == nil { + t.Errorf("There should be one error, but now there isn't.") + return + } + + /* + # newkey 已存在时, RENAME 会覆盖旧 newkey + + redis> SET pc "lenovo" + OK + + redis> SET personal_computer "dell" + OK + + redis> RENAME pc personal_computer + OK + + redis> GET pc + (nil) + + redis:1> GET personal_computer # 原来的值 dell 被覆盖了 + "lenovo" + */ + key = "pc" + value = "lenovo" + expected = true + got, err = redisPoolObj_db.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + key = "personal_computer" + value = "dell" + expected = true + got, err = redisPoolObj_db.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + key = "pc" + newkey = "personal_computer" + err = redisPoolObj_db.Rename(key, newkey) + if err != nil { + t.Fail() + } + + expected = false + got, err = redisPoolObj_db.Exists(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + expected = true + got, err = redisPoolObj_db.Exists(newkey) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + deleteKeys = append(deleteKeys, newkey) +} + +func TestRenameNX(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # newkey 不存在,改名成功 + + redis> SET player "MPlyaer" + OK + + redis> EXISTS best_player + (integer) 0 + + redis> RENAMENX player best_player + (integer) 1 + */ + key := "player" + value := "MPlayer" + expected := true + got, err := redisPoolObj_db.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + newkey := "best_player" + expected = false + got, err = redisPoolObj_db.Exists(newkey) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + expected = true + got, err = redisPoolObj_db.RenameNX(key, newkey) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + deleteKeys = append(deleteKeys, newkey) + + /* + # newkey存在时,失败 + + redis> SET animal "bear" + OK + + redis> SET favorite_animal "butterfly" + OK + + redis> RENAMENX animal favorite_animal + (integer) 0 + + redis> get animal + "bear" + + redis> get favorite_animal + "butterfly" + */ + key = "animal" + value = "bear" + expected = true + got, err = redisPoolObj_db.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + key = "favorite_animal" + value = "butterfly" + expected = true + got, err = redisPoolObj_db.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + expected = false + got, err = redisPoolObj_db.RenameNX(key, newkey) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + key2 := "animal" + expected2 := "bear" + got2_interface, exist2, err := redisPoolObj_db.Get(key2) + if err != nil { + t.Fail() + } + if !exist2 { + t.Errorf("The key:%s should exist, but now it doesn't.", key2) + return + } + got2, err := redisPoolObj_db.String(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %s, but now got %s", expected2, got2) + return + } + + key3 := "animal" + expected3 := "bear" + got3_interface, exist3, err := redisPoolObj_db.Get(key3) + if err != nil { + t.Fail() + } + if !exist3 { + t.Errorf("The key:%s should exist, but now it doesn't.", key3) + return + } + got3, err := redisPoolObj_db.String(got3_interface) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %s, but now got %s", expected3, got3) + return + } +} + +func TestDel(t *testing.T) { + /* + # 删除单个 key + + redis> SET name huangz + OK + + redis> DEL name + (integer) 1 + */ + key := "name" + value := "huangz" + expected := true + got, err := redisPoolObj_db.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + expected2 := 1 + got2, err := redisPoolObj_db.Del(key) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but now got %d", expected2, got2) + return + } + + /* + # 删除一个不存在的 key + + redis> EXISTS phone + (integer) 0 + + redis> DEL phone # 失败,没有 key 被删除 + (integer) 0 + */ + key = "phone" + expected = false + got, err = redisPoolObj_db.Exists(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + expected3 := 0 + got3, err := redisPoolObj_db.Del(key) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %d, but now got %d", expected3, got3) + return + } + + /* + # 同时删除多个 key + + redis> SET name "redis" + OK + + redis> SET type "key-value store" + OK + + redis> SET website "redis.com" + OK + + redis> DEL name type website + (integer) 3 + */ + key1 := "name" + value1 := "redis" + expected = true + got, err = redisPoolObj_db.Set(key1, value1, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + key2 := "type" + value2 := "key-value store" + expected = true + got, err = redisPoolObj_db.Set(key2, value2, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + key3 := "website" + value3 := "redis.com" + expected = true + got, err = redisPoolObj_db.Set(key3, value3, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + expected4 := 3 + got4, err := redisPoolObj_db.Del(key1, key2, key3) + if err != nil { + t.Fail() + } + if got4 != expected4 { + t.Errorf("Expected to get %d, but now got %d", expected4, got4) + return + } +} + +func TestRandomKey(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 数据库为空 + + redis> RANDOMKEY + (nil) + */ + expected := "" + got, exist, err := redisPoolObj_db.RandomKey() + if err != nil { + t.Fail() + } + if exist { + t.Errorf("RandomKey doesn't exist, but now it does.") + return + } + if got != expected { + t.Errorf("Expected to get %s, but now got %s", expected, got) + return + } + + /* + # 数据库不为空 + + redis> MSET fruit "apple" drink "beer" food "cookies" # 设置多个 key + OK + + redis> RANDOMKEY + "fruit" + + redis> RANDOMKEY + "food" + + redis> KEYS * # 查看数据库内所有key,证明 RANDOMKEY 并不删除 key + 1) "food" + 2) "drink" + 3) "fruit" + */ + key_value_map := make(map[string]interface{}) + key_value_map["fruit"] = "apple" + key_value_map["drink"] = "beer" + key_value_map["food"] = "cookies" + err = redisPoolObj_db.MSet(key_value_map) + if err != nil { + t.Fail() + } + + got, exist, err = redisPoolObj_db.RandomKey() + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("RandomKey should exist, but now it doesn't.") + return + } + if _, exist = key_value_map[got]; !exist { + t.Errorf("RandomKey should exist, but now it doesn't.") + return + } + + expected2 := make([]string, 0, len(key_value_map)) + for k := range key_value_map { + expected2 = append(expected2, k) + deleteKeys = append(deleteKeys, k) + } + got2, err := redisPoolObj_db.Keys("*") + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } +} + +func TestDBSize(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> DBSIZE + (integer) 0 + + redis> SET new_key "hello_moto" # 增加一个 key 试试 + OK + + redis> DBSIZE + (integer) 1 + */ + expected := 0 + got, err := redisPoolObj_db.DBSize() + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + key := "new_key" + value := "hello_moto" + redisPoolObj_db.Set(key, value, "", 0, "") + + deleteKeys = append(deleteKeys, key) + + expected = 1 + got, err = redisPoolObj_db.DBSize() + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } +} + +func TestKeys(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> MSET one 1 two 2 three 3 four 4 # 一次设置 4 个 key + OK + */ + key_value_map := make(map[string]interface{}) + key_value_map["one"] = "1" + key_value_map["two"] = "2" + key_value_map["three"] = "3" + key_value_map["four"] = "4" + err := redisPoolObj_db.MSet(key_value_map) + if err != nil { + t.Fail() + } + + for k := range key_value_map { + deleteKeys = append(deleteKeys, k) + } + + /* + redis> KEYS *o* + 1) "four" + 2) "two" + 3) "one" + */ + pattern := "*o*" + expected := []string{"four", "two", "one"} + got, err := redisPoolObj_db.Keys(pattern) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } + + /* + redis> KEYS t?? + 1) "two" + */ + pattern = "t??" + expected = []string{"two"} + got, err = redisPoolObj_db.Keys(pattern) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } + + /* + redis> KEYS t[w]* + 1) "two" + */ + pattern = "t[w]*" + expected = []string{"two"} + got, err = redisPoolObj_db.Keys(pattern) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } + + /* + redis> KEYS * # 匹配数据库内所有 key + 1) "four" + 2) "three" + 3) "two" + 4) "one" + */ + pattern = "*" + expected = []string{"two", "one", "three", "four"} + got, err = redisPoolObj_db.Keys(pattern) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } +} diff --git a/trunk/goutil/redisUtil/redisPool_expire.go b/trunk/goutil/redisUtil/redisPool_expire.go new file mode 100644 index 0000000..ecfe708 --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_expire.go @@ -0,0 +1,233 @@ +/* +未实现的哈希表方法: +MOVE、SCAN、SORT、FLUSHDB、FLUSHALL、SELECT、SWAPDB +*/ +package redisUtil + +import ( + "github.com/gomodule/redigo/redis" +) + +/* +EXPIRE key seconds +可用版本: >= 1.0.0 +时间复杂度: O(1) +为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。 + +在 Redis 中,带有生存时间的 key 被称为『易失的』(volatile)。 + +生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆写(overwrite),这意味着,如果一个命令只是修改(alter)一个带生存时间的 key 的值而不是用一个新的 key 值来代替(replace)它的话,那么生存时间不会被改变。 + +比如说,对一个 key 执行 INCR 命令,对一个列表进行 LPUSH 命令,或者对一个哈希表执行 HSET 命令,这类操作都不会修改 key 本身的生存时间。 + +另一方面,如果使用 RENAME 对一个 key 进行改名,那么改名后的 key 的生存时间和改名前一样。 + +RENAME 命令的另一种可能是,尝试将一个带生存时间的 key 改名成另一个带生存时间的 another_key ,这时旧的 another_key (以及它的生存时间)会被删除,然后旧的 key 会改名为 another_key ,因此,新的 another_key 的生存时间也和原本的 key 一样。 + +使用 PERSIST 命令可以在不删除 key 的情况下,移除 key 的生存时间,让 key 重新成为一个『持久的』(persistent) key 。 + +更新生存时间 +可以对一个已经带有生存时间的 key 执行 EXPIRE 命令,新指定的生存时间会取代旧的生存时间。 + +过期时间的精确度 +在 Redis 2.4 版本中,过期时间的延迟在 1 秒钟之内 —— 也即是,就算 key 已经过期,但它还是可能在过期之后一秒钟之内被访问到,而在新的 Redis 2.6 版本中,延迟被降低到 1 毫秒之内。 + +Redis 2.1.3 之前的不同之处 +在 Redis 2.1.3 之前的版本中,修改一个带有生存时间的 key 会导致整个 key 被删除,这一行为是受当时复制(replication)层的限制而作出的,现在这一限制已经被修复。 + +返回值 +设置成功返回 1 。 当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。 +*/ +func (this *RedisPool) Expire(key string, seconds int64) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + if result, err = redis.Int(conn.Do("EXPIRE", key, seconds)); err != nil { + return + } + + if result == 1 { + successful = true + } + + return +} + +/* +EXPIREAT key timestamp +可用版本: >= 1.2.0 +时间复杂度: O(1) +EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。 + +不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 + +返回值 +如果生存时间设置成功,返回 1 ; 当 key 不存在或没办法设置生存时间,返回 0 。 +*/ +func (this *RedisPool) ExpireAt(key string, timestamp int64) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + if result, err = redis.Int(conn.Do("EXPIREAT", key, timestamp)); err != nil { + return + } + + if result == 1 { + successful = true + } + + return +} + +/* +TTL key +可用版本: >= 1.0.0 +时间复杂度: O(1) +以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 + +返回值 +当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。 + +Note + +在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。 +*/ +func (this *RedisPool) TTL(key string) (ttl int64, exist, persisted bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + ttl, err = redis.Int64(conn.Do("TTL", key)) + if err != nil { + return + } + + if ttl == -2 { + exist = false + persisted = false + } else if ttl == -1 { + exist = true + persisted = true + } else { + exist = true + persisted = false + } + + return +} + +/* +PERSIST key +可用版本: >= 2.2.0 +时间复杂度: O(1) +移除给定 key 的生存时间,将这个 key 从“易失的”(带生存时间 key )转换成“持久的”(一个不带生存时间、永不过期的 key )。 + +返回值 +当生存时间移除成功时,返回 1 . 如果 key 不存在或 key 没有设置生存时间,返回 0 。 +*/ +func (this *RedisPool) Persist(key string) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + if result, err = redis.Int(conn.Do("PERSIST", key)); err != nil { + return + } + + if result == 1 { + successful = true + } + + return +} + +/* +PEXPIRE key milliseconds +可用版本: >= 2.6.0 +时间复杂度: O(1) +这个命令和 EXPIRE 命令的作用类似,但是它以毫秒为单位设置 key 的生存时间,而不像 EXPIRE 命令那样,以秒为单位。 + +返回值 +设置成功,返回 1 key 不存在或设置失败,返回 0 +*/ +func (this *RedisPool) PExpire(key string, milliseconds int64) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + if result, err = redis.Int(conn.Do("PEXPIRE", key, milliseconds)); err != nil { + return + } + + if result == 1 { + successful = true + } + + return +} + +/* +PEXPIREAT key milliseconds-timestamp +可用版本: >= 2.6.0 +时间复杂度: O(1) +这个命令和 expireat 命令类似,但它以毫秒为单位设置 key 的过期 unix 时间戳,而不是像 expireat 那样,以秒为单位。 + +返回值 +如果生存时间设置成功,返回 1 。 当 key 不存在或没办法设置生存时间时,返回 0 。(查看 EXPIRE key seconds 命令获取更多信息) +*/ +func (this *RedisPool) PExpireAt(key string, milliseconds_timestamp int64) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + if result, err = redis.Int(conn.Do("PEXPIREAT", key, milliseconds_timestamp)); err != nil { + return + } + + if result == 1 { + successful = true + } + + return +} + +/* +PTTL key +可用版本: >= 2.6.0 +复杂度: O(1) +这个命令类似于 TTL 命令,但它以毫秒为单位返回 key 的剩余生存时间,而不是像 TTL 命令那样,以秒为单位。 + +返回值 +当 key 不存在时,返回 -2 。 + +当 key 存在但没有设置剩余生存时间时,返回 -1 。 + +否则,以毫秒为单位,返回 key 的剩余生存时间。 + +Note + +在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。 +*/ +func (this *RedisPool) PTTL(key string) (pttl int64, exist bool, persisted bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + pttl, err = redis.Int64(conn.Do("PTTL", key)) + if err != nil { + return + } + + if pttl == -2 { + exist = false + persisted = false + } else if pttl == -1 { + exist = true + persisted = true + } else { + exist = true + persisted = false + } + + return +} diff --git a/trunk/goutil/redisUtil/redisPool_expire_test.go b/trunk/goutil/redisUtil/redisPool_expire_test.go new file mode 100644 index 0000000..21449b3 --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_expire_test.go @@ -0,0 +1,592 @@ +package redisUtil + +import ( + "testing" + "time" +) + +var ( + redisPoolObj_expire *RedisPool +) + +func init() { + redisPoolObj_expire = NewRedisPool("testPool", "10.1.0.21:6379", "redis_pwd", 5, 500, 200, 10*time.Second, 5*time.Second) +} + +func TestExpire(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SET cache_page "www.google.com" + OK + + redis> TTL cache_page + (integer) -1 + + redis> EXPIRE cache_page 30 # 设置过期时间为 30 秒 + (integer) 1 + + redis> TTL cache_page # 查看剩余生存时间 + (integer) 23 + + redis> EXPIRE cache_page 30000 # 更新过期时间 + (integer) 1 + + redis> TTL cache_page + (integer) 29996 + */ + key := "cache_page" + value := "www.google.com" + expected := true + got, err := redisPoolObj_expire.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + expected = true + _, _, got, err = redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + seconds := int64(30) + got, err = redisPoolObj_expire.Expire(key, seconds) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + expected2 := seconds - 5 + got2, exist, _, err := redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + if got2 <= expected2 { + t.Errorf("Expected to get a number bigger than %d, but now get %d.", expected2, got2) + return + } + + seconds = int64(3000) + got, err = redisPoolObj_expire.Expire(key, seconds) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + expected2 = seconds - 5 + got2, exist, _, err = redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + if got2 <= expected2 { + t.Errorf("Expected to get a number bigger than %d, but now get %d.", expected2, got2) + return + } +} + +func TestExpireAt(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SET cache www.google.com + OK + + redis> EXPIREAT cache '1609403601' # 这个 key 将在 Now()+1day 过期 + (integer) 1 + + redis> TTL cache + (integer) 45081860 + */ + key := "cache_page" + value := "www.google.com" + expected := true + got, err := redisPoolObj_expire.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + timestamp := time.Now().AddDate(0, 0, 1).Unix() + expected = true + got, err = redisPoolObj_expire.ExpireAt(key, timestamp) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + expected2 := int64(86400 - 5) + got2, exist, _, err := redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + if got2 <= expected2 { + t.Errorf("Expected to get a number bigger than %d, but now get %d.", expected2, got2) + return + } +} + +func TestTTL(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 不存在的 key + + redis> TTL key + (integer) -2 + */ + key := "key" + expected := false + _, got, _, err := redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + /* + # key 存在,但没有设置剩余生存时间 + + redis> SET key value + OK + + redis> TTL key + (integer) -1 + */ + value := "value" + redisPoolObj_expire.Set(key, value, "", 0, "") + deleteKeys = append(deleteKeys, key) + + expected2 := int64(-1) + got2, exist, _, err := redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but got %d", expected2, got2) + return + } + + /* + # 有剩余生存时间的 key + + redis> EXPIRE key 10086 + (integer) 1 + + redis> TTL key + (integer) 10084 + */ + seconds := int64(10086) + expected = true + got, err = redisPoolObj_expire.Expire(key, seconds) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + expected2 = seconds - 5 + got2, exist, _, err = redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + if got2 <= expected2 { + t.Errorf("Expected to get a number bigger than %d, but now get %d.", expected2, got2) + return + } +} + +func TestPersist(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SET mykey "Hello" + OK + */ + key := "mykey" + value := "Hello" + redisPoolObj_expire.Set(key, value, "", 0, "") + + /* + redis> EXPIRE mykey 10 # 为 key 设置生存时间 + (integer) 1 + */ + seconds := int64(10) + expected := true + got, err := redisPoolObj_expire.Expire(key, seconds) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> TTL mykey + (integer) 10 + */ + expected2 := seconds - 5 + got2, _, _, err := redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if got2 <= expected2 { + t.Errorf("Expected to get %d, but got %d", expected2, got2) + return + } + + /* + redis> PERSIST mykey # 移除 key 的生存时间 + (integer) 1 + */ + redisPoolObj_expire.Persist(key) + + /* + redis> TTL mykey + (integer) -1 + */ + expected = true + _, _, got, err = redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } +} + +func TestPExpire(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SET mykey "Hello" + OK + */ + key := "mykey" + value := "Hello" + expected := true + got, err := redisPoolObj_expire.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> PEXPIRE mykey 1500 + (integer) 1 + */ + milliseconds := int64(1500) + expected = true + got, err = redisPoolObj_expire.PExpire(key, milliseconds) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + /* + redis> TTL mykey # TTL 的返回值以秒为单位 + (integer) 2 + + redis> PTTL mykey # PTTL 可以给出准确的毫秒数 + (integer) 1499 + */ + expected2 := int64(1) + got2, _, _, err := redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if got2 < expected2 { + t.Errorf("TTL %d should be no less than %d", got2, expected2) + return + } + + expected2 = int64(1000) + got2, _, _, err = redisPoolObj_expire.PTTL(key) + if err != nil { + t.Fail() + } + if got2 < expected2 { + t.Errorf("TTL %d should be no less than %d", got2, expected2) + return + } +} + +func TestPExpireAt(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SET mykey "Hello" + OK + */ + key := "mykey" + value := "Hello" + expected := true + got, err := redisPoolObj_expire.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> PEXPIREAT mykey 1609403601005 + (integer) 1 + */ + milliseconds_timestamp := time.Now().AddDate(0, 0, 1).Unix() * 1000 + redisPoolObj_expire.PExpireAt(key, milliseconds_timestamp) + + /* + redis> TTL mykey # TTL 返回秒 + (integer) 223157079 + + redis> PTTL mykey # PTTL 返回毫秒 + (integer) 223157079318 + */ + expected2 := int64(86400 - 5) + got2, exist, _, err := redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + if got2 <= expected2 { + t.Errorf("Expected to get a number bigger than %d, but now get %d.", expected2, got2) + return + } + + expected2 = int64(86400000 - 5) + got2, exist, _, err = redisPoolObj_expire.PTTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + if got2 <= expected2 { + t.Errorf("Expected to get a number bigger than %d, but now get %d.", expected2, got2) + return + } +} + +func TestPTTL(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 不存在的 key + + redis> PTTL key + (integer) -2 + */ + key := "key" + expected := false + _, got, _, err := redisPoolObj_expire.PTTL(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + /* + # key 存在,但没有设置剩余生存时间 + + redis> SET key value + OK + + redis> PTTL key + (integer) -1 + */ + value := "value" + redisPoolObj_expire.Set(key, value, "", 0, "") + deleteKeys = append(deleteKeys, key) + + expected = true + _, _, got, err = redisPoolObj_expire.TTL(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + /* + # 有剩余生存时间的 key + + redis> PEXPIRE key 10086 + (integer) 1 + + redis> PTTL key + (integer) 6179 + */ + milliseconds := int64(10086) + expected = true + got, err = redisPoolObj_expire.PExpire(key, milliseconds) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but got %t", expected, got) + return + } + + expected2 := milliseconds - 500 + got2, exist, _, err := redisPoolObj_expire.PTTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + if got2 <= expected2 { + t.Errorf("Expected to get a number bigger than %d, but now get %d.", expected2, got2) + return + } +} diff --git a/trunk/goutil/redisUtil/redisPool_hash.go b/trunk/goutil/redisUtil/redisPool_hash.go new file mode 100644 index 0000000..2741112 --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_hash.go @@ -0,0 +1,412 @@ +/* +未实现的哈希表方法: +HSCAN +*/ +package redisUtil + +import ( + "github.com/gomodule/redigo/redis" +) + +/* +HSET hash field value +可用版本: >= 2.0.0 +时间复杂度: O(1) +将哈希表 hash 中域 field 的值设置为 value 。 + +如果给定的哈希表并不存在, 那么一个新的哈希表将被创建并执行 HSET 操作。 + +如果域 field 已经存在于哈希表中, 那么它的旧值将被新值 value 覆盖。 + +返回值 +当 HSET 命令在哈希表中新创建 field 域并成功为它设置值时, 命令返回 1 ; 如果域 field 已经存在于哈希表, 并且 HSET 命令成功使用新值覆盖了它的旧值, 那么命令返回 0 。 +*/ +func (this *RedisPool) HSet(key, field string, value interface{}) (result int, err error) { + conn := this.GetConnection() + defer conn.Close() + + result, err = redis.Int(conn.Do("HSET", key, field, value)) + return +} + +/* +HSETNX hash field value +可用版本: >= 2.0.0 +时间复杂度: O(1) +当且仅当域 field 尚未存在于哈希表的情况下, 将它的值设置为 value 。 + +如果给定域已经存在于哈希表当中, 那么命令将放弃执行设置操作。 + +如果哈希表 hash 不存在, 那么一个新的哈希表将被创建并执行 HSETNX 命令。 + +返回值 +HSETNX 命令在设置成功时返回 1 , 在给定域已经存在而放弃执行设置操作时返回 0 。 +*/ +func (this *RedisPool) HSetNX(key, field string, value interface{}) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + result, err = redis.Int(conn.Do("HSETNX", key, field, value)) + if err != nil { + return + } + + if result == 1 { + successful = true + } + + return +} + +/* +HGET hash field +可用版本: >= 2.0.0 +时间复杂度: O(1) +返回哈希表中给定域的值。 + +返回值 +HGET 命令在默认情况下返回给定域的值。 + +如果给定域不存在于哈希表中, 又或者给定的哈希表并不存在, 那么命令返回 nil 。 +*/ +func (this *RedisPool) HGet(key, field string) (value interface{}, exist bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + value, err = conn.Do("HGET", key, field) + if err != nil { + return + } + if value == nil { + return + } + + exist = true + return +} + +/* +HEXISTS hash field +可用版本: >= 2.0.0 +时间复杂度: O(1) +检查给定域 field 是否存在于哈希表 hash 当中。 + +返回值 +HEXISTS 命令在给定域存在时返回 1 , 在给定域不存在时返回 0 。 +*/ +func (this *RedisPool) HExists(key, field string) (exist bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + result, err = redis.Int(conn.Do("HEXISTS", key, field)) + if err != nil { + return + } + + if result == 1 { + exist = true + } + + return +} + +/* +HDEL +HDEL key field [field …] + +删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。 + +Note + +在Redis2.4以下的版本里, HDEL 每次只能删除单个域,如果你需要在一个原子时间内删除多个域,请将命令包含在 MULTI / EXEC 块内。 + +可用版本: +>= 2.0.0 + +时间复杂度: +O(N), N 为要删除的域的数量。 + +返回值: +被成功移除的域的数量,不包括被忽略的域。 +*/ +func (this *RedisPool) HDel(key string, field ...string) (succeedCount int, err error) { + conn := this.GetConnection() + defer conn.Close() + + succeedCount, err = redis.Int(conn.Do("HDEL", redis.Args{}.Add(key).AddFlat(field)...)) + return +} + +/* +HLEN +HLEN key + +返回哈希表 key 中域的数量。 + +时间复杂度: +O(1) + +返回值: +哈希表中域的数量。 +当 key 不存在时,返回 0 。 +*/ +func (this *RedisPool) HLen(key string) (count int, err error) { + conn := this.GetConnection() + defer conn.Close() + + count, err = redis.Int(conn.Do("HLEN", key)) + return +} + +/* +HSTRLEN +HSTRLEN key field + +返回哈希表 key 中, 与给定域 field 相关联的值的字符串长度(string length)。 + +如果给定的键或者域不存在, 那么命令返回 0 。 + +可用版本: +>= 3.2.0 + +时间复杂度: +O(1) + +返回值: +一个整数。 +*/ +func (this *RedisPool) HStrlen(key, field string) (length int, err error) { + conn := this.GetConnection() + defer conn.Close() + + length, err = redis.Int(conn.Do("HSTRLEN", key, field)) + return +} + +/* +HINCRBY +HINCRBY key field increment + +为哈希表 key 中的域 field 的值加上增量 increment 。 + +增量也可以为负数,相当于对给定域进行减法操作。 + +如果 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。 + +如果域 field 不存在,那么在执行命令前,域的值被初始化为 0 。 + +对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。 + +本操作的值被限制在 64 位(bit)有符号数字表示之内。 + +可用版本: +>= 2.0.0 + +时间复杂度: +O(1) + +返回值: +执行 HINCRBY 命令之后,哈希表 key 中域 field 的值。 +*/ +func (this *RedisPool) HIncrBy(key, field string, increment int64) (newValue int64, err error) { + conn := this.GetConnection() + defer conn.Close() + + newValue, err = redis.Int64(conn.Do("HINCRBY", key, field, increment)) + return +} + +/* +HINCRBYFLOAT +HINCRBYFLOAT key field increment + +为哈希表 key 中的域 field 加上浮点数增量 increment 。 + +如果哈希表中没有域 field ,那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ,然后再执行加法操作。 + +如果键 key 不存在,那么 HINCRBYFLOAT 会先创建一个哈希表,再创建域 field ,最后再执行加法操作。 + +当以下任意一个条件发生时,返回一个错误: + +域 field 的值不是字符串类型(因为 redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型) + +域 field 当前的值或给定的增量 increment 不能解释(parse)为双精度浮点数(double precision floating point number) + +HINCRBYFLOAT 命令的详细功能和 INCRBYFLOAT key increment 命令类似,请查看 INCRBYFLOAT key increment 命令获取更多相关信息。 + +可用版本: +>= 2.6.0 + +时间复杂度: +O(1) + +返回值: +执行加法操作之后 field 域的值。 +*/ +func (this *RedisPool) HIncrByFloat(key, field string, increment float64) (newValue float64, err error) { + conn := this.GetConnection() + defer conn.Close() + + newValue, err = redis.Float64(conn.Do("HINCRBYFLOAT", key, field, increment)) + return +} + +/* +HMSET +HMSET key field value [field value …] + +同时将多个 field-value (域-值)对设置到哈希表 key 中。 + +此命令会覆盖哈希表中已存在的域。 + +如果 key 不存在,一个空哈希表被创建并执行 HMSET 操作。 + +可用版本: +>= 2.0.0 + +时间复杂度: +O(N), N 为 field-value 对的数量。 + +返回值: +如果命令执行成功,返回 OK 。 +当 key 不是哈希表(hash)类型时,返回一个错误。 +*/ +func (this *RedisPool) HMSet(key string, value interface{}) (err error) { + conn := this.GetConnection() + defer conn.Close() + + _, err = conn.Do("HMSET", redis.Args{}.Add(key).AddFlat(value)...) + return +} + +/* +HMGET +HMGET key field [field …] + +返回哈希表 key 中,一个或多个给定域的值。 + +如果给定的域不存在于哈希表,那么返回一个 nil 值。 + +因为不存在的 key 被当作一个空哈希表来处理,所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。 + +可用版本: +>= 2.0.0 + +时间复杂度: +O(N), N 为给定域的数量。 + +返回值: +一个包含多个给定域的关联值的表,表值的排列顺序和给定域参数的请求顺序一样。 +*/ +func (this *RedisPool) HMGet(key string, fields ...string) (reply interface{}, exist bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + reply, err = conn.Do("HMGET", redis.Args{}.Add(key).AddFlat(fields)...) + if err != nil { + return + } + if reply == nil { + return + } + + exist = true + return +} + +/* +HKEYS +HKEYS key + +返回哈希表 key 中的所有域。 + +可用版本: +>= 2.0.0 + +时间复杂度: +O(N), N 为哈希表的大小。 + +返回值: +一个包含哈希表中所有域的表。 +当 key 不存在时,返回一个空表。 +*/ +func (this *RedisPool) HKeys(key string) (fieldList []string, err error) { + conn := this.GetConnection() + defer conn.Close() + + fieldList, err = redis.Strings(conn.Do("HKEYS", key)) + return +} + +/* +VALS +HVALS key + +返回哈希表 key 中所有域的值。 + +可用版本: +>= 2.0.0 + +时间复杂度: +O(N), N 为哈希表的大小。 + +返回值: +一个包含哈希表中所有值的表。 +当 key 不存在时,返回一个空表。 +*/ +func (this *RedisPool) HVals(key string) (reply interface{}, err error) { + conn := this.GetConnection() + defer conn.Close() + + reply, err = conn.Do("HVALS", key) + return +} + +/* +HGETALL +HGETALL key + +返回哈希表 key 中,所有的域和值。 + +在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。 + +可用版本: +>= 2.0.0 + +时间复杂度: +O(N), N 为哈希表的大小。 + +返回值: +以列表形式返回哈希表的域和域的值。 +若 key 不存在,返回空列表。 +*/ +func (this *RedisPool) HGetAll(key string) (reply interface{}, err error) { + conn := this.GetConnection() + defer conn.Close() + + reply, err = conn.Do("HGETALL", key) + return +} + +func (this *RedisPool) HGetAll_Struct(key string, obj interface{}) (exist bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + values, err := redis.Values(conn.Do("HGETALL", key)) + if err != nil { + return + } + if len(values) == 0 { + return + } + + err = redis.ScanStruct(values, obj) + if err != nil { + return + } + + exist = true + return +} diff --git a/trunk/goutil/redisUtil/redisPool_hash_test.go b/trunk/goutil/redisUtil/redisPool_hash_test.go new file mode 100644 index 0000000..0139d3d --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_hash_test.go @@ -0,0 +1,1501 @@ +package redisUtil + +import ( + "fmt" + "testing" + "time" + + "github.com/gomodule/redigo/redis" +) + +var ( + redisPoolObj_hash *RedisPool +) + +func init() { + redisPoolObj_hash = NewRedisPool("testPool", "10.1.0.21:6379", "redis_pwd", 5, 500, 200, 10*time.Second, 5*time.Second) +} + +func TestHSet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 设置一个新域: + + redis> HSET website google "www.g.cn" + (integer) 1 + */ + key := "website" + field := "google" + value := "www.g.cn" + expected := 1 + got, err := redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> HGET website google + "www.g.cn" + */ + expected2 := "www.g.cn" + got2, exist, err := redisPoolObj_hash.HGet(key, field) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s and field:%s should exist, but now it doesn't", key, field) + return + } + got2_str, err := redis.String(got2, err) + if err != nil { + t.Fail() + } + if got2_str != expected2 { + t.Errorf("Expected to get %s, but got %s", expected2, got2_str) + return + } + + /* + 对一个已存在的域进行更新: + redis> HSET website google "www.google.com" + (integer) 0 + + */ + field = "google" + value = "www.google.com" + expected = 0 + got, err = redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + + /* + redis> HGET website google + "www.google.com" + */ + expected3 := "www.google.com" + got3, exist, err := redisPoolObj_hash.HGet(key, field) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s and field:%s should exist, but now it doesn't", key, field) + return + } + got3_str, err := redis.String(got3, nil) + if err != nil { + t.Fail() + } + if got3_str != expected3 { + t.Errorf("Expected to get %s, but got %s", expected3, got3_str) + return + } + + /* + 对一个已存在的域进行更新: + redis> HSET website google 1 + (integer) 0 + + */ + field = "google" + value4 := 1 + expected = 0 + got, err = redisPoolObj_hash.HSet(key, field, value4) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + + /* + redis> HGET website google + "www.google.com" + */ + expected4 := 1 + got4, exist, err := redisPoolObj_hash.HGet(key, field) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s and field:%s should exist, but now it doesn't", key, field) + return + } + got4_int, err := redis.Int(got4, nil) + if err != nil { + t.Fail() + } + if got4_int != expected4 { + t.Errorf("Expected to get %d, but got %d", expected4, got4_int) + return + } +} + +func TestHSetNX(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 域尚未存在, 设置成功: + + redis> HSETNX database key-value-store Redis + (integer) 1 + */ + key := "database" + field := "key-value-store" + value := "Redis" + successful, err := redisPoolObj_hash.HSetNX(key, field, value) + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("HSetNX key:%s, field:%s should be successful, but now it's not.", key, field) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> HGET database key-value-store + "Redis" + */ + expected := "Redis" + gottmp, exist, err := redisPoolObj_hash.HGet(key, field) + got, err := redis.String(gottmp, err) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("Key:%s, field:%s should exist, but now it doesn't.", key, field) + return + } + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } + + /* + 域已经存在, 设置未成功, 域原有的值未被改变: + + redis> HSETNX database key-value-store Riak + (integer) 0 + */ + value = "Riak" + successful, err = redisPoolObj_hash.HSetNX(key, field, value) + if err != nil { + t.Fail() + } + if successful { + t.Errorf("HSetNX key:%s, field:%s should be not successful, but now it is.", key, field) + return + } + + /* + redis> HGET database key-value-store + "Redis" + */ + expected = "Redis" + gottmp, exist, err = redisPoolObj_hash.HGet(key, field) + got, err = redis.String(gottmp, err) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("Key:%s, field:%s should exist, but now it doesn't.", key, field) + return + } + if got != expected { + t.Errorf("Expected to get %s, but got %s", expected, got) + return + } +} + +func TestHGet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 域存在的情况: + + redis> HSET homepage redis redis.com + (integer) 1 + */ + key := "homepage" + field := "redis" + value := "redis.com" + expected := 1 + got, err := redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> HGET homepage redis + "redis.com" + */ + expected2 := "redis.com" + got2, exist, err := redisPoolObj_hash.HGet(key, field) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s and field:%s should exist, but now it doesn't", key, field) + return + } + got2_str, err := redis.String(got2, err) + if err != nil { + t.Fail() + } + if got2_str != expected2 { + t.Errorf("Expected to get %s, but got %s", expected2, got2_str) + return + } + + /* + 域不存在的情况: + + redis> HGET site mysql + (nil) + */ + key = "site" + field = "mysql" + _, exist, err = redisPoolObj_hash.HGet(key, field) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s and field:%s should not exist, but now it does", key, field) + return + } +} + +func TestHExists(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 给定域不存在: + + redis> HEXISTS phone myphone + (integer) 0 + 给定域存在: + */ + key := "phone" + field := "myphone" + expected := false + got, err := redisPoolObj_hash.HExists(key, field) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t", expected, got) + return + } + + /* + redis> HSET phone myphone nokia-1110 + (integer) 1 + */ + value := "nokia-1110" + expected2 := 1 + got2, err := redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but now got %d", expected2, got2) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> HEXISTS phone myphone + (integer) 1 + */ + expected3 := true + got3, err := redisPoolObj_hash.HExists(key, field) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %t, but now got %t", expected3, got3) + return + } +} + +func TestHDel(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 测试数据 + */ + key := "abbr" + redisPoolObj_hash.HSet(key, "a", "apple") + redisPoolObj_hash.HSet(key, "b", "banana") + redisPoolObj_hash.HSet(key, "c", "cat") + redisPoolObj_hash.HSet(key, "d", "dog") + deleteKeys = append(deleteKeys, key) + + /* + # 删除单个域 + + redis> HDEL abbr a + (integer) 1 + */ + field := "a" + expected := 1 + got, err := redisPoolObj_hash.HDel(key, field) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + # 删除不存在的域 + + redis> HDEL abbr not-exists-field + (integer) 0 + */ + field = "not-exists-field" + expected = 0 + got, err = redisPoolObj_hash.HDel(key, field) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + # 删除多个域 + + redis> HDEL abbr b c + (integer) 2 + */ + fields := []string{"b", "c"} + expected = 2 + got, err = redisPoolObj_hash.HDel(key, fields...) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> HGETALL abbr + 1) "d" + 2) "dog" + */ +} + +func TestHLen(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> HSET db redis redis.com + (integer) 1 + */ + key := "db" + field := "redis" + value := "redis.com" + expected := 1 + got, err := redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> HSET db mysql mysql.com + (integer) 1 + */ + field = "mysql" + value = "mysql.com" + expected = 1 + got, err = redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> HLEN db + (integer) 2 + */ + expected = 2 + got, err = redisPoolObj_hash.HLen(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> HSET db mongodb mongodb.org + (integer) 1 + */ + field = "mongodb" + value = "mongodb.org" + expected = 1 + got, err = redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> HLEN db + (integer) 3 + */ + expected = 3 + got, err = redisPoolObj_hash.HLen(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } +} + +func TestHStrlen(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> HMSET myhash f1 "HelloWorld" f2 "99" f3 "-256" + OK + */ + key := "myhash" + redisPoolObj_hash.HSet(key, "f1", "HelloWorld") + redisPoolObj_hash.HSet(key, "f2", "99") + redisPoolObj_hash.HSet(key, "f3", "-256") + deleteKeys = append(deleteKeys, key) + + /* + redis> HSTRLEN myhash f1 + (integer) 10 + */ + expected := 10 + field := "f1" + got, err := redisPoolObj_hash.HStrlen(key, field) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> HSTRLEN myhash f2 + (integer) 2 + */ + expected = 2 + field = "f2" + got, err = redisPoolObj_hash.HStrlen(key, field) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> HSTRLEN myhash f3 + (integer) 4 + */ + expected = 4 + field = "f3" + got, err = redisPoolObj_hash.HStrlen(key, field) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } +} + +func TestHIncrBy(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # increment 为正数 + + redis> HEXISTS counter page_view # 对空域进行设置 + (integer) 0 + */ + key := "counter" + field := "page_view" + exist, err := redisPoolObj_hash.HExists(key, field) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s, field:%s should not exist, but now it does.", key, field) + return + } + + /* + redis> HINCRBY counter page_view 200 + (integer) 200 + */ + increment := int64(200) + expected := int64(200) + got, err := redisPoolObj_hash.HIncrBy(key, field, increment) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> HGET counter page_view + "200" + */ + expected = 200 + got2, exist, err := redisPoolObj_hash.HGet(key, field) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't.", key, field) + return + } + got2_int64, err := redis.Int64(got2, err) + if err != nil { + t.Fail() + } + if got2_int64 != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got2_int64) + return + } + + /* + # increment 为负数 + + redis> HGET counter page_view + "200" + */ + /* + redis> HINCRBY counter page_view -50 + (integer) 150 + */ + increment = -50 + expected = 150 + got, err = redisPoolObj_hash.HIncrBy(key, field, increment) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> HGET counter page_view + "150" + */ + expected = 150 + got3, exist, err := redisPoolObj_hash.HGet(key, field) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't.", key, field) + return + } + got3_int64, err := redis.Int64(got3, err) + if err != nil { + t.Fail() + } + if got3_int64 != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got3_int64) + return + } + + /* + # 尝试对字符串值的域执行HINCRBY命令 + + redis> HSET myhash string hello,world # 设定一个字符串值 + (integer) 1 + */ + key2 := "myhash" + field2 := "string" + value2 := "hello,world" + expected4 := 1 + got4, err := redisPoolObj_hash.HSet(key2, field2, value2) + if err != nil { + t.Fail() + } + if got4 != expected4 { + t.Errorf("Expected to get %d, but now got %d", expected4, got4) + return + } + deleteKeys = append(deleteKeys, key2) + + /* + redis> HGET myhash string + "hello,world" + */ + expected5 := "hello,world" + got5_interface, exist, err := redisPoolObj_hash.HGet(key2, field2) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't.", key, field) + return + } + got5, err := redis.String(got5_interface, err) + if err != nil { + t.Fail() + } + if got5 != expected5 { + t.Errorf("Expected to get %s, but now got %s", expected5, got5) + return + } + + /* + redis> HINCRBY myhash string 1 # 命令执行失败,错误。 + (error) ERR hash value is not an integer + */ + increment = 1 + got, err = redisPoolObj_hash.HIncrBy(key2, field2, increment) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + /* + redis> HGET myhash string # 原值不变 + "hello,world" + */ + expected6 := "hello,world" + got6_interface, exist, err := redisPoolObj_hash.HGet(key2, field2) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't.", key, field) + return + } + got6, err := redis.String(got6_interface, err) + if err != nil { + t.Fail() + } + if got6 != expected6 { + t.Errorf("Expected to get %s, but now got %s", expected6, got6) + return + } +} + +func TestHIncrByFloat(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 值和增量都是普通小数 + + redis> HSET mykey field 10.50 + (integer) 1 + redis> HINCRBYFLOAT mykey field 0.1 + "10.6" + */ + key := "mykey" + field := "field" + value := 10.50 + expected := 1 + got, err := redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + increment := 0.1 + expected2 := 10.6 + got2, err := redisPoolObj_hash.HIncrByFloat(key, field, increment) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %f, but now got %f", expected2, got2) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + # 值和增量都是指数符号 + + redis> HSET mykey field 5.0e3 + (integer) 0 + redis> HINCRBYFLOAT mykey field 2.0e2 + "5200" + */ + value3 := 5.0e3 + expected3 := 0 + got3, err := redisPoolObj_hash.HSet(key, field, value3) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %d, but now got %d", expected3, got3) + return + } + + increment4 := 2.0e2 + expected4 := 5200.0 + got4, err := redisPoolObj_hash.HIncrByFloat(key, field, increment4) + if err != nil { + t.Fail() + } + if got4 != expected4 { + t.Errorf("Expected to get %f, but now got %f", expected4, got4) + return + } + + /* + # 对不存在的键执行 HINCRBYFLOAT + + redis> EXISTS price + (integer) 0 + redis> HINCRBYFLOAT price milk 3.5 + "3.5" + redis> HGETALL price + 1) "milk" + 2) "3.5" + */ + key5 := "price" + exist5, err := redisPoolObj_hash.Exists(key5) + if err != nil { + t.Fail() + } + if exist5 { + t.Errorf("The key:%s should not exist, but now it does.", key5) + return + } + + field5 := "milk" + increment5 := 3.5 + expected5 := 3.5 + got5, err := redisPoolObj_hash.HIncrByFloat(key5, field5, increment5) + if err != nil { + t.Fail() + } + if got5 != expected5 { + t.Errorf("Expected to get %f, but now got %f", expected5, got5) + return + } + + got5_interface, exist, err := redisPoolObj_hash.HGet(key5, field5) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't", key5, field5) + return + } + got5, err = redis.Float64(got5_interface, err) + if err != nil { + t.Fail() + } + if got5 != expected5 { + t.Errorf("Expected to get %f, but now got %f", expected5, got5) + return + } + + deleteKeys = append(deleteKeys, key5) + + /* + # 对不存在的域进行 HINCRBYFLOAT + + redis> HGETALL price + 1) "milk" + 2) "3.5" + redis> HINCRBYFLOAT price coffee 4.5 # 新增 coffee 域 + "4.5" + redis> HGETALL price + 1) "milk" + 2) "3.5" + 3) "coffee" + 4) "4.5" + */ + key6 := "price" + field6 := "coffee" + increment6 := 4.5 + expected6 := 4.5 + got6, err := redisPoolObj_hash.HIncrByFloat(key6, field6, increment6) + if err != nil { + t.Fail() + } + if got6 != expected6 { + t.Errorf("Expected to get %f, but now got %f", expected6, got6) + return + } + + got6_interface, exist, err := redisPoolObj_hash.HGet(key6, field6) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't", key6, field6) + return + } + got6, err = redis.Float64(got6_interface, err) + if got6 != expected6 { + t.Errorf("Expected to get %f, but now got %f", expected6, got6) + return + } +} + +func TestHMSet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + // HMSet + key := "people" + people := &People{ + Jack: "Jack Ma", + Gump: "Gump Li", + } + err := redisPoolObj_hash.HMSet(key, people) + if err != nil { + t.Fail() + } + + data := make(map[string]interface{}) + data["Jordan"] = "Jordan Zuo" + err = redisPoolObj_hash.HMSet(key, data) + if err != nil { + t.Fail() + } + + deleteKeys = append(deleteKeys, key) + + // HGet + field1 := "Jack" + expected1 := "Jack Ma" + got1_interface, exist1, err := redisPoolObj_hash.HGet(key, field1) + if err != nil { + t.Fail() + } + if !exist1 { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't.", key, field1) + return + } + got1, err := redis.String(got1_interface, nil) + if err != nil { + t.Fail() + } + if got1 != expected1 { + t.Errorf("Expected to get %s, but now got %s", expected1, got1) + return + } + + // HGet + field2 := "Gump" + expected2 := "Gump Li" + got2_interface, exist2, err := redisPoolObj_hash.HGet(key, field2) + if err != nil { + t.Fail() + } + if !exist2 { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't.", key, field2) + return + } + got2, err := redis.String(got2_interface, nil) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %s, but now got %s", expected2, got2) + return + } + + field3 := "Jordan" + expected3 := "Jordan Zuo" + got3_interface, exist3, err := redisPoolObj_hash.HGet(key, field3) + if err != nil { + t.Fail() + } + if !exist3 { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't.", key, field3) + return + } + got3, err := redis.String(got3_interface, nil) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %s, but now got %s", expected3, got3) + return + } +} + +func TestHMGet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> HMSET pet dog "doudou" cat "nounou" # 一次设置多个域 + OK + */ + key := "pet" + field_value_map := make(map[string]interface{}) + field_value_map["dog"] = "doudou" + field_value_map["cat"] = "nounou" + err := redisPoolObj_hash.HMSet(key, field_value_map) + if err != nil { + t.Fail() + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> HMGET pet dog cat fake_pet # 返回值的顺序和传入参数的顺序一样 + 1) "doudou" + 2) "nounou" + 3) (nil) # 不存在的域返回nil值 + */ + expected := []string{"doudou", "nounou", ""} + reply, exist, err := redisPoolObj_hash.HMGet(key, "dog", "cat", "fake_pet") + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should have items, but now it doesn't.", key) + return + } + got, err := redisPoolObj_hash.Strings(reply) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(got, expected) == false { + t.Errorf("Expected to get %v, but got %v", expected, got) + } +} + +func TestHKeys(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 哈希表非空 + + redis> HMSET website google www.google.com yahoo www.yahoo.com + OK + */ + key := "website" + field_value_map := make(map[string]interface{}) + field_value_map["google"] = "www.google.com" + field_value_map["yahoo"] = "www.yahoo.com" + err := redisPoolObj_hash.HMSet(key, field_value_map) + if err != nil { + t.Fail() + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> HKEYS website + 1) "google" + 2) "yahoo" + */ + expected := make([]string, 0, len(field_value_map)) + for k := range field_value_map { + expected = append(expected, k) + } + got, err := redisPoolObj_hash.HKeys(key) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(got, expected) == false { + t.Errorf("Expected to get %v, but got %v", expected, got) + } + + /* + # 空哈希表/key不存在 + + redis> EXISTS fake_key + (integer) 0 + */ + key2 := "fake_key" + exist, err := redisPoolObj_hash.Exists(key2) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s should not exist, but now it does.", key2) + return + } + + /* + redis> HKEYS fake_key + (empty list or set)* + */ + expected2 := make([]string, 0, len(field_value_map)) + got2, err := redisPoolObj_hash.HKeys(key2) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(got2, expected2) == false { + t.Errorf("Expected to get %v, but got %v", expected2, got2) + } +} + +func TestHVals(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 哈希表非空 + + redis> HMSET website google www.google.com yahoo www.yahoo.com + OK + */ + key := "website" + field_value_map := make(map[string]interface{}) + field_value_map["google"] = "www.google.com" + field_value_map["yahoo"] = "www.yahoo.com" + err := redisPoolObj_hash.HMSet(key, field_value_map) + if err != nil { + t.Fail() + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> HVALS website + 1) "www.google.com" + 2) "www.yahoo.com" + */ + expected := make([]string, 0, len(field_value_map)) + for _, v := range field_value_map { + if v_str, ok := v.(string); ok { + expected = append(expected, v_str) + } + } + reply, err := redisPoolObj_hash.HVals(key) + if err != nil { + t.Fail() + } + got, err := redisPoolObj_hash.Strings(reply) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(got, expected) == false { + t.Errorf("Expected to get %v, but got %v", expected, got) + } + + /* + # 空哈希表/不存在的key + + redis> EXISTS not_exists + (integer) 0 + */ + key2 := "fake_key" + exist, err := redisPoolObj_hash.Exists(key2) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s should not exist, but now it does.", key2) + return + } + + /* + redis> HVALS not_exists + (empty list or set) + */ + expected2 := make([]string, 0, len(field_value_map)) + for _, v := range field_value_map { + if v_str, ok := v.(string); ok { + expected2 = append(expected2, v_str) + } + } + reply2, err := redisPoolObj_hash.HVals(key) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_hash.Strings(reply2) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(got2, expected2) == false { + t.Errorf("Expected to get %v, but got %v", expected2, got2) + } +} + +func TestHGetAll(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> HSET people jack "Jack Sparrow" + (integer) 1 + */ + key := "people" + field1 := "Jack" + value1 := "Jack Sparrow" + expected := 1 + got, err := redisPoolObj_hash.HSet(key, field1, value1) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> HSET people gump "Forrest Gump" + (integer) 1 + */ + field2 := "Gump" + value2 := "Forrest Gump" + expected = 1 + got, err = redisPoolObj_hash.HSet(key, field2, value2) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> HGETALL people + 1) "Jack" # 域 + 2) "Jack Sparrow" # 值 + 3) "Gump" + 4) "Forrest Gump" + */ + + reply, err := redisPoolObj_hash.HGetAll(key) + if err != nil { + t.Fail() + } + field_value_map, err := redisPoolObj_hash.StringMap(reply) + if err != nil { + t.Fail() + } + + expected1 := "Jack Sparrow" + got1, exist1 := field_value_map[field1] + if !exist1 { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't.", key, field1) + return + } + if got1 != expected1 { + t.Errorf("Expected to get %s, but now got %s", expected1, got1) + return + } + + expected2 := "Forrest Gump" + got2, exist2 := field_value_map[field2] + if !exist2 { + t.Errorf("The key:%s, field:%s should exist, but now it doesn't.", key, field2) + return + } + if got2 != expected2 { + t.Errorf("Expected to get %s, but now got %s", expected2, got2) + return + } +} + +func TestHGetAll_Struct(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> HSET people jack "Jack Sparrow" + (integer) 1 + */ + key := "people" + field := "Jack" + value := "Jack Sparrow" + expected := 1 + got, err := redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> HSET people gump "Forrest Gump" + (integer) 1 + */ + field = "Gump" + value = "Forrest Gump" + expected = 1 + got, err = redisPoolObj_hash.HSet(key, field, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> HGETALL people + 1) "Jack" # 域 + 2) "Jack Sparrow" # 值 + 3) "Gump" + 4) "Forrest Gump" + */ + + got2 := new(People) + expected2 := &People{ + Jack: "Jack Sparrow", + Gump: "Forrest Gump", + } + exist, err := redisPoolObj_hash.HGetAll_Struct(key, got2) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + if got2.TheSame(expected2) == false { + t.Errorf("Expected to get:%s, but now got %s", expected2, got2) + } +} + +type People struct { + Jack string + Gump string +} + +func (this *People) TheSame(other *People) bool { + return this.Jack == other.Jack && this.Gump == other.Gump +} + +func (this *People) String() string { + return fmt.Sprintf("{\"Jack\": %s, \"Gump\":%s}", this.Jack, this.Gump) +} diff --git a/trunk/goutil/redisUtil/redisPool_list.go b/trunk/goutil/redisUtil/redisPool_list.go new file mode 100644 index 0000000..bf5a8fb --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_list.go @@ -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 +} diff --git a/trunk/goutil/redisUtil/redisPool_list_test.go b/trunk/goutil/redisUtil/redisPool_list_test.go new file mode 100644 index 0000000..c6654de --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_list_test.go @@ -0,0 +1,1888 @@ +package redisUtil + +import ( + "testing" + "time" +) + +var ( + redisPoolObj_list *RedisPool +) + +func init() { + redisPoolObj_list = NewRedisPool("testPool", "10.1.0.21:6379", "redis_pwd", 5, 500, 200, 10*time.Second, 5*time.Second) +} + +func TestLPush(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 加入单个元素 + + redis> LPUSH languages python + (integer) 1 + */ + key := "languages" + value := "python" + expected := 1 + got, err := redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + # 加入重复元素 + + redis> LPUSH languages python + (integer) 2 + */ + value = "python" + expected = 2 + got, err = redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> LRANGE languages 0 -1 # 列表允许重复元素 + 1) "python" + 2) "python" + */ + expected2 := []string{"python", "python"} + got2_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_list.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } + + /* + # 加入多个元素 + + redis> LPUSH mylist a b c + (integer) 3 + */ + key3 := "mylist" + expected3 := 3 + got3, err := redisPoolObj_list.LPush(key3, "a", "b", "c") + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected length %d, but got %d", expected3, got3) + return + } + deleteKeys = append(deleteKeys, key3) + + /* + redis> LRANGE mylist 0 -1 + 1) "c" + 2) "b" + 3) "a" + */ + expected4 := []string{"c", "b", "a"} + got4_interface, err := redisPoolObj_list.LRange(key3, 0, -1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_list.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } +} + +func TestLPushX(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 对空列表执行 LPUSHX + + redis> LLEN greet # greet 是一个空列表 + (integer) 0 + */ + key := "greet" + expected := 0 + got, err := redisPoolObj_list.LLen(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> LPUSHX greet "hello" # 尝试 LPUSHX,失败,因为列表为空 + (integer) 0 + */ + value := "hello" + expected = 0 + got, err = redisPoolObj_list.LPushX(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + # 对非空列表执行 LPUSHX + + redis> LPUSH greet "hello" # 先用 LPUSH 创建一个有一个元素的列表 + (integer) 1 + */ + expected = 1 + got, err = redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> LPUSHX greet "good morning" # 这次 LPUSHX 执行成功 + (integer) 2 + */ + value = "good morning" + expected = 2 + got, err = redisPoolObj_list.LPushX(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> LRANGE greet 0 -1 + 1) "good morning" + 2) "hello" + */ + expected2 := []string{"good morning", "hello"} + got2_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_list.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } +} + +func TestRPush(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 添加单个元素 + + redis> RPUSH languages c + (integer) 1 + */ + key := "languages" + value := "c" + expected := 1 + got, err := redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + # 添加重复元素 + + redis> RPUSH languages c + (integer) 2 + */ + key = "languages" + value = "c" + expected = 2 + got, err = redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> LRANGE languages 0 -1 # 列表允许重复元素 + 1) "c" + 2) "c" + */ + expected2 := []string{"c", "c"} + got2_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_list.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } + + /* + # 添加多个元素 + + redis> RPUSH mylist a b c + (integer) 3 + */ + key3 := "mylist" + expected3 := 3 + got3, err := redisPoolObj_list.RPush(key3, "a", "b", "c") + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected length %d, but got %d", expected3, got3) + return + } + deleteKeys = append(deleteKeys, key3) + + /* + redis> LRANGE mylist 0 -1 + 1) "a" + 2) "b" + 3) "c" + */ + expected4 := []string{"a", "b", "c"} + got4_interface, err := redisPoolObj_list.LRange(key3, 0, -1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_list.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } +} + +func TestRPushX(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # key不存在 + + redis> LLEN greet + (integer) 0 + */ + key := "greet" + expected := 0 + got, err := redisPoolObj_list.LLen(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> RPUSHX greet "hello" # 对不存在的 key 进行 RPUSHX,PUSH 失败。 + (integer) 0 + */ + value := "hello" + expected = 0 + got, err = redisPoolObj_list.RPushX(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + # key 存在且是一个非空列表 + + redis> RPUSH greet "hi" # 先用 RPUSH 插入一个元素 + (integer) 1 + */ + value = "hi" + expected = 1 + got, err = redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> RPUSHX greet "hello" # greet 现在是一个列表类型,RPUSHX 操作成功。 + (integer) 2 + */ + value = "hello" + expected = 2 + got, err = redisPoolObj_list.RPushX(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> LRANGE greet 0 -1 + 1) "hi" + 2) "hello" + */ + expected2 := []string{"hi", "hello"} + got2_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_list.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } +} + +func TestLPop(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> LLEN course + (integer) 0 + */ + key := "course" + expected := 0 + got, err := redisPoolObj_list.LLen(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> RPUSH course algorithm001 + (integer) 1 + */ + value := "algorithm001" + expected = 1 + got, err = redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> RPUSH course c++101 + (integer) 2 + */ + value = "c++101" + expected = 2 + got, err = redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> LPOP course # 移除头元素 + "algorithm001" + */ + expected2 := "algorithm001" + got2_interface, exist, err := redisPoolObj_list.LPop(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should have item, but now it doesn't.", key) + return + } + got2, err := redisPoolObj_list.String(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %s, but now got %s", expected2, got2) + return + } +} + +func TestRPop(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> RPUSH mylist "one" + (integer) 1 + */ + key := "mylist" + value := "one" + expected := 1 + got, err := redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> RPUSH mylist "two" + (integer) 2 + */ + value = "two" + expected = 2 + got, err = redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> RPUSH mylist "three" + (integer) 3 + */ + value = "three" + expected = 3 + got, err = redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> RPOP mylist # 返回被弹出的元素 + "three" + */ + expected2 := "three" + got2_interface, exist, err := redisPoolObj_list.RPop(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should have item, but now it doesn't.", key) + return + } + got2, err := redisPoolObj_list.String(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %s, but now got %s", expected2, got2) + return + } + + /* + redis> LRANGE mylist 0 -1 # 列表剩下的元素 + 1) "one" + 2) "two" + */ + expected3 := []string{"one", "two"} + got3_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got3, err := redisPoolObj_list.Strings(got3_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected3, got3) == false { + t.Errorf("Expected to get %v, but got %v\n", expected3, got3) + return + } +} + +func TestRPopLPush(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # source 和 destination 不同 + + redis> LRANGE alpha 0 -1 # 查看所有元素 + 1) "a" + 2) "b" + 3) "c" + 4) "d" + */ + key := "alpha" + redisPoolObj_list.RPush(key, "a") + redisPoolObj_list.RPush(key, "b") + redisPoolObj_list.RPush(key, "c") + redisPoolObj_list.RPush(key, "d") + deleteKeys = append(deleteKeys, key) + + expected := []string{"a", "b", "c", "d"} + got_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got, err := redisPoolObj_list.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } + + /* + redis> RPOPLPUSH alpha reciver # 执行一次 RPOPLPUSH 看看 + "d" + */ + source := "alpha" + destination := "receiver" + expected2 := "d" + got2_interface, err := redisPoolObj_list.RPopLPush(source, destination) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_list.String(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %s, but now got %s", expected2, got2) + return + } + + deleteKeys = append(deleteKeys, source) + deleteKeys = append(deleteKeys, destination) + + /* + redis> LRANGE alpha 0 -1 + 1) "a" + 2) "b" + 3) "c" + */ + expected3 := []string{"a", "b", "c"} + got3_interface, err := redisPoolObj_list.LRange(source, 0, -1) + if err != nil { + t.Fail() + } + got3, err := redisPoolObj_list.Strings(got3_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected3, got3) == false { + t.Errorf("Expected to get %v, but got %v\n", expected3, got3) + return + } + + /* + redis> LRANGE reciver 0 -1 + 1) "d" + */ + expected4 := []string{"d"} + got4_interface, err := redisPoolObj_list.LRange(destination, 0, -1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_list.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got) + return + } + + /* + redis> RPOPLPUSH alpha reciver # 再执行一次,证实 RPOP 和 LPUSH 的位置正确 + "c" + */ + expected5 := "c" + got5_interface, err := redisPoolObj_list.RPopLPush(source, destination) + if err != nil { + t.Fail() + } + got5, err := redisPoolObj_list.String(got5_interface) + if err != nil { + t.Fail() + } + if got5 != expected5 { + t.Errorf("Expected to get %s, but now got %s", expected5, got5) + return + } + + /* + redis> LRANGE alpha 0 -1 + 1) "a" + 2) "b" + */ + expected6 := []string{"a", "b"} + got6_interface, err := redisPoolObj_list.LRange(source, 0, -1) + if err != nil { + t.Fail() + } + got6, err := redisPoolObj_list.Strings(got6_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected6, got6) == false { + t.Errorf("Expected to get %v, but got %v\n", expected6, got6) + return + } + + /* + redis> LRANGE reciver 0 -1 + 1) "c" + 2) "d" + */ + expected7 := []string{"c", "d"} + got7_interface, err := redisPoolObj_list.LRange(destination, 0, -1) + if err != nil { + t.Fail() + } + got7, err := redisPoolObj_list.Strings(got7_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected7, got7) == false { + t.Errorf("Expected to get %v, but got %v\n", expected7, got7) + return + } + + /* + # source 和 destination 相同 + + redis> LRANGE number 0 -1 + 1) "1" + 2) "2" + 3) "3" + 4) "4" + */ + source = "number" + destination = "number" + + redisPoolObj_list.RPush(source, "1") + redisPoolObj_list.RPush(source, "2") + redisPoolObj_list.RPush(source, "3") + redisPoolObj_list.RPush(source, "4") + + deleteKeys = append(deleteKeys, source) + deleteKeys = append(deleteKeys, destination) + + expected8 := []string{"1", "2", "3", "4"} + got8_interface, err := redisPoolObj_list.LRange(source, 0, -1) + if err != nil { + t.Fail() + } + got8, err := redisPoolObj_list.Strings(got8_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected8, got8) == false { + t.Errorf("Expected to get %v, but got %v\n", expected8, got8) + return + } + + /* + redis> RPOPLPUSH number number + "4" + */ + expected9 := 4 + got9_interface, err := redisPoolObj_list.RPopLPush(source, destination) + if err != nil { + t.Fail() + } + got9, err := redisPoolObj_list.Int(got9_interface) + if err != nil { + t.Fail() + } + if got9 != expected9 { + t.Errorf("Expected to get %d, but now got %d", expected9, got9) + return + } + + /* + redis> LRANGE number 0 -1 # 4 被旋转到了表头 + 1) "4" + 2) "1" + 3) "2" + 4) "3" + */ + expected10 := []string{"4", "1", "2", "3"} + got10_interface, err := redisPoolObj_list.LRange(source, 0, -1) + if err != nil { + t.Fail() + } + got10, err := redisPoolObj_list.Strings(got10_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected10, got10) == false { + t.Errorf("Expected to get %v, but got %v\n", expected10, got10) + return + } + + /* + redis> RPOPLPUSH number number + "3" + */ + expected11 := 3 + got11_interface, err := redisPoolObj_list.RPopLPush(source, destination) + if err != nil { + t.Fail() + } + got11, err := redisPoolObj_list.Int(got11_interface) + if err != nil { + t.Fail() + } + if got11 != expected11 { + t.Errorf("Expected to get %d, but now got %d", expected11, got11) + return + } + + /* + redis> LRANGE number 0 -1 # 这次是 3 被旋转到了表头 + 1) "3" + 2) "4" + 3) "1" + 4) "2" + */ + expected12 := []string{"3", "4", "1", "2"} + got12_interface, err := redisPoolObj_list.LRange(source, 0, -1) + if err != nil { + t.Fail() + } + got12, err := redisPoolObj_list.Strings(got12_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected12, got12) == false { + t.Errorf("Expected to get %v, but got %v\n", expected12, got12) + return + } +} + +func TestLRem(t *testing.T) { + deleteKeys := make([]string, 0, 8) + // defer func() { + // // Delete the test keys + // distinctKeyList := getDistinctKeyList(deleteKeys) + // count, err := redisPoolObj_list.Del(distinctKeyList...) + // if err != nil { + // t.Fail() + // } + // if count != len(distinctKeyList) { + // t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + // return + // } + // }() + + /* + # 先创建一个表,内容排列是 + # morning hello morning helllo morning + + redis> LPUSH greet "morning" + (integer) 1 + redis> LPUSH greet "hello" + (integer) 2 + redis> LPUSH greet "morning" + (integer) 3 + redis> LPUSH greet "hello" + (integer) 4 + redis> LPUSH greet "morning" + (integer) 5 + */ + key := "greet" + value := "morning" + expected := 1 + got, err := redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + value = "hello" + expected = 2 + got, err = redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + value = "morning" + expected = 3 + got, err = redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + value = "hello" + expected = 4 + got, err = redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + value = "morning" + expected = 5 + got, err = redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> LRANGE greet 0 4 # 查看所有元素 + 1) "morning" + 2) "hello" + 3) "morning" + 4) "hello" + 5) "morning" + */ + expected2 := []string{"morning", "hello", "morning", "hello", "morning"} + got2_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_list.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } + + /* + redis> LREM greet 2 morning # 移除从表头到表尾,最先发现的两个 morning + (integer) 2 # 两个元素被移除 + */ + value3 := "morning" + expected3 := 2 + got3, err := redisPoolObj_list.LRem(key, 2, value3) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %d, but now got %d", expected3, got3) + return + } + + /* + redis> LLEN greet # 还剩 3 个元素 + (integer) 3 + */ + expected4 := 3 + got4, err := redisPoolObj_list.LLen(key) + if err != nil { + t.Fail() + } + if got4 != expected4 { + t.Errorf("Expected to get %d, but now got %d", expected4, got4) + return + } + + /* + redis> LRANGE greet 0 2 + 1) "hello" + 2) "hello" + 3) "morning" + */ + expected5 := []string{"hello", "hello", "morning"} + got5_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got5, err := redisPoolObj_list.Strings(got5_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected5, got5) == false { + t.Errorf("Expected to get %v, but got %v\n", expected5, got5) + return + } + + /* + redis> LREM greet -1 morning # 移除从表尾到表头,第一个 morning + (integer) 1 + */ + + value6 := "morning" + expected6 := 1 + got6, err := redisPoolObj_list.LRem(key, -1, value6) + if err != nil { + t.Fail() + } + if got6 != expected6 { + t.Errorf("Expected to get %d, but now got %d", expected6, got6) + return + } + + /* + redis> LLEN greet # 剩下两个元素 + (integer) 2 + */ + expected7 := 2 + got7, err := redisPoolObj_list.LLen(key) + if err != nil { + t.Fail() + } + if got7 != expected7 { + t.Errorf("Expected to get %d, but now got %d", expected7, got7) + return + } + + /* + redis> LRANGE greet 0 1 + 1) "hello" + 2) "hello" + */ + expected8 := []string{"hello", "hello"} + got8_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got8, err := redisPoolObj_list.Strings(got8_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected8, got8) == false { + t.Errorf("Expected to get %v, but got %v\n", expected8, got8) + return + } + + /* + redis> LREM greet 0 hello # 移除表中所有 hello + (integer) 2 # 两个 hello 被移除 + */ + value9 := "hello" + expected9 := 2 + got9, err := redisPoolObj_list.LRem(key, 0, value9) + if err != nil { + t.Fail() + } + if got9 != expected9 { + t.Errorf("Expected to get %d, but now got %d", expected9, got9) + return + } + + /* + redis> LLEN greet + (integer) 0 + */ + expected10 := 0 + got10, err := redisPoolObj_list.LLen(key) + if err != nil { + t.Fail() + } + if got10 != expected10 { + t.Errorf("Expected to get %d, but now got %d", expected10, got10) + return + } + + expected11 := false + got11, err := redisPoolObj_list.Exists(key) + if err != nil { + t.Fail() + } + if got11 != expected11 { + t.Errorf("Expected to get %t, but now got %t", expected11, got11) + return + } +} + +func TestLLen(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 空列表 + + redis> LLEN job + (integer) 0 + */ + key := "job" + expected := 0 + got, err := redisPoolObj_list.LLen(key) + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + # 非空列表 + + redis> LPUSH job "cook food" + (integer) 1 + */ + value := "cook food" + expected = 1 + got, err = redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> LPUSH job "have lunch" + (integer) 2 + */ + value = "have lunch" + expected = 2 + got, err = redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> LLEN job + (integer) 2 + */ + expected = 2 + got, err = redisPoolObj_list.LLen(key) + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } +} + +func TestLIndex(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> LPUSH mylist "World" + (integer) 1 + */ + key := "mylist" + value := "World" + expected := 1 + got, err := redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> LPUSH mylist "Hello" + (integer) 2 + */ + value = "Hello" + expected = 2 + got, err = redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + + /* + redis> LINDEX mylist 0 + "Hello" + */ + expected2 := "Hello" + index := 0 + got2_interface, exist, err := redisPoolObj_list.LIndex(key, index) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s, index:%d should exist, but now it doesn't.", key, index) + return + } + got2, err := redisPoolObj_list.String(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected length %s, but got %s", expected2, got2) + return + } + + /* + redis> LINDEX mylist -1 + "World" + */ + expected3 := "World" + index = -1 + got3_interface, exist, err := redisPoolObj_list.LIndex(key, index) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s, index:%d should exist, but now it doesn't.", key, index) + return + } + got3, err := redisPoolObj_list.String(got3_interface) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected length %s, but got %s", expected3, got3) + return + } + + /* + redis> LINDEX mylist 3 # index不在 mylist 的区间范围内 + (nil) + */ + index = 3 + _, exist, err = redisPoolObj_list.LIndex(key, index) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s, index:%d should not exist, but now it does.", key, index) + return + } +} + +func TestLInsert(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> RPUSH mylist "Hello" + (integer) 1 + + redis> RPUSH mylist "World" + (integer) 2 + */ + key := "mylist" + value := "Hello" + expected := 1 + got, err := redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + + value = "World" + expected = 2 + got, err = redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> LINSERT mylist BEFORE "World" "There" + (integer) 3 + */ + pivot := "World" + value = "There" + expected = 3 + got, err = redisPoolObj_list.LInsert(key, "BEFORE", pivot, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + + /* + redis> LRANGE mylist 0 -1 + 1) "Hello" + 2) "There" + 3) "World" + */ + expected2 := []string{"Hello", "There", "World"} + got2_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_list.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } + + /* + # 对一个非空列表插入,查找一个不存在的 pivot + + redis> LINSERT mylist BEFORE "go" "let's" + (integer) -1 # 失败 + */ + pivot = "go" + value = "let's" + expected = -1 + got, err = redisPoolObj_list.LInsert(key, "BEFORE", pivot, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %d, but got %d", expected, got) + return + } + + /* + # 对一个空列表执行 LINSERT 命令 + + redis> EXISTS fake_list + (integer) 0 + */ + key3 := "fake_list" + expected3 := false + got3, err := redisPoolObj_list.Exists(key3) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected length %t, but got %t", expected3, got3) + return + } + + /* + redis> LINSERT fake_list BEFORE "nono" "gogogog" + (integer) 0 # 失败 + */ + pivot = "nono" + value = "gogogog" + expected4 := 0 + got4, err := redisPoolObj_list.LInsert(key3, "BEFORE", pivot, value) + if err != nil { + t.Fail() + } + if got4 != expected4 { + t.Errorf("Expected to get %d, but got %d", expected4, got4) + return + } +} + +func TestLSet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 对空列表(key 不存在)进行 LSET + + redis> EXISTS list + (integer) 0 + */ + key := "list" + expected := false + got, err := redisPoolObj_list.Exists(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected length %t, but got %t", expected, got) + return + } + + /* + redis> LSET list 0 item + (error) ERR no such key + */ + index := 0 + value := "item" + err = redisPoolObj_list.LSet(key, index, value) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + /* + # 对非空列表进行 LSET + + redis> LPUSH job "cook food" + (integer) 1 + */ + key = "job" + value = "cook food" + expected2 := 1 + got2, err := redisPoolObj_list.LPush(key, value) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected length %d, but got %d", expected2, got2) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> LRANGE job 0 0 + 1) "cook food" + */ + expected3 := []string{"cook food"} + got3_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got3, err := redisPoolObj_list.Strings(got3_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected3, got3) == false { + t.Errorf("Expected to get %v, but got %v\n", expected3, got3) + return + } + + /* + redis> LSET job 0 "play game" + OK + */ + index = 0 + value = "play game" + err = redisPoolObj_list.LSet(key, index, value) + if err != nil { + t.Fail() + } + + /* + redis> LRANGE job 0 0 + 1) "play game" + */ + expected4 := []string{"play game"} + got4_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_list.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + # index 超出范围 + + redis> LLEN list # 列表长度为 1 + (integer) 1 + */ + key = "list" + redisPoolObj_list.RPush(key, "init") + + expected5 := 1 + got5, err := redisPoolObj_list.LLen(key) + if err != nil { + t.Fail() + } + if got5 != expected5 { + t.Errorf("Expected length %d, but got %d", expected5, got5) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> LSET list 3 'out of range' + (error) ERR index out of range + */ + index = 3 + value = "out of range" + err = redisPoolObj_list.LSet(key, index, value) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } +} + +func TestLRange(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> RPUSH fp-language lisp + (integer) 1 + */ + key := "fp-language" + value := "lisp" + expected := 1 + got, err := redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> LRANGE fp-language 0 0 + 1) "lisp" + */ + expected2 := []string{"lisp"} + got2_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_list.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } + + /* + redis> RPUSH fp-language scheme + (integer) 2 + */ + value = "scheme" + expected = 2 + got, err = redisPoolObj_list.RPush(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + + /* + redis> LRANGE fp-language 0 1 + 1) "lisp" + 2) "scheme" + */ + expected3 := []string{"lisp", "scheme"} + got3_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got3, err := redisPoolObj_list.Strings(got3_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected3, got3) == false { + t.Errorf("Expected to get %v, but got %v\n", expected3, got3) + return + } +} + +func TestLTrim(t *testing.T) { + // deleteKeys := make([]string, 0, 8) + // defer func() { + // // Delete the test keys + // distinctKeyList := getDistinctKeyList(deleteKeys) + // count, err := redisPoolObj_list.Del(distinctKeyList...) + // if err != nil { + // t.Fail() + // } + // if count != len(distinctKeyList) { + // t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + // return + // } + // }() + + /* + # 情况 1: 常见情况, start 和 stop 都在列表的索引范围之内 + */ + key := "alpha" + redisPoolObj_list.RPush(key, "h", "e", "l", "l", "o") + + /* + redis> LRANGE alpha 0 -1 # alpha 是一个包含 5 个字符串的列表 + 1) "h" + 2) "e" + 3) "l" + 4) "l" + 5) "o" + */ + expected := []string{"h", "e", "l", "l", "o"} + got_interface, err := redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got, err := redisPoolObj_list.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } + + /* + redis> LTRIM alpha 1 -1 # 删除 alpha 列表索引为 0 的元素 + OK + + redis> LRANGE alpha 0 -1 # "h" 被删除了 + 1) "e" + 2) "l" + 3) "l" + 4) "o" + */ + start := 1 + stop := -1 + err = redisPoolObj_list.LTrim(key, start, stop) + if err != nil { + t.Fail() + } + + expected = []string{"e", "l", "l", "o"} + got_interface, err = redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got, err = redisPoolObj_list.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } + + /* + # 情况 2: stop 比列表的最大下标还要大 + + redis> LTRIM alpha 1 10086 # 保留 alpha 列表索引 1 至索引 10086 上的元素 + OK + + redis> LRANGE alpha 0 -1 # 只有索引 0 上的元素 "e" 被删除了,其他元素还在 + 1) "l" + 2) "l" + 3) "o" + */ + start = 1 + stop = 10086 + err = redisPoolObj_list.LTrim(key, start, stop) + if err != nil { + t.Fail() + } + + expected = []string{"l", "l", "o"} + got_interface, err = redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got, err = redisPoolObj_list.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } + + /* + # 情况 3: start 和 stop 都比列表的最大下标要大,并且 start < stop + + redis> LTRIM alpha 10086 123321 + OK + + redis> LRANGE alpha 0 -1 # 列表被清空 + (empty list or set) + */ + start = 10086 + stop = 123321 + err = redisPoolObj_list.LTrim(key, start, stop) + if err != nil { + t.Fail() + } + + expected = []string{} + got_interface, err = redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got, err = redisPoolObj_list.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } + + /* + # 情况 4: start 和 stop 都比列表的最大下标要大,并且 start > stop + + redis> RPUSH new-alpha "h" "e" "l" "l" "o" # 重新建立一个新列表 + (integer) 5 + + redis> LRANGE new-alpha 0 -1 + 1) "h" + 2) "e" + 3) "l" + 4) "l" + 5) "o" + + redis> LTRIM new-alpha 123321 10086 # 执行 LTRIM + OK + + redis> LRANGE new-alpha 0 -1 # 同样被清空 + (empty list or set) + */ + key = "new-alpha" + redisPoolObj_list.RPush(key, "h", "e", "l", "l", "o") + expected = []string{"h", "e", "l", "l", "o"} + got_interface, err = redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got, err = redisPoolObj_list.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } + + start = 123321 + stop = 10086 + err = redisPoolObj_list.LTrim(key, start, stop) + if err != nil { + t.Fail() + } + + expected = []string{} + got_interface, err = redisPoolObj_list.LRange(key, 0, -1) + if err != nil { + t.Fail() + } + got, err = redisPoolObj_list.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(expected, got) == false { + t.Errorf("Expected to get %v, but got %v\n", expected, got) + return + } +} diff --git a/trunk/goutil/redisUtil/redisPool_set.go b/trunk/goutil/redisUtil/redisPool_set.go new file mode 100644 index 0000000..991fe37 --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_set.go @@ -0,0 +1,309 @@ +/* +未实现的列表方法: +SSCAN +*/ +package redisUtil + +import ( + "github.com/gomodule/redigo/redis" +) + +/* +SADD key member [member …] +可用版本: >= 1.0.0 +时间复杂度: O(N), N 是被添加的元素的数量。 +将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。 + +假如 key 不存在,则创建一个只包含 member 元素作成员的集合。 + +当 key 不是集合类型时,返回一个错误。 + +Note + +在Redis2.4版本以前, SADD 只接受单个 member 值。 + +返回值 +被添加到集合中的新元素的数量,不包括被忽略的元素。 +*/ +func (this *RedisPool) SAdd(key string, values ...interface{}) (newCount int, err error) { + conn := this.GetConnection() + defer conn.Close() + + newCount, err = redis.Int(conn.Do("SADD", redis.Args{}.Add(key).AddFlat(values)...)) + return +} + +/* +SISMEMBER key member +可用版本: >= 1.0.0 +时间复杂度: O(1) +判断 member 元素是否集合 key 的成员。 + +返回值 +如果 member 元素是集合的成员,返回 1 。 如果 member 元素不是集合的成员,或 key 不存在,返回 0 。 +*/ +func (this *RedisPool) SIsMember(key string, value interface{}) (isMember bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + isMember, err = redis.Bool(conn.Do("SISMEMBER", key, value)) + return +} + +/* +SPOP key +可用版本: >= 1.0.0 +时间复杂度: O(1) +移除并返回集合中的一个随机元素。 + +如果只想获取一个随机元素,但不想该元素从集合中被移除的话,可以使用 SRANDMEMBER key [count] 命令。 + +返回值 +被移除的随机元素。 当 key 不存在或 key 是空集时,返回 nil 。 +*/ +func (this *RedisPool) SPop(key string) (value interface{}, err error) { + conn := this.GetConnection() + defer conn.Close() + + value, err = conn.Do("SPOP", key) + return +} + +/* +SRANDMEMBER key [count] +可用版本: >= 1.0.0 +时间复杂度: 只提供 key 参数时为 O(1) 。如果提供了 count 参数,那么为 O(N) ,N 为返回数组的元素个数。 +如果命令执行时,只提供了 key 参数,那么返回集合中的一个随机元素。 + +从 Redis 2.6 版本开始, SRANDMEMBER 命令接受可选的 count 参数: + +如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。 + +如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。 + +该操作和 SPOP key 相似,但 SPOP key 将随机元素从集合中移除并返回,而 SRANDMEMBER 则仅仅返回随机元素,而不对集合进行任何改动。 + +返回值 +只提供 key 参数时,返回一个元素;如果集合为空,返回 nil 。 如果提供了 count 参数,那么返回一个数组;如果集合为空,返回空数组。 +*/ +func (this *RedisPool) SRandMember(key string, count int) (reply interface{}, err error) { + conn := this.GetConnection() + defer conn.Close() + + reply, err = conn.Do("SRANDMEMBER", key, count) + return +} + +/* +SREM key member [member …] +可用版本: >= 1.0.0 +时间复杂度: O(N), N 为给定 member 元素的数量。 +移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。 + +当 key 不是集合类型,返回一个错误。 + +Note + +在 Redis 2.4 版本以前, SREM 只接受单个 member 值。 + +返回值 +被成功移除的元素的数量,不包括被忽略的元素。 +*/ +func (this *RedisPool) SRem(key string, values ...interface{}) (delCount int, err error) { + conn := this.GetConnection() + defer conn.Close() + + delCount, err = redis.Int(conn.Do("SREM", redis.Args{}.Add(key).AddFlat(values)...)) + return +} + +/* +SMOVE source destination member +可用版本: >= 1.0.0 +时间复杂度: O(1) +将 member 元素从 source 集合移动到 destination 集合。 + +SMOVE 是原子性操作。 + +如果 source 集合不存在或不包含指定的 member 元素,则 SMOVE 命令不执行任何操作,仅返回 0 。否则, member 元素从 source 集合中被移除,并添加到 destination 集合中去。 + +当 destination 集合已经包含 member 元素时, SMOVE 命令只是简单地将 source 集合中的 member 元素删除。 + +当 source 或 destination 不是集合类型时,返回一个错误。 + +返回值 +如果 member 元素被成功移除,返回 1 。 如果 member 元素不是 source 集合的成员,并且没有任何操作对 destination 集合执行,那么返回 0 。 +*/ +func (this *RedisPool) SMove(source, destination string, member interface{}) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var result int + result, err = redis.Int(conn.Do("SMOVE", source, destination, member)) + if err != nil { + return + } + if result == 1 { + successful = true + } + + return +} + +/* +SCARD key +可用版本: >= 1.0.0 +时间复杂度: O(1) +返回集合 key 的基数(集合中元素的数量)。 + +返回值 +集合的基数。 当 key 不存在时,返回 0 。 +*/ +func (this *RedisPool) SCard(key string) (nowCount int, err error) { + conn := this.GetConnection() + defer conn.Close() + + nowCount, err = redis.Int(conn.Do("SCARD", key)) + return +} + +/* +SMEMBERS key +可用版本: >= 1.0.0 +时间复杂度: O(N), N 为集合的基数。 +返回集合 key 中的所有成员。 + +不存在的 key 被视为空集合。 + +返回值 +集合中的所有成员。 +*/ +func (this *RedisPool) SMembers(key string) (reply interface{}, err error) { + conn := this.GetConnection() + defer conn.Close() + + reply, err = conn.Do("SMEMBERS", key) + return +} + +/* +SINTER key [key …] +可用版本: >= 1.0.0 +时间复杂度: O(N * M), N 为给定集合当中基数最小的集合, M 为给定集合的个数。 +返回一个集合的全部成员,该集合是所有给定集合的交集。 + +不存在的 key 被视为空集。 + +当给定集合当中有一个空集时,结果也为空集(根据集合运算定律)。 + +返回值 +交集成员的列表。 +*/ +func (this *RedisPool) SInter(key string, keyList ...string) (reply interface{}, err error) { + conn := this.GetConnection() + defer conn.Close() + + reply, err = conn.Do("SINTER", redis.Args{}.Add(key).AddFlat(keyList)...) + return +} + +/* +SINTERSTORE destination key [key …] +可用版本: >= 1.0.0 +时间复杂度: O(N * M), N 为给定集合当中基数最小的集合, M 为给定集合的个数。 +这个命令类似于 SINTER key [key …] 命令,但它将结果保存到 destination 集合,而不是简单地返回结果集。 + +如果 destination 集合已经存在,则将其覆盖。 + +destination 可以是 key 本身。 + +返回值 +结果集中的成员数量。 +*/ +func (this *RedisPool) SInterStore(destination string, key string, keyList ...string) (count int, err error) { + conn := this.GetConnection() + defer conn.Close() + + count, err = redis.Int(conn.Do("SINTERSTORE", redis.Args{}.Add(destination).Add(key).AddFlat(keyList)...)) + return +} + +/* +SUNION key [key …] +可用版本: >= 1.0.0 +时间复杂度: O(N), N 是所有给定集合的成员数量之和。 +返回一个集合的全部成员,该集合是所有给定集合的并集。 + +不存在的 key 被视为空集。 + +返回值 +并集成员的列表。 +*/ +func (this *RedisPool) SUnion(key string, keyList ...string) (reply interface{}, err error) { + conn := this.GetConnection() + defer conn.Close() + + reply, err = conn.Do("SUNION", redis.Args{}.Add(key).AddFlat(keyList)...) + return +} + +/* +SUNIONSTORE destination key [key …] +可用版本: >= 1.0.0 +时间复杂度: O(N), N 是所有给定集合的成员数量之和。 +这个命令类似于 SUNION key [key …] 命令,但它将结果保存到 destination 集合,而不是简单地返回结果集。 + +如果 destination 已经存在,则将其覆盖。 + +destination 可以是 key 本身。 + +返回值 +结果集中的元素数量。 +*/ +func (this *RedisPool) SUnionStore(destination string, key string, keyList ...string) (count int, err error) { + conn := this.GetConnection() + defer conn.Close() + + count, err = redis.Int(conn.Do("SUNIONSTORE", redis.Args{}.Add(destination).Add(key).AddFlat(keyList)...)) + return +} + +/* +SDIFF key [key …] +可用版本: >= 1.0.0 +时间复杂度: O(N), N 是所有给定集合的成员数量之和。 +返回一个集合的全部成员,该集合是所有给定集合之间的差集。 + +不存在的 key 被视为空集。 + +返回值 +一个包含差集成员的列表。 +*/ +func (this *RedisPool) SDiff(key string, keyList ...string) (reply interface{}, err error) { + conn := this.GetConnection() + defer conn.Close() + + reply, err = conn.Do("SDIFF", redis.Args{}.Add(key).AddFlat(keyList)...) + return +} + +/* +SDIFFSTORE destination key [key …] +可用版本: >= 1.0.0 +时间复杂度: O(N), N 是所有给定集合的成员数量之和。 +这个命令的作用和 SDIFF key [key …] 类似,但它将结果保存到 destination 集合,而不是简单地返回结果集。 + +如果 destination 集合已经存在,则将其覆盖。 + +destination 可以是 key 本身。 + +返回值 +结果集中的元素数量。 +*/ +func (this *RedisPool) SDiffStore(destination string, key string, keyList ...string) (count int, err error) { + conn := this.GetConnection() + defer conn.Close() + + count, err = redis.Int(conn.Do("SDIFFSTORE", redis.Args{}.Add(destination).Add(key).AddFlat(keyList)...)) + return +} diff --git a/trunk/goutil/redisUtil/redisPool_set_test.go b/trunk/goutil/redisUtil/redisPool_set_test.go new file mode 100644 index 0000000..19ce417 --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_set_test.go @@ -0,0 +1,1575 @@ +package redisUtil + +import ( + "testing" + "time" +) + +var ( + redisPoolObj_set *RedisPool +) + +func init() { + redisPoolObj_set = NewRedisPool("testPool", "10.1.0.21:6379", "redis_pwd", 5, 500, 200, 10*time.Second, 5*time.Second) +} + +func TestSAdd(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 添加单个元素 + + redis> SADD bbs "discuz.net" + (integer) 1 + */ + key := "bbs" + value := "discuz.net" + expected := 1 + got, err := redisPoolObj_set.SAdd(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + # 添加重复元素 + + redis> SADD bbs "discuz.net" + (integer) 0 + */ + expected = 0 + got, err = redisPoolObj_set.SAdd(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + /* + # 添加多个元素 + + redis> SADD bbs "tianya.cn" "groups.google.com" + (integer) 2 + */ + expected = 2 + got, err = redisPoolObj_set.SAdd(key, "tianya.cn", "groups.google.com") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + /* + redis> SMEMBERS bbs + 1) "discuz.net" + 2) "groups.google.com" + 3) "tianya.cn" + */ + expected2 := []string{"discuz.net", "groups.google.com", "tianya.cn"} + got2_interface, err := redisPoolObj_set.SMembers(key) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_set.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } +} + +func TestSIsMember(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + key := "joe's_movies" + expected := 3 + got, err := redisPoolObj_set.SAdd(key, "hi, lady", "Fast Five", "2012") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> SMEMBERS joe's_movies + 1) "hi, lady" + 2) "Fast Five" + 3) "2012" + */ + expected2 := []string{"hi, lady", "Fast Five", "2012"} + got2_interface, err := redisPoolObj_set.SMembers(key) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_set.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } + + /* + redis> SISMEMBER joe's_movies "bet man" + (integer) 0 + */ + expected3 := false + value := "bet man" + got3, err := redisPoolObj_set.SIsMember(key, value) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %t, but now got %t", expected3, got3) + return + } + + /* + redis> SISMEMBER joe's_movies "Fast Five" + (integer) 1 + */ + expected3 = true + value = "Fast Five" + got3, err = redisPoolObj_set.SIsMember(key, value) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %t, but now got %t", expected3, got3) + return + } + +} + +func TestSPop(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + key := "db" + expected := 3 + got, err := redisPoolObj_set.SAdd(key, "MySQL", "MongoDB", "Redis") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> SMEMBERS db + 1) "MySQL" + 2) "MongoDB" + 3) "Redis" + */ + expected2 := []string{"MySQL", "MongoDB", "Redis"} + got2_interface, err := redisPoolObj_set.SMembers(key) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_set.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } + + /* + redis> SPOP db + "Redis" + + redis> SMEMBERS db + 1) "MySQL" + 2) "MongoDB" + */ + expected3 := make(map[string]struct{}) + for _, item := range expected2 { + expected3[item] = struct{}{} + } + got3_interface, err := redisPoolObj_set.SPop(key) + if err != nil { + t.Fail() + } + got3, err := redisPoolObj_set.String(got3_interface) + if err != nil { + t.Fail() + } + if _, exist := expected3[got3]; !exist { + t.Errorf("Expected to get one of key from %v, but now get %s.", expected3, got3) + return + } + delete(expected3, got3) + + expected4 := make([]string, 0, len(expected3)) + for k := range expected3 { + expected4 = append(expected4, k) + } + + got4_interface, err := redisPoolObj_set.SMembers(key) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SPOP db + "MySQL" + + redis> SMEMBERS db + 1) "MongoDB" + */ + + expected5 := make(map[string]struct{}) + for _, item := range expected4 { + expected5[item] = struct{}{} + } + got5_interface, err := redisPoolObj_set.SPop(key) + if err != nil { + t.Fail() + } + got5, err := redisPoolObj_set.String(got5_interface) + if err != nil { + t.Fail() + } + if _, exist := expected5[got5]; !exist { + t.Errorf("Expected to get one of key from %v, but now get %s.", expected5, got5) + return + } + delete(expected5, got5) + + expected6 := make([]string, 0, len(expected5)) + for k := range expected5 { + expected6 = append(expected6, k) + } + + got6_interface, err := redisPoolObj_set.SMembers(key) + if err != nil { + t.Fail() + } + got6, err := redisPoolObj_set.Strings(got6_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected6, got6) == false { + t.Errorf("Expected to get %v, but got %v\n", expected6, got6) + return + } +} + +func TestSRandMember(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 添加元素 + + redis> SADD fruit apple banana cherry + (integer) 3 + */ + key := "fruit" + expected := 3 + got, err := redisPoolObj_set.SAdd(key, "apple", "banana", "cherry") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + # 只给定 key 参数,返回一个随机元素 + + redis> SRANDMEMBER fruit + "cherry" + */ + expected2 := make(map[string]struct{}) + expected2["apple"] = struct{}{} + expected2["banana"] = struct{}{} + expected2["cherry"] = struct{}{} + + count := 1 + got2_interface, err := redisPoolObj_set.SRandMember(key, count) + if err != nil { + t.Fail() + } + got2_slice, err := redisPoolObj_set.Strings(got2_interface) + if err != nil { + t.Fail() + } + if len(got2_slice) != count { + t.Errorf("Expected to have %d items, but now get %d.", count, len(got2_slice)) + return + } + got2 := got2_slice[0] + if _, exist := expected2[got2]; !exist { + t.Errorf("Expected length %s, but got %s", expected2, got2) + return + } + + /* + # 给定 3 为 count 参数,返回 3 个随机元素 + # 每个随机元素都不相同 + + redis> SRANDMEMBER fruit 3 + 1) "apple" + 2) "banana" + 3) "cherry" + */ + count = 3 + expected3 := make([]string, 0, len(expected2)) + for k := range expected2 { + expected3 = append(expected3, k) + } + got3_interface, err := redisPoolObj_set.SRandMember(key, count) + if err != nil { + t.Fail() + } + got3_slice, err := redisPoolObj_set.Strings(got3_interface) + if err != nil { + t.Fail() + } + if len(got3_slice) != count { + t.Errorf("Expected to have %d items, but now get %d.", count, len(got3_slice)) + return + } + if isTwoUnorderedSliceEqual(expected3, got3_slice) == false { + t.Errorf("Expected to get %v, but got %v\n", expected3, got3_slice) + return + } + + /* + # 给定 -3 为 count 参数,返回 3 个随机元素 + # 元素可能会重复出现多次 + + redis> SRANDMEMBER fruit -3 + 1) "banana" + 2) "cherry" + 3) "apple" + + # 如果 count 是整数,且大于等于集合基数,那么返回整个集合 + + redis> SRANDMEMBER fruit 10 + 1) "apple" + 2) "banana" + 3) "cherry" + + # 如果 count 是负数,且 count 的绝对值大于集合的基数 + # 那么返回的数组的长度为 count 的绝对值 + + redis> SRANDMEMBER fruit -10 + 1) "banana" + 2) "apple" + 3) "banana" + 4) "cherry" + 5) "apple" + 6) "apple" + 7) "cherry" + 8) "apple" + 9) "apple" + 10) "banana" + + # SRANDMEMBER 并不会修改集合内容 + + redis> SMEMBERS fruit + 1) "apple" + 2) "cherry" + 3) "banana" + + # 集合为空时返回 nil 或者空数组 + + redis> SRANDMEMBER not-exists + (nil) + + redis> SRANDMEMBER not-eixsts 10 + (empty list or set) + */ +} + +func TestSRem(t *testing.T) { + deleteKeys := make([]string, 0, 8) + // defer func() { + // // Delete the test keys + // distinctKeyList := getDistinctKeyList(deleteKeys) + // count, err := redisPoolObj_list.Del(distinctKeyList...) + // if err != nil { + // t.Fail() + // } + // if count != len(distinctKeyList) { + // t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + // return + // } + // }() + + /* + # 添加元素 + + redis> SADD languages c lisp python ruby + (integer) 4 + */ + key := "languages" + expected := 4 + got, err := redisPoolObj_set.SAdd(key, "c", "lisp", "python", "ruby") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + # 测试数据 + + redis> SMEMBERS languages + 1) "c" + 2) "lisp" + 3) "python" + 4) "ruby" + */ + expected2 := make(map[string]struct{}) + expected2["c"] = struct{}{} + expected2["lisp"] = struct{}{} + expected2["python"] = struct{}{} + expected2["ruby"] = struct{}{} + + expected4 := make([]string, 0, len(expected2)) + for k := range expected2 { + expected4 = append(expected4, k) + } + + got4_interface, err := redisPoolObj_set.SMembers(key) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + # 移除单个元素 + + redis> SREM languages ruby + (integer) 1 + */ + value := "ruby" + expected5 := 1 + got5, err := redisPoolObj_set.SRem(key, value) + if err != nil { + t.Fail() + } + if got5 != expected5 { + t.Errorf("Expected length %d, but got %d", expected5, got5) + return + } + + /* + # 移除不存在元素 + + redis> SREM languages non-exists-language + (integer) 0 + */ + value = "non-exists-language" + expected6 := 0 + got6, err := redisPoolObj_set.SRem(key, value) + if err != nil { + t.Fail() + } + if got6 != expected6 { + t.Errorf("Expected length %d, but got %d", expected6, got6) + return + } + + /* + # 移除多个元素 + + redis> SREM languages lisp python c + (integer) 3 + + redis> SMEMBERS languages + (empty list or set) + */ + expected7 := 3 + got7, err := redisPoolObj_set.SRem(key, "c", "lisp", "python") + if err != nil { + t.Fail() + } + if got7 != expected7 { + t.Errorf("Expected length %d, but got %d", expected7, got7) + return + } + + expected8 := make([]string, 0) + got8_interface, err := redisPoolObj_set.SMembers(key) + if err != nil { + t.Fail() + } + got8, err := redisPoolObj_set.Strings(got8_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected8, got8) == false { + t.Errorf("Expected to get %v, but got %v\n", expected8, got8) + return + } +} + +func TestSMove(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # 添加元素 + + redis> SADD languages c lisp python ruby + (integer) 4 + */ + source := "songs" + expected := 2 + got, err := redisPoolObj_set.SAdd(source, "Billie Jean", "Believe Me") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, source) + + /* + redis> SMEMBERS songs + 1) "Billie Jean" + 2) "Believe Me" + */ + expected2 := []string{"Billie Jean", "Believe Me"} + got2_interface, err := redisPoolObj_set.SMembers(source) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_set.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } + + /* + redis> SMEMBERS my_songs + (empty list or set) + */ + destination := "my_songs" + expected3 := []string{} + got3_interface, err := redisPoolObj_set.SMembers(destination) + if err != nil { + t.Fail() + } + got3, err := redisPoolObj_set.Strings(got3_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected3, got3) == false { + t.Errorf("Expected to get %v, but got %v\n", expected3, got3) + return + } + + deleteKeys = append(deleteKeys, destination) + + /* + redis> SMOVE songs my_songs "Believe Me" + (integer) 1 + */ + value := "Believe Me" + expected4 := true + got4, err := redisPoolObj_set.SMove(source, destination, value) + if err != nil { + t.Fail() + } + if got4 != expected4 { + t.Errorf("Expected length %t, but got %t", expected4, got4) + return + } + + /* + redis> SMEMBERS songs + 1) "Billie Jean" + */ + expected5 := []string{"Billie Jean"} + got5_interface, err := redisPoolObj_set.SMembers(source) + if err != nil { + t.Fail() + } + got5, err := redisPoolObj_set.Strings(got5_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected5, got5) == false { + t.Errorf("Expected to get %v, but got %v\n", expected5, got5) + return + } + + /* + redis> SMEMBERS my_songs + 1) "Believe Me" + */ + expected6 := []string{"Believe Me"} + got6_interface, err := redisPoolObj_set.SMembers(destination) + if err != nil { + t.Fail() + } + got6, err := redisPoolObj_set.Strings(got6_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected6, got6) == false { + t.Errorf("Expected to get %v, but got %v\n", expected6, got6) + return + } +} + +func TestSCard(t *testing.T) { + /* + redis> SADD tool pc printer phone + (integer) 3 + */ + key := "tool" + expected := 3 + got, err := redisPoolObj_set.SAdd(key, "pc", "printer", "phone") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + /* + redis> SCARD tool # 非空集合 + (integer) 3 + */ + expected = 3 + got, err = redisPoolObj_set.SCard(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + /* + redis> DEL tool + (integer) 1 + */ + expected = 1 + got, err = redisPoolObj_set.Del(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + /* + redis> SCARD tool # 空集合 + (integer) 0 + */ + expected = 0 + got, err = redisPoolObj_set.SCard(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } +} + +func TestSMembers(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + # key 不存在或集合为空 + + redis> EXISTS not_exists_key + (integer) 0 + */ + key := "not_exists_key" + expected := false + got, err := redisPoolObj_set.Exists(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %t, but now got %t.", expected, got) + return + } + + /* + redis> SMEMBERS not_exists_key + (empty list or set) + */ + expected2 := make([]string, 0) + got2_interface, err := redisPoolObj_set.SMembers(key) + if err != nil { + t.Fail() + } + got2, err := redisPoolObj_set.Strings(got2_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected2, got2) == false { + t.Errorf("Expected to get %v, but got %v\n", expected2, got2) + return + } + + /* + # 非空集合 + + redis> SADD language Ruby Python Clojure + (integer) 3 + */ + key = "language" + expected3 := 3 + got3, err := redisPoolObj_set.SAdd(key, "Clojure", "Python", "Ruby") + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %d, but now got %d.", expected3, got3) + return + } + + deleteKeys = append(deleteKeys, key) + + /* + redis> SMEMBERS language + 1) "Python" + 2) "Ruby" + 3) "Clojure" + */ + expected4 := []string{"Clojure", "Python", "Ruby"} + got4_interface, err := redisPoolObj_set.SMembers(key) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } +} + +func TestSInter(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + key1 := "group_1" + expected := 3 + got, err := redisPoolObj_set.SAdd(key1, "LI LEI", "TOM", "JACK") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key1) + + key2 := "group_2" + expected = 2 + got, err = redisPoolObj_set.SAdd(key2, "HAN MEIMEI", "JACK") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key2) + + /* + redis> SMEMBERS group_1 + 1) "LI LEI" + 2) "TOM" + 3) "JACK" + */ + expected4 := []string{"LI LEI", "TOM", "JACK"} + got4_interface, err := redisPoolObj_set.SMembers(key1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SMEMBERS group_2 + 1) "HAN MEIMEI" + 2) "JACK" + */ + expected4 = []string{"HAN MEIMEI", "JACK"} + got4_interface, err = redisPoolObj_set.SMembers(key2) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SINTER group_1 group_2 + 1) "JACK" + */ + expected4 = []string{"JACK"} + got4_interface, err = redisPoolObj_set.SInter(key1, key2) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } +} + +func TestSInterStore(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SMEMBERS songs + 1) "good bye joe" + 2) "hello,peter" + */ + key1 := "songs" + expected := 2 + got, err := redisPoolObj_set.SAdd(key1, "good bye joe", "hello,peter") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key1) + + expected4 := []string{"good bye joe", "hello,peter"} + got4_interface, err := redisPoolObj_set.SMembers(key1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SMEMBERS my_songs + 1) "good bye joe" + 2) "falling" + */ + key2 := "my_songs" + expected = 2 + got, err = redisPoolObj_set.SAdd(key2, "good bye joe", "falling") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key2) + + expected4 = []string{"good bye joe", "falling"} + got4_interface, err = redisPoolObj_set.SMembers(key2) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SINTERSTORE song_interset songs my_songs + (integer) 1 + */ + destination := "song_interset" + expected2 := 1 + got2, err := redisPoolObj_set.SInterStore(destination, key1, key2) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but now got %d.", expected2, got2) + return + } + + deleteKeys = append(deleteKeys, destination) + + /* + redis> SMEMBERS song_interset + 1) "good bye joe" + */ + expected4 = []string{"good bye joe"} + got4_interface, err = redisPoolObj_set.SMembers(destination) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } +} + +func TestSUnion(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SMEMBERS songs + 1) "Billie Jean" + */ + key1 := "songs" + expected := 1 + got, err := redisPoolObj_set.SAdd(key1, "Billie Jean") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key1) + + expected4 := []string{"Billie Jean"} + got4_interface, err := redisPoolObj_set.SMembers(key1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SMEMBERS my_songs + 1) "Believe Me" + */ + key2 := "my_songs" + expected = 1 + got, err = redisPoolObj_set.SAdd(key2, "Believe Me") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key2) + + expected4 = []string{"Believe Me"} + got4_interface, err = redisPoolObj_set.SMembers(key2) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SUNION songs my_songs + 1) "Billie Jean" + 2) "Believe Me" + */ + expected4 = []string{"Billie Jean", "Believe Me"} + got4_interface, err = redisPoolObj_set.SUnion(key1, key2) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } +} + +func TestSUnionStore(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SMEMBERS NoSQL + 1) "MongoDB" + 2) "Redis" + */ + key1 := "NoSQL" + expected := 2 + got, err := redisPoolObj_set.SAdd(key1, "MongoDB", "Redis") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key1) + + expected4 := []string{"MongoDB", "Redis"} + got4_interface, err := redisPoolObj_set.SMembers(key1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SMEMBERS SQL + 1) "sqlite" + 2) "MySQL" + */ + key2 := "SQL" + expected = 2 + got, err = redisPoolObj_set.SAdd(key2, "sqlite", "MySQL") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key2) + + expected4 = []string{"sqlite", "MySQL"} + got4_interface, err = redisPoolObj_set.SMembers(key2) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SUNIONSTORE db NoSQL SQL + (integer) 4 + */ + destination := "db" + expected2 := 4 + got2, err := redisPoolObj_set.SUnionStore(destination, key1, key2) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but now got %d.", expected2, got2) + return + } + + deleteKeys = append(deleteKeys, destination) + + /* + redis> SMEMBERS db + 1) "MySQL" + 2) "sqlite" + 3) "MongoDB" + 4) "Redis" + */ + expected4 = []string{"MongoDB", "Redis", "sqlite", "MySQL"} + got4_interface, err = redisPoolObj_set.SMembers(destination) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } +} + +func TestSDiff(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SMEMBERS peter's_movies + 1) "bet man" + 2) "start war" + 3) "2012" + */ + key1 := "peter's_movies" + expected := 3 + got, err := redisPoolObj_set.SAdd(key1, "bet man", "start war", "2012") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key1) + + expected4 := []string{"bet man", "start war", "2012"} + got4_interface, err := redisPoolObj_set.SMembers(key1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SMEMBERS joe's_movies + 1) "hi, lady" + 2) "Fast Five" + 3) "2012" + */ + key2 := "joe's_movies" + expected = 3 + got, err = redisPoolObj_set.SAdd(key2, "hi, lady", "Fast Five", "2012") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key2) + + expected4 = []string{"hi, lady", "Fast Five", "2012"} + got4_interface, err = redisPoolObj_set.SMembers(key2) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SDIFF peter's_movies joe's_movies + 1) "bet man" + 2) "start war" + */ + expected4 = []string{"bet man", "start war"} + got4_interface, err = redisPoolObj_set.SDiff(key1, key2) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SDIFF joe's_movies peter's_movies + 1) "hi, lady" + 2) "Fast Five" + */ + expected4 = []string{"hi, lady", "Fast Five"} + got4_interface, err = redisPoolObj_set.SDiff(key2, key1) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } +} + +func TestSDiffStore(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_list.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + redis> SMEMBERS peter's_movies + 1) "bet man" + 2) "start war" + 3) "2012" + */ + key1 := "peter's_movies" + expected := 3 + got, err := redisPoolObj_set.SAdd(key1, "bet man", "start war", "2012") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key1) + + expected4 := []string{"bet man", "start war", "2012"} + got4_interface, err := redisPoolObj_set.SMembers(key1) + if err != nil { + t.Fail() + } + got4, err := redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SMEMBERS joe's_movies + 1) "hi, lady" + 2) "Fast Five" + 3) "2012" + */ + key2 := "joe's_movies" + expected = 3 + got, err = redisPoolObj_set.SAdd(key2, "hi, lady", "Fast Five", "2012") + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d.", expected, got) + return + } + + deleteKeys = append(deleteKeys, key2) + + expected4 = []string{"hi, lady", "Fast Five", "2012"} + got4_interface, err = redisPoolObj_set.SMembers(key2) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SDIFFSTORE joe_diff_peter joe's_movies peter's_movies + (integer) 2 + */ + destination := "joe_diff_peter" + expected2 := 2 + got2, err := redisPoolObj_set.SDiffStore(destination, key1, key2) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but now got %d.", expected2, got2) + return + } + + deleteKeys = append(deleteKeys, destination) + + /* + redis> SMEMBERS joe_diff_peter + 1) "bet man" + 2) "start war" + */ + expected4 = []string{"bet man", "start war"} + got4_interface, err = redisPoolObj_set.SMembers(destination) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } + + /* + redis> SDIFFSTORE peter_diff_joe peter's_movies, joe's_movies + (integer) 2 + */ + destination = "peter_diff_joe" + expected2 = 2 + got2, err = redisPoolObj_set.SDiffStore(destination, key2, key1) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but now got %d.", expected2, got2) + return + } + + deleteKeys = append(deleteKeys, destination) + + /* + redis> SMEMBERS joe_diff_peter + 1) "hi, lady" + 2) "Fast Five" + */ + expected4 = []string{"hi, lady", "Fast Five"} + got4_interface, err = redisPoolObj_set.SMembers(destination) + if err != nil { + t.Fail() + } + got4, err = redisPoolObj_set.Strings(got4_interface) + if err != nil { + t.Fail() + } + if isTwoUnorderedSliceEqual(expected4, got4) == false { + t.Errorf("Expected to get %v, but got %v\n", expected4, got4) + return + } +} diff --git a/trunk/goutil/redisUtil/redisPool_string.go b/trunk/goutil/redisUtil/redisPool_string.go new file mode 100644 index 0000000..bc6596a --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_string.go @@ -0,0 +1,420 @@ +package redisUtil + +import ( + "github.com/gomodule/redigo/redis" +) + +/* +SET key value [EX seconds] [PX milliseconds] [NX|XX] +可用版本: >= 1.0.0 +时间复杂度: O(1) +将字符串值 value 关联到 key 。 + +如果 key 已经持有其他值, SET 就覆写旧值, 无视类型。 + +当 SET 命令对一个带有生存时间(TTL)的键进行设置之后, 该键原有的 TTL 将被清除。 + +可选参数 +从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改: + +EX seconds : 将键的过期时间设置为 seconds 秒。 执行 SET key value EX seconds 的效果等同于执行 SETEX key seconds value 。 + +PX milliseconds : 将键的过期时间设置为 milliseconds 毫秒。 执行 SET key value PX milliseconds 的效果等同于执行 PSETEX key milliseconds value 。 + +NX : 只在键不存在时, 才对键进行设置操作。 执行 SET key value NX 的效果等同于执行 SETNX key value 。 + +XX : 只在键已经存在时, 才对键进行设置操作。 + +Note + +因为 SET 命令可以通过参数来实现 SETNX 、 SETEX 以及 PSETEX 命令的效果, 所以 Redis 将来的版本可能会移除并废弃 SETNX 、 SETEX 和 PSETEX 这三个命令。 + +返回值 +在 Redis 2.6.12 版本以前, SET 命令总是返回 OK 。 + +从 Redis 2.6.12 版本开始, SET 命令只在设置操作成功完成时才返回 OK ; 如果命令使用了 NX 或者 XX 选项, 但是因为条件没达到而造成设置操作未执行, 那么命令将返回空批量回复(NULL Bulk Reply)。 +*/ +/* +expireType: "EX"|"PX"|""(参照上面的说明) +expireTime: seconds|milliseconds|无(根据expireType的不同而不同,参照上面的说明) +setType: "NX"|"XX"|""(参照上面的说明) +*/ +/* +返回值: +successful: 是否成功 +err: 错误对象 +*/ +func (this *RedisPool) Set(key string, value interface{}, expireType string, expireTime int, setType string) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + args := redis.Args{}.Add(key).Add(value) + if expireType != "" { + args = args.Add(expireType).Add(expireTime) + } + if setType != "" { + args = args.Add(setType) + } + + reply, err := conn.Do("SET", args...) + if err != nil { + return + } + if reply == nil { + return + } + + successful = true + return +} + +/* +GET key +可用版本: >= 1.0.0 +时间复杂度: O(1) +返回与键 key 相关联的字符串值。 + +返回值 +如果键 key 不存在, 那么返回特殊值 nil ; 否则, 返回键 key 的值。 + +如果键 key 的值并非字符串类型, 那么返回一个错误, 因为 GET 命令只能用于字符串值。 +*/ +func (this *RedisPool) Get(key string) (reply interface{}, exist bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + reply, err = conn.Do("GET", key) + if err != nil { + return + } + if reply == nil { + return + } + + exist = true + return +} + +/* +GETSET key value +可用版本: >= 1.0.0 +时间复杂度: O(1) +将键 key 的值设为 value , 并返回键 key 在被设置之前的旧值。 + +返回值 +返回给定键 key 的旧值。 + +如果键 key 没有旧值, 也即是说, 键 key 在被设置之前并不存在, 那么命令返回 nil 。 + +当键 key 存在但不是字符串类型时, 命令返回一个错误。 +*/ +func (this *RedisPool) GetSet(key, value interface{}) (oldValue interface{}, exist bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + oldValue, err = conn.Do("GetSet", key, value) + if err != nil { + return + } + if oldValue == nil { + return + } + + exist = true + return +} + +/* +STRLEN key +可用版本: >= 2.2.0 +复杂度: O(1) +返回键 key 储存的字符串值的长度。 + +返回值 +STRLEN 命令返回字符串值的长度。 + +当键 key 不存在时, 命令返回 0 。 + +当 key 储存的不是字符串值时, 返回一个错误。 +*/ +func (this *RedisPool) StrLen(key string) (length int, err error) { + conn := this.GetConnection() + defer conn.Close() + + length, err = redis.Int(conn.Do("STRLEN", key)) + return +} + +/* +APPEND key value +可用版本: >= 2.0.0 +时间复杂度: 平摊O(1) +如果键 key 已经存在并且它的值是一个字符串, APPEND 命令将把 value 追加到键 key 现有值的末尾。 + +如果 key 不存在, APPEND 就简单地将键 key 的值设为 value , 就像执行 SET key value 一样。 + +返回值 +追加 value 之后, 键 key 的值的长度。 +*/ +func (this *RedisPool) Append(key, value string) (length int, err error) { + conn := this.GetConnection() + defer conn.Close() + + length, err = redis.Int(conn.Do("APPEND", key, value)) + return +} + +/* +SETRANGE key offset value +可用版本: >= 2.2.0 +时间复杂度:对于长度较短的字符串,命令的平摊复杂度O(1);对于长度较大的字符串,命令的复杂度为 O(M) ,其中 M 为 value 的长度。 +从偏移量 offset 开始, 用 value 参数覆写(overwrite)键 key 储存的字符串值。 + +不存在的键 key 当作空白字符串处理。 + +SETRANGE 命令会确保字符串足够长以便将 value 设置到指定的偏移量上, 如果键 key 原来储存的字符串长度比偏移量小(比如字符串只有 5 个字符长,但你设置的 offset 是 10 ), 那么原字符和偏移量之间的空白将用零字节(zerobytes, "\x00" )进行填充。 + +因为 Redis 字符串的大小被限制在 512 兆(megabytes)以内, 所以用户能够使用的最大偏移量为 2^29-1(536870911) , 如果你需要使用比这更大的空间, 请使用多个 key 。 + +Warning + +当生成一个很长的字符串时, Redis 需要分配内存空间, 该操作有时候可能会造成服务器阻塞(block)。 在2010年出产的Macbook Pro上, 设置偏移量为 536870911(512MB 内存分配)将耗费约 300 毫秒, 设置偏移量为 134217728(128MB 内存分配)将耗费约 80 毫秒, 设置偏移量 33554432(32MB 内存分配)将耗费约 30 毫秒, 设置偏移量为 8388608(8MB 内存分配)将耗费约 8 毫秒。 + +返回值 +SETRANGE 命令会返回被修改之后, 字符串值的长度。 +*/ +func (this *RedisPool) SetRange(key string, offset int, value string) (length int, err error) { + conn := this.GetConnection() + defer conn.Close() + + length, err = redis.Int(conn.Do("SETRANGE", key, offset, value)) + return +} + +/* +GETRANGE key start end +可用版本: >= 2.4.0 +时间复杂度: O(N),其中 N 为被返回的字符串的长度。 +返回键 key 储存的字符串值的指定部分, 字符串的截取范围由 start 和 end 两个偏移量决定 (包括 start 和 end 在内)。 + +负数偏移量表示从字符串的末尾开始计数, -1 表示最后一个字符, -2 表示倒数第二个字符, 以此类推。 + +GETRANGE 通过保证子字符串的值域(range)不超过实际字符串的值域来处理超出范围的值域请求。 + +Note + +GETRANGE 命令在 Redis 2.0 之前的版本里面被称为 SUBSTR 命令。 + +返回值 +GETRANGE 命令会返回字符串值的指定部分。 +*/ +func (this *RedisPool) GetRange(key string, start, end int) (value string, err error) { + conn := this.GetConnection() + defer conn.Close() + + value, err = redis.String(conn.Do("GETRANGE", key, start, end)) + return +} + +/* +INCR key +可用版本: >= 1.0.0 +时间复杂度: O(1) +为键 key 储存的数字值加上一。 + +如果键 key 不存在, 那么它的值会先被初始化为 0 , 然后再执行 INCR 命令。 + +如果键 key 储存的值不能被解释为数字, 那么 INCR 命令将返回一个错误。 + +本操作的值限制在 64 位(bit)有符号数字表示之内。 + +Note + +INCR 命令是一个针对字符串的操作。 因为 Redis 并没有专用的整数类型, 所以键 key 储存的值在执行 INCR 命令时会被解释为十进制 64 位有符号整数。 + +返回值 +INCR 命令会返回键 key 在执行加一操作之后的值。 +*/ +func (this *RedisPool) Incr(key string) (newValue int64, err error) { + conn := this.GetConnection() + defer conn.Close() + + newValue, err = redis.Int64(conn.Do("INCR", key)) + return +} + +/* +INCRBY key increment +可用版本: >= 1.0.0 +时间复杂度: O(1) +为键 key 储存的数字值加上增量 increment 。 + +如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 INCRBY 命令。 + +如果键 key 储存的值不能被解释为数字, 那么 INCRBY 命令将返回一个错误。 + +本操作的值限制在 64 位(bit)有符号数字表示之内。 + +关于递增(increment) / 递减(decrement)操作的更多信息, 请参见 INCR 命令的文档。 + +返回值 +在加上增量 increment 之后, 键 key 当前的值。 +*/ +func (this *RedisPool) IncrBy(key string, increment int64) (newValue int64, err error) { + conn := this.GetConnection() + defer conn.Close() + + newValue, err = redis.Int64(conn.Do("INCRBY", key, increment)) + return +} + +/* +INCRBYFLOAT key increment +可用版本: >= 2.6.0 +时间复杂度: O(1) +为键 key 储存的值加上浮点数增量 increment 。 + +如果键 key 不存在, 那么 INCRBYFLOAT 会先将键 key 的值设为 0 , 然后再执行加法操作。 + +如果命令执行成功, 那么键 key 的值会被更新为执行加法计算之后的新值, 并且新值会以字符串的形式返回给调用者。 + +无论是键 key 的值还是增量 increment , 都可以使用像 2.0e7 、 3e5 、 90e-2 那样的指数符号(exponential notation)来表示, 但是, 执行 INCRBYFLOAT 命令之后的值总是以同样的形式储存, 也即是, 它们总是由一个数字, 一个(可选的)小数点和一个任意长度的小数部分组成(比如 3.14 、 69.768 ,诸如此类), 小数部分尾随的 0 会被移除, 如果可能的话, 命令还会将浮点数转换为整数(比如 3.0 会被保存成 3 )。 + +此外, 无论加法计算所得的浮点数的实际精度有多长, INCRBYFLOAT 命令的计算结果最多只保留小数点的后十七位。 + +当以下任意一个条件发生时, 命令返回一个错误: + +键 key 的值不是字符串类型(因为 Redis 中的数字和浮点数都以字符串的形式保存,所以它们都属于字符串类型); + +键 key 当前的值或者给定的增量 increment 不能被解释(parse)为双精度浮点数。 + +返回值 +在加上增量 increment 之后, 键 key 的值。 +*/ +func (this *RedisPool) IncrByFloat(key string, increment float64) (newValue float64, err error) { + conn := this.GetConnection() + defer conn.Close() + + newValue, err = redis.Float64(conn.Do("INCRBYFLOAT", key, increment)) + return +} + +/* +DECR key +可用版本: >= 1.0.0 +时间复杂度: O(1) +为键 key 储存的数字值减去一。 + +如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 DECR 操作。 + +如果键 key 储存的值不能被解释为数字, 那么 DECR 命令将返回一个错误。 + +本操作的值限制在 64 位(bit)有符号数字表示之内。 + +关于递增(increment) / 递减(decrement)操作的更多信息, 请参见 INCR 命令的文档。 + +返回值 +DECR 命令会返回键 key 在执行减一操作之后的值。 +*/ +func (this *RedisPool) Decr(key string) (newValue int64, err error) { + conn := this.GetConnection() + defer conn.Close() + + newValue, err = redis.Int64(conn.Do("DECR", key)) + return +} + +/* +DECRBY key decrement +可用版本: >= 1.0.0 +时间复杂度: O(1) +将键 key 储存的整数值减去减量 decrement 。 + +如果键 key 不存在, 那么键 key 的值会先被初始化为 0 , 然后再执行 DECRBY 命令。 + +如果键 key 储存的值不能被解释为数字, 那么 DECRBY 命令将返回一个错误。 + +本操作的值限制在 64 位(bit)有符号数字表示之内。 + +关于更多递增(increment) / 递减(decrement)操作的更多信息, 请参见 INCR 命令的文档。 + +返回值 +DECRBY 命令会返回键在执行减法操作之后的值。 +*/ +func (this *RedisPool) DecrBy(key string, decrement int64) (newValue int64, err error) { + conn := this.GetConnection() + defer conn.Close() + + newValue, err = redis.Int64(conn.Do("DECRBY", key, decrement)) + return +} + +/* +MSET key value [key value …] +可用版本: >= 1.0.1 +时间复杂度: O(N),其中 N 为被设置的键数量。 +同时为多个键设置值。 + +如果某个给定键已经存在, 那么 MSET 将使用新值去覆盖旧值, 如果这不是你所希望的效果, 请考虑使用 MSETNX 命令, 这个命令只会在所有给定键都不存在的情况下进行设置。 + +MSET 是一个原子性(atomic)操作, 所有给定键都会在同一时间内被设置, 不会出现某些键被设置了但是另一些键没有被设置的情况。 + +返回值 +MSET 命令总是返回 OK 。 +*/ +func (this *RedisPool) MSet(key_value_map map[string]interface{}) (err error) { + conn := this.GetConnection() + defer conn.Close() + + _, err = conn.Do("MSET", redis.Args{}.AddFlat(key_value_map)...) + return +} + +/* +MSETNX key value [key value …] +可用版本: >= 1.0.1 +时间复杂度: O(N), 其中 N 为被设置的键数量。 +当且仅当所有给定键都不存在时, 为所有给定键设置值。 + +即使只有一个给定键已经存在, MSETNX 命令也会拒绝执行对所有键的设置操作。 + +MSETNX 是一个原子性(atomic)操作, 所有给定键要么就全部都被设置, 要么就全部都不设置, 不可能出现第三种状态。 + +返回值 +当所有给定键都设置成功时, 命令返回 1 ; 如果因为某个给定键已经存在而导致设置未能成功执行, 那么命令返回 0 。 +*/ +func (this *RedisPool) MSetNX(key_value_map map[string]interface{}) (successful bool, err error) { + conn := this.GetConnection() + defer conn.Close() + + var count int + count, err = redis.Int(conn.Do("MSETNX", redis.Args{}.AddFlat(key_value_map)...)) + if err != nil { + return + } + if count == 1 { + successful = true + } + + return +} + +/* +MGET key [key …] +可用版本: >= 1.0.0 +时间复杂度: O(N) ,其中 N 为给定键的数量。 +返回给定的一个或多个字符串键的值。 + +如果给定的字符串键里面, 有某个键不存在, 那么这个键的值将以特殊值 nil 表示。 + +返回值 +MGET 命令将返回一个列表, 列表中包含了所有给定键的值。 +*/ +func (this *RedisPool) MGet(keyList []string) (reply interface{}, err error) { + conn := this.GetConnection() + defer conn.Close() + + // valueList, err = redis.Values(conn.Do("MGET", redis.Args{}.AddFlat(keyList)...)) + reply, err = conn.Do("MGET", redis.Args{}.AddFlat(keyList)...) + return +} diff --git a/trunk/goutil/redisUtil/redisPool_string_test.go b/trunk/goutil/redisUtil/redisPool_string_test.go new file mode 100644 index 0000000..94ae227 --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_string_test.go @@ -0,0 +1,1733 @@ +package redisUtil + +import ( + "testing" + "time" +) + +var ( + redisPoolObj_string *RedisPool +) + +func init() { + redisPoolObj_string = NewRedisPool("testPool", "10.1.0.21:6379", "redis_pwd", 5, 500, 200, 10*time.Second, 5*time.Second) +} + +func TestSet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 对不存在的键进行设置: + + redis> SET key "value" + OK + */ + + key := "key" + value := "value" + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET key + "value" + */ + got_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err := redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value { + t.Errorf("Expected to get %s, but now got %s.", value, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + 对已存在的键进行设置: + + redis> SET key "new-value" + OK + */ + value = "new-value" + successful, err = redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET key + "new-value" + */ + got_interface, exist, err = redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err = redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value { + t.Errorf("Expected to get %s, but now got %s.", value, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + 使用 EX 选项: + + redis> SET key-with-expire-time "hello" EX 10086 + OK + */ + key = "key-with-expire-time" + value = "hello" + successful, err = redisPoolObj_string.Set(key, value, "EX", 10086, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET key-with-expire-time + "hello" + */ + got_interface, exist, err = redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", "key-with-expire-time") + return + } + got, err = redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value { + t.Errorf("Expected to get %s, but now got %s.", value, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> TTL key-with-expire-time + (integer) 10069 + */ + ttl, exist, _, err := redisPoolObj_string.TTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't", key) + return + } + if ttl < 10086-5 { + t.Errorf("The TTL of key:%s is wrong. Now it's %d", key, ttl) + return + } + deleteKeys = append(deleteKeys, key) + + /* + 使用 PX 选项: + + redis> SET key-with-pexpire-time "moto" PX 123321 + OK + */ + key = "key-with-pexpire-time" + value = "moto" + successful, err = redisPoolObj_string.Set(key, value, "PX", 123321, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET key-with-pexpire-time + "moto" + */ + got_interface, exist, err = redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err = redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value { + t.Errorf("Expected to get %s, but now got %s.", value, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> PTTL key-with-pexpire-time + (integer) 111939 + */ + ttl, exist, _, err = redisPoolObj_string.PTTL(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't", key) + return + } + if ttl < 123321-5000 { + t.Errorf("The TTL of key:%s is wrong. Now it's %d", key, ttl) + return + } + deleteKeys = append(deleteKeys, key) + + /* + 使用 NX 选项: + + redis> SET not-exists-key "value" NX + OK # 键不存在,设置成功 + */ + key = "not-exists-key" + value = "value" + successful, err = redisPoolObj_string.Set(key, value, "", 0, "NX") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET not-exists-key + "value" + */ + got_interface, exist, err = redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err = redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value { + t.Errorf("Expected to get %s, but now got %s.", value, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> SET not-exists-key "new-value" NX + (nil) # 键已经存在,设置失败 + + */ + newValue := "new-value" + successful, err = redisPoolObj_string.Set(key, newValue, "", 0, "NX") + if err != nil { + t.Fail() + } + if successful { + t.Errorf("Seting key:%s should fail, but now it doesn't.", key) + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GEt not-exists-key + "value" # 维持原值不变 + */ + got_interface, exist, err = redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err = redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value { + t.Errorf("Expected to get %s, but now got %s.", value, got) + return + } + + /* + 使用 XX 选项: + + redis> EXISTS exists-key + (integer) 0 + */ + key = "exists-key" + value = "value" + exist, err = redisPoolObj_string.Exists(key) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("Key:%s should not exist, but now it does exist", key) + } + deleteKeys = append(deleteKeys, key) + + /* + redis> SET exists-key "value" XX + (nil) # 因为键不存在,设置失败 + */ + successful, err = redisPoolObj_string.Set(key, value, "", 0, "XX") + if err != nil { + t.Fail() + } + if successful { + t.Errorf("Seting key:%s should fail, but now it doesn't.", key) + } + deleteKeys = append(deleteKeys, key) + + /* + redis> SET exists-key "value" + OK # 先给键设置一个值 + */ + successful, err = redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Seting key:%s should succeed, but now it doesn't.", key) + } + deleteKeys = append(deleteKeys, key) + + /* + redis> SET exists-key "new-value" XX + OK # 设置新值成功 + */ + value = "new-value" + successful, err = redisPoolObj_string.Set(key, value, "", 0, "XX") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Seting key:%s should succeed, but now it doesn't.", key) + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET exists-key + "new-value" + */ + got_interface, exist, err = redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err = redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value { + t.Errorf("Expected to get %s, but now got %s.", value, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> SET int-key 5 + OK # 设置新值成功 + */ + key2 := "int-key" + value2 := 5 + successful, err = redisPoolObj_string.Set(key2, value2, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Seting key:%s should succeed, but now it doesn't.", key) + } + deleteKeys = append(deleteKeys, key2) + + /* + redis> GET int-key + 5 + */ + got_interface, exist, err = redisPoolObj_string.Get(key2) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got2, err := redisPoolObj_string.Int(got_interface) + if err != nil { + t.Fail() + } + if got2 != value2 { + t.Errorf("Expected to get %d, but now got %d.", value2, got2) + return + } +} + +func TestGet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 对不存在的键 key 或是字符串类型的键 key 执行 GET 命令: + + redis> GET db + (nil) + */ + key := "db" + _, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s should not exist, but now it does.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> SET db redis + OK + */ + value := "redis" + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET db + "redis" + */ + got_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err := redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value { + t.Errorf("Expected to get %s, but now got %s.", value, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + 对不是字符串类型的键 key 执行 GET 命令: + + redis> DEL db + (integer) 1 + */ + count, err := redisPoolObj_string.Del(key) + if err != nil { + t.Fail() + } + if count != 1 { + t.Errorf("Expected to get 1, but now got %d", count) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> LPUSH db redis mongodb mysql + (integer) 3 + */ + _, err = redisPoolObj_string.LPush(key, "redis", "mongodb", "mysql") + if err != nil { + t.Fail() + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET db + (error) ERR Operation against a key holding the wrong kind of value + */ + _, exist, err = redisPoolObj_string.Get(key) + if err == nil { + t.Fail() + } + deleteKeys = append(deleteKeys, key) +} + +func TestGetSet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + redis> GETSET db mongodb # 没有旧值,返回 nil + (nil) + */ + key := "db" + value1 := "mongodb" + got, exist, err := redisPoolObj_string.GetSet(key, value1) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("There should be no old value, but now there is.") + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET db + "mongodb" + */ + got_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err = redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value1 { + t.Errorf("Expected to get %s, but now got %s.", value1, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GETSET db redis # 返回旧值 mongodb + "mongodb" + */ + value2 := "redis" + got_interface, exist, err = redisPoolObj_string.GetSet(key, value2) + if err != nil { + t.Fail() + } + got, err = redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value1 { + t.Errorf("Expected to get %s, but now got %s.", value1, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET db + "redis" + */ + got_interface, exist, err = redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err = redisPoolObj_string.String(got_interface) + if err != nil { + t.Fail() + } + if got != value2 { + t.Errorf("Expected to get %s, but now got %s.", value2, got) + return + } + deleteKeys = append(deleteKeys, key) +} + +func TestStrLen(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 获取字符串值的长度: + + redis> SET mykey "Hello world" + OK + */ + key := "mykey" + value := "Hello world" + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> STRLEN mykey + (integer) 11 + 不存在的键的长度为 0 : + */ + expectedLength := 11 + got, err := redisPoolObj_string.StrLen(key) + if err != nil { + t.Fail() + } + if got != expectedLength { + t.Errorf("Expected length %d, but got %d", expectedLength, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> STRLEN nonexisting + (integer) 0 + */ + key = "nonexisting" + expectedLength = 0 + got, err = redisPoolObj_string.StrLen(key) + if err != nil { + t.Fail() + } + if got != expectedLength { + t.Errorf("Expected length %d, but got %d", expectedLength, got) + return + } +} + +func TestAppend(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 示例代码 + 对不存在的 key 执行 APPEND : + + redis> EXISTS myphone # 确保 myphone 不存在 + (integer) 0 + */ + key := "myphone" + value := "value" + exist, err := redisPoolObj_string.Exists(key) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("Key:%s should not exist, but now it does exist", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> APPEND myphone "nokia" # 对不存在的 key 进行 APPEND ,等同于 SET myphone "nokia" + (integer) 5 # 字符长度 + 对已存在的字符串进行 APPEND : + */ + value = "nokia" + expected := 5 + got, err := redisPoolObj_string.Append(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> APPEND myphone " - 1110" # 长度从 5 个字符增加到 12 个字符 + (integer) 12 + */ + value = " - 1110" + expected = 12 + got, err = redisPoolObj_string.Append(key, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET myphone + "nokia - 1110" + */ + expected2 := "nokia - 1110" + got2_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("Key:%s should exist, but now it doesn't exist", key) + return + } + got2, err := redisPoolObj_string.String(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %s, but now got %s", expected2, got2) + return + } + deleteKeys = append(deleteKeys, key) +} + +func TestSetRange(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 对非空字符串执行 SETRANGE 命令: + + redis> SET greeting "hello world" + OK + */ + key := "greeting" + value := "hello world" + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> SETRANGE greeting 6 "Redis" + (integer) 11 + */ + offset := 6 + value = "Redis" + expected := 11 + got, err := redisPoolObj_string.SetRange(key, offset, value) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET greeting + "hello Redis" + */ + expected2 := "hello Redis" + got2_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got2, err := redisPoolObj_string.String(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %s, but now got %s", expected2, got2) + return + } + deleteKeys = append(deleteKeys, key) + + /* + 对空字符串/不存在的键执行 SETRANGE 命令: + + redis> EXISTS empty_string + (integer) 0 + + redis> SETRANGE empty_string 5 "Redis!" # 对不存在的 key 使用 SETRANGE + (integer) 11 + + redis> GET empty_string # 空白处被"\x00"填充 + "\x00\x00\x00\x00\x00Redis!" + */ + key = "empty_string" + _, exist, err = redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s should not exist, but now it does.", key) + return + } + deleteKeys = append(deleteKeys, key) + + offset = 5 + value = "Redis!" + expected3 := 11 + got3, err := redisPoolObj_string.SetRange(key, offset, value) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %d, but now got %d", expected3, got3) + return + } + deleteKeys = append(deleteKeys, key) + + expected4 := "\x00\x00\x00\x00\x00Redis!" + got4_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got4, err := redisPoolObj_string.String(got4_interface) + if err != nil { + t.Fail() + } + if got4 != expected4 { + t.Errorf("Expected to get %s, but now got %s", expected4, got4) + return + } + deleteKeys = append(deleteKeys, key) +} + +func TestGetRange(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + redis> SET greeting "hello, my friend" + OK + */ + key := "greeting" + value := "hello, my friend" + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GETRANGE greeting 0 4 # 返回索引0-4的字符,包括4。 + "hello" + */ + start, end := 0, 4 + expected := "hello" + got, err := redisPoolObj_string.GetRange(key, start, end) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %s, but now got %s", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GETRANGE greeting -1 -5 # 不支持回绕操作 + "" + */ + start, end = -1, -5 + expected = "" + got, err = redisPoolObj_string.GetRange(key, start, end) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %s, but now got %s", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GETRANGE greeting -3 -1 # 负数索引 + "end" + */ + start, end = -3, -1 + expected = "end" + got, err = redisPoolObj_string.GetRange(key, start, end) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %s, but now got %s", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GETRANGE greeting 0 -1 # 从第一个到最后一个 + "hello, my friend" + */ + start, end = 0, -1 + expected = "hello, my friend" + got, err = redisPoolObj_string.GetRange(key, start, end) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %s, but now got %s", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GETRANGE greeting 0 1008611 # 值域范围不超过实际字符串,超过部分自动被符略 + "hello, my friend" + */ + start, end = 0, 1008611 + expected = "hello, my friend" + got, err = redisPoolObj_string.GetRange(key, start, end) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %s, but now got %s", expected, got) + return + } + deleteKeys = append(deleteKeys, key) +} + +func TestIncr(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + redis> SET page_view 20 + OK + */ + key := "page_view" + value := "20" + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> INCR page_view + (integer) 21 + */ + expected := int64(21) + got, err := redisPoolObj_string.Incr(key) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET page_view # 数字值在 Redis 中以字符串的形式保存 + "21" + */ + expected2 := 21 + got2_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got2, err := redisPoolObj_string.Int(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but now got %d", expected2, got2) + return + } + deleteKeys = append(deleteKeys, key) +} + +func TestIncrBy(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 键存在,并且值为数字: + + redis> SET rank 50 + OK + + redis> INCRBY rank 20 + (integer) 70 + + redis> GET rank + "70" + */ + key := "rank" + value := "50" + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + increment := int64(20) + expected := int64(70) + got, err := redisPoolObj_string.IncrBy(key, increment) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + expected2 := 70 + got2_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got2, err := redisPoolObj_string.Int(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %d, but now got %d", expected2, got2) + return + } + deleteKeys = append(deleteKeys, key) + + /* + 键不存在: + + redis> EXISTS counter + (integer) 0 + */ + key = "counter" + _, exist, err = redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s should not exist, but now it does.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> INCRBY counter 30 + (integer) 30 + */ + increment = int64(30) + expected3 := int64(30) + got3, err := redisPoolObj_string.IncrBy(key, 30) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %d, but now got %d", expected3, got3) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET counter + "30" + */ + expected4 := 30 + got4_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got4, err := redisPoolObj_string.Int(got4_interface) + if err != nil { + t.Fail() + } + if got4 != expected4 { + t.Errorf("Expected to get %d, but now got %d", expected4, got4) + return + } + deleteKeys = append(deleteKeys, key) + + /* + 键存在,但值无法被解释为数字: + + redis> SET book "long long ago..." + OK + + redis> INCRBY book 200 + (error) ERR value is not an integer or out of range + */ + // This feature can't be tested, because the IncrBy function needs an int64 parameter. +} + +func TestIncrByFloat(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + redis> GET decimal + "3.0" + */ + key := "decimal" + value := "3.0" + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + expected := 3.0 + got_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got, err := redisPoolObj_string.Float64(got_interface) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %f, but now got %f", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> INCRBYFLOAT decimal 2.56 + "5.56" + */ + increment := 2.56 + expected2 := 5.56 + got2, err := redisPoolObj_string.IncrByFloat(key, increment) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %f, but now got %f", expected2, got2) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> GET decimal + "5.56" + */ + expected3 := 5.56 + got3_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got3, err := redisPoolObj_string.Float64(got3_interface) + if err != nil { + t.Fail() + } + if got3 != expected3 { + t.Errorf("Expected to get %f, but now got %f", expected3, got3) + return + } + deleteKeys = append(deleteKeys, key) +} + +func TestDecrBy(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 对已经存在的键执行 DECRBY 命令: + + redis> SET count 100 + OK + */ + key := "count" + value := 100 + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> DECRBY count 20 + (integer) 80 + 对不存在的键执行 DECRBY 命令: + */ + expected := int64(80) + decrement := int64(20) + got_interface, err := redisPoolObj_string.DecrBy(key, decrement) + if err != nil { + t.Fail() + } + got, err := redisPoolObj_string.Int64(got_interface) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> EXISTS pages + (integer) 0 + */ + key = "pages" + exist, err := redisPoolObj_string.Exists(key) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s should not exist, but now it does", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> DECRBY pages 10 + (integer) -10 + */ + expected = int64(-10) + decrement = int64(10) + got, err = redisPoolObj_string.DecrBy(key, decrement) + if err != nil { + t.Fail() + } + if got != expected { + t.Errorf("Expected to get %d, but now got %d", expected, got) + return + } + deleteKeys = append(deleteKeys, key) +} + +func TestMSet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 同时对多个键进行设置: + + redis> MSET date "2012.3.30" time "11:00 a.m." weather "sunny" + OK + */ + key_value_map := make(map[string]interface{}) + key_value_map["date"] = "2012.3.30" + key_value_map["time"] = "11:00 a.m." + key_value_map["weather"] = "sunny" + err := redisPoolObj_string.MSet(key_value_map) + if err != nil { + t.Fail() + } + + /* + redis> MGET date time weather + 1) "2012.3.30" + 2) "11:00 a.m." + 3) "sunny" + */ + keyList := make([]string, 0, len(key_value_map)) + expected := make([]string, 0, len(key_value_map)) + for k, v := range key_value_map { + keyList = append(keyList, k) + if v_str, ok := v.(string); ok { + expected = append(expected, v_str) + } + } + got_interface, err := redisPoolObj_string.MGet(keyList) + if err != nil { + t.Fail() + } + got, err := redisPoolObj_string.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(got, expected) == false { + t.Errorf("Expected to get %v, but got %v", expected, got) + return + } + deleteKeys = append(deleteKeys, keyList...) + + /* + 覆盖已有的值: + + redis> MGET k1 k2 + 1) "hello" + 2) "world" + */ + key_value_map = make(map[string]interface{}) + key_value_map["k1"] = "hello" + key_value_map["k2"] = "world" + err = redisPoolObj_string.MSet(key_value_map) + if err != nil { + t.Fail() + } + + /* + + redis> MSET k1 "good" k2 "bye" + OK + */ + key_value_map["k1"] = "good" + key_value_map["k2"] = "bye" + err = redisPoolObj_string.MSet(key_value_map) + if err != nil { + t.Fail() + } + + /* + redis> MGET k1 k2 + 1) "good" + 2) "bye" + */ + keyList = make([]string, 0, len(key_value_map)) + expected = make([]string, 0, len(key_value_map)) + for k, v := range key_value_map { + keyList = append(keyList, k) + if v_str, ok := v.(string); ok { + expected = append(expected, v_str) + } + } + + got_interface, err = redisPoolObj_string.MGet(keyList) + if err != nil { + t.Fail() + } + got, err = redisPoolObj_string.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(got, expected) == false { + t.Errorf("Expected to get %v, but got %v", expected, got) + return + } + deleteKeys = append(deleteKeys, keyList...) +} + +func TestMSetNX(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + 对不存在的键执行 MSETNX 命令: + + redis> MSETNX rmdbs "MySQL" nosql "MongoDB" key-value-store "redis" + (integer) 1 + */ + key_value_map := make(map[string]interface{}) + key_value_map["rmdbs"] = "MySQL" + key_value_map["nosql"] = "MongoDB" + key_value_map["key-value-store"] = "redis" + successful, err := redisPoolObj_string.MSetNX(key_value_map) + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("It should be successful, but now it's not.") + return + } + + /* + + redis> MGET rmdbs nosql key-value-store + 1) "MySQL" + 2) "MongoDB" + 3) "redis" + 对某个已经存在的键进行设置: + */ + keyList := make([]string, 0, len(key_value_map)) + expected := make([]string, 0, len(key_value_map)) + for k, v := range key_value_map { + keyList = append(keyList, k) + if v_str, ok := v.(string); ok { + expected = append(expected, v_str) + } + } + got_interface, err := redisPoolObj_string.MGet(keyList) + if err != nil { + t.Fail() + } + got, err := redisPoolObj_string.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(got, expected) == false { + t.Errorf("Expected to get %v, but got %v", expected, got) + return + } + deleteKeys = append(deleteKeys, keyList...) + + /* + redis> MSETNX rmdbs "Sqlite" language "python" # rmdbs 键已经存在,操作失败 + (integer) 0 + */ + key_value_map = make(map[string]interface{}) + key_value_map["rmdbs"] = "Sqlite" + key_value_map["language"] = "python" + successful, err = redisPoolObj_string.MSetNX(key_value_map) + if err != nil { + t.Fail() + } + if successful { + t.Errorf("It should be not successful, but now it is.") + return + } + + /* + redis> EXISTS language # 因为 MSETNX 命令没有成功执行 + (integer) 0 # 所以 language 键没有被设置 + */ + key := "language" + exist, err := redisPoolObj_string.Exists(key) + if err != nil { + t.Fail() + } + if exist { + t.Errorf("The key:%s should not exist, but now it does.", key) + return + } + + /* + redis> GET rmdbs # rmdbs 键也没有被修改 + "MySQL" + */ + key = "rmdbs" + expected2 := "MySQL" + got2_interface, exist, err := redisPoolObj_string.Get(key) + if err != nil { + t.Fail() + } + if !exist { + t.Errorf("The key:%s should exist, but now it doesn't.", key) + return + } + got2, err := redisPoolObj_string.String(got2_interface) + if err != nil { + t.Fail() + } + if got2 != expected2 { + t.Errorf("Expected to get %v, but got %v", expected2, got2) + return + } +} + +func TestMGet(t *testing.T) { + deleteKeys := make([]string, 0, 8) + defer func() { + // Delete the test keys + distinctKeyList := getDistinctKeyList(deleteKeys) + count, err := redisPoolObj_string.Del(distinctKeyList...) + if err != nil { + t.Fail() + } + if count != len(distinctKeyList) { + t.Errorf("Expected to get %d, but now got %d", len(distinctKeyList), count) + return + } + }() + + /* + 代码示例 + redis> SET redis redis.com + OK + */ + key := "redis" + value := "redis.com" + successful, err := redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> SET mongodb mongodb.org + OK + */ + key = "mongodb" + value = "mongodb.org" + successful, err = redisPoolObj_string.Set(key, value, "", 0, "") + if err != nil { + t.Fail() + } + if !successful { + t.Errorf("Set key:%s should be successful, but now it's not.", key) + return + } + deleteKeys = append(deleteKeys, key) + + /* + redis> MGET redis mongodb + 1) "redis.com" + 2) "mongodb.org" + */ + keys := []string{"redis", "mongodb"} + expected := []string{"redis.com", "mongodb.org"} + got_interface, err := redisPoolObj_string.MGet(keys) + if err != nil { + t.Fail() + } + got, err := redisPoolObj_string.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(got, expected) == false { + t.Errorf("Expected to get %v, but got %v", expected, got) + return + } + + /* + redis> MGET redis mongodb mysql # 不存在的 mysql 返回 nil + 1) "redis.com" + 2) "mongodb.org" + 3) (nil) + */ + keys = []string{"redis", "mongodb", "mysql"} + expected = []string{"redis.com", "mongodb.org", ""} + got_interface, err = redisPoolObj_string.MGet(keys) + if err != nil { + t.Fail() + } + got, err = redisPoolObj_string.Strings(got_interface) + if err != nil { + t.Fail() + } + if isTwoOrderedSliceEqual(got, expected) == false { + t.Errorf("Expected to get %v, but got %v", expected, got) + return + } +} diff --git a/trunk/goutil/redisUtil/redisPool_test.go b/trunk/goutil/redisUtil/redisPool_test.go new file mode 100644 index 0000000..e8dd3cd --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_test.go @@ -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 +} diff --git a/trunk/goutil/redisUtil/redisPool_type.go b/trunk/goutil/redisUtil/redisPool_type.go new file mode 100644 index 0000000..1e2afb3 --- /dev/null +++ b/trunk/goutil/redisUtil/redisPool_type.go @@ -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) +} diff --git a/trunk/goutil/routineCtrlUtil/routineCtrl.go b/trunk/goutil/routineCtrlUtil/routineCtrl.go new file mode 100644 index 0000000..8f3ebec --- /dev/null +++ b/trunk/goutil/routineCtrlUtil/routineCtrl.go @@ -0,0 +1,77 @@ +// Package routineCtrlUtil +// +// @description: 协程控制-用于海量循环中,限制并发的最大同时执行协程数 +// @author: +// @revision history: +// @create date: 2022-02-23 17:23:06 +package routineCtrlUtil + +import "sync" + +// RoutineCtrl +// +// @description: 控制同时运行的协程数 +type RoutineCtrl struct { + wg *sync.WaitGroup + chNum chan struct{} +} + +// Run +// +// @description: 循环内待执行的协程,封装在f函数参数内执行。f函数的最大并发运行数受New参数maxNum限制 +// 注意:*** 待执行函数引用外变变量,程序可能会出现不符合逻辑的结果(外部变量变化时,闭包函数获取到的是变化后的变量值) *** +// *** 闭包函数避坑说明 *** +// 方式1)将待执行函数内使用的外部变量,用参数传入,可避免不符合逻辑的结果 +// 方式2)将待执行函数内使用的外部变量,使用一个临时局部变量将其值“固定”下来,闭包内使用临时局部变量(临时局部变量不会再被修改) +// 示例:routine_ctrl_test.go -> TestRoutineCtrl +// +// parameter: +// +// @receiver rtCtrl: +// @f: 循环内待执行的协程 +// +// return: +func (rtCtrl *RoutineCtrl) Run(f func(interface{}), arg interface{}) { + rtCtrl.wg.Add(1) + rtCtrl.chNum <- struct{}{} + go func() { + defer func() { + <-rtCtrl.chNum + rtCtrl.wg.Done() + recover() // 异常捕获 + }() + + f(arg) // 调用实际函数体 + }() +} + +// Wait +// +// @description: 在循环外面等待所有协程执行结束后返回 +// +// parameter: +// +// @receiver rtCtrl: +// +// return: +func (rtCtrl *RoutineCtrl) Wait() { + rtCtrl.wg.Wait() +} + +// New +// +// @description: 产生一个协程控制对象 +// +// parameter: +// +// @maxNum: 限制调用Run时的f函数的最大运行协程数 +// +// return: +// +// @*RoutineCtrl: +func New(maxNum int) *RoutineCtrl { + return &RoutineCtrl{ + wg: &sync.WaitGroup{}, + chNum: make(chan struct{}, maxNum), + } +} diff --git a/trunk/goutil/routineCtrlUtil/routineCtrl_test.go b/trunk/goutil/routineCtrlUtil/routineCtrl_test.go new file mode 100644 index 0000000..17d587c --- /dev/null +++ b/trunk/goutil/routineCtrlUtil/routineCtrl_test.go @@ -0,0 +1,48 @@ +package routineCtrlUtil + +import ( + "fmt" + "testing" + "time" +) + +func TestRoutineCtrl(t *testing.T) { + rtCtrl := New(3) + + for i := 0; i < 10; i++ { + // 闭包函数避坑注意!!! + // 本段代码有坑,请特别注意!!! + // *** 待执行函数引用外变变量i,程序可能会出现不符合逻辑的结果(外部变量变化时,闭包函数获取到的是变化后的变量值) *** + //rtCtrl.Run(func(arg interface{}) { + // fmt.Println(".", i) + // time.Sleep(time.Second * 1) + // fmt.Println("*", i) + // panic("111") + //}, nil) + + //------------------------------------------------- + // 以下两种方式为正确示例: + + // 避坑方式一:将闭包内使用的外部变量作为参数传入 + // 将待执行函数内使用的外部变量,用参数传入,可避免不符合逻辑的结果 + //rtCtrl.Run(func(arg interface{}) { + // ii, _ := arg.(int) + // fmt.Println(".", ii) + // time.Sleep(time.Second * 1) + // fmt.Println("*", ii) + // panic("111") + //}, i) + + // 避坑方式二:将闭包内使用的外部变量固定下来,不让其再被修改 + temp := i // 这种方式将产生一个局变变量temp,并且temp不会被修改 + rtCtrl.Run(func(arg interface{}) { + fmt.Println(".", temp) + time.Sleep(time.Second * 1) + fmt.Println("*", temp) + panic("111") + }, nil) + } + + rtCtrl.Wait() + fmt.Println("\n==============") +} diff --git a/trunk/goutil/runtimeUtil/memstate.go b/trunk/goutil/runtimeUtil/memstate.go new file mode 100644 index 0000000..5a8d915 --- /dev/null +++ b/trunk/goutil/runtimeUtil/memstate.go @@ -0,0 +1,16 @@ +package runtimeUtil + +import "runtime" + +// 获取当前正在使用的内存大小,单位:字节数 +// 具体参见文档: +// 1. http://blog.csdn.net/webxscan/article/details/72857292 +// 2. https://studygolang.com/static/pkgdoc/pkg/runtime.htm#MemStats +// 返回值: +// int64:正在使用的内存大小 +func GetMemSize() uint64 { + var memStat runtime.MemStats + runtime.ReadMemStats(&memStat) + + return memStat.Alloc +} diff --git a/trunk/goutil/securityUtil/aes.go b/trunk/goutil/securityUtil/aes.go new file mode 100644 index 0000000..93eccef --- /dev/null +++ b/trunk/goutil/securityUtil/aes.go @@ -0,0 +1,99 @@ +package securityUtil + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "errors" +) + +var ( + // ErrInvalidBlockSize indicates hash blocksize <= 0. + ErrInvalidBlockSize = errors.New("invalid blocksize") + + // ErrInvalidPKCS7Data indicates bad input to PKCS7 pad or unpad. + ErrInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)") + + // ErrInvalidPKCS7Padding indicates PKCS7 unpad fails to bad input. + ErrInvalidPKCS7Padding = errors.New("invalid padding on input") +) + +// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where +// n is the block size. The size of the result is x times n, where x +// is at least 1. +func pkcs7Pad(b []byte, blocksize int) ([]byte, error) { + if b == nil || len(b) == 0 { + return nil, ErrInvalidPKCS7Data + } + if blocksize <= 0 { + return nil, ErrInvalidBlockSize + } + n := blocksize - (len(b) % blocksize) + pb := make([]byte, len(b)+n) + copy(pb, b) + copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n)) + + return pb, nil +} + +// pkcs7Unpad validates and unpads data from the given bytes slice. +// The returned value will be 1 to n bytes smaller depending on the +// amount of padding, where n is the block size. +func pkcs7Unpad(b []byte, blocksize int) ([]byte, error) { + if b == nil || len(b) == 0 { + return nil, ErrInvalidPKCS7Data + } + if blocksize <= 0 { + return nil, ErrInvalidBlockSize + } + if len(b)%blocksize != 0 { + return nil, ErrInvalidPKCS7Padding + } + + c := b[len(b)-1] + n := int(c) + if n == 0 || n > len(b) { + return nil, ErrInvalidPKCS7Padding + } + + for i := 0; i < n; i++ { + if b[len(b)-n+i] != c { + return nil, ErrInvalidPKCS7Padding + } + } + + return b[:len(b)-n], nil +} + +func AESEncrypt_CBC_Pkcs7(src []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + src, err = pkcs7Pad(src, block.BlockSize()) + if err != nil { + return nil, err + } + + blockmode := cipher.NewCBCEncrypter(block, key) + blockmode.CryptBlocks(src, src) + + return src, nil +} + +func AESDecrypt_CBC_Pkcs7(src []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + blockmode := cipher.NewCBCDecrypter(block, key) + blockmode.CryptBlocks(src, src) + src, err = pkcs7Unpad(src, block.BlockSize()) + if err != nil { + return nil, err + } + + return src, nil +} diff --git a/trunk/goutil/securityUtil/aes_test.go b/trunk/goutil/securityUtil/aes_test.go new file mode 100644 index 0000000..61eddde --- /dev/null +++ b/trunk/goutil/securityUtil/aes_test.go @@ -0,0 +1,95 @@ +package securityUtil + +import ( + "encoding/base64" + "fmt" + "testing" +) + +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)) +} + +func TestEncryptAndDecrypt(t *testing.T) { + x := []byte("6d15fbbf-4913-40f5-afd1-c02effc2373a") + key := []byte("WB6aEKK5LoGpetJv") + x1, _ := AESEncrypt_CBC_Pkcs7(x, key) + x1_base64 := Base64Encode2(x1) + x1_base64_str := string(x1_base64) + fmt.Printf("Base64 of encrypted data:%s\n", x1_base64_str) + + // x1_base64_str = "OoV781MTCRIEKBaWDn4NDuS3Iq1stwnORQA30Ip/eewTEzaNDQl/TgVQU09Bm7pcIP6GxGfzO7vKhRITgKCghpTi9/D+oz/GdKn8KjF/gmE=" + x2_init_str, _ := Base64Decode(x1_base64_str) + x2_init := []byte(x2_init_str) + fmt.Printf("x2_init:%d\n", len(x2_init)) + + x2, _ := AESDecrypt_CBC_Pkcs7(x2_init, key) + fmt.Printf("Decrypted data:%s\n", string(x2)) + if string(x) != string(x2) { + t.Errorf("Expected %s, but got %s", string(x), string(x2)) + } +} + +func TestDecrypt(t *testing.T) { + key := []byte("WB6aEKK5LoGpetJv") + x2_init_str, _ := Base64Decode("Oit72+aWraykW7i0e/q+zZ77w5yEU/5KuNpRYoJaxpw93i6zWKiFB8c6/PIedZxz") + x2_init := []byte(x2_init_str) + x2, _ := AESDecrypt_CBC_Pkcs7(x2_init, key) + fmt.Printf("Decrypted data:%s\n", string(x2)) + + x2_init_str, _ = Base64Decode("r0KHpfSmQ8jmx/FR4IJPOGBLTYF9lDRWbo9P8lIwekjkkU8BOO0QvfypgHZRIJWS") + x2_init = []byte(x2_init_str) + x2, _ = AESDecrypt_CBC_Pkcs7(x2_init, key) + fmt.Printf("Decrypted data:%s\n", string(x2)) + + x2_init_str, _ = Base64Decode("ev2P8zTEnebpBm43Dd8YlA==") + x2_init = []byte(x2_init_str) + x2, _ = AESDecrypt_CBC_Pkcs7(x2_init, key) + fmt.Printf("Decrypted data:%s\n", string(x2)) +} diff --git a/trunk/goutil/securityUtil/doc.go b/trunk/goutil/securityUtil/doc.go new file mode 100644 index 0000000..bd52877 --- /dev/null +++ b/trunk/goutil/securityUtil/doc.go @@ -0,0 +1,11 @@ +/* +这个包包含安全方面的util对象和方法,例如md5,sha1,rsa等。 +使用时需要先import "goutil.security"。 + +当前这里面包括有2个对象md5和sha1,在使用时,可以参照如下方式 +s := "hello world" +result := Md5String(s, true) +*/ +package securityUtil + +//此文档是专为写包注释而添加的,无实际意义。因此其它文件也不用再添加包注释了 diff --git a/trunk/goutil/securityUtil/hmac.go b/trunk/goutil/securityUtil/hmac.go new file mode 100644 index 0000000..fb0742c --- /dev/null +++ b/trunk/goutil/securityUtil/hmac.go @@ -0,0 +1,63 @@ +package securityUtil + +import ( + "crypto/hmac" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" +) + +// Hmac-SHA1编码 +// source:编码原数据 +// key:编码密钥 +// 返回值:编码结果 +func HmacSha1(source, key string) (result []byte, err error) { + mac := hmac.New(sha1.New, []byte(key)) + if _, err = mac.Write([]byte(source)); err != nil { + return + } + + return mac.Sum(nil), nil +} + +// Hmac-SHA1 Base64编码 +// source:编码原数据 +// key:编码密钥 +// 返回值:编码结果 +func Base64HmacSha1(source, key string) (result string, err error) { + mac := hmac.New(sha1.New, []byte(key)) + if _, err = mac.Write([]byte(source)); err != nil { + return + } + + bytes := mac.Sum(nil) + result = base64.StdEncoding.EncodeToString(bytes) + return +} + +// Hmac-SHA256编码 +// source:编码原数据 +// key:编码密钥 +// 返回值:编码结果 +func HmacSha256(source, key string) (result []byte, err error) { + mac := hmac.New(sha256.New, []byte(key)) + if _, err = mac.Write([]byte(source)); err != nil { + return + } + + return mac.Sum(nil), nil +} + +// Hmac-SHA512编码 +// source:编码原数据 +// key:编码密钥 +// 返回值:编码结果 +func HmacSha512(source, key string) (result []byte, err error) { + mac := hmac.New(sha512.New, []byte(key)) + if _, err = mac.Write([]byte(source)); err != nil { + return + } + + return mac.Sum(nil), nil +} diff --git a/trunk/goutil/securityUtil/hmac_test.go b/trunk/goutil/securityUtil/hmac_test.go new file mode 100644 index 0000000..262b335 --- /dev/null +++ b/trunk/goutil/securityUtil/hmac_test.go @@ -0,0 +1,38 @@ +package securityUtil + +import ( + "encoding/base64" + "testing" +) + +func TestHmacSha1(t *testing.T) { + source := "oauthConsumerKey=1Nocz0wk0Hi8oGgSosogC4K4k&oauthToken=TOKEN_%2B8vQAR1eoD3ujiGstjzdyakEgbkyvWhfzF1fChQJ46EH07n%2FQvrazkMqy%2BhuprqU&oauthSignatureMethod=HMAC-SHA1&oauthTimestamp=1508486834&oauthNonce=5409983431934290948&oauthVersion=1.0&" + key := "f724054cbBF8710D2c3a2500Ec65Fa9F&" + sign := "+2zYxghf5BOqAXp/o9yax4TI56c=" + + buf, err := HmacSha1(source, key) + if err != nil { + t.Fatalf("Hmac-SHA1编码错误:%v", err) + } + + base64Result := base64.StdEncoding.EncodeToString(buf) + if base64Result != sign { + t.Fatalf("Hmac-SHA1编码结果不一致") + } +} + +func TestHmacSha256(t *testing.T) { + source := "oauthConsumerKey=1Nocz0wk0Hi8oGgSosogC4K4k&oauthToken=TOKEN_%2B8vQAR1eoD3ujiGstjzdyakEgbkyvWhfzF1fChQJ46EH07n%2FQvrazkMqy%2BhuprqU&oauthSignatureMethod=HMAC-SHA1&oauthTimestamp=1508486834&oauthNonce=5409983431934290948&oauthVersion=1.0&" + key := "f724054cbBF8710D2c3a2500Ec65Fa9F&" + sign := "6GsbOxY0ldpTqGIdIATUuJceMgCGSwcylODhXxPHZLE=" + + buf, err := HmacSha256(source, key) + if err != nil { + t.Fatalf("Hmac-SHA256编码错误:%v", err) + } + + base64Result := base64.StdEncoding.EncodeToString(buf) + if base64Result != sign { + t.Fatalf("Hmac-SHA256编码结果不一致") + } +} diff --git a/trunk/goutil/securityUtil/md5.go b/trunk/goutil/securityUtil/md5.go new file mode 100644 index 0000000..f37592d --- /dev/null +++ b/trunk/goutil/securityUtil/md5.go @@ -0,0 +1,38 @@ +package securityUtil + +import ( + "crypto/md5" + "errors" + "fmt" +) + +// 对字符串进行MD5加密,并且可以选择返回大、小写 +// s:输入字符串 +// ifUpper:输出是否大写 +// 返回值:md5加密后的字符串 +func Md5String(s string, ifUpper bool) string { + if len(s) == 0 { + panic(errors.New("input string can't be empty")) + } + + return Md5Bytes([]byte(s), ifUpper) +} + +// 对字符数组进行MD5加密,并且可以选择返回大、小写 +// b:输入字符数组 +// ifUpper:输出是否大写 +// 返回值:md5加密后的字符串 +func Md5Bytes(b []byte, ifUpper bool) string { + if len(b) == 0 { + panic(errors.New("input []byte can't be empty")) + } + + md5Instance := md5.New() + md5Instance.Write(b) + result := md5Instance.Sum([]byte("")) + if ifUpper { + return fmt.Sprintf("%X", result) + } else { + return fmt.Sprintf("%x", result) + } +} diff --git a/trunk/goutil/securityUtil/md5_test.go b/trunk/goutil/securityUtil/md5_test.go new file mode 100644 index 0000000..81b20bb --- /dev/null +++ b/trunk/goutil/securityUtil/md5_test.go @@ -0,0 +1,40 @@ +package securityUtil + +import ( + "fmt" + "testing" +) + +var ( + ExpectedUpperString = "5EB63BBBE01EEED093CB22BB8F5ACDC3" + ExpectedLowerString = "5eb63bbbe01eeed093cb22bb8f5acdc3" +) + +func TestMd5String(t *testing.T) { + s := fmt.Sprintf("&GroupId=%s&ClusterId=%s&IP=%s&ServiceName=%s&ServiceId=%s&Timestamp=%d&GroupSecret=%s", + "DSN", "DSN_AREA1", "10.255.0.226", "gs1", "4fa2b870-f8eb-4a47-a4bb-becf14f3b628", 1649212351304327500, "88F5BC56-96D3-4021-A358-BBDAFFBF772A") + //s := "hello world" + result := Md5String(s, true) + if result != ExpectedUpperString { + t.Errorf("Md5String(\"hello world\") failed.Got %s, expected %s", result, ExpectedUpperString) + } + + result = Md5String(s, false) + if result != ExpectedLowerString { + t.Errorf("Md5String(\"hello world\") failed.Got %s, expected %s", result, ExpectedLowerString) + } +} + +func TestMd5Bytes(t *testing.T) { + s := "hello world" + b := []byte(s) + result := Md5Bytes(b, true) + if result != ExpectedUpperString { + t.Errorf("Md5String(\"hello world\") failed.Got %s, expected %s", result, ExpectedUpperString) + } + + result = Md5Bytes(b, false) + if result != ExpectedLowerString { + t.Errorf("Md5String(\"hello world\") failed.Got %s, expected %s", result, ExpectedLowerString) + } +} diff --git a/trunk/goutil/securityUtil/rsa.go b/trunk/goutil/securityUtil/rsa.go new file mode 100644 index 0000000..6f82889 --- /dev/null +++ b/trunk/goutil/securityUtil/rsa.go @@ -0,0 +1,251 @@ +package securityUtil + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "strings" +) + +// RsaSha256验证签名 +// publicKey:公钥 +// source:原字符串 +// targetSign:用以验证的目标签名(会进行Base64解码) +// 返回值:返回nil为成功 +func VerifyRsaWithSha256(publicKey, source, targetSign string) error { + //未加标记的PEM密钥加上公钥标记 + if !strings.HasPrefix(publicKey, "-") { + publicKey = fmt.Sprintf(`-----BEGIN PUBLIC KEY----- +%s +-----END PUBLIC KEY-----`, publicKey) + } + + //base64解码目标签名 + signBuf, err := base64.StdEncoding.DecodeString(targetSign) + if err != nil { + return err + } + + //解码公钥 + block, _ := pem.Decode([]byte(publicKey)) + if block == nil { + return fmt.Errorf("RSA.VerifyRsaWithSha256,Block is nil,public key error!") + } + + //转化为公钥对象 + pubObj, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + + pub := pubObj.(*rsa.PublicKey) + + //验证签名 + err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, Sha256Bytes([]byte(source)), signBuf) + + return err +} + +// RsaSha1验证签名 +// publicKey:公钥 +// source:原字符串 +// targetSign:用以验证的目标签名(会进行Base64解码) +// 返回值:返回nil为成功 +func VerifyRsaWithSha1(publicKey, source, targetSign string) error { + //未加标记的PEM密钥加上公钥标记 + if !strings.HasPrefix(publicKey, "-") { + publicKey = fmt.Sprintf(`-----BEGIN PUBLIC KEY----- +%s +-----END PUBLIC KEY-----`, publicKey) + } + + //base64解码目标签名 + signBuf, err := base64.StdEncoding.DecodeString(targetSign) + if err != nil { + return err + } + + //解码公钥 + block, _ := pem.Decode([]byte(publicKey)) + if block == nil { + return fmt.Errorf("RSA.VerifyRsaWithSha1,Block is nil,public key error!") + } + + //转化为公钥对象 + pubObj, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + + pub := pubObj.(*rsa.PublicKey) + + //验证签名 + err = rsa.VerifyPKCS1v15(pub, crypto.SHA1, Sha1StringToBytes(source), signBuf) + + return err +} + +// 对字符数组进行SHA1加密 +// b:输入字符数组 +// 返回值:sha1加密后的原数据 +func Sha1StringToBytes(b string) []byte { + if len(b) == 0 { + panic(errors.New("input []byte can't be empty")) + } + + sha1Instance := sha1.New() + sha1Instance.Write([]byte(b)) + result := sha1Instance.Sum(nil) + + return result +} + +func packageData(originalData []byte, packageSize int) (r [][]byte) { + var src = make([]byte, len(originalData)) + copy(src, originalData) + + r = make([][]byte, 0) + if len(src) <= packageSize { + return append(r, src) + } + for len(src) > 0 { + var p = src[:packageSize] + r = append(r, p) + src = src[packageSize:] + if len(src) <= packageSize { + r = append(r, src) + break + } + } + return r +} + +// RSAEncrypt 数据加密 +// plaintext:待加密的数据 +// key:公钥 +// 返回值: +// []byte:加密后的数据 +// error:错误信息 +func RSAEncrypt(plaintext, key []byte) ([]byte, error) { + var err error + var block *pem.Block + block, _ = pem.Decode(key) + if block == nil { + return nil, errors.New("public key error") + } + + var pubInterface interface{} + pubInterface, err = x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + var pub = pubInterface.(*rsa.PublicKey) + + var data = packageData(plaintext, pub.N.BitLen()/8-11) + var cipherData []byte = make([]byte, 0, 0) + + for _, d := range data { + var c, e = rsa.EncryptPKCS1v15(rand.Reader, pub, d) + if e != nil { + return nil, e + } + cipherData = append(cipherData, c...) + } + + return cipherData, nil +} + +// RSADecrypt 数据解密 +// plaintext:待解密的数据 +// key:私钥 +// 返回值: +// []byte:解密后的数据 +// error:错误信息 +func RSADecrypt(ciphertext, key []byte) ([]byte, error) { + var err error + var block *pem.Block + block, _ = pem.Decode(key) + if block == nil { + return nil, errors.New("private key error") + } + + var pri *rsa.PrivateKey + pri, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + var data = packageData(ciphertext, pri.PublicKey.N.BitLen()/8) + var plainData []byte = make([]byte, 0, 0) + + for _, d := range data { + var p, e = rsa.DecryptPKCS1v15(rand.Reader, pri, d) + if e != nil { + return nil, e + } + plainData = append(plainData, p...) + } + return plainData, nil +} + +// 基于RSA PKCS1V15进行签名 +// src:待签名的原始字符串 +// key:签名用的私钥 +// hash:签名配置 +// 返回值: +// []byte:得到的签名字节流 +// error:错误信息 +func SignPKCS1v15(src, key []byte, hash crypto.Hash) ([]byte, error) { + var h = hash.New() + h.Write(src) + var hashed = h.Sum(nil) + + var err error + var block *pem.Block + block, _ = pem.Decode(key) + if block == nil { + return nil, errors.New("private key error") + } + + var pri *rsa.PrivateKey + pri, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return rsa.SignPKCS1v15(rand.Reader, pri, hash, hashed) +} + +// 验证RSA PKCS1V15进行签名 +// src:待签名的原始字符串 +// sig:签名字节流 +// key:签名用的私钥 +// hash:签名配置 +// 返回值: +// error:错误信息 +func VerifyPKCS1v15(src, sig, key []byte, hash crypto.Hash) error { + var h = hash.New() + h.Write(src) + var hashed = h.Sum(nil) + + var err error + var block *pem.Block + block, _ = pem.Decode(key) + if block == nil { + return errors.New("public key error") + } + + var pubInterface interface{} + pubInterface, err = x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + var pub = pubInterface.(*rsa.PublicKey) + + return rsa.VerifyPKCS1v15(pub, hash, hashed, sig) +} diff --git a/trunk/goutil/securityUtil/rsa_test.go b/trunk/goutil/securityUtil/rsa_test.go new file mode 100644 index 0000000..603a72d --- /dev/null +++ b/trunk/goutil/securityUtil/rsa_test.go @@ -0,0 +1,27 @@ +package securityUtil + +import ( + "testing" +) + +func TestVerifyRsaWithSha1(t *testing.T) { + publicKey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmreYIkPwVovKR8rLHWlFVw7YDfm9uQOJKL89Smt6ypXGVdrAKKl0wNYc3/jecAoPi2ylChfa2iRu5gunJyNmpWZzlCNRIau55fxGW0XEu553IiprOZcaw5OuYGlf60ga8QT6qToP0/dpiL/ZbmNUO9kUhosIjEu22uFgR+5cYyQIDAQAB" + source := "notifyId=GC201710201145455410100040000&partnerOrder=601_2055_1508471145_42087&productName=元宝&productDesc=1000元宝&price=5000&count=1&attach=601_2055_1508471145_42087" + sign := "JdzsJlVOgJ6gXTCJjWAXisyFeS0ztvB5m6WOgx9XqqdfxthLVC0gvxXdoqT1SnzzkaznebtbgvVrIeAFlyEBiVpShH76yZ9KO781wiBdJMY/BUwKkHlnMWjtFZx7pjqj6xBMoZ3HFl9j5loFYuYLMg+MDUCpvXV+Kg/wAqkkOnY=" + + err := VerifyRsaWithSha1(publicKey, source, sign) + if err != nil { + t.Fatalf("VerifyRsaWithSha1验证错误:%v", err) + } +} + +func TestVerifyRsaWithSha256(t *testing.T) { + publicKey := "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJm8eeTR8mPWuPdCFo5boenHe+Yj8zC82ohIuTeMu+4QJuRK/MI+wtJlYheKtE0s4lXzL0rw/KQzMB+KO9F/WM0CAwEAAQ==" + source := "accessMode=0&amount=6.00&bankId=HuaWei¬ifyTime=1500370048019&orderId=H20170718172707521578B0C13&orderTime=2017-07-18 17:27:07&payType=0&productName=元宝&requestId=606_2001_1500370020_19213&result=0&spending=0.00&tradeTime=2017-07-18 17:27:07&userName=900086000021763400" + sign := "d9PDjSwgItg8eTTAYbP2OHD5t+F8wgrgjMOCXHXI7pe3qCe7sixraHmLrOQfFoAvxi4e2eYxZocN4QRoqD3/zw==" + + err := VerifyRsaWithSha256(publicKey, source, sign) + if err != nil { + t.Fatalf("VerifyRsaWithSha256验证错误:%v", err) + } +} diff --git a/trunk/goutil/securityUtil/sha1.go b/trunk/goutil/securityUtil/sha1.go new file mode 100644 index 0000000..63b6017 --- /dev/null +++ b/trunk/goutil/securityUtil/sha1.go @@ -0,0 +1,38 @@ +package securityUtil + +import ( + "crypto/sha1" + "errors" + "fmt" +) + +// 对字符串进行SHA1加密,并且可以选择返回大、小写 +// s:输入字符串 +// ifUpper:输出是否大写 +// 返回值:md5加密后的字符串 +func Sha1String(s string, ifUpper bool) string { + if len(s) == 0 { + panic(errors.New("input string can't be empty")) + } + + return Sha1Bytes([]byte(s), ifUpper) +} + +// 对字符数组进行SHA1加密,并且可以选择返回大、小写 +// b:输入字符数组 +// ifUpper:输出是否大写 +// 返回值:md5加密后的字符串 +func Sha1Bytes(b []byte, ifUpper bool) string { + if len(b) == 0 { + panic(errors.New("input []byte can't be empty")) + } + + sha1Instance := sha1.New() + sha1Instance.Write(b) + result := sha1Instance.Sum([]byte("")) + if ifUpper { + return fmt.Sprintf("%X", result) + } else { + return fmt.Sprintf("%x", result) + } +} diff --git a/trunk/goutil/securityUtil/sha1_test.go b/trunk/goutil/securityUtil/sha1_test.go new file mode 100644 index 0000000..3d317b0 --- /dev/null +++ b/trunk/goutil/securityUtil/sha1_test.go @@ -0,0 +1,32 @@ +package securityUtil + +import ( + "testing" +) + +func TestSha1String(t *testing.T) { + s := "hello world" + result := Sha1String(s, true) + if result != "2AAE6C35C94FCFB415DBE95F408B9CE91EE846ED" { + t.Errorf("Sha1String(\"hello world\") failed.Got %s, expected %s", result, "2AAE6C35C94FCFB415DBE95F408B9CE91EE846ED") + } + + result = Sha1String(s, false) + if result != "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed" { + t.Errorf("Sha1String(\"hello world\") failed.Got %s, expected %s", result, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed") + } +} + +func TestSha1Bytes(t *testing.T) { + s := "hello world" + b := []byte(s) + result := Sha1Bytes(b, true) + if result != "2AAE6C35C94FCFB415DBE95F408B9CE91EE846ED" { + t.Errorf("Sha1Bytes(\"hello world\") failed.Got %s, expected %s", result, "2AAE6C35C94FCFB415DBE95F408B9CE91EE846ED") + } + + result = Sha1Bytes(b, false) + if result != "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed" { + t.Errorf("Sha1Bytes(\"hello world\") failed.Got %s, expected %s", result, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed") + } +} diff --git a/trunk/goutil/securityUtil/sha256.go b/trunk/goutil/securityUtil/sha256.go new file mode 100644 index 0000000..7cba3dc --- /dev/null +++ b/trunk/goutil/securityUtil/sha256.go @@ -0,0 +1,40 @@ +package securityUtil + +import ( + "crypto/sha256" + "errors" + "fmt" +) + +// 对字符串进行SHA256加密,并且可以选择返回大、小写 +// s:输入字符串 +// ifUpper:输出是否大写 +// 返回值:sha256加密后的十六进制字符串 +func Sha256String(s string, ifUpper bool) string { + if len(s) == 0 { + panic(errors.New("input string can't be empty")) + } + + buf := Sha256Bytes([]byte(s)) + + if ifUpper { + return fmt.Sprintf("%X", buf) + } else { + return fmt.Sprintf("%x", buf) + } +} + +// 对字符数组进行SHA256加密 +// b:输入字符数组 +// 返回值:sha256加密后的原数据 +func Sha256Bytes(b []byte) []byte { + if len(b) == 0 { + panic(errors.New("input []byte can't be empty")) + } + + sha1Instance := sha256.New() + sha1Instance.Write(b) + result := sha1Instance.Sum(nil) + + return result +} diff --git a/trunk/goutil/securityUtil/sha256_test.go b/trunk/goutil/securityUtil/sha256_test.go new file mode 100644 index 0000000..3c68c5b --- /dev/null +++ b/trunk/goutil/securityUtil/sha256_test.go @@ -0,0 +1,15 @@ +package securityUtil + +import ( + "testing" +) + +func TestSha256(t *testing.T) { + source := "oauthConsumerKey=1Nocz0wk0Hi8oGgSosogC4K4k&oauthToken=TOKEN_%2B8vQAR1eoD3ujiGstjzdyakEgbkyvWhfzF1fChQJ46EH07n%2FQvrazkMqy%2BhuprqU&oauthSignatureMethod=HMAC-SHA1&oauthTimestamp=1508486834&oauthNonce=5409983431934290948&oauthVersion=1.0&" + sign := "813c8202a31c73371ae0bbe13cb49d65c94da3de2877345603271ca14e5e4bcd" + + result := Sha256String(source, false) + if result != sign { + t.Fatalf("Sha256编码结果不一致") + } +} diff --git a/trunk/goutil/smsUtil/qcloud/commonField.go b/trunk/goutil/smsUtil/qcloud/commonField.go new file mode 100644 index 0000000..c6e19c6 --- /dev/null +++ b/trunk/goutil/smsUtil/qcloud/commonField.go @@ -0,0 +1,28 @@ +package qcloud + +// 公有字段 +type commonField struct { + // 签名 + Sig string `json:"sig"` + // Unix时间戳 + Time int64 `json:"time"` + // 通道扩展码,可选字段,默认没有开通(需要填空)。 + Extend string `json:"extend"` + // 用户的session内容,腾讯server回包中会原样返回 + Ext string `json:"ext"` + + // 接收方 + // 单发短信时填mobile + // 群发时填[]mobile + Tel interface{} `json:"tel"` +} + +func newCommonField(sig string, time int64, extend string, ext string, tel interface{}) *commonField { + return &commonField{ + Sig: sig, + Time: time, + Extend: extend, + Ext: ext, + Tel: tel, + } +} diff --git a/trunk/goutil/smsUtil/qcloud/msgSmsField.go b/trunk/goutil/smsUtil/qcloud/msgSmsField.go new file mode 100644 index 0000000..c80be63 --- /dev/null +++ b/trunk/goutil/smsUtil/qcloud/msgSmsField.go @@ -0,0 +1,16 @@ +package qcloud + +// 直接发送短信字段 +type msgSmsField struct { + //0:普通短信;1:营销短信(强调:要按需填值,不然会影响到业务的正常使用) + Type interface{} `json:"type,omitempty"` + // 短信内容,需要匹配审核通过的模板内容 + Msg string `json:"msg,omitempty"` +} + +func newMsgSmsField(t int, msg string) *msgSmsField { + return &msgSmsField{ + Type: t, + Msg: msg, + } +} diff --git a/trunk/goutil/smsUtil/qcloud/qcloud.go b/trunk/goutil/smsUtil/qcloud/qcloud.go new file mode 100644 index 0000000..c88046a --- /dev/null +++ b/trunk/goutil/smsUtil/qcloud/qcloud.go @@ -0,0 +1,144 @@ +package qcloud + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "strings" + "time" + + "goutil/mathUtil" + "goutil/smsUtil/sms" +) + +const ( + SINGLE_SMS_URL = "https://yun.tim.qq.com/v5/tlssmssvr/sendsms" + MULTI_SMS_URL = "https://yun.tim.qq.com/v5/tlssmssvr/sendmultisms2" +) + +// 创建msg类型短信(直接发送短信内容) +// 参数: +// +// appid +// appkey +// nation []string 国家代码 eg: 86 +// numbers []string 电话号码 +// msg string 短信内容 +// extend string 通道扩展码,可选字段,默认没有开通(需要填空串) +// ext string 用户的session内容,腾讯server回包中会原样返回,可选字段,不需要就填空 +// +// nation、numbers 需要一一对应 (nation[0]将与numbers[0]组合) +// msg 必须与已通过审核的模板匹配 +func NewMsgSms(appid, appkey string, + nation, numbers []string, + msg, extend, ext string) (sms.Sms, error) { + + return generateSms(appid, appkey, + nation, numbers, + extend, ext, + newMsgSmsField(0, msg), + nil) +} + +// 创建tmpl类型短信(通过模板发送短信) +// 参数: +// +// appid +// appkey +// nation []string 国家代码 eg: 86 +// numbers []string 电话号码 +// tmplID int 模板ID +// params []string 模板参数 +// sign string 短信签名(前缀) +// extend string 通道扩展码,可选字段,默认没有开通(需要填空串) +// ext string 用户的session内容,腾讯server回包中会原样返回,可选字段,不需要就填空 +// +// nation、numbers 需要一一对应 (nation[0]将与numbers[0]组合) +func NewTmplSms(appid, appkey string, + nation, numbers []string, + tmplID int, params []string, + sign, extend, ext string) (sms.Sms, error) { + + return generateSms(appid, appkey, + nation, numbers, + extend, ext, + nil, + newTmplSmsField(sign, tmplID, params)) + +} + +// create qcloud sms instance +func generateSms(appid, appkey string, + nation, numbers []string, + extend, ext string, + msg *msgSmsField, + tmpl *tmplSmsField) (s sms.Sms, err error) { + + err = validateMobile(nation, numbers) + if err != nil { + return + } + + var url string + // 区分 + if len(nation) > 1 { + url = MULTI_SMS_URL + } else { + url = SINGLE_SMS_URL + } + + // 生成随机数、时间戳,并计算签名 + rnd := mathUtil.GetRand().GetRandRangeInt(100000, 999999) + timestap := time.Now().Unix() + sig := calcSig(appkey, rnd, timestap, numbers) + + // 生成公共字段 + comField := newCommonField(sig, timestap, extend, ext, generateTelField(nation, numbers)) + + // 生成smsData + data := newSmsData(comField, msg, tmpl) + + // 生成qcloudsms + s = newSms(url, data, rnd, appid) + + return +} + +// calculate sign-string for phone numbers +func calcSig(appkey string, rnd int, tm int64, numbers []string) string { + mobile := strings.Join(numbers, ",") + + sum := sha256.Sum256([]byte(fmt.Sprintf("appkey=%v&random=%v&time=%v&mobile=%v", + appkey, rnd, tm, mobile))) + + return hex.EncodeToString(sum[:]) +} + +// 验证地区代码与手机号数量是否匹配 +func validateMobile(nation, numbers []string) error { + if len(nation) != len(numbers) { + return errors.New("loadReq: nation、numbers 数量不同") + } else if len(nation) == 0 { + return errors.New("loadReq: nation、numbers 不能为空") + } + + return nil +} + +// 生成Tel字段 +// 当多个号码时,生成[]telField +// 单个号码时,生成telField +func generateTelField(nation, numbers []string) interface{} { + res := make([](*telField), len(nation)) + + for i := 0; i < len(nation); i++ { + res[i] = newTelField(nation[i], numbers[i]) + } + + if len(res) == 1 { + return res[0] + } + + return res +} diff --git a/trunk/goutil/smsUtil/qcloud/response.go b/trunk/goutil/smsUtil/qcloud/response.go new file mode 100644 index 0000000..9e488d3 --- /dev/null +++ b/trunk/goutil/smsUtil/qcloud/response.go @@ -0,0 +1,22 @@ +package qcloud + +// 腾讯云平台的响应信息 + +// 响应信息 +type QCloudResponse struct { + Result int // 0表示成功(计费依据),非0表示失败 + Errmsg string // result非0时的具体错误信息 + Ext string // 用户的session内容,腾讯server回包中会原样返回 + Sid string // 标识本次发送id,标识一次短信下发记录 + Fee int // 短信计费的条数 + Detail []responsDetailItem // 群发短信时才有 +} + +type responsDetailItem struct { + Result int + Errmsg string + Mobile string + Nationcode string + Sid string + Fee int +} diff --git a/trunk/goutil/smsUtil/qcloud/sms.go b/trunk/goutil/smsUtil/qcloud/sms.go new file mode 100644 index 0000000..c034cb9 --- /dev/null +++ b/trunk/goutil/smsUtil/qcloud/sms.go @@ -0,0 +1,86 @@ +package qcloud + +import ( + "encoding/json" + "fmt" + + "goutil/debugUtil" + "goutil/smsUtil/sender/httpSender" +) + +// qcloud sms +type qcloudsms struct { + url string + data *smsData + rnd int + appid string + + rspn *QCloudResponse +} + +func newSms(url string, data *smsData, rnd int, appid string) *qcloudsms { + return &qcloudsms{ + url: url, + data: data, + rnd: rnd, + appid: appid, + } +} + +// 实现sms.Sms、httpSender.Requester、httpSender.Responser接口 + +// httpSender.Requester接口 +// 返回请求方式 +func (*qcloudsms) GetMethod() string { + return "POST" +} + +// httpSender.Requester接口 +// 返回请求url +func (this *qcloudsms) GetUrl() string { + // url 额外参数 + url := fmt.Sprintf("%s?sdkappid=%s&random=%d", this.url, this.appid, this.rnd) + + return url +} + +// httpSender.Requester接口 +// 返回发送数据 +func (this *qcloudsms) GetData() []byte { + bytes, err := json.Marshal(this.data) + if err != nil { + debugUtil.Println("failed to json marshal sms data") + return []byte("") + } + return bytes +} + +// 解析返回数据,判断是否发送成功 +func (this *qcloudsms) ParseReponse(rspn []byte) (bool, error) { + this.rspn = new(QCloudResponse) + err := json.Unmarshal(rspn, this.rspn) + if err != nil { + return false, err + } + + if this.rspn.Result == 0 { + return true, nil + } else { + return false, fmt.Errorf(this.rspn.Errmsg) + } +} + +// sms.Sms接口 +func (this *qcloudsms) Send() (bool, error) { + sender := httpSender.New() + rspn, err := sender.Send(this) + if err != nil { + return false, err + } + + return this.ParseReponse(rspn) +} + +func (this *qcloudsms) GetResponse() interface{} { + return this.rspn +} diff --git a/trunk/goutil/smsUtil/qcloud/smsData.go b/trunk/goutil/smsUtil/qcloud/smsData.go new file mode 100644 index 0000000..f9b78ef --- /dev/null +++ b/trunk/goutil/smsUtil/qcloud/smsData.go @@ -0,0 +1,16 @@ +package qcloud + +// 发送短信请求结构 +type smsData struct { + // 公共字段 + *commonField + + // 短信字段 + // 根据类型,对其中一个结构的字段进行赋值 + *msgSmsField + *tmplSmsField +} + +func newSmsData(common *commonField, msg *msgSmsField, tmpl *tmplSmsField) *smsData { + return &smsData{common, msg, tmpl} +} diff --git a/trunk/goutil/smsUtil/qcloud/telField.go b/trunk/goutil/smsUtil/qcloud/telField.go new file mode 100644 index 0000000..7b1f148 --- /dev/null +++ b/trunk/goutil/smsUtil/qcloud/telField.go @@ -0,0 +1,14 @@ +package qcloud + +// 电话结构 +type telField struct { + Nationcode string `json:"nationcode"` + Mobile string `json:"mobile"` +} + +func newTelField(nation, number string) *telField { + return &telField{ + Nationcode: nation, + Mobile: number, + } +} diff --git a/trunk/goutil/smsUtil/qcloud/tmplSmsField.go b/trunk/goutil/smsUtil/qcloud/tmplSmsField.go new file mode 100644 index 0000000..ca819e2 --- /dev/null +++ b/trunk/goutil/smsUtil/qcloud/tmplSmsField.go @@ -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, + } +} diff --git a/trunk/goutil/smsUtil/qirui/qirui.go b/trunk/goutil/smsUtil/qirui/qirui.go new file mode 100644 index 0000000..1be16f4 --- /dev/null +++ b/trunk/goutil/smsUtil/qirui/qirui.go @@ -0,0 +1,26 @@ +package qirui + +import ( + "fmt" + + "goutil/webUtil" +) + +const ( + SEND_MESSAGE_URL = "http://api.qirui.com:7891/mt" +) + +func SendMessage(apiKey, apiSecret, mobile, message string) (bool, error) { + smsUrl := fmt.Sprintf("%s?dc=15&un=%s&pw=%s&da=%s&sm=%s&tf=3&rf=2&rd=1", SEND_MESSAGE_URL, apiKey, apiSecret, mobile, message) + status, resp, err := webUtil.GetWebData3(smsUrl, "", webUtil.GetJsonHeader(), nil) + if err != nil { + return false, err + } + + if status != 200 { + return false, fmt.Errorf("StatusCode is %d", status) + } + + fmt.Printf("qirui.SendMessage:%s, %s, %s\n", mobile, message, string(resp)) + return true, nil +} diff --git a/trunk/goutil/smsUtil/sender/httpSender/httpSender.go b/trunk/goutil/smsUtil/sender/httpSender/httpSender.go new file mode 100644 index 0000000..2539dfb --- /dev/null +++ b/trunk/goutil/smsUtil/sender/httpSender/httpSender.go @@ -0,0 +1,47 @@ +package httpSender + +import ( + "errors" + "fmt" + + // "goutil/debugUtil" + "goutil/webUtil" +) + +// 实现Sender相关接口 + +type httpClient struct{} + +// 发送Requester +func (this *httpClient) Send(req Requester) (rspn []byte, err error) { + if req.GetMethod() == "POST" { + rspn, err = this.post(req) + return + } else { + err = errors.New(fmt.Sprintf("request: unsupport method (%s)", req.GetMethod())) + return + } +} + +// 发送 post 请求 +func (*httpClient) post(req Requester) ([]byte, error) { + url := req.GetUrl() + bytes := req.GetData() + + // debugUtil.Printf("httpClient-POST %s\r\n%v\n", url, string(bytes)) + + rspn, err := webUtil.PostByteData(url, bytes, nil) + if err != nil { + return nil, err + } + + // body := []byte("{\"result\":0,\"errmsg\":\"OK\",\"ext\":\"hello-world\",\"sid\":\"6:59106203271444582828\",\"fee\":1}") + + // debugUtil.Printf("httpClient-Res: %s", string(rspn)) + + return rspn, nil +} + +func New() *httpClient { + return new(httpClient) +} diff --git a/trunk/goutil/smsUtil/sender/httpSender/requester.go b/trunk/goutil/smsUtil/sender/httpSender/requester.go new file mode 100644 index 0000000..5191635 --- /dev/null +++ b/trunk/goutil/smsUtil/sender/httpSender/requester.go @@ -0,0 +1,8 @@ +package httpSender + +// 请求 +type Requester interface { + GetMethod() string + GetUrl() string + GetData() []byte +} diff --git a/trunk/goutil/smsUtil/sender/httpSender/sender.go b/trunk/goutil/smsUtil/sender/httpSender/sender.go new file mode 100644 index 0000000..0160c32 --- /dev/null +++ b/trunk/goutil/smsUtil/sender/httpSender/sender.go @@ -0,0 +1,7 @@ +package httpSender + +// 发送器 +// 用于发送Requester +type Sender interface { + Send(req Requester) ([]byte, error) +} diff --git a/trunk/goutil/smsUtil/sms/sms.go b/trunk/goutil/smsUtil/sms/sms.go new file mode 100644 index 0000000..e079fd6 --- /dev/null +++ b/trunk/goutil/smsUtil/sms/sms.go @@ -0,0 +1,9 @@ +package sms + +type Sms interface { + // 发送 + Send() (bool, error) + + // 用于获取准确的返回数据 + GetResponse() interface{} +} diff --git a/trunk/goutil/stringUtil/base64.go b/trunk/goutil/stringUtil/base64.go new file mode 100644 index 0000000..08b6989 --- /dev/null +++ b/trunk/goutil/stringUtil/base64.go @@ -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)) +} diff --git a/trunk/goutil/stringUtil/base64_test.go b/trunk/goutil/stringUtil/base64_test.go new file mode 100644 index 0000000..1c5773c --- /dev/null +++ b/trunk/goutil/stringUtil/base64_test.go @@ -0,0 +1,53 @@ +package stringUtil + +import ( + "testing" +) + +func TestBase64Encode(t *testing.T) { + greeting := "Hello world" + encoded := Base64Encode(greeting) + decoded, err := Base64Decode(encoded) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if greeting != decoded { + t.Errorf("Expected %s, but got %s", greeting, decoded) + return + } +} + +func TestBase64Encode2(t *testing.T) { + greeting := []byte("Hello world") + encoded := Base64Encode2(greeting) + decoded, err := Base64Decode2(encoded) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if isEqual(greeting, decoded) == false { + t.Errorf("Expected %s, but got %s", greeting, decoded) + return + } +} + +func isEqual(s1, s2 []byte) bool { + if s1 == nil || s2 == nil { + return true + } + + if len(s1) != len(s2) { + return false + } + + for i := 0; i < len(s1); i++ { + if s1[i] != s2[i] { + return false + } + } + + return true +} diff --git a/trunk/goutil/stringUtil/doc.go b/trunk/goutil/stringUtil/doc.go new file mode 100644 index 0000000..f6a8476 --- /dev/null +++ b/trunk/goutil/stringUtil/doc.go @@ -0,0 +1,4 @@ +/* +字符串助手类包 +*/ +package stringUtil diff --git a/trunk/goutil/stringUtil/emojiCharFilter.go b/trunk/goutil/stringUtil/emojiCharFilter.go new file mode 100644 index 0000000..90a70e3 --- /dev/null +++ b/trunk/goutil/stringUtil/emojiCharFilter.go @@ -0,0 +1,198 @@ +package stringUtil + +// 表情符号集合 +var emojiData map[rune]rune + +func init() { + emojiData = make(map[rune]rune, 1024) + + addEmojiChar() +} + +// 添加一个范围的unicode +// start:unicode起始位置 +// endlist:unicode结束位置 +func addUnicodeRange(start rune, endlist ...rune) { + if len(endlist) <= 0 { + // 添加单个的 + emojiData[start] = start + return + } + + end := endlist[0] + if start > end { + return + } + + // 添加范围的 + for i := start; i <= end; i++ { + emojiData[i] = i + } +} + +// 增加emoji表情符号 +// 表情字符大全参考: +// https://zh.wikipedia.org/wiki/%E7%B9%AA%E6%96%87%E5%AD%97 +// 对应unicode版本号:Unicode 10.0版本 +func addEmojiChar() { + addUnicodeRange(0x00A9) + addUnicodeRange(0x00AE) + + addUnicodeRange(0x203C) + addUnicodeRange(0x2049) + addUnicodeRange(0x2122) + addUnicodeRange(0x2139) + addUnicodeRange(0x2194, 0x2199) + addUnicodeRange(0x21A9, 0x21AA) + addUnicodeRange(0x231A, 0x231B) + addUnicodeRange(0x2328) + addUnicodeRange(0x23CF) + addUnicodeRange(0x23E9, 0x23F3) + addUnicodeRange(0x23F8, 0x23FA) + addUnicodeRange(0x24C2) + addUnicodeRange(0x25AA, 0x25AB) + addUnicodeRange(0x25B6) + addUnicodeRange(0x25C0) + addUnicodeRange(0x25FB, 0x25FE) + + addUnicodeRange(0x2600, 0x2604) + addUnicodeRange(0x260E) + addUnicodeRange(0x2611) + addUnicodeRange(0x2614, 0x2615) + addUnicodeRange(0x2618) + addUnicodeRange(0x261D) + addUnicodeRange(0x2620) + addUnicodeRange(0x2622, 0x2623) + addUnicodeRange(0x2626) + addUnicodeRange(0x262A) + addUnicodeRange(0x262E, 0x262F) + addUnicodeRange(0x2638, 0x263A) + addUnicodeRange(0x2640) + addUnicodeRange(0x2642) + addUnicodeRange(0x2648, 0x2653) + addUnicodeRange(0x2660) + addUnicodeRange(0x2663) + addUnicodeRange(0x2665, 0x2666) + addUnicodeRange(0x2668) + addUnicodeRange(0x267B) + addUnicodeRange(0x267F) + addUnicodeRange(0x2692, 0x2697) + addUnicodeRange(0x2699) + addUnicodeRange(0x269B, 0x269C) + + addUnicodeRange(0x26A0, 0x26A1) + addUnicodeRange(0x26AA, 0x26AB) + addUnicodeRange(0x26B0, 0x26B1) + addUnicodeRange(0x26BD, 0x26BE) + addUnicodeRange(0x26C4, 0x26C5) + addUnicodeRange(0x26C8) + addUnicodeRange(0x26CE, 0x26CF) + addUnicodeRange(0x26D1) + addUnicodeRange(0x26D3, 0x26D4) + addUnicodeRange(0x26E9, 0x26EA) + addUnicodeRange(0x26F0, 0x26F5) + addUnicodeRange(0x26F7, 0x26FA) + addUnicodeRange(0x26FD) + + addUnicodeRange(0x2702) + addUnicodeRange(0x2705) + addUnicodeRange(0x2708, 0x270D) + addUnicodeRange(0x270F) + addUnicodeRange(0x2712) + addUnicodeRange(0x2714) + addUnicodeRange(0x2716) + addUnicodeRange(0x271D) + addUnicodeRange(0x2721) + addUnicodeRange(0x2728) + addUnicodeRange(0x2733, 0x2734) + addUnicodeRange(0x2744) + addUnicodeRange(0x2747) + addUnicodeRange(0x274C) + addUnicodeRange(0x274E) + addUnicodeRange(0x2753, 0x2755) + addUnicodeRange(0x2757) + addUnicodeRange(0x2763, 0x2764) + addUnicodeRange(0x2795, 0x2797) + addUnicodeRange(0x27A1) + addUnicodeRange(0x27B0) + addUnicodeRange(0x27BF) + addUnicodeRange(0x2934, 0x2935) + addUnicodeRange(0x2B05, 0x2B07) + addUnicodeRange(0x2B1B, 0x2B1C) + addUnicodeRange(0x2B50) + addUnicodeRange(0x2B55) + addUnicodeRange(0x3030) + addUnicodeRange(0x303D) + addUnicodeRange(0x3297, 0x3299) + addUnicodeRange(0x3299) + addUnicodeRange(0x1F004) + addUnicodeRange(0x1F0CF) + addUnicodeRange(0x1F170, 0x1F171) + addUnicodeRange(0x1F17E, 0x1F17F) + addUnicodeRange(0x1F18E) + + addUnicodeRange(0x1F191, 0x1F19A) + addUnicodeRange(0x1F201, 0x1F202) + addUnicodeRange(0x1F21A) + addUnicodeRange(0x1F22F) + addUnicodeRange(0x1F232, 0x1F23A) + addUnicodeRange(0x1F250, 0x1F251) + addUnicodeRange(0x1F300, 0x1F321) + addUnicodeRange(0x1F324, 0x1F393) + addUnicodeRange(0x1F396, 0x1F397) + addUnicodeRange(0x1F399, 0x1F39B) + addUnicodeRange(0x1F39E, 0x1F3F0) + addUnicodeRange(0x1F3F3, 0x1F3F5) + addUnicodeRange(0x1F3F7, 0x1F53D) + + addUnicodeRange(0x1F549, 0x1F54E) + addUnicodeRange(0x1F550, 0x1F567) + addUnicodeRange(0x1F56F, 0x1F570) + addUnicodeRange(0x1F573, 0x1F57A) + addUnicodeRange(0x1F587) + addUnicodeRange(0x1F58A, 0x1F58D) + addUnicodeRange(0x1F590) + addUnicodeRange(0x1F595, 0x1F596) + addUnicodeRange(0x1F5A4, 0x1F5A5) + addUnicodeRange(0x1F5A8) + addUnicodeRange(0x1F5B1, 0x1F5B2) + addUnicodeRange(0x1F5BC) + addUnicodeRange(0x1F5C2, 0x1F5C4) + addUnicodeRange(0x1F5D1, 0x1F5D3) + addUnicodeRange(0x1F5DC, 0x1F5DE) + addUnicodeRange(0x1F5E1) + addUnicodeRange(0x1F5E3) + addUnicodeRange(0x1F5E8) + addUnicodeRange(0x1F5EF) + addUnicodeRange(0x1F5F3) + addUnicodeRange(0x1F5FA, 0x1F6C5) + addUnicodeRange(0x1F6CB, 0x1F6D2) + addUnicodeRange(0x1F6E0, 0x1F6E5) + addUnicodeRange(0x1F6E8) + addUnicodeRange(0x1F6EB, 0x1F6EC) + addUnicodeRange(0x1F6F0) + addUnicodeRange(0x1F6F3, 0x1F6F8) + addUnicodeRange(0x1F910, 0x1F93A) + addUnicodeRange(0x1F93B, 0x1F93E) + addUnicodeRange(0x1F940, 0x1F945) + addUnicodeRange(0x1F947, 0x1F94C) + addUnicodeRange(0x1F950, 0x1F96B) + addUnicodeRange(0x1F980, 0x1F997) + addUnicodeRange(0x1F9C0) + addUnicodeRange(0x1F9D0, 0x1F9E6) +} + +// 检查是否含有表情字符 +// val:待查看的字符串 +// 返回值: +// 是否包含有表情字符 +func IfHaveEmoji(val string) bool { + // 由于golang在内存中本来就是使用的Unicode,所以可以直接进行匹配操作 + for _, charItem := range val { + if _, eixst := emojiData[charItem]; eixst { + return true + } + } + + return false +} diff --git a/trunk/goutil/stringUtil/emojiCharFilter_test.go b/trunk/goutil/stringUtil/emojiCharFilter_test.go new file mode 100644 index 0000000..3658722 --- /dev/null +++ b/trunk/goutil/stringUtil/emojiCharFilter_test.go @@ -0,0 +1,34 @@ +package stringUtil + +import ( + "testing" +) + +// test 特殊字符 +func TestEmoji1(t *testing.T) { + tstVal := map[string]string{ + "中文": "你好啊", + "繁体中文": "這是什麼天氣", + "泰文": "สวัสดีครับ !", + "英文": "helloworld", + "越南语": "Đó là gì thời tiết.", + "日语": "これは何の天気ですか", + "标点符号": "!@#$%^^&*())(__+{}[]|:<>", + } + + for key, val := range tstVal { + if IfHaveEmoji(val) { + t.Errorf("语言处理错误:%s", key) + } + } + + emojiVal := "☀" + if IfHaveEmoji(emojiVal) == false { + t.Errorf("表情符号匹配错误:") + } + + specialChar := "\\'\"" + if IfHaveEmoji(specialChar) { + t.Errorf("特殊字符匹配错误:") + } +} diff --git a/trunk/goutil/stringUtil/guid.go b/trunk/goutil/stringUtil/guid.go new file mode 100644 index 0000000..737c125 --- /dev/null +++ b/trunk/goutil/stringUtil/guid.go @@ -0,0 +1,60 @@ +package stringUtil + +import ( + "crypto/rand" + "encoding/base64" + "io" + "strings" + + "goutil/securityUtil" +) + +// 获取新的GUID字符串 +// 返回值: +// 新的GUID字符串 +func GetNewGUID() string { + b := make([]byte, 48) + if _, err := io.ReadFull(rand.Reader, b); err != nil { + return "" + } + + return securityUtil.Md5String(base64.URLEncoding.EncodeToString(b), true) +} + +// 生成空的GUID字符串 +// 返回值: +// 空的GUID字符串 +func GetEmptyGUID() string { + return "00000000-0000-0000-0000-000000000000" +} + +// 判断GUID是否为空 +// guid:GUID +// 返回值: +// 是否为空 +func IsGUIDEmpty(guid string) bool { + if guid == "" || guid == "00000000-0000-0000-0000-000000000000" { + return true + } + + return false +} + +// 获取新的GUID字符串 +// 返回值: +// 新的GUID字符串 +func GetNewUUID() string { + str := GetNewGUID() + var builder strings.Builder + builder.WriteString(Substring(str, 0, 8)) + builder.WriteString("-") + builder.WriteString(Substring(str, 8, 4)) + builder.WriteString("-") + builder.WriteString(Substring(str, 12, 4)) + builder.WriteString("-") + builder.WriteString(Substring(str, 16, 4)) + builder.WriteString("-") + builder.WriteString(Substring(str, 20, 12)) + + return strings.ToLower(builder.String()) +} diff --git a/trunk/goutil/stringUtil/guid_test.go b/trunk/goutil/stringUtil/guid_test.go new file mode 100644 index 0000000..6976fa8 --- /dev/null +++ b/trunk/goutil/stringUtil/guid_test.go @@ -0,0 +1,60 @@ +package stringUtil + +import ( + "testing" +) + +func TestGetNewGUID(t *testing.T) { + guidMap := make(map[string]bool, 1024) + count := 10 + for i := 0; i < count; i++ { + guid := GetNewGUID() + guidMap[guid] = true + } + + if len(guidMap) != count { + t.Errorf("there should be %d 条不重复的数据,但是现在只有%d条", count, len(guidMap)) + } +} + +func TestGetEmptyGUID(t *testing.T) { + guid := GetEmptyGUID() + expected := "00000000-0000-0000-0000-000000000000" + if guid != expected { + t.Errorf("guid should be %s, but now is %s", expected, guid) + } +} + +// test IsGUIDEmpty +func TestIsGUIDEmpty(t *testing.T) { + isOk := IsGUIDEmpty("") + if isOk == false { + t.Error("test is Not pass:") + return + } + + isOk = IsGUIDEmpty("00000000-0000-0000-0000-000000000000") + if isOk == false { + t.Error("test is Not pass:00000000-0000-0000-0000-000000000000") + return + } + + isOk = IsGUIDEmpty("00000000-0000-0000-0000-000000000001") + if isOk == true { + t.Error("test is Not pass:00000000-0000-0000-0000-000000000001") + return + } +} + +func TestGetNewUUID(t *testing.T) { + guidMap := make(map[string]bool, 1024) + count := 10 + for i := 0; i < count; i++ { + guid := GetNewUUID() + guidMap[guid] = true + } + + if len(guidMap) != count { + t.Errorf("there should be %d 条不重复的数据,但是现在只有%d条", count, len(guidMap)) + } +} diff --git a/trunk/goutil/stringUtil/hash.go b/trunk/goutil/stringUtil/hash.go new file mode 100644 index 0000000..ac662fd --- /dev/null +++ b/trunk/goutil/stringUtil/hash.go @@ -0,0 +1,16 @@ +package stringUtil + +import ( + "hash/crc32" +) + +// HashCode 获取字符串对应的hashCode值 +func HashCode(s string) int { + v := int(crc32.ChecksumIEEE([]byte(s))) + + if v < 0 { + return -v + } + + return v +} diff --git a/trunk/goutil/stringUtil/hash_test.go b/trunk/goutil/stringUtil/hash_test.go new file mode 100644 index 0000000..1104c01 --- /dev/null +++ b/trunk/goutil/stringUtil/hash_test.go @@ -0,0 +1,23 @@ +package stringUtil + +import ( + "testing" +) + +func Test1(t *testing.T) { + s := HashCode("abc") + s1 := HashCode("abc") + + if s != s1 { + t.Errorf("s1 hashcode:%v,s2 hashcode:%v,2个code值不相等", s, s1) + } +} + +func Test2(t *testing.T) { + s := HashCode("abc") + s1 := HashCode("bcd") + + if s == s1 { + t.Errorf("s1 hashcode:%v,s2 hashcode:%v,2个code值相等", s, s1) + } +} diff --git a/trunk/goutil/stringUtil/similarity.go b/trunk/goutil/stringUtil/similarity.go new file mode 100644 index 0000000..a08f56d --- /dev/null +++ b/trunk/goutil/stringUtil/similarity.go @@ -0,0 +1,86 @@ +/* + 使用编辑距离Edit Distance的方式来计算两个字符串的相似类。 + 参考资料:https://en.wikipedia.org/wiki/Edit_distance +*/ +package stringUtil + +// 计算两个字符串的相似度 +// word1: 第一个字符串 +// word2: 第二个字符串 +// 返回值: +// 两个字符串的距离,表示两个字符串经过多少次变换,可以变成同一个字符串 +// 两个字符串的相似度,范围为[0, 1] +func Similarity(word1, word2 string) (distance int, similarity float64) { + // 内部方法,用于计算最小值、最大值 + min := func(nums ...int) int { + _min := nums[0] + for _, v := range nums { + if v < _min { + _min = v + } + } + return _min + } + max := func(nums ...int) int { + _max := nums[0] + for _, v := range nums { + if v > _max { + _max = v + } + } + return _max + } + + // 如果有单词为空,或者单词相同,则直接计算出结果,无需进一步计算 + m, n := len(word1), len(word2) + if m == 0 { + distance = n + return + } + if n == 0 { + distance = m + return + } + if m == n && word1 == word2 { + distance = 0 + similarity = 1 + return + } + + // 使用动态规划计算编辑距离(Edit Distance) + // Step 1: define the data structure + dp := make([][]int, m+1, m+1) + for i := 0; i <= m; i++ { + dp[i] = make([]int, n+1, n+1) + } + + // Step 2: init the data + for i := 0; i <= m; i++ { + for j := 0; j <= n; j++ { + if i == 0 { + dp[i][j] = j + } + if j == 0 { + dp[i][j] = i + } + } + } + + // Step 3: state transition equation + for i := 1; i <= m; i++ { + for j := 1; j <= n; j++ { + if word1[i-1] == word2[j-1] { + dp[i][j] = dp[i-1][j-1] + } else { + dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1 + } + } + } + + // 得到距离并计算相似度 + distance = dp[m][n] + maxLength := max(m, n) + similarity = float64(maxLength-distance) / float64(maxLength) + + return +} diff --git a/trunk/goutil/stringUtil/similarity_test.go b/trunk/goutil/stringUtil/similarity_test.go new file mode 100644 index 0000000..62b18ea --- /dev/null +++ b/trunk/goutil/stringUtil/similarity_test.go @@ -0,0 +1,91 @@ +package stringUtil + +import ( + "testing" +) + +func TestSimilarity(t *testing.T) { + source := "" + target := "" + expectedDistance := 0 + expectedSimilarity := 0.0 + gotDistance, gotSimilarity := Similarity(source, target) + if gotDistance != expectedDistance { + t.Errorf("Expected to get %d, now got %d", expectedDistance, gotDistance) + return + } + if gotSimilarity != expectedSimilarity { + t.Errorf("Expected to get %f, now got %f", expectedSimilarity, gotSimilarity) + return + } + + source = "Hello" + target = "" + expectedDistance = 5 + expectedSimilarity = 0.0 + gotDistance, gotSimilarity = Similarity(source, target) + if gotDistance != expectedDistance { + t.Errorf("Expected to get %d, now got %d", expectedDistance, gotDistance) + return + } + if gotSimilarity != expectedSimilarity { + t.Errorf("Expected to get %f, now got %f", expectedSimilarity, gotSimilarity) + return + } + + source = "" + target = "Hello" + expectedDistance = 5 + expectedSimilarity = 0.0 + gotDistance, gotSimilarity = Similarity(source, target) + if gotDistance != expectedDistance { + t.Errorf("Expected to get %d, now got %d", expectedDistance, gotDistance) + return + } + if gotSimilarity != expectedSimilarity { + t.Errorf("Expected to get %f, now got %f", expectedSimilarity, gotSimilarity) + return + } + + source = "Helo" + target = "Hello" + expectedDistance = 1 + expectedSimilarity = 4.0 / 5.0 + gotDistance, gotSimilarity = Similarity(source, target) + if gotDistance != expectedDistance { + t.Errorf("Expected to get %d, now got %d", expectedDistance, gotDistance) + return + } + if gotSimilarity != expectedSimilarity { + t.Errorf("Expected to get %f, now got %f", expectedSimilarity, gotSimilarity) + return + } + + source = "kitten" + target = "sitten" + expectedDistance = 1 + expectedSimilarity = 5.0 / 6.0 + gotDistance, gotSimilarity = Similarity(source, target) + if gotDistance != expectedDistance { + t.Errorf("Expected to get %d, now got %d", expectedDistance, gotDistance) + return + } + if gotSimilarity != expectedSimilarity { + t.Errorf("Expected to get %f, now got %f", expectedSimilarity, gotSimilarity) + return + } + + source = "Michael Jordan" + target = "Michael Jordan" + expectedDistance = 0 + expectedSimilarity = 1 + gotDistance, gotSimilarity = Similarity(source, target) + if gotDistance != expectedDistance { + t.Errorf("Expected to get %d, now got %d", expectedDistance, gotDistance) + return + } + if gotSimilarity != expectedSimilarity { + t.Errorf("Expected to get %f, now got %f", expectedSimilarity, gotSimilarity) + return + } +} diff --git a/trunk/goutil/stringUtil/split.go b/trunk/goutil/stringUtil/split.go new file mode 100644 index 0000000..9f5ec23 --- /dev/null +++ b/trunk/goutil/stringUtil/split.go @@ -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 +} diff --git a/trunk/goutil/stringUtil/split_test.go b/trunk/goutil/stringUtil/split_test.go new file mode 100644 index 0000000..dc86fb1 --- /dev/null +++ b/trunk/goutil/stringUtil/split_test.go @@ -0,0 +1,78 @@ +package stringUtil + +import ( + "fmt" + "testing" +) + +func TestSplit(t *testing.T) { + s := "1,2;3|4||5,6;7|8||9," + // seps := []string{",", ";", "|", "||"} + retList := Split(s, nil) + if retList[0] != "1" || retList[1] != "2" || retList[2] != "3" || retList[3] != "4" || retList[4] != "5" || retList[5] != "6" || retList[6] != "7" || retList[7] != "8" || retList[8] != "9" { + t.Errorf("ecptected:123456789, but now got:%v", retList) + } +} + +func TestSplitToIntSlice(t *testing.T) { + s := "1, 2, 3, 4, 5, a" + if _, err := SplitToIntSlice(s, ","); err == nil { + t.Errorf("Expected got err, but got nil") + } + + s = "1, 5, 39," + if intSlice, err := SplitToIntSlice(s, ","); err != nil { + t.Errorf("Expected got nil, but got error:%s", err) + } else { + // fmt.Printf("intSlice:%v\n", intSlice) + if intSlice[0] != 1 || intSlice[1] != 5 || intSlice[2] != 39 { + t.Errorf("Expected got %s, but got %v", s, intSlice) + } + } +} + +func TestSplitToIntRegion(t *testing.T) { + idRegionStr := "" + outerSep := "," + innerSep := "-" + var err error + + if _, err = SplitToIntRegion(idRegionStr, outerSep, innerSep); err == nil { + t.Errorf("PraseIdRegion should failed, but now not.err:%s", err) + } + + idRegionStr = "," + if _, err = SplitToIntRegion(idRegionStr, outerSep, innerSep); err == nil { + t.Errorf("PraseIdRegion should failed, but now not.err:%s", err) + } + + idRegionStr = "1-100,101,200" + if _, err = SplitToIntRegion(idRegionStr, outerSep, innerSep); err == nil { + t.Errorf("PraseIdRegion should failed, but now not.err:%s", err) + } + + idRegionStr = "1-100,101-20" + if _, err = SplitToIntRegion(idRegionStr, outerSep, innerSep); err == nil { + t.Errorf("PraseIdRegion should failed, but now not.err:%s", err) + } + + idRegionStr = "1-100,101-200" + if idRegionList, err := SplitToIntRegion(idRegionStr, outerSep, innerSep); err != nil { + t.Errorf("PraseIdRegion should succeed, but now failed.err:%s", err) + } else { + if idRegionList[0].Lower != 1 || idRegionList[0].Upper != 100 || + idRegionList[1].Lower != 101 || idRegionList[1].Upper != 200 { + t.Errorf("SplitToIntRegion should succeed, but now failed. idRegionStr:%s, idRegionList:%v", idRegionStr, idRegionList) + } + } +} + +func TestSplitToFloat64(t *testing.T) { + result, err := SplitToFloat64Slice("1.11,2.22", ",") + if err != nil { + t.Error(err) + return + } + + fmt.Printf("%v\n", result) +} diff --git a/trunk/goutil/stringUtil/string.go b/trunk/goutil/stringUtil/string.go new file mode 100644 index 0000000..a5085f2 --- /dev/null +++ b/trunk/goutil/stringUtil/string.go @@ -0,0 +1,142 @@ +package stringUtil + +import ( + "regexp" + "runtime" + "sort" + "strconv" + "strings" + "unicode" +) + +// 检查一个字符串是否是空字符串 +// content:上下文字符串 +// 返回值: +// bool:true:空字符串 false:非空字符串 +func IsEmpty(content string) bool { + if len(content) <= 0 { + return true + } + + return strings.IndexFunc(content, func(item rune) bool { + return unicode.IsSpace(item) == false + }) < 0 +} + +// 截取字符串 +// start:开始位置 +// length:截取长度 +// 返回值: +// 截取后的字符串 +func Substring(str string, start, length int) string { + // 先将字符串转化为[]rune格式(由于rune是字符串的基本单位) + runeString := []rune(str) + runeLength := len(runeString) + end := 0 + + // 计算起始位置 + if start > runeLength { + start = runeLength + } + + // 计算终止位置 + end = start + length + if end > runeLength { + end = runeLength + } + + if start > end { + start, end = end, start + } + + return string(runeString[start:end]) +} + +// 根据不同平台获取换行符 +// 返回值: +// 换行符 +func GetNewLineString() string { + switch os := runtime.GOOS; os { + case "windows": + return "\r\n" + default: + return "\n" + } +} + +// 检查是否存在特殊符号 +// 1. emoji字符 +// 2. ascii控制字符 +// 3. \ " ' +// val:待检查的字符串 +// 返回值: +// bool:true:有特殊字符 false:无特殊字符 +func IfHaveSpecialChar(val string) bool { + if len(val) <= 0 { + return false + } + + // 表情符号过滤 + // Wide UCS-4 build + emojiReg, _ := regexp.Compile("[^\U00000000-\U0000FFFF]+") + if emojiReg.Match([]byte(val)) { + return true + } + + // 排除控制字符和特殊字符 + for _, charItem := range val { + // 排除控制字符 + if (charItem > 0 && charItem < 0x20) || charItem == 0x7F { + return true + } + + // 排除部分特殊字符: \ " ' + switch charItem { + case '\\': + fallthrough + case '"': + fallthrough + case '\'': + return true + } + } + + return false +} + +// 判断string数组是否内容唯一 +func IsDistinct_string(list []string) (result bool) { + if len(list) == 0 || len(list) == 1 { + result = true + return + } + + sort.Strings(list) + + for i := 0; i < len(list)-1; i++ { + if list[i] == list[i+1] { + result = false + return + } + } + + result = true + return +} + +// 验证是否是电话号码 +func VerifyPhone(phone string) bool { + regular := "^1[3-9]\\d{9}$" + + reg := regexp.MustCompile(regular) + return reg.MatchString(phone) +} + +// 转型成int64 +func StringToInt64(str string) int64 { + num, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0 + } + return num +} diff --git a/trunk/goutil/stringUtil/string_builder.go b/trunk/goutil/stringUtil/string_builder.go new file mode 100644 index 0000000..61c37f0 --- /dev/null +++ b/trunk/goutil/stringUtil/string_builder.go @@ -0,0 +1,57 @@ +package stringUtil + +import ( + "bytes" + "fmt" +) + +type StringBuilder struct { + buffer bytes.Buffer +} + +func NewStringBuilder() *StringBuilder { + var builder StringBuilder + return &builder +} + +func (builder *StringBuilder) Append(format string, args ...interface{}) *StringBuilder { + if len(args) > 0 { + format = fmt.Sprintf(format, args...) + } + + builder.buffer.WriteString(format) + return builder +} + +func (builder *StringBuilder) AppendLine(format string, args ...interface{}) *StringBuilder { + if len(args) > 0 { + format = fmt.Sprintf(format, args...) + } + + builder.buffer.WriteString(format + "\n") + return builder +} + +func (builder *StringBuilder) AppendStrings(ss ...string) *StringBuilder { + for i := range ss { + builder.buffer.WriteString(ss[i]) + } + return builder +} + +func (builder *StringBuilder) AppendLines(ss ...string) *StringBuilder { + for i := range ss { + builder.buffer.WriteString(ss[i] + "\n") + } + return builder +} + +func (builder *StringBuilder) Clear() *StringBuilder { + var buffer bytes.Buffer + builder.buffer = buffer + return builder +} + +func (builder *StringBuilder) ToString() string { + return builder.buffer.String() +} diff --git a/trunk/goutil/stringUtil/string_test.go b/trunk/goutil/stringUtil/string_test.go new file mode 100644 index 0000000..31f65f4 --- /dev/null +++ b/trunk/goutil/stringUtil/string_test.go @@ -0,0 +1,120 @@ +package stringUtil + +import ( + "fmt" + "testing" +) + +// test IsEmpty +func TestIsEmpty(t *testing.T) { + isOk := IsEmpty("") + if isOk == false { + t.Error("\"\" test is Not pass") + return + } + + isOk = IsEmpty(" ") + if isOk == false { + t.Error("\" \" test is Not pass") + return + } + + isOk = IsEmpty(" \t\n") + if isOk == false { + t.Error("\" \\t\\n\" test is Not pass") + return + } +} + +func TestSubstr(t *testing.T) { + str := "Hello, Jordan.左贤清" + substr := Substring(str, 0, 5) + expectedstr := "Hello" + + if substr != expectedstr { + t.Errorf("Failed. Expected:%s, Got %s\n", expectedstr, substr) + } + + substr = Substring(str, 0, 10) + expectedstr = "Hello, Jor" + + if substr != expectedstr { + t.Errorf("Failed. Expected:%s, Got %s\n", expectedstr, substr) + } + + substr = Substring(str, 0, 15) + expectedstr = "Hello, Jordan.左" + + if substr != expectedstr { + t.Errorf("Failed. Expected:%s, Got %s\n", expectedstr, substr) + } + + substr = Substring(str, 0, 20) + expectedstr = "Hello, Jordan.左贤清" + + if substr != expectedstr { + t.Errorf("Failed. Expected:%s, Got %s\n", expectedstr, substr) + } + + guid1 := GetNewGUID() + guid2 := GetNewGUID() + fmt.Printf("guid1:%s, guid2:%s\n", guid1, guid2) + fmt.Printf("length of %s is %d\n", guid1, len(guid1)) + if guid1 == guid2 { + t.Errorf("%s should not be equal with %s", guid1, guid2) + } +} + +// test 特殊字符 +func TestIfHaveSpecialChar(t *testing.T) { + tstVal := map[string]string{ + "中文": "你好啊", + "繁体中文": "這是什麼天氣", + "泰文": "สวัสดีครับ !", + "英文": "helloworld", + "越南语": "Đó là gì thời tiết.", + "日语": "これは何の天気ですか", + "标点符号": "!@#$%^^&*())(__+{}[]|:<>", + } + + for key, val := range tstVal { + if IfHaveSpecialChar(val) { + t.Errorf("语言处理错误:%s", key) + } + } + + specialChar := "\\'\"" + if IfHaveSpecialChar(specialChar) == false { + t.Errorf("特殊字符匹配错误:") + } +} + +func TestIsDistinct_string(t *testing.T) { + list := make([]string, 0, 8) + result := IsDistinct_string(list) + fmt.Printf("list:%v,result:%v-------1\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------1") + } + + list = append(list, "Hello") + result = IsDistinct_string(list) + fmt.Printf("list:%v,result:%v-------2\n", list, result) + if result == false { + t.Errorf("it's should be true, but now false-------2") + } + + list = append(list, "Hello") + result = IsDistinct_string(list) + fmt.Printf("list:%v,result:%v-------3\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------3") + } + + list = append(list, "") + result = IsDistinct_string(list) + fmt.Printf("list:%v,result:%v-------4\n", list, result) + if result { + t.Errorf("it's should be false, but now true-------4") + } +} diff --git a/trunk/goutil/stringUtil/toString.go b/trunk/goutil/stringUtil/toString.go new file mode 100644 index 0000000..5da0cc3 --- /dev/null +++ b/trunk/goutil/stringUtil/toString.go @@ -0,0 +1,158 @@ +package stringUtil + +import ( + "bytes" + "fmt" + "reflect" +) + +// map转换为字符串(如果类型不匹配) +// data:map数据 +// separator1:间隔符1 +// separator1:间隔符2 +// 返回值: +// result:转换后的字符串 +// err:错误信息 +func MapToString(data interface{}, separator1, separator2 string) (result string, err error) { + if data == nil { + return + } + + val := reflect.ValueOf(data) + if val.Kind() != reflect.Map { + err = fmt.Errorf("只能转换Map类型的,当前类型是:%v", val.Kind().String()) + return + } + + if val.Len() <= 0 { + return + } + + for _, keyItem := range val.MapKeys() { + valItem := val.MapIndex(keyItem) + result = result + fmt.Sprintf("%v%s%v%s", keyItem.Interface(), separator1, valItem.Interface(), separator2) + } + + result = result[:len(result)-1] + + return +} + +// map转换为字符串(如果类型不匹配) +// data:map数据 +// separator1:间隔符1 +// separator1:间隔符2 +// valGetFunc:结果值获取函数 +// 返回值: +// result:转换后的字符串 +// err:错误信息 +func MapToString2(data interface{}, separator1, separator2 string, valGetFunc func(val interface{}) interface{}) (result string, err error) { + if data == nil { + return + } + + val := reflect.ValueOf(data) + if val.Kind() != reflect.Map { + err = fmt.Errorf("只能转换Map类型的,当前类型是:%v", val.Kind().String()) + return + } + + if val.Len() <= 0 { + return + } + + for _, keyItem := range val.MapKeys() { + valItem := val.MapIndex(keyItem) + result = result + fmt.Sprintf("%v%s%v%s", keyItem.Interface(), separator1, valGetFunc(valItem.Interface()), separator2) + } + + result = result[:len(result)-1] + + return +} + +// 把一个集合转换成字符串 +// data:slice类型的集合 +// separator:分隔符 +// 返回值: +// result:转换后的字符串 +// err:错误信息对象 +func SliceToString(data interface{}, separator string) (result string, err error) { + if data == nil { + return + } + + value := reflect.ValueOf(data) + if value.Kind() != reflect.Slice && value.Kind() != reflect.Array { + err = fmt.Errorf("目标类型不正确,只能是slice或array 当前类型是:%v", value.Kind().String()) + return + } + + if value.Len() <= 0 { + return + } + + for i := 0; i < value.Len(); i++ { + valItem := value.Index(i) + result = result + fmt.Sprintf("%s%v", separator, valItem.Interface()) + } + result = result[1:] + + return +} + +func StringListToString(list []string, delimiter string) string { + var buffer bytes.Buffer + for i, v := range list { + if i != len(list)-1 { + buffer.WriteString(v) + buffer.WriteString(delimiter) + } else { + buffer.WriteString(v) + } + } + + return buffer.String() +} + +func IntListToString(list []int, delimiter string) string { + var buffer bytes.Buffer + for i, v := range list { + if i != len(list)-1 { + buffer.WriteString(fmt.Sprintf("%d", v)) + buffer.WriteString(delimiter) + } else { + buffer.WriteString(fmt.Sprintf("%d", v)) + } + } + + return buffer.String() +} + +func Int32ListToString(list []int32, delimiter string) string { + var buffer bytes.Buffer + for i, v := range list { + if i != len(list)-1 { + buffer.WriteString(fmt.Sprintf("%d", v)) + buffer.WriteString(delimiter) + } else { + buffer.WriteString(fmt.Sprintf("%d", v)) + } + } + + return buffer.String() +} + +func Int64ListToString(list []int64, delimiter string) string { + var buffer bytes.Buffer + for i, v := range list { + if i != len(list)-1 { + buffer.WriteString(fmt.Sprintf("%d", v)) + buffer.WriteString(delimiter) + } else { + buffer.WriteString(fmt.Sprintf("%d", v)) + } + } + + return buffer.String() +} diff --git a/trunk/goutil/stringUtil/toString_test.go b/trunk/goutil/stringUtil/toString_test.go new file mode 100644 index 0000000..b045108 --- /dev/null +++ b/trunk/goutil/stringUtil/toString_test.go @@ -0,0 +1,169 @@ +package stringUtil + +import ( + "testing" +) + +func TestMapToString(t *testing.T) { + var data map[string]int + separator1 := "," + separator2 := ";" + + got, err := MapToString(data, separator1, separator2) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + data1 := make([]int, 0, 4) + data1 = append(data1, 1) + got, err = MapToString(data1, separator1, separator2) + if err == nil { + t.Errorf("There should be an error, but now there is not") + return + } + + data2 := make(map[string]int, 4) + data2["Jordan"] = 34 + data2["Thomas"] = 6 + expected1 := "Jordan,34;Thomas,6" + expected2 := "Thomas,6;Jordan,34" + + got, err = MapToString(data2, separator1, separator2) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + if got != expected1 && got != expected2 { + t.Errorf("Expected to get:%s or %s, but got:%s", expected1, expected2, got) + } +} + +func TestMapToString2(t *testing.T) { + var data map[string]int + separator1 := "," + separator2 := ";" + + got, err := MapToString2(data, separator1, separator2, valGetFunc) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + + data1 := make([]int, 0, 4) + data1 = append(data1, 1) + got, err = MapToString2(data1, separator1, separator2, valGetFunc) + if err == nil { + t.Errorf("There should be an error, but now there is not") + return + } + + data2 := make(map[string]int, 4) + data2["Jordan"] = 34 + data2["Thomas"] = 6 + expected1 := "Jordan,34;Thomas,6" + expected2 := "Thomas,6;Jordan,34" + + got, err = MapToString2(data2, separator1, separator2, valGetFunc) + if err != nil { + t.Errorf("There should be no error, but now there is:%s", err) + return + } + if got != expected1 && got != expected2 { + t.Errorf("Expected to get:%s or %s, but got:%s", expected1, expected2, got) + } +} + +func valGetFunc(val interface{}) interface{} { + return val +} + +func TestSliceToString(t *testing.T) { + // Test with nil value + var value interface{} = nil + expected := "" + got, err := SliceToString(value, ",") + if err != nil { + t.Errorf("There should be no error, but now got one. %s", err) + return + } + + // Test with wrong type value + value = "hello" + got, err = SliceToString(value, ",") + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with correct value + value = []int{1, 2, 3, 4, 5} + expected = "1,2,3,4,5" + + got, err = SliceToString(value, ",") + if err != nil { + t.Errorf("There should be no error, but now got one. %s", err) + return + } + + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } +} + +func TestStringListToString(t *testing.T) { + list := make([]string, 0, 4) + list = append(list, "Hello") + list = append(list, "World") + list = append(list, "Hello") + list = append(list, "Apple") + + expected := "Hello,World,Hello,Apple" + got := StringListToString(list, ",") + if expected != got { + t.Errorf("Expected:%s, but got:%s", expected, got) + } +} + +func TestIntListToString(t *testing.T) { + list := make([]int, 0, 4) + list = append(list, 1) + list = append(list, 2) + list = append(list, 3) + list = append(list, 4) + + expected := "1,2,3,4" + got := IntListToString(list, ",") + if expected != got { + t.Errorf("Expected:%s, but got:%s", expected, got) + } +} + +func TestInt64ListToString(t *testing.T) { + list := make([]int64, 0, 4) + list = append(list, 1) + list = append(list, 2) + list = append(list, 3) + list = append(list, 4) + + expected := "1,2,3,4" + got := Int64ListToString(list, ",") + if expected != got { + t.Errorf("Expected:%s, but got:%s", expected, got) + } +} + +func TestInt32ListToString(t *testing.T) { + list := make([]int32, 0, 4) + list = append(list, 1) + list = append(list, 2) + list = append(list, 3) + list = append(list, 4) + + expected := "1,2,3,4" + got := Int32ListToString(list, ",") + if expected != got { + t.Errorf("Expected:%s, but got:%s", expected, got) + } +} diff --git a/trunk/goutil/stringUtil/transform.go b/trunk/goutil/stringUtil/transform.go new file mode 100644 index 0000000..2d3db62 --- /dev/null +++ b/trunk/goutil/stringUtil/transform.go @@ -0,0 +1,190 @@ +package stringUtil + +import ( + "fmt" + "strconv" +) + +// 首字母小写 +func FirstCharToLower(str string) string { + if len(str) < 1 { + return "" + } + + runeArray := []rune(str) + if runeArray[0] >= 65 && runeArray[0] <= 90 { + runeArray[0] += 32 + } + return string(runeArray) +} + +// 首字母大写 +func FirstCharToUpper(str string) string { + if len(str) < 1 { + return "" + } + + runeArray := []rune(str) + if runeArray[0] >= 97 && runeArray[0] <= 122 { + runeArray[0] -= 32 + } + return string(runeArray) +} + +// 将形如1,2|3,4|5,6的字符串转化成map +// 返回值: +// map[string]string +// 错误对象 +func StringToMap_String_String(str string, seps []string) (data map[string]string, err error) { + strList := Split(str, seps) + if len(strList) == 0 { + err = fmt.Errorf("str is empty.") + return + } + + if len(strList)%2 != 0 { + err = fmt.Errorf("str has odd items.") + return + } + + data = make(map[string]string, len(strList)/2) + for i := 0; i < len(strList); i += 2 { + data[strList[i]] = strList[i+1] + } + + return +} + +// 将形如1,2|3,4|5,6的字符串转化成map +// 返回值: +// map[string]int +// 错误对象 +func StringToMap_String_Int(str string, seps []string) (data map[string]int, err error) { + strList := Split(str, seps) + if len(strList) == 0 { + err = fmt.Errorf("str is empty.") + return + } + + if len(strList)%2 != 0 { + err = fmt.Errorf("str has odd items.") + return + } + + data = make(map[string]int, len(strList)/2) + for i := 0; i < len(strList); i += 2 { + key := strList[i] + value, err1 := strconv.Atoi(strList[i+1]) + if err1 != nil { + err = fmt.Errorf("Type convertion failed. Value:%s, Error:%v", strList[i+1], err1) + return + } + data[key] = value + } + + return +} + +// 将形如1,2|3,4|5,6的字符串转化成map +// 返回值: +// map[int]int +// 错误对象 +func StringToMap_Int_Int(str string, seps []string) (data map[int]int, err error) { + strList := Split(str, seps) + if len(strList) == 0 { + err = fmt.Errorf("str is empty.") + return + } + + if len(strList)%2 != 0 { + err = fmt.Errorf("str has odd items.") + return + } + + data = make(map[int]int, len(strList)/2) + for i := 0; i < len(strList); i += 2 { + key, err1 := strconv.Atoi(strList[i]) + if err1 != nil { + err = fmt.Errorf("Type convertion failed. Value:%s, Error:%v", strList[i], err1) + return + } + value, err2 := strconv.Atoi(strList[i+1]) + if err2 != nil { + err = fmt.Errorf("Type convertion failed. Value:%s, Error:%v", strList[i+1], err2) + return + } + data[key] = value + } + + return +} + +// 将形如1,2|3,4|5,6的字符串转化成map +// 返回值: +// map[int32]int32 +// 错误对象 +func StringToMap_Int32_Int32(str string, seps []string) (data map[int32]int32, err error) { + strList := Split(str, seps) + if len(strList) == 0 { + err = fmt.Errorf("str is empty.") + return + } + + if len(strList)%2 != 0 { + err = fmt.Errorf("str has odd items.") + return + } + + data = make(map[int32]int32, len(strList)/2) + for i := 0; i < len(strList); i += 2 { + key, err1 := strconv.Atoi(strList[i]) + if err1 != nil { + err = fmt.Errorf("Type convertion failed. Value:%s, Error:%v", strList[i], err1) + return + } + value, err2 := strconv.Atoi(strList[i+1]) + if err2 != nil { + err = fmt.Errorf("Type convertion failed. Value:%s, Error:%v", strList[i+1], err2) + return + } + data[int32(key)] = int32(value) + } + + return +} + +// 将形如1,2|3,4|5,6的字符串转化成map +// 返回值: +// map[int32]int64 +// 错误对象 +func StringToMap_Int32_Int64(str string, seps []string) (data map[int32]int64, err error) { + strList := Split(str, seps) + if len(strList) == 0 { + err = fmt.Errorf("str is empty.") + return + } + + if len(strList)%2 != 0 { + err = fmt.Errorf("str has odd items.") + return + } + + data = make(map[int32]int64, len(strList)/2) + for i := 0; i < len(strList); i += 2 { + key, err1 := strconv.Atoi(strList[i]) + if err1 != nil { + err = fmt.Errorf("Type convertion failed. Value:%s, Error:%v", strList[i], err1) + return + } + + value, err2 := strconv.ParseInt(strList[i+1], 10, 64) + if err2 != nil { + err = fmt.Errorf("Type convertion failed. Value:%s, Error:%v", strList[i+1], err2) + return + } + + data[int32(key)] = value + } + + return +} diff --git a/trunk/goutil/stringUtil/transform_test.go b/trunk/goutil/stringUtil/transform_test.go new file mode 100644 index 0000000..712fe6a --- /dev/null +++ b/trunk/goutil/stringUtil/transform_test.go @@ -0,0 +1,259 @@ +package stringUtil + +import ( + "testing" +) + +func TestStringToMap_String_String(t *testing.T) { + str := "" + seps := []string{",", "|"} + data, err := StringToMap_String_String(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3" + data, err = StringToMap_String_String(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3,5" + data, err = StringToMap_String_String(str, seps) + if err != nil { + t.Errorf("Expected to get no error. But now there is one:%v.", err) + return + } + + expected := make(map[string]string, 2) + expected["1"] = "2" + expected["3"] = "5" + + if len(expected) != len(data) { + t.Errorf("The length of expected:%d is not equals to length of data:%d", len(expected), len(data)) + return + } + + for k, v := range data { + if v1, exists := expected[k]; !exists { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } else if v != v1 { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } + } +} + +func TestStringToMap_String_Int(t *testing.T) { + str := "" + seps := []string{",", "|"} + data, err := StringToMap_String_Int(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3" + data, err = StringToMap_String_Int(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3,abc" + data, err = StringToMap_String_Int(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3,5" + data, err = StringToMap_String_Int(str, seps) + if err != nil { + t.Errorf("Expected to get no error. But now there is one:%v.", err) + return + } + + expected := make(map[string]int, 2) + expected["1"] = 2 + expected["3"] = 5 + + if len(expected) != len(data) { + t.Errorf("The length of expected:%d is not equals to length of data:%d", len(expected), len(data)) + return + } + + for k, v := range data { + if v1, exists := expected[k]; !exists { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } else if v != v1 { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } + } +} + +func TestStringToMap_Int_Int(t *testing.T) { + str := "" + seps := []string{",", "|"} + data, err := StringToMap_Int_Int(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3" + data, err = StringToMap_Int_Int(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3,abc" + data, err = StringToMap_Int_Int(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|a,3" + data, err = StringToMap_Int_Int(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3,5" + data, err = StringToMap_Int_Int(str, seps) + if err != nil { + t.Errorf("Expected to get no error. But now there is one:%v.", err) + return + } + + expected := make(map[int]int, 2) + expected[1] = 2 + expected[3] = 5 + + if len(expected) != len(data) { + t.Errorf("The length of expected:%d is not equals to length of data:%d", len(expected), len(data)) + return + } + + for k, v := range data { + if v1, exists := expected[k]; !exists { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } else if v != v1 { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } + } +} + +func TestStringToMap_Int32_Int32(t *testing.T) { + str := "" + seps := []string{",", "|"} + data, err := StringToMap_Int32_Int32(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3" + data, err = StringToMap_Int32_Int32(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3,abc" + data, err = StringToMap_Int32_Int32(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|a,3" + data, err = StringToMap_Int32_Int32(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3,5" + data, err = StringToMap_Int32_Int32(str, seps) + if err != nil { + t.Errorf("Expected to get no error. But now there is one:%v.", err) + return + } + + expected := make(map[int32]int32, 2) + expected[1] = 2 + expected[3] = 5 + + if len(expected) != len(data) { + t.Errorf("The length of expected:%d is not equals to length of data:%d", len(expected), len(data)) + return + } + + for k, v := range data { + if v1, exists := expected[k]; !exists { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } else if v != v1 { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } + } +} + +func TestStringToMap_Int32_Int64(t *testing.T) { + str := "" + seps := []string{",", "|"} + data, err := StringToMap_Int32_Int64(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3" + data, err = StringToMap_Int32_Int64(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|3,abc" + data, err = StringToMap_Int32_Int64(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,2|a,3" + data, err = StringToMap_Int32_Int64(str, seps) + if err == nil { + t.Errorf("Expected to get an error. But now there isn't.") + return + } + + str = "1,36524569852|3,365245698521" + data, err = StringToMap_Int32_Int64(str, seps) + if err != nil { + t.Errorf("Expected to get no error. But now there is one:%v.", err) + return + } + + expected := make(map[int32]int64, 2) + expected[1] = 36524569852 + expected[3] = 365245698521 + + if len(expected) != len(data) { + t.Errorf("The length of expected:%d is not equals to length of data:%d", len(expected), len(data)) + return + } + + for k, v := range data { + if v1, exists := expected[k]; !exists { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } else if v != v1 { + t.Errorf("data is not equals to expected. %v, %v", expected, data) + } + } +} diff --git a/trunk/goutil/syncUtil/locker.go b/trunk/goutil/syncUtil/locker.go new file mode 100644 index 0000000..9308d58 --- /dev/null +++ b/trunk/goutil/syncUtil/locker.go @@ -0,0 +1,90 @@ +package syncUtil + +import ( + "fmt" + "runtime/debug" + "sync" + "time" +) + +// 写锁对象 +type Locker struct { + locking bool + prevStack []byte + mutex sync.Mutex +} + +// 内部锁 +// 返回值: +// 加锁是否成功 +func (this *Locker) lock() bool { + this.mutex.Lock() + defer this.mutex.Unlock() + + // 如果已经被锁定,则返回失败 + if this.locking { + return false + } + + // 否则进行锁定,并返回成功 + this.locking = true + + // 记录Stack信息 + if if_record_stack_info { + this.prevStack = debug.Stack() + } + + return true +} + +// 尝试加锁,如果在指定的时间内失败,则会返回失败;否则返回成功 +// timeout:指定的毫秒数,timeout<=0则将会死等 +// 返回值: +// 成功或失败 +// 如果失败,返回上一次成功加锁时的堆栈信息 +// 如果失败,返回当前的堆栈信息 +func (this *Locker) Lock(timeout int) (successful bool, prevStack string, currStack string) { + timeout = getTimeout(timeout) + + // 遍历指定的次数(即指定的超时时间) + for i := 0; i < timeout; i++ { + // 如果锁定成功,则返回成功 + if this.lock() { + successful = true + break + } + + // 如果锁定失败,则休眠1ms,然后再重试 + time.Sleep(time.Millisecond) + } + + // 如果时间结束仍然是失败,则返回上次成功的堆栈信息,以及当前的堆栈信息 + if successful == false { + if this.prevStack != nil && len(this.prevStack) > 0 { + prevStack = string(this.prevStack) + } + currStack = string(debug.Stack()) + } + + return +} + +// 锁定(死等方式) +func (this *Locker) WaitLock() { + successful, prevStack, currStack := this.Lock(0) + if successful == false { + fmt.Printf("Locker.WaitLock():{PrevStack:%s, currStack:%s}\n", prevStack, currStack) + } +} + +// 解锁 +func (this *Locker) Unlock() { + this.mutex.Lock() + defer this.mutex.Unlock() + this.locking = false +} + +// 创建新的锁对象 +func NewLocker() *Locker { + return &Locker{} +} diff --git a/trunk/goutil/syncUtil/lockerUtil.go b/trunk/goutil/syncUtil/lockerUtil.go new file mode 100644 index 0000000..9467cfb --- /dev/null +++ b/trunk/goutil/syncUtil/lockerUtil.go @@ -0,0 +1,58 @@ +package syncUtil + +import ( + "sync" +) + +// 锁工具类 +type LockerUtil struct { + // 锁集合 + lockerMap map[string]*Locker + + // 锁对象 + rwMutex sync.RWMutex +} + +// 创建新的锁工具类 +func NewLockerUtil() *LockerUtil { + return &LockerUtil{ + lockerMap: make(map[string]*Locker, 8), + } +} + +// 获取锁对象 +// lockName:锁名 +// 返回值: +// *Locker:锁对象 +func (this *LockerUtil) GetLock(lockName string) *Locker { + var lockerObj *Locker + var exists bool + + func() { + this.rwMutex.RLock() + defer this.rwMutex.RUnlock() + lockerObj, exists = this.lockerMap[lockName] + }() + if exists { + return lockerObj + } + + this.rwMutex.Lock() + defer this.rwMutex.Unlock() + + lockerObj, exists = this.lockerMap[lockName] + if !exists { + lockerObj = NewLocker() + this.lockerMap[lockName] = lockerObj + } + + return lockerObj +} + +// 释放锁对象 +// lockName:锁名 +func (this *LockerUtil) ReleaseLock(lockName string) { + this.rwMutex.Lock() + defer this.rwMutex.Unlock() + delete(this.lockerMap, lockName) +} diff --git a/trunk/goutil/syncUtil/lockerUtil_test.go b/trunk/goutil/syncUtil/lockerUtil_test.go new file mode 100644 index 0000000..a4f9c7b --- /dev/null +++ b/trunk/goutil/syncUtil/lockerUtil_test.go @@ -0,0 +1,27 @@ +package syncUtil + +import ( + "fmt" + "testing" +) + +var ( + lockerUtilObj = NewLockerUtil() +) + +func LockerUtil_TestGetLock(t *testing.T) { + count := 100 + for i := 1; i <= count; i++ { + lockerUtilObj.GetLock(fmt.Sprintf("%d", i)) + if lockerCount := len(lockerUtilObj.lockerMap); lockerCount != i { + t.Errorf("(GetLock)Expected %d locker, but now got: %d", count, lockerCount) + } + } + + for i := count; i > 0; i-- { + lockerUtilObj.ReleaseLock(fmt.Sprintf("%d", i)) + if lockerCount := len(lockerUtilObj.lockerMap); lockerCount != i-1 { + t.Errorf("(ReleaseLock)Expected %d locker, but now got: %d", count, lockerCount) + } + } +} diff --git a/trunk/goutil/syncUtil/locker_test.go b/trunk/goutil/syncUtil/locker_test.go new file mode 100644 index 0000000..5cc2f76 --- /dev/null +++ b/trunk/goutil/syncUtil/locker_test.go @@ -0,0 +1,98 @@ +package syncUtil + +import ( + "fmt" + "testing" + "time" +) + +func TestNewLocker1(t *testing.T) { + count := 1000000 + succeedCount := 0 + expected := 1000000 + goroutineCount := 1 + + lockerObj := NewLocker() + ch := make(chan bool, goroutineCount) + + for i := 0; i < goroutineCount; i++ { + go lockerTest(lockerObj, &succeedCount, count/goroutineCount, ch) + } + + for i := 0; i < goroutineCount; i++ { + <-ch + } + + if succeedCount != expected { + t.Errorf("Expected %d, but got %d", expected, succeedCount) + } +} + +func TestNewLocker2(t *testing.T) { + count := 1000000 + succeedCount := 0 + expected := 1000000 + goroutineCount := 100 + + lockerObj := NewLocker() + ch := make(chan bool, goroutineCount) + + for i := 0; i < goroutineCount; i++ { + go lockerTest(lockerObj, &succeedCount, count/goroutineCount, ch) + } + + for i := 0; i < goroutineCount; i++ { + <-ch + } + + if succeedCount != expected { + t.Errorf("Expected %d, but got %d", expected, succeedCount) + } +} + +func TestNewLocker3(t *testing.T) { + count := 1000000 + succeedCount := 0 + expected := 1000000 + goroutineCount := 10000 + + lockerObj := NewLocker() + ch := make(chan bool, goroutineCount) + + for i := 0; i < goroutineCount; i++ { + go lockerTest(lockerObj, &succeedCount, count/goroutineCount, ch) + } + + for i := 0; i < goroutineCount; i++ { + <-ch + } + + if succeedCount != expected { + t.Errorf("Expected %d, but got %d", expected, succeedCount) + } +} + +func TestNewLocker4(t *testing.T) { + lockerObj := NewLocker() + if successful, _, _ := lockerObj.Lock(100); successful == false { + t.Errorf("Lock should be successful, but now it fails.") + } + + if successful, _, _ := lockerObj.Lock(100); successful { + t.Errorf("Lock should be failed, but now it succeeds.") + } +} + +func lockerTest(lockerObj *Locker, succeedCount *int, count int, ch chan bool) { + if success, _, _ := lockerObj.Lock(10000); !success { + fmt.Printf("[%v]获取锁超时\n", time.Now()) + return + } + defer lockerObj.Unlock() + + for i := 0; i < count; i++ { + *succeedCount += 1 + } + + ch <- true +} diff --git a/trunk/goutil/syncUtil/mutex.go b/trunk/goutil/syncUtil/mutex.go new file mode 100644 index 0000000..d8bd6ad --- /dev/null +++ b/trunk/goutil/syncUtil/mutex.go @@ -0,0 +1,171 @@ +package syncUtil + +import ( + "context" + "sync" + "time" +) + +// Set the behavier on unlock unlocked mutex +var PanicOnBug = true + +// another mutex implementation with TryLock method +type Mutex interface { + Lock() + UnLock() + // TryLock return true if it fetch mutex + TryLock() bool + // TryLockTimeout return true if it fetch mutex, return false if timeout + TryLockTimeout(timeout time.Duration) bool + // TryLockTimeout return true if it fetch mutex, return false if context done + TryLockContext(ctx context.Context) bool +} + +func NewMutex() Mutex { + m := &mutex{ch: make(chan struct{}, 1)} + m.ch <- struct{}{} + return m +} + +type mutex struct { + ch chan struct{} +} + +func (m *mutex) Lock() { + <-m.ch +} + +func (m *mutex) UnLock() { + select { + case m.ch <- struct{}{}: + default: + if PanicOnBug { + panic("unlock of unlocked mutex") + } + } +} + +func (m *mutex) TryLock() bool { + select { + case <-m.ch: + return true + default: + } + return false +} + +func (m *mutex) TryLockTimeout(timeout time.Duration) bool { + tm := time.NewTimer(timeout) + select { + case <-m.ch: + tm.Stop() + return true + case <-tm.C: + } + return false +} + +func (m *mutex) TryLockContext(ctx context.Context) bool { + select { + case <-m.ch: + return true + case <-ctx.Done(): + } + return false +} + +type MutexGroup interface { + Lock(i interface{}) + UnLock(i interface{}) + UnLockAndFree(i interface{}) + // TryLock return true if it fetch mutex + TryLock(i interface{}) bool + // TryLockTimeout return true if it fetch mutex, return false if timeout + TryLockTimeout(i interface{}, timeout time.Duration) bool + // TryLockTimeout return true if it fetch mutex, return false if context done + TryLockContext(i interface{}, ctx context.Context) bool +} + +func NewMutexGroup() MutexGroup { + return &mutexGroup{group: make(map[interface{}]*entry)} +} + +type mutexGroup struct { + mu sync.Mutex + group map[interface{}]*entry +} + +type entry struct { + ref int + mu Mutex +} + +func (m *mutexGroup) get(i interface{}, ref int) Mutex { + m.mu.Lock() + defer m.mu.Unlock() + en, ok := m.group[i] + if !ok { + if ref > 0 { + en = &entry{mu: NewMutex()} + m.group[i] = en + } else if PanicOnBug { + panic("unlock of unlocked mutex") + } else { + return nil + } + } + en.ref += ref + return en.mu +} + +func (m *mutexGroup) Lock(i interface{}) { + m.get(i, 1).Lock() +} + +func (m *mutexGroup) UnLock(i interface{}) { + mu := m.get(i, -1) + if mu != nil { + mu.UnLock() + } +} + +func (m *mutexGroup) UnLockAndFree(i interface{}) { + m.mu.Lock() + defer m.mu.Unlock() + en, ok := m.group[i] + if !ok { + if PanicOnBug { + panic("unlock of unlocked mutex") + } + return + } + en.ref-- + if en.ref == 0 { + delete(m.group, i) + } + en.mu.UnLock() +} + +func (m *mutexGroup) TryLock(i interface{}) bool { + locked := m.get(i, 1).TryLock() + if !locked { + m.get(i, -1) + } + return locked +} + +func (m *mutexGroup) TryLockTimeout(i interface{}, timeout time.Duration) bool { + locked := m.get(i, 1).TryLockTimeout(timeout) + if !locked { + m.get(i, -1) + } + return locked +} + +func (m *mutexGroup) TryLockContext(i interface{}, ctx context.Context) bool { + locked := m.get(i, 1).TryLockContext(ctx) + if !locked { + m.get(i, -1) + } + return locked +} diff --git a/trunk/goutil/syncUtil/mutex_test.go b/trunk/goutil/syncUtil/mutex_test.go new file mode 100644 index 0000000..18cd929 --- /dev/null +++ b/trunk/goutil/syncUtil/mutex_test.go @@ -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 +} diff --git a/trunk/goutil/syncUtil/rwLocker.go b/trunk/goutil/syncUtil/rwLocker.go new file mode 100644 index 0000000..b7f2eb0 --- /dev/null +++ b/trunk/goutil/syncUtil/rwLocker.go @@ -0,0 +1,178 @@ +/* +通过在RWLocker对象中引入writeProtectEndTime(写锁保护结束时间),来提高获取写锁的成功率。 +当写锁获取失败时,就设置一个写锁保护结束时间,在这段时间内,只允许写锁进行获取,而读锁的获取请求会被拒绝。 +通过重置写锁保护结束时间的时机,对写锁的优先级程度进行调整。有两个重置写锁保护结束时间的时机: +1、在成功获取到写锁时:此时重置,有利于下一个写锁需求者在当前写锁持有者处理逻辑时设置保护时间,从而当当前写锁持有者释放锁时,下一个写锁需求者可以立刻获得写锁; +2、在写锁解锁时:此时重置,给了读锁和写锁的需求者同样的机会进行锁的竞争机会; +综上:RWLocker可以提供3中级别的写锁优先级: +1、高级:在获取写锁失败时设置写锁保护结束时间;在获取写锁成功时重置。 +2、中级:在获取写锁失败时设置写锁保护结束时间;在释放锁时重置。 +3、无:不设置写锁保护时间。 +*/ +package syncUtil + +import ( + "fmt" + "runtime/debug" + "sync" + "time" +) + +// 读写锁对象 +type RWLocker struct { + read int + write int // 使用int而不是bool值的原因,是为了与read保持类型的一致; + writeProtectEndTime int64 // 写锁保护结束时间。如果当前时间小于该值,则会阻塞读锁请求;以便于提高写锁的优先级,避免连续的读锁导致写锁无法获得; + prevStack []byte + mutex sync.Mutex +} + +// 尝试加写锁 +// 返回值:加写锁是否成功 +func (this *RWLocker) lock() bool { + this.mutex.Lock() + defer this.mutex.Unlock() + + // 如果已经被锁定,则返回失败;并且设置写锁保护结束时间;以便于写锁可以优先竞争锁; + if this.write == 1 || this.read > 0 { + this.writeProtectEndTime = time.Now().UnixNano() + con_Write_Protect_Nanoseconds + return false + } + + // 否则,将写锁数量设置为1,并返回成功;并重置写锁保护结束时间;这样读锁和写锁都可以参与锁的竞争; + this.write = 1 + this.writeProtectEndTime = time.Now().UnixNano() + + // 记录Stack信息 + if if_record_stack_info { + this.prevStack = debug.Stack() + } + + return true +} + +// 写锁定 +// timeout:超时毫秒数,timeout<=0则将会死等 +// 返回值: +// 成功或失败 +// 如果失败,返回上一次成功加锁时的堆栈信息 +// 如果失败,返回当前的堆栈信息 +func (this *RWLocker) Lock(timeout int) (successful bool, prevStack string, currStack string) { + timeout = getTimeout(timeout) + + // 遍历指定的次数(即指定的超时时间) + for i := 0; i < timeout; i++ { + // 如果锁定成功,则返回成功 + if this.lock() { + successful = true + break + } + + // 如果锁定失败,则休眠1ms,然后再重试 + time.Sleep(time.Millisecond) + } + + // 如果时间结束仍然是失败,则返回上次成功的堆栈信息,以及当前的堆栈信息 + if successful == false { + if this.prevStack != nil && len(this.prevStack) > 0 { + prevStack = string(this.prevStack) + } + currStack = string(debug.Stack()) + } + + return +} + +// 写锁定(死等) +func (this *RWLocker) WaitLock() { + successful, prevStack, currStack := this.Lock(0) + if successful == false { + fmt.Printf("RWLocker:WaitLock():{PrevStack:%s, currStack:%s}\n", prevStack, currStack) + } +} + +// 释放写锁 +func (this *RWLocker) Unlock() { + this.mutex.Lock() + defer this.mutex.Unlock() + this.write = 0 + //释放写锁保护时间,避免调时间导致读锁超时 + this.writeProtectEndTime = 0 +} + +// 尝试加读锁 +// 返回值:加读锁是否成功 +func (this *RWLocker) rlock() bool { + this.mutex.Lock() + defer this.mutex.Unlock() + + // 如果已经被锁定,或者处于写锁保护时间段内,则返回失败 + if this.write == 1 || time.Now().UnixNano() < this.writeProtectEndTime { + return false + } + + // 否则,将读锁数量加1,并返回成功 + this.read += 1 + + // 记录Stack信息 + if if_record_stack_info { + this.prevStack = debug.Stack() + } + + return true +} + +// 读锁定 +// timeout:超时毫秒数,timeout<=0则将会死等 +// 返回值: +// 成功或失败 +// 如果失败,返回上一次成功加锁时的堆栈信息 +// 如果失败,返回当前的堆栈信息 +func (this *RWLocker) RLock(timeout int) (successful bool, prevStack string, currStack string) { + timeout = getTimeout(timeout) + + // 遍历指定的次数(即指定的超时时间) + // 读锁比写锁优先级更低,所以每次休眠2ms,所以尝试的次数就是时间/2 + for i := 0; i < timeout; i++ { + // 如果锁定成功,则返回成功 + if this.rlock() { + successful = true + break + } + + // 如果锁定失败,则休眠1ms,然后再重试 + time.Sleep(time.Millisecond) + } + + // 如果时间结束仍然是失败,则返回上次成功的堆栈信息,以及当前的堆栈信息 + if successful == false { + if this.prevStack != nil && len(this.prevStack) > 0 { + prevStack = string(this.prevStack) + } + currStack = string(debug.Stack()) + } + + return +} + +// 读锁定(死等) +func (this *RWLocker) WaitRLock() { + successful, prevStack, currStack := this.RLock(0) + if successful == false { + fmt.Printf("RWLocker:WaitRLock():{PrevStack:%s, currStack:%s}\n", prevStack, currStack) + } +} + +// 释放读锁 +func (this *RWLocker) RUnlock() { + this.mutex.Lock() + defer this.mutex.Unlock() + if this.read > 0 { + this.read -= 1 + } +} + +// 创建新的读写锁对象 +func NewRWLocker() *RWLocker { + return &RWLocker{} +} diff --git a/trunk/goutil/syncUtil/rwLockerUtil.go b/trunk/goutil/syncUtil/rwLockerUtil.go new file mode 100644 index 0000000..34e6f24 --- /dev/null +++ b/trunk/goutil/syncUtil/rwLockerUtil.go @@ -0,0 +1,58 @@ +package syncUtil + +import ( + "sync" +) + +// 读写锁工具类 +type RWLockerUtil struct { + // 锁集合 + lockerMap map[string]*RWLocker + + // 锁对象 + rwMutex sync.RWMutex +} + +// 创建新的锁工具类 +func NewRWLockerUtil() *RWLockerUtil { + return &RWLockerUtil{ + lockerMap: make(map[string]*RWLocker, 8), + } +} + +// 获取锁对象 +// lockName:锁名 +// 返回值: +// RWLocker:读写锁对象 +func (this *RWLockerUtil) GetLock(lockName string) *RWLocker { + var rwLockerObj *RWLocker + var exists bool + + func() { + this.rwMutex.RLock() + defer this.rwMutex.RUnlock() + rwLockerObj, exists = this.lockerMap[lockName] + }() + if exists { + return rwLockerObj + } + + this.rwMutex.Lock() + defer this.rwMutex.Unlock() + + rwLockerObj, exists = this.lockerMap[lockName] + if exists == false { + rwLockerObj = NewRWLocker() + this.lockerMap[lockName] = rwLockerObj + } + + return rwLockerObj +} + +// 释放读写锁对象 +// lockName:锁名 +func (this *RWLockerUtil) ReleaseLock(lockName string) { + this.rwMutex.Lock() + defer this.rwMutex.Unlock() + delete(this.lockerMap, lockName) +} diff --git a/trunk/goutil/syncUtil/rwLockerUtil_test.go b/trunk/goutil/syncUtil/rwLockerUtil_test.go new file mode 100644 index 0000000..4931c7e --- /dev/null +++ b/trunk/goutil/syncUtil/rwLockerUtil_test.go @@ -0,0 +1,27 @@ +package syncUtil + +import ( + "fmt" + "testing" +) + +var ( + rwLockerUtilObj = NewRWLockerUtil() +) + +func RWLockerUtil_TestGetLock(t *testing.T) { + count := 100 + for i := 1; i <= count; i++ { + rwLockerUtilObj.GetLock(fmt.Sprintf("%d", i)) + if lockerCount := len(rwLockerUtilObj.lockerMap); lockerCount != i { + t.Errorf("(GetLock)Expected %d locker, but now got: %d", count, lockerCount) + } + } + + for i := count; i > 0; i-- { + rwLockerUtilObj.ReleaseLock(fmt.Sprintf("%d", i)) + if lockerCount := len(rwLockerUtilObj.lockerMap); lockerCount != i-1 { + t.Errorf("(ReleaseLock)Expected %d locker, but now got: %d", count, lockerCount) + } + } +} diff --git a/trunk/goutil/syncUtil/rwLocker_test.go b/trunk/goutil/syncUtil/rwLocker_test.go new file mode 100644 index 0000000..22389d2 --- /dev/null +++ b/trunk/goutil/syncUtil/rwLocker_test.go @@ -0,0 +1,154 @@ +package syncUtil + +import ( + "fmt" + "testing" + "time" +) + +func TestRWNewLocker1(t *testing.T) { + count := 1000000 + succeedCount := 0 + expected := 1000000 + goroutineCount := 1 + + lockerObj := NewRWLocker() + ch := make(chan bool, goroutineCount) + + for i := 0; i < goroutineCount; i++ { + go rwLockerTest(lockerObj, &succeedCount, count/goroutineCount, ch) + } + + for i := 0; i < goroutineCount; i++ { + <-ch + } + + if succeedCount != expected { + t.Errorf("Expected %d, but got %d", expected, succeedCount) + } +} + +func TestRWNewLocker2(t *testing.T) { + count := 1000000 + succeedCount := 0 + expected := 1000000 + goroutineCount := 100 + + lockerObj := NewRWLocker() + ch := make(chan bool, goroutineCount) + + for i := 0; i < goroutineCount; i++ { + go rwLockerTest(lockerObj, &succeedCount, count/goroutineCount, ch) + } + + for i := 0; i < goroutineCount; i++ { + <-ch + } + + if succeedCount != expected { + t.Errorf("Expected %d, but got %d", expected, succeedCount) + } +} + +func TestRWNewLocker3(t *testing.T) { + count := 1000000 + succeedCount := 0 + expected := 1000000 + goroutineCount := 10000 + + lockerObj := NewRWLocker() + ch := make(chan bool, goroutineCount) + + for i := 0; i < goroutineCount; i++ { + go rwLockerTest(lockerObj, &succeedCount, count/goroutineCount, ch) + } + + for i := 0; i < goroutineCount; i++ { + <-ch + } + + if succeedCount != expected { + t.Errorf("Expected %d, but got %d", expected, succeedCount) + } +} + +func TestRWNewLocker4(t *testing.T) { + lockerObj := NewRWLocker() + if successful, _, _ := lockerObj.RLock(100); successful == false { + t.Errorf("It should be successful to get a read lock, but now it fails.") + return + } + if successful, _, _ := lockerObj.RLock(100); successful == false { + t.Errorf("It should be successful to get a read lock, but now it fails.") + return + } + if successful, _, _ := lockerObj.RLock(100); successful == false { + t.Errorf("It should be successful to get a read lock, but now it fails.") + return + } + lockerObj.RUnlock() + lockerObj.RUnlock() + lockerObj.RUnlock() + + if successful, _, _ := lockerObj.Lock(100); successful == false { + t.Errorf("It should be successful to get a write lock, but now it fails.") + return + } + if successful, _, _ := lockerObj.Lock(100); successful { + t.Errorf("It should be failed to get a write lock, but now it succeeds.") + return + } + if successful, _, _ := lockerObj.RLock(100); successful { + t.Errorf("It should be failed to get a read lock, but now it succeeds.") + return + } + + lockerObj.Unlock() +} + +func TestRWNewLocker5(t *testing.T) { + count := 100 + rwLockerObj := NewRWLocker() + ch := make(chan bool, 100) + + for i := 0; i < count; i++ { + go func(num int, ch chan bool) { + if num%2 == 0 { + if successful, _, _ := rwLockerObj.Lock(100); successful { + fmt.Println("I get write lock.") + time.Sleep(2 * time.Millisecond) + rwLockerObj.Unlock() + } else { + fmt.Println("Write lock timeout") + } + } else { + if successful, _, _ := rwLockerObj.RLock(100); successful { + fmt.Println("I get read lock.") + time.Sleep(2 * time.Millisecond) + rwLockerObj.RUnlock() + } else { + fmt.Println("Read lock timeout") + } + } + ch <- true + }(i, ch) + } + + for i := 0; i < count; i++ { + <-ch + } +} + +func rwLockerTest(lockerObj *RWLocker, succeedCount *int, count int, ch chan bool) { + if success, _, _ := lockerObj.Lock(10000); !success { + fmt.Printf("[%v]获取锁超时\n", time.Now()) + return + } + defer lockerObj.Unlock() + + for i := 0; i < count; i++ { + *succeedCount += 1 + } + + ch <- true +} diff --git a/trunk/goutil/syncUtil/syncUtil.go b/trunk/goutil/syncUtil/syncUtil.go new file mode 100644 index 0000000..aadb398 --- /dev/null +++ b/trunk/goutil/syncUtil/syncUtil.go @@ -0,0 +1,33 @@ +/* +由于Go不提供超时锁,所以自己实现了支持超时机制的互斥锁Locker和读写锁RWLocker。 +为了方便供第三方程序使用,提供了根据Key获取超时互斥锁和超时读写锁的复合对象LockerUtil和RWLockerUtil。 +为了在出现锁超时时方便查找问题,会记录上次成功获得锁时的堆栈信息;并且在本次获取锁失败时,同时返回上次成功时的堆栈信息和本次的堆栈信息。 +*/ +package syncUtil + +const ( + // 默认超时的毫秒数(1小时) + con_Default_Timeout_Milliseconds = 60 * 60 * 1000 + + // 写锁保护时间(纳秒) + con_Write_Protect_Nanoseconds = 5 * 1000 * 1000 +) + +var ( + // 是否记录堆栈信息的状态 + if_record_stack_info = false +) + +// 获取超时时间 +func getTimeout(timeout int) int { + if timeout > 0 { + return timeout + } else { + return con_Default_Timeout_Milliseconds + } +} + +// 设置是否记录堆栈信息的状态 +func SetIfRecordStackInfo(value bool) { + if_record_stack_info = value +} diff --git a/trunk/goutil/timeUtil/check.go b/trunk/goutil/timeUtil/check.go new file mode 100644 index 0000000..4701a44 --- /dev/null +++ b/trunk/goutil/timeUtil/check.go @@ -0,0 +1,87 @@ +package timeUtil + +import ( + "time" +) + +// 检查一个日期的时分秒 是否在指定的时间范围内 +// checkTime:待检查的时间 +// timeSpan1:时间范围1 +// addSecond1:需要加上的时间偏差值 +// timeSpan2:时间范围2 +// addSecond2:需要加上的时间偏差值 +func CheckIfInRange(checkTime time.Time, timeSpan1 string, addSecond1 int, timeSpan2 string, addSecond2 int) bool { + var ( + hour1, minute1, second1 int + hour2, minute2, second2 int + ) + + // 取出字符串的时分秒 + _, hour1, minute1, second1 = ParseTimeString(timeSpan1) + _, hour2, minute2, second2 = ParseTimeString(timeSpan2) + + // 取出当前时间的时分秒 + checkTime = checkTime.Local() + hour := checkTime.Hour() + minute := checkTime.Minute() + second := checkTime.Second() + + // 转换成时间值 + val := int64(time.Hour)*int64(hour) + int64(time.Minute)*int64(minute) + int64(time.Second)*int64(second) + val1 := int64(time.Hour)*int64(hour1) + int64(time.Minute)*int64(minute1) + int64(time.Second)*int64((second1+addSecond1)) + val2 := int64(time.Hour)*int64(hour2) + int64(time.Minute)*int64(minute2) + int64(time.Second)*int64((second2+addSecond2)) + + if val1 <= val && val <= val2 { + return true + } + + if val2 <= val && val <= val1 { + return true + } + + return false +} + +// 检查一个日期的时分秒 是否在指定的时间范围内 +// checkTime:待检查的时间 +// timeSpan1:时间范围1 +// timeSpan2:时间范围2 +func CheckIfInRange2(checkTime time.Time, timeSpan1 string, timeSpan2 string) bool { + return CheckIfInRange(checkTime, timeSpan1, 0, timeSpan2, 0) +} + +// 检查两个日期是否在同一天 +// time1:时间1 +// time2:时间2 +// 返回值: +// bool:true:在同一天 false:不在同一天 +func CheckIfInSameDate(time1, time2 time.Time) bool { + y1, m1, d1 := time1.Date() + y2, m2, d2 := time2.Date() + + return y1 == y2 && m1 == m2 && d1 == d2 +} + +/* + 下面的时间判断函数,是为了解决time.Time的比较问题 + 可以去掉单调时钟的影响 + see: https://cloud.tencent.com/developer/article/2129493 +*/ + +// After 比较两个时间,t1是否在t2之后,如果是,返回true,否则返回false +// 封装这个方法的原因是,可以去掉单调时钟的影响 +func After(t1, t2 time.Time) bool { + return t1.Local().After(t2.Local()) +} + +// Before 比较两个时间,t1是否在t2之前,如果是,返回true,否则返回false +// 封装这个方法的原因是,可以去掉单调时钟的影响 +func Before(t1, t2 time.Time) bool { + return t1.Local().Before(t2.Local()) +} + +// Equal 比较两个时间,t1是否等于t2,如果是,返回true,否则返回false +// 封装这个方法的原因是,可以去掉单调时钟的影响 +func Equal(t1, t2 time.Time) bool { + return t1.Local().Equal(t2.Local()) +} diff --git a/trunk/goutil/timeUtil/check_test.go b/trunk/goutil/timeUtil/check_test.go new file mode 100644 index 0000000..c4c07b5 --- /dev/null +++ b/trunk/goutil/timeUtil/check_test.go @@ -0,0 +1,227 @@ +package timeUtil + +import ( + "testing" + "time" +) + +// 检查是否在范围内的常规测试 +func TestCheckIfInRange(t *testing.T) { + // Check with correct data + timeSpan1 := "13:00:00" + timeSpan2 := "21:00:00" + + t1, err := ToDateTime("2017-09-04 15:00:00") + if err != nil { + t.Error(err) + return + } + + if CheckIfInRange(t1, timeSpan1, 0, timeSpan2, 0) == false { + t.Fail() + return + } + + if CheckIfInRange(t1, timeSpan2, 0, timeSpan1, 0) == false { + t.Fail() + return + } + + if CheckIfInRange(t1, timeSpan1, 0, timeSpan2, 5) == false { + t.Fail() + return + } + + if CheckIfInRange(t1, timeSpan2, 0, timeSpan1, 5) == false { + t.Fail() + return + } + + // Check with incorrect data + t1, err = ToDateTime("2017-09-04 22:00:00") + if err != nil { + t.Error(err) + return + } + + if CheckIfInRange(t1, timeSpan1, 0, timeSpan2, 0) { + t.Fail() + return + } + + if CheckIfInRange(t1, timeSpan2, 0, timeSpan1, 0) { + t.Fail() + return + } +} + +func TestCheckIfInRange2(t *testing.T) { + timeSpan1 := "13:00:00" + timeSpan2 := "21:00:00" + + t1, err := ToDateTime("2017-09-04 15:00:00") + if err != nil { + t.Error(err) + return + } + + if CheckIfInRange2(t1, timeSpan1, timeSpan2) == false { + t.Fail() + return + } + + if CheckIfInRange2(t1, timeSpan2, timeSpan1) == false { + t.Fail() + return + } + + if CheckIfInRange2(t1, timeSpan1, timeSpan2) == false { + t.Fail() + return + } + + if CheckIfInRange2(t1, timeSpan2, timeSpan1) == false { + t.Fail() + return + } + + t1, err = ToDateTime("2017-09-04 22:00:00") + if err != nil { + t.Error(err) + return + } + + if CheckIfInRange2(t1, timeSpan1, timeSpan2) { + t.Fail() + return + } + + if CheckIfInRange2(t1, timeSpan2, timeSpan1) { + t.Fail() + return + } +} + +func TestCheckIfInSameDate(t *testing.T) { + // Check with the same date time + t1, err := ToDateTime("2017-09-04 00:00:00") + if err != nil { + t.Error(err) + return + } + t2, err := ToDateTime("2017-09-04 15:00:00") + if err != nil { + t.Error(err) + return + } + + if CheckIfInSameDate(t1, t2) == false { + t.Fail() + return + } + + // Check with different date time + t2, err = ToDateTime("2019-12-26 15:00:00") + if err != nil { + t.Error(err) + return + } + + if CheckIfInSameDate(t1, t2) { + t.Fail() + return + } +} + +func TestAfter(t *testing.T) { + // Check with the same date time + t1, err := ToDateTime("2017-09-04 02:00:00") + if err != nil { + t.Error(err) + return + } + t1 = t1.In(time.UTC) + + t2, err := ToDateTime("2017-09-04 01:00:00") + if err != nil { + t.Error(err) + return + } + t2 = t2.In(time.Local) + + if After(t2, t1) { + t.Fail() + return + } + + if After(t1, t2) == false { + t.Fail() + return + } +} + +func TestBefore(t *testing.T) { + // Check with the same date time + t1, err := ToDateTime("2017-09-04 02:00:00") + if err != nil { + t.Error(err) + return + } + t1 = t1.In(time.UTC) + + t2, err := ToDateTime("2017-09-04 01:00:00") + if err != nil { + t.Error(err) + return + } + t2 = t2.In(time.Local) + + if Before(t2, t1) == false { + t.Fail() + return + } + + if Before(t1, t2) { + t.Fail() + return + } +} + +func TestEqual(t *testing.T) { + // Check with the same date time + t1, err := ToDateTime("2017-09-04 02:00:00") + if err != nil { + t.Error(err) + return + } + t1 = t1.In(time.UTC) + + t2, err := ToDateTime("2017-09-04 01:00:00") + if err != nil { + t.Error(err) + return + } + t2 = t2.In(time.Local) + + t3, err := ToDateTime("2017-09-04 01:00:00") + if err != nil { + t.Error(err) + return + } + + if Equal(t2, t1) { + t.Fail() + return + } + + if Equal(t1, t2) { + t.Fail() + return + } + + if Equal(t2, t3) == false { + t.Fail() + return + } + +} diff --git a/trunk/goutil/timeUtil/convert.go b/trunk/goutil/timeUtil/convert.go new file mode 100644 index 0000000..6480a8d --- /dev/null +++ b/trunk/goutil/timeUtil/convert.go @@ -0,0 +1,102 @@ +package timeUtil + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +// 将字符串转换为标准的时间格式 +// str:输入,格式为:2015-10-25T17:07:30 +// 返回值: +// 标准时间格式对象 +// 错误对象 +func ConverToStandardFormat(str string) (result time.Time, err error) { + newStr := strings.Replace(str, "T", ":", -1) + newStr = strings.Replace(newStr, "-", ":", -1) + newStr = strings.Replace(newStr, ".", ":", -1) + + slice := strings.Split(newStr, ":") + slice = slice[:6] // 只取前6位(表示年-月-日 时:分:秒) + + intSlice := make([]int, len(slice)) + for index, item := range slice { + if intItem, err1 := strconv.Atoi(item); err1 != nil { + err = fmt.Errorf("输入字符串的格式错误:%s,转换后的格式为:%s", str, newStr) + return + } else { + intSlice[index] = intItem + } + } + + result = time.Date(intSlice[0], time.Month(intSlice[1]), intSlice[2], intSlice[3], intSlice[4], intSlice[5], 0, time.Local) + return +} + +// 将时间转换为int类型(20160120,共8位) +// t:时间 +// 返回值: +// int类型的数字 +func ConvertToInt(t time.Time) int { + year := int(t.Year()) + month := int(t.Month()) + day := int(t.Day()) + + return year*10e3 + month*10e1 + day +} + +// 计算两个时间的日期差值 +func SubDay(time1, time2 time.Time) int { + // 当前时间距离00:00:00的秒数 + awayFromZero := func(val time.Time) int64 { + hour := val.Hour() + minute := val.Minute() + second := val.Second() + return int64(hour*3600 + minute*60 + second) + } + + // 每天对应的秒数 + var eachDaySecond int64 = 24 * 3600 + + // 计算出两个时间对应的00:00:00时的时间戳 + unix1 := time1.Unix() - awayFromZero(time1) + unix2 := time2.Unix() - awayFromZero(time2) + + if unix1 < unix2 { + return int((unix2 - unix1) / eachDaySecond) + } else { + return int((unix1 - unix2) / eachDaySecond) + } +} + +// 解析时间字符串,要求时间格式形式为:12:59:59 这种形式 +// timeStr:时间字符串 +// 返回值: +// err:错误信息 +// hour:小时值 +// minute:分钟值 +// second:秒数 +func ParseTimeString(timeStr string) (err error, hour int, minute int, second int) { + timeSlice := strings.Split(timeStr, ":") + if len(timeSlice) != 3 { + err = fmt.Errorf("时间字符串格式不正确:%v", timeStr) + return + } + + hour, _ = strconv.Atoi(timeSlice[0]) + minute, _ = strconv.Atoi(timeSlice[1]) + second, _ = strconv.Atoi(timeSlice[2]) + + return +} + +// 获取时间的日期值 +// timeVal:时间值 +// 返回值: +// time.Time:日期值 +func GetDate(timeVal time.Time) time.Time { + year, month, day := timeVal.Date() + + return time.Date(year, month, day, 0, 0, 0, 0, timeVal.Location()) +} diff --git a/trunk/goutil/timeUtil/convert_test.go b/trunk/goutil/timeUtil/convert_test.go new file mode 100644 index 0000000..f264a26 --- /dev/null +++ b/trunk/goutil/timeUtil/convert_test.go @@ -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() + } +} diff --git a/trunk/goutil/timeUtil/doc.go b/trunk/goutil/timeUtil/doc.go new file mode 100644 index 0000000..5a3794f --- /dev/null +++ b/trunk/goutil/timeUtil/doc.go @@ -0,0 +1,4 @@ +/* +提供时间相关的一些助手方法 +*/ +package timeUtil diff --git a/trunk/goutil/timeUtil/format.go b/trunk/goutil/timeUtil/format.go new file mode 100644 index 0000000..cb6a69c --- /dev/null +++ b/trunk/goutil/timeUtil/format.go @@ -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) +} diff --git a/trunk/goutil/timeUtil/format_test.go b/trunk/goutil/timeUtil/format_test.go new file mode 100644 index 0000000..c4b3541 --- /dev/null +++ b/trunk/goutil/timeUtil/format_test.go @@ -0,0 +1,29 @@ +package timeUtil + +import ( + "fmt" + "testing" + "time" +) + +func TestFormat(t *testing.T) { + now := time.Date(2015, 9, 11, 10, 10, 10, 0, time.Local) + expectedString := "2015/09/11" + result := Format(now, "yyyy/MM/dd") + if result != expectedString { + t.Errorf("Format Error, expected %s, Got %s", expectedString, result) + } + + expectedString = "2015-09-11 %d:%s:%d" + minutes := "" + if now.Minute() >= 10 { + minutes = fmt.Sprintf("%d", now.Minute()) + } else { + minutes = fmt.Sprintf("0%d", now.Minute()) + } + expectedString = fmt.Sprintf(expectedString, now.Hour(), minutes, now.Second()) + result = Format(now, "yyyy-MM-dd HH:mm:ss") + if result != expectedString { + t.Errorf("Format Error, expected %s, Got %s", expectedString, result) + } +} diff --git a/trunk/goutil/timeUtil/timeZone.go b/trunk/goutil/timeUtil/timeZone.go new file mode 100644 index 0000000..c2c480f --- /dev/null +++ b/trunk/goutil/timeUtil/timeZone.go @@ -0,0 +1,64 @@ +package timeUtil + +import ( + "time" +) + +// 把时区转换到UTC时区,但时间值不变(去掉时区的影响) +func GetUTCTime(timeVal time.Time) time.Time { + _, offset := timeVal.Zone() + timeVal = timeVal.Add(time.Duration(offset) * time.Second) + timeVal = timeVal.In(time.UTC) + + return timeVal +} + +// 把时间转换成本地时区,但时间值不变(去掉时区的影响) +func GetLocalTime(timeVal time.Time) time.Time { + // 获取本地时区的时间偏移 + tmpVal := time.Now() + _, localOffset := tmpVal.Zone() + + // 获取指定时间值的时区偏移 + _, timeOffset := timeVal.Zone() + timeVal = timeVal.Add(time.Duration(timeOffset-localOffset) * time.Second) + timeVal = timeVal.In(time.Local) + + return timeVal +} + +// 增加本地时区的值到指定时间上 +func AddLocalTimeZone(timeVal int64) int64 { + // 获取本地时区的时间偏移 + tmpVal := time.Now() + _, localOffset := tmpVal.Zone() + + return timeVal + int64(localOffset) +} + +// 增加本地时区的值到指定时间上 +func AddLocalTimeZone2(timeVal time.Time) time.Time { + // 获取本地时区的时间偏移 + tmpVal := time.Now() + _, localOffset := tmpVal.Zone() + + return timeVal.Add(time.Duration(localOffset) * time.Second) +} + +// 减去本地时区到指定时间上 +func SubLocalTimeZone(timeVal int64) int64 { + // 获取本地时区的时间偏移 + tmpVal := time.Now() + _, localOffset := tmpVal.Zone() + + return timeVal + -1*int64(localOffset) +} + +// 减去本地时区到指定时间上 +func SubLocalTimeZone2(timeVal time.Time) time.Time { + // 获取本地时区的时间偏移 + tmpVal := time.Now() + _, localOffset := tmpVal.Zone() + + return timeVal.Add(-1 * time.Duration(localOffset) * time.Second) +} diff --git a/trunk/goutil/timeUtil/timeZone_test.go b/trunk/goutil/timeUtil/timeZone_test.go new file mode 100644 index 0000000..bf69716 --- /dev/null +++ b/trunk/goutil/timeUtil/timeZone_test.go @@ -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时间不对等") + } +} diff --git a/trunk/goutil/typeUtil/bool.go b/trunk/goutil/typeUtil/bool.go new file mode 100644 index 0000000..5d5e3cc --- /dev/null +++ b/trunk/goutil/typeUtil/bool.go @@ -0,0 +1,25 @@ +package typeUtil + +// bool值转换为int +// value:待转换的值 +// 返回值: +// bool:转换结果 +func BoolToInt(value bool) int { + if value { + return 1 + } else { + return 0 + } +} + +// int转换为bool值 +// value:待转换的值 +// 返回值: +// bool:转换结果 +func IntToBool(value int) bool { + if value > 0 { + return true + } else { + return false + } +} diff --git a/trunk/goutil/typeUtil/bool_test.go b/trunk/goutil/typeUtil/bool_test.go new file mode 100644 index 0000000..b424d0f --- /dev/null +++ b/trunk/goutil/typeUtil/bool_test.go @@ -0,0 +1,63 @@ +package typeUtil + +import ( + "testing" +) + +func TestBoolToInt(t *testing.T) { + // Test with true value + value := true + expected := 1 + got := BoolToInt(value) + if expected != got { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with false value + value = false + expected = 0 + got = BoolToInt(value) + if expected != got { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestIntToBool(t *testing.T) { + // Test with 0 value + value := 0 + expected := false + got := IntToBool(value) + if expected != got { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with negative value + value = -1 + expected = false + got = IntToBool(value) + if expected != got { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with positive value + value = 1 + expected = true + got = IntToBool(value) + if expected != got { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with bigger positive value + value = 100 + expected = true + got = IntToBool(value) + if expected != got { + t.Errorf("Expected %t, but got %t", expected, got) + return + } +} diff --git a/trunk/goutil/typeUtil/doc.go b/trunk/goutil/typeUtil/doc.go new file mode 100644 index 0000000..a3b48b2 --- /dev/null +++ b/trunk/goutil/typeUtil/doc.go @@ -0,0 +1,4 @@ +/* +数据类型转换类(主要针对interface) +*/ +package typeUtil diff --git a/trunk/goutil/typeUtil/interface.go b/trunk/goutil/typeUtil/interface.go new file mode 100644 index 0000000..983bfb5 --- /dev/null +++ b/trunk/goutil/typeUtil/interface.go @@ -0,0 +1,1229 @@ +package typeUtil + +import ( + "fmt" + "reflect" + "strconv" + "time" + + "goutil/timeUtil" +) + +// 字节数据类型转换(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Byte(val interface{}) (result byte, err error) { + return Uint8(val) +} + +// 字节数据类型转换(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func ByteArray(valArray []interface{}) (result []uint8, err error) { + return Uint8Array(valArray) +} + +// 类型转换为int(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Int(val interface{}) (result int, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = int(val.(int)) + case uint: + result = int(val.(uint)) + case int8: + result = int(val.(int8)) + case uint8: + result = int(val.(uint8)) + case int16: + result = int(val.(int16)) + case uint16: + result = int(val.(uint16)) + case int32: + result = int(val.(int32)) + case uint32: + result = int(val.(uint32)) + case int64: + result = int(val.(int64)) + case uint64: + result = int(val.(uint64)) + case float32: + result = int(val.(float32)) + case float64: + result = int(val.(float64)) + case string: + var tmp int64 + tmp, err = strconv.ParseInt(val.(string), 10, 64) + if err != nil { + return + } + result = int(tmp) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为Int列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func IntArray(valArray []interface{}) (result []int, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]int, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Int(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为int8(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Int8(val interface{}) (result int8, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = int8(val.(int)) + case uint: + result = int8(val.(uint)) + case int8: + result = int8(val.(int8)) + case uint8: + result = int8(val.(uint8)) + case int16: + result = int8(val.(int16)) + case uint16: + result = int8(val.(uint16)) + case int32: + result = int8(val.(int32)) + case uint32: + result = int8(val.(uint32)) + case int64: + result = int8(val.(int64)) + case uint64: + result = int8(val.(uint64)) + case float32: + result = int8(val.(float32)) + case float64: + result = int8(val.(float64)) + case string: + var tmp int64 + tmp, err = strconv.ParseInt(val.(string), 10, 64) + if err != nil { + return + } + result = int8(tmp) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为int8列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func Int8Array(valArray []interface{}) (result []int8, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]int8, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Int8(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为int16(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Int16(val interface{}) (result int16, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = int16(val.(int)) + case uint: + result = int16(val.(uint)) + case int8: + result = int16(val.(int8)) + case uint8: + result = int16(val.(uint8)) + case int16: + result = int16(val.(int16)) + case uint16: + result = int16(val.(uint16)) + case int32: + result = int16(val.(int32)) + case uint32: + result = int16(val.(uint32)) + case int64: + result = int16(val.(int64)) + case uint64: + result = int16(val.(uint64)) + case float32: + result = int16(val.(float32)) + case float64: + result = int16(val.(float64)) + case string: + var tmp int64 + tmp, err = strconv.ParseInt(val.(string), 10, 64) + if err != nil { + return + } + result = int16(tmp) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为int16列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func Int16Array(valArray []interface{}) (result []int16, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]int16, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Int16(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为int32(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Int32(val interface{}) (result int32, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = int32(val.(int)) + case uint: + result = int32(val.(uint)) + case int8: + result = int32(val.(int8)) + case uint8: + result = int32(val.(uint8)) + case int16: + result = int32(val.(int16)) + case uint16: + result = int32(val.(uint16)) + case int32: + result = int32(val.(int32)) + case uint32: + result = int32(val.(uint32)) + case int64: + result = int32(val.(int64)) + case uint64: + result = int32(val.(uint64)) + case float32: + result = int32(val.(float32)) + case float64: + result = int32(val.(float64)) + case string: + var tmp int64 + tmp, err = strconv.ParseInt(val.(string), 10, 64) + if err != nil { + return + } + result = int32(tmp) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为int32列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func Int32Array(valArray []interface{}) (result []int32, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]int32, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Int32(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为int64(转换过程不是类型安全的) +// 返回值: +// result:结果 +// err:错误数据 +func Int64(val interface{}) (result int64, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = int64(val.(int)) + case uint: + result = int64(val.(uint)) + case int8: + result = int64(val.(int8)) + case uint8: + result = int64(val.(uint8)) + case int16: + result = int64(val.(int16)) + case uint16: + result = int64(val.(uint16)) + case int32: + result = int64(val.(int32)) + case uint32: + result = int64(val.(uint32)) + case int64: + result = int64(val.(int64)) + case uint64: + result = int64(val.(uint64)) + case float32: + result = int64(val.(float32)) + case float64: + result = int64(val.(float64)) + case string: + var tmp int64 + tmp, err = strconv.ParseInt(val.(string), 10, 64) + if err != nil { + return + } + result = tmp + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为int64列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func Int64Array(valArray []interface{}) (result []int64, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]int64, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Int64(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为uint(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Uint(val interface{}) (result uint, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = uint(val.(int)) + case uint: + result = uint(val.(uint)) + case int8: + result = uint(val.(int8)) + case uint8: + result = uint(val.(uint8)) + case int16: + result = uint(val.(int16)) + case uint16: + result = uint(val.(uint16)) + case int32: + result = uint(val.(int32)) + case uint32: + result = uint(val.(uint32)) + case int64: + result = uint(val.(int64)) + case uint64: + result = uint(val.(uint64)) + case float32: + result = uint(val.(float32)) + case float64: + result = uint(val.(float64)) + case string: + var tmp uint64 + tmp, err = strconv.ParseUint(val.(string), 10, 64) + if err != nil { + return + } + result = uint(tmp) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为uint列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func UintArray(valArray []interface{}) (result []uint, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]uint, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Uint(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// uint8数据类型转换(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Uint8(val interface{}) (result uint8, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = uint8(val.(int)) + case uint: + result = uint8(val.(uint)) + case int8: + result = uint8(val.(int8)) + case uint8: + result = uint8(val.(uint8)) + case int16: + result = uint8(val.(int16)) + case uint16: + result = uint8(val.(uint16)) + case int32: + result = uint8(val.(int32)) + case uint32: + result = uint8(val.(uint32)) + case int64: + result = uint8(val.(int64)) + case uint64: + result = uint8(val.(uint64)) + case float32: + result = uint8(val.(float32)) + case float64: + result = uint8(val.(float64)) + case string: + var tmp uint64 + tmp, err = strconv.ParseUint(val.(string), 10, 64) + if err != nil { + return + } + result = uint8(tmp) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// uint8数据类型转换(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Uint8Array(valArray []interface{}) (result []uint8, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]uint8, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Uint8(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为uint16(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Uint16(val interface{}) (result uint16, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = uint16(val.(int)) + case uint: + result = uint16(val.(uint)) + case int8: + result = uint16(val.(int8)) + case uint8: + result = uint16(val.(uint8)) + case int16: + result = uint16(val.(int16)) + case uint16: + result = uint16(val.(uint16)) + case int32: + result = uint16(val.(int32)) + case uint32: + result = uint16(val.(uint32)) + case int64: + result = uint16(val.(int64)) + case uint64: + result = uint16(val.(uint64)) + case float32: + result = uint16(val.(float32)) + case float64: + result = uint16(val.(float64)) + case string: + var tmp uint64 + tmp, err = strconv.ParseUint(val.(string), 10, 64) + if err != nil { + return + } + result = uint16(tmp) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为uint16列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func Uint16Array(valArray []interface{}) (result []uint16, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]uint16, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Uint16(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为uint32(转换过程不是类型安全的) +// val:待转换的值 +// 返回值: +// result:结果 +// err:错误数据 +func Uint32(val interface{}) (result uint32, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = uint32(val.(int)) + case uint: + result = uint32(val.(uint)) + case int8: + result = uint32(val.(int8)) + case uint8: + result = uint32(val.(uint8)) + case int16: + result = uint32(val.(int16)) + case uint16: + result = uint32(val.(uint16)) + case int32: + result = uint32(val.(int32)) + case uint32: + result = uint32(val.(uint32)) + case int64: + result = uint32(val.(int64)) + case uint64: + result = uint32(val.(uint64)) + case float32: + result = uint32(val.(float32)) + case float64: + result = uint32(val.(float64)) + case string: + var tmp uint64 + tmp, err = strconv.ParseUint(val.(string), 10, 64) + if err != nil { + return + } + result = uint32(tmp) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为uint32列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func Uint32Array(valArray []interface{}) (result []uint32, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]uint32, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Uint32(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为uint64(转换过程不是类型安全的) +// 返回值: +// result:结果 +// err:错误数据 +func Uint64(val interface{}) (result uint64, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = uint64(val.(int)) + case uint: + result = uint64(val.(uint)) + case int8: + result = uint64(val.(int8)) + case uint8: + result = uint64(val.(uint8)) + case int16: + result = uint64(val.(int16)) + case uint16: + result = uint64(val.(uint16)) + case int32: + result = uint64(val.(int32)) + case uint32: + result = uint64(val.(uint32)) + case int64: + result = uint64(val.(int64)) + case uint64: + result = uint64(val.(uint64)) + case float32: + result = uint64(val.(float32)) + case float64: + result = uint64(val.(float64)) + case string: + var tmp uint64 + tmp, err = strconv.ParseUint(val.(string), 10, 64) + if err != nil { + return + } + result = tmp + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为uint64列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func Uint64Array(valArray []interface{}) (result []uint64, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]uint64, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Uint64(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为float32(转换过程不是类型安全的) +// 返回值: +// result:结果 +// err:错误数据 +func Float32(val interface{}) (result float32, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = float32(val.(int)) + case uint: + result = float32(val.(uint)) + case int8: + result = float32(val.(int8)) + case uint8: + result = float32(val.(uint8)) + case int16: + result = float32(val.(int16)) + case uint16: + result = float32(val.(uint16)) + case int32: + result = float32(val.(int32)) + case uint32: + result = float32(val.(uint32)) + case int64: + result = float32(val.(int64)) + case uint64: + result = float32(val.(uint64)) + case float32: + result = float32(val.(float32)) + case float64: + result = float32(val.(float64)) + case string: + tmp, err1 := strconv.ParseFloat(val.(string), 64) + if err1 != nil { + err = fmt.Errorf("string convert error") + return + } + + result = float32(tmp) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为float32列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func Float32Array(valArray []interface{}) (result []float32, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]float32, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Float32(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为float64(转换过程不是类型安全的) +// 返回值: +// result:结果 +// err:错误数据 +func Float64(val interface{}) (result float64, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = float64(val.(int)) + case uint: + result = float64(val.(uint)) + case int8: + result = float64(val.(int8)) + case uint8: + result = float64(val.(uint8)) + case int16: + result = float64(val.(int16)) + case uint16: + result = float64(val.(uint16)) + case int32: + result = float64(val.(int32)) + case uint32: + result = float64(val.(uint32)) + case int64: + result = float64(val.(int64)) + case uint64: + result = float64(val.(uint64)) + case float32: + result = float64(val.(float32)) + case float64: + result = float64(val.(float64)) + case string: + tmp, err1 := strconv.ParseFloat(val.(string), 64) + if err1 != nil { + err = fmt.Errorf("string convert error") + return + } + + result = tmp + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为Int列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func Float64Array(valArray []interface{}) (result []float64, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]float64, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Float64(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为bool(转换过程不是类型安全的) +// 返回值: +// result:结果 +// err:错误数据 +func Bool(val interface{}) (result bool, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = (val.(int)) > 0 + case uint: + result = (val.(uint)) > 0 + case int8: + result = (val.(int8)) > 0 + case uint8: + result = (val.(uint8)) > 0 + case int16: + result = (val.(int16)) > 0 + case uint16: + result = (val.(uint16)) > 0 + case int32: + result = (val.(int32)) > 0 + case uint32: + result = (val.(uint32)) > 0 + case int64: + result = (val.(int64)) > 0 + case uint64: + result = (val.(uint64)) > 0 + case float32: + result = int(val.(float32)) > 0 + case float64: + result = int(val.(float64)) > 0 + case bool: + result = val.(bool) + case string: + tmp1, err1 := strconv.ParseBool(val.(string)) + if err1 != nil { + // 先尝试转换成数值,再进行bool转换 + tmp2, err2 := strconv.ParseFloat(val.(string), 64) + if err2 != nil { + err = fmt.Errorf("string convert error") + return + } + + result = int(tmp2) > 0 + break + } + result = tmp1 + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为Int列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func BoolArray(valArray []interface{}) (result []bool, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]bool, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := Bool(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换为字符串(转换过程不是类型安全的) +// 返回值: +// result:结果 +// err:错误数据 +func String(val interface{}) (result string, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case int: + result = strconv.FormatInt(int64(val.(int)), 10) + case uint: + result = strconv.FormatUint(uint64(val.(uint)), 10) + case int8: + result = strconv.FormatInt(int64(val.(int8)), 10) + case uint8: + result = strconv.FormatUint(uint64(val.(uint8)), 10) + case int16: + result = strconv.FormatInt(int64(val.(int16)), 10) + case uint16: + result = strconv.FormatUint(uint64(val.(uint16)), 10) + case int32: + result = strconv.FormatInt(int64(val.(int32)), 10) + case uint32: + result = strconv.FormatUint(uint64(val.(uint32)), 10) + case int64: + result = strconv.FormatInt(int64(val.(int64)), 10) + case uint64: + result = strconv.FormatUint(uint64(val.(uint64)), 10) + case float32: + result = strconv.FormatFloat(float64(val.(float32)), 'f', -1, 32) + case float64: + result = strconv.FormatFloat(val.(float64), 'f', -1, 64) + case string: + result = val.(string) + default: + err = fmt.Errorf("val is not base type") + } + + return +} + +// 转换为Int列表(转换过程不是类型安全的) +// valArray:待转换的数据列表 +// 返回值: +// result:结果 +// err:错误数据 +func StringArray(valArray []interface{}) (result []string, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]string, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := String(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 转换成时间格式 +// val:待转换的数据,如果是字符串,则要求是格式:2006-01-02 15:04:05 +// result:结果 +// err:错误数据 +func DateTime(val interface{}) (result time.Time, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case time.Time: + result = val.(time.Time) + case string: + result, err = timeUtil.ToDateTime(val.(string)) + case int, int64, float32, float64: + intVal, err1 := Int64(val) + if err1 != nil { + err = err1 + return + } + result = time.Unix(intVal, 0).Local() + default: + err = fmt.Errorf("unknown data type") + } + + return +} + +// 转换成时间格式 +// valArray:待转换的数据,如果是字符串,则要求是格式:2006-01-02 15:04:05 +// result:结果 +// err:错误数据 +func DateTimeArray(valArray []interface{}) (result []time.Time, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]time.Time, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := DateTime(item) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 转换成时间格式 +// val:待转换的数据,如果是字符串,则使用format进行转换 +// format:时间格式 +// result:结果 +// err:错误数据 +func DateTimeByFormat(val interface{}, format string) (result time.Time, err error) { + if val == nil { + err = fmt.Errorf("val is nil") + return + } + + switch val.(type) { + case time.Time: + result = val.(time.Time) + case string: + result, err = time.ParseInLocation(val.(string), format, time.Local) + case int, int64, float32, float64: + intVal, err1 := Int64(val) + if err1 != nil { + err = err1 + return + } + result = time.Unix(intVal, 0).Local() + default: + err = fmt.Errorf("unknown data type") + } + + return +} + +// 转换成时间格式 +// valArray:待转换的数据,如果是字符串,则使用format进行转换 +// format:时间格式 +// result:结果 +// err:错误数据 +func DateTimeArrayByFormat(valArray []interface{}, format string) (result []time.Time, err error) { + if valArray == nil { + err = fmt.Errorf("valArray is nil") + return + } + + result = make([]time.Time, 0, len(valArray)) + for _, item := range valArray { + tmp, err1 := DateTimeByFormat(item, format) + if err1 != nil { + err = err1 + return + } + + result = append(result, tmp) + } + + return +} + +// 类型转换(基础数据类型) +// val:原始值 +// targetType:目标值类型 +// 返回值: +// interface{}:结果 +// error:错误信息 +func Convert(val interface{}, targetType reflect.Kind) (result interface{}, err error) { + switch targetType { + case reflect.Int: + result, err = Int(val) + case reflect.Int8: + result, err = Int8(val) + case reflect.Int16: + result, err = Int16(val) + case reflect.Int32: + result, err = Int32(val) + case reflect.Int64: + result, err = Int64(val) + case reflect.Uint: + result, err = Uint(val) + case reflect.Uint8: + result, err = Uint8(val) + case reflect.Uint16: + result, err = Uint16(val) + case reflect.Uint32: + result, err = Uint32(val) + case reflect.Uint64: + result, err = Uint64(val) + case reflect.Float32: + result, err = Float32(val) + case reflect.Float64: + result, err = Float64(val) + case reflect.Bool: + result, err = Bool(val) + case reflect.String: + result, err = String(val) + default: + err = fmt.Errorf("Unknown DataType:%s", targetType.String()) + } + + return +} diff --git a/trunk/goutil/typeUtil/interface_test.go b/trunk/goutil/typeUtil/interface_test.go new file mode 100644 index 0000000..3fd05b4 --- /dev/null +++ b/trunk/goutil/typeUtil/interface_test.go @@ -0,0 +1,3487 @@ +package typeUtil + +import ( + "reflect" + "testing" + "time" +) + +func TestInt(t *testing.T) { + var val interface{} + expected := int(100) + + // Test with value == nil + got, err := Int(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Int(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Int(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Int(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Int(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Int(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Int(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Int(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Int(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Int(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Int(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Int(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Int(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Int(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Int(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Int(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestIntArray(t *testing.T) { + got, err := IntArray(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []int{1, 2} + + got, err = IntArray(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestInt8(t *testing.T) { + var val interface{} + expected := int8(100) + + // Test with value == nil + got, err := Int8(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Int8(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Int8(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Int8(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Int8(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Int8(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Int8(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Int8(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Int8(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Int8(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Int8(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Int8(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Int8(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Int8(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Int8(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Int8(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestInt8Array(t *testing.T) { + got, err := Int8Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []int8{1, 2} + + got, err = Int8Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestInt16(t *testing.T) { + var val interface{} + expected := int16(100) + + // Test with value == nil + got, err := Int16(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Int16(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Int16(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Int16(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Int16(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Int16(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Int16(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Int16(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Int16(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Int16(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Int16(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Int16(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Int16(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Int16(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Int16(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Int16(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestInt16Array(t *testing.T) { + got, err := Int16Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []int16{1, 2} + + got, err = Int16Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestInt32(t *testing.T) { + var val interface{} + expected := int32(100) + + // Test with value == nil + got, err := Int32(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Int32(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Int32(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Int32(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Int32(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Int32(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Int32(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Int32(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Int32(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Int32(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Int32(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Int32(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Int32(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Int32(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Int32(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Int32(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestInt32Array(t *testing.T) { + got, err := Int32Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []int32{1, 2} + + got, err = Int32Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestInt64(t *testing.T) { + var val interface{} + expected := int64(5630344025715245057) + + // Test with value == nil + got, err := Int64(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int64 type + val_int64 := int64(5630344025715245057) + got, err = Int64(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(5630344025715245057) + got, err = Int64(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(5630344025715245057) + got, err = Int64(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "5630344025715245057" + got, err = Int64(val_string1) + if err != nil { + t.Errorf("There should be an error, but now there isn't: %v", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + val_string2 := "5630344025715245057" + got, err = Int64(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestInt64Array(t *testing.T) { + got, err := Int64Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []int64{1, 2} + + got, err = Int64Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestUint(t *testing.T) { + var val interface{} + expected := uint(100) + + // Test with value == nil + got, err := Uint(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Uint(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Uint(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Uint(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Uint(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Uint(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Uint(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Uint(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Uint(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Uint(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Uint(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Uint(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Uint(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Uint(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Uint(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Uint(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestUintArray(t *testing.T) { + got, err := UintArray(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []uint{1, 2} + + got, err = UintArray(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestUint8(t *testing.T) { + var val interface{} + expected := uint8(100) + + // Test with value == nil + got, err := Uint8(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Uint8(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Uint8(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Uint8(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Uint8(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Uint8(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Uint8(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Uint8(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Uint8(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Uint8(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Uint8(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Uint8(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Uint8(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Uint8(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Uint8(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Uint8(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestUint8Array(t *testing.T) { + got, err := Uint8Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []uint8{1, 2} + + got, err = Uint8Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestByte(t *testing.T) { + var val interface{} + expected := byte(100) + + // Test with value == nil + got, err := Byte(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Byte(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Byte(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Byte(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Byte(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Byte(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Byte(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Byte(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Byte(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Byte(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Byte(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Byte(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Byte(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Byte(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Byte(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Byte(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestByteArray(t *testing.T) { + got, err := ByteArray(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []byte{1, 2} + + got, err = ByteArray(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestUint16(t *testing.T) { + var val interface{} + expected := uint16(100) + + // Test with value == nil + got, err := Uint16(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Uint16(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Uint16(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Uint16(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Uint16(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Uint16(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Uint16(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Uint16(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Uint16(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Uint16(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Uint16(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Uint16(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Uint16(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Uint16(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Uint16(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Uint16(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestUint16Array(t *testing.T) { + got, err := Uint16Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []uint16{1, 2} + + got, err = Uint16Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestUint32(t *testing.T) { + var val interface{} + expected := uint32(100) + + // Test with value == nil + got, err := Uint32(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Uint32(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Uint32(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Uint32(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Uint32(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Uint32(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Uint32(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Uint32(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Uint32(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Uint32(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Uint32(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Uint32(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Uint32(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Uint32(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Uint32(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Uint32(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestUint32Array(t *testing.T) { + got, err := Uint32Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []uint32{1, 2} + + got, err = Uint32Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestUint64(t *testing.T) { + var val interface{} + expected := uint64(100) + + // Test with value == nil + got, err := Uint64(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Uint64(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Uint64(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Uint64(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Uint64(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Uint64(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Uint64(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Uint64(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Uint64(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Uint64(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Uint64(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Uint64(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Uint64(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Uint64(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Uint64(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Uint64(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %d, but got %d", expected, got) + return + } +} + +func TestUint64Array(t *testing.T) { + got, err := Uint64Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []uint64{1, 2} + + got, err = Uint64Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestFloat32(t *testing.T) { + var val interface{} + expected := float32(100.0) + + // Test with value == nil + got, err := Float32(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Float32(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Float32(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Float32(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Float32(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Float32(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Float32(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Float32(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Float32(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Float32(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Float32(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Float32(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Float32(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Float32(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Float32(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Float32(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } +} + +func TestFloat32Array(t *testing.T) { + got, err := Float32Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []float32{1.0, 2.0} + + got, err = Float32Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestFloat64(t *testing.T) { + var val interface{} + expected := float64(100.0) + + // Test with value == nil + got, err := Float64(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Float64(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Float64(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Float64(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Float64(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Float64(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Float64(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Float64(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Float64(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Float64(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Float64(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Float64(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Float64(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Float64(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Float64(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Float64(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %f, but got %f", expected, got) + return + } +} + +func TestFloat64Array(t *testing.T) { + got, err := Float64Array(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []float64{1.0, 2.0} + + got, err = Float64Array(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestBool(t *testing.T) { + var val interface{} + expected := true + + // Test with value == nil + got, err := Bool(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = Bool(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = Bool(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = Bool(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = Bool(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = Bool(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = Bool(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = Bool(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = Bool(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = Bool(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = Bool(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = Bool(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100.0) + got, err = Bool(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100.0) + got, err = Bool(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as string type + val_string1 := "abc" + got, err = Bool(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 := "100" + got, err = Bool(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with false result + expected = false + + // Test with value as int type + val_int = int(0) + got, err = Bool(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as int8 type + val_int8 = int8(0) + got, err = Bool(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as int16 type + val_int16 = int16(0) + got, err = Bool(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as int32 type + val_int32 = int32(0) + got, err = Bool(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as int64 type + val_int64 = int64(0) + got, err = Bool(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint type + val_uint = uint(0) + got, err = Bool(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 = uint8(0) + got, err = Bool(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 = uint16(0) + got, err = Bool(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 = uint32(0) + got, err = Bool(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 = uint64(0) + got, err = Bool(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as float32 type + val_float32 = float32(0.0) + got, err = Bool(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as float64 type + val_float64 = float64(0.0) + got, err = Bool(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } + + // Test with value as string type + val_string1 = "abc" + got, err = Bool(val_string1) + if err == nil { + t.Errorf("There should be an error, but now there isn't") + return + } + + val_string2 = "0" + got, err = Bool(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %t, but got %t", expected, got) + return + } +} + +func TestBoolArray(t *testing.T) { + got, err := BoolArray(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 0) + expected := []bool{true, false} + + got, err = BoolArray(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestString(t *testing.T) { + var val interface{} + expected := "100" + + // Test with value == nil + got, err := String(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + got, err = String(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as int type + val_int := int(100) + got, err = String(val_int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as int8 type + val_int8 := int8(100) + got, err = String(val_int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as int16 type + val_int16 := int16(100) + got, err = String(val_int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as int32 type + val_int32 := int32(100) + got, err = String(val_int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as int64 type + val_int64 := int64(100) + got, err = String(val_int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as uint type + val_uint := uint(100) + got, err = String(val_uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as uint8 type + val_uint8 := uint8(100) + got, err = String(val_uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as uint16 type + val_uint16 := uint16(100) + got, err = String(val_uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as uint32 type + val_uint32 := uint32(100) + got, err = String(val_uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as uint64 type + val_uint64 := uint64(100) + got, err = String(val_uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as float32 type + val_float32 := float32(100) + got, err = String(val_float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as float64 type + val_float64 := float64(100) + got, err = String(val_float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } + + // Test with value as string type + val_string2 := "100" + got, err = String(val_string2) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %s, but got %s", expected, got) + return + } +} + +func TestStringArray(t *testing.T) { + got, err := StringArray(nil) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + valArray := make([]interface{}, 0, 2) + valArray = append(valArray, 1) + valArray = append(valArray, 2) + expected := []string{"1", "2"} + + got, err = StringArray(valArray) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + + if len(expected) != len(got) || expected[0] != got[0] || expected[1] != got[1] { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestDateTime(t *testing.T) { + var val interface{} + expected := time.Date(2017, time.February, 14, 5, 20, 0, 0, time.Local) + + // Test with value == nil + _, err := DateTime(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test with value as non basic type + val = make([]int, 0, 32) + _, err = DateTime(val) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + timeVal := "2017-02-14 05:20:00" + got, err := DateTime(timeVal) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %v, but got %v", expected, got) + return + } + + got, err = DateTime(got) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %v, but got %v", expected, got) + return + } + + got, err = DateTime(got.Unix()) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got != expected { + t.Errorf("Expected %v, but got %v", expected, got) + return + } +} + +func TestDateTimeArray(t *testing.T) { + +} + +func TestDateTimeByFormat(t *testing.T) { + // var val interface{} + // expected := time.Date(2017, time.February, 14, 5, 20, 0, 0, time.Local) + + // // Test with value == nil + // _, err := DateTimeByFormat(val, "2006-01-02 15:04:05") + // if err == nil { + // t.Errorf("There should be an error, but now there isn't.") + // return + // } + + // // Test with value as non basic type + // val = make([]int, 0, 32) + // _, err = DateTimeByFormat(val, "2006-01-02 15:04:05") + // if err == nil { + // t.Errorf("There should be an error, but now there isn't.") + // return + // } + + // timeVal := "2017-02-14 05:20:00" + // got, err := DateTimeByFormat(timeVal, "2006-01-02 15:04:05") + // if err != nil { + // t.Errorf("There should be no error, but now there is one:%s", err) + // return + // } + // if got != expected { + // t.Errorf("Expected %v, but got %v", expected, got) + // return + // } + + // got, err = DateTimeByFormat(got, "2006-01-02 15:04:05") + // if err != nil { + // t.Errorf("There should be no error, but now there is one:%s", err) + // return + // } + // if got != expected { + // t.Errorf("Expected %v, but got %v", expected, got) + // return + // } + + // got, err = DateTimeByFormat(got.Unix(), "2006-01-02 15:04:05") + // if err != nil { + // t.Errorf("There should be no error, but now there is one:%s", err) + // return + // } + // if got != expected { + // t.Errorf("Expected %v, but got %v", expected, got) + // return + // } +} + +func TestDateTimeArrayByFormat(t *testing.T) { + +} + +func TestConvert(t *testing.T) { + result, err := Convert([]int{}, reflect.Map) + if err == nil { + t.Errorf("There should be an error, but now there isn't.") + return + } + + // Test Convert with type int + expected_int := 1 + result, err = Convert(1, reflect.Int) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(int); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_int { + t.Errorf("Expected %d, but got %d", expected_int, got) + return + } + + // Test Convert with type int8 + expected_int8 := int8(1) + result, err = Convert(1, reflect.Int8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(int8); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_int8 { + t.Errorf("Expected %d, but got %d", expected_int8, got) + return + } + + // Test Convert with type int16 + expected_int16 := int16(1) + result, err = Convert(1, reflect.Int16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(int16); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_int16 { + t.Errorf("Expected %d, but got %d", expected_int16, got) + return + } + + // Test Convert with type int32 + expected_int32 := int32(1) + result, err = Convert(1, reflect.Int32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(int32); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_int32 { + t.Errorf("Expected %d, but got %d", expected_int32, got) + return + } + + // Test Convert with type int64 + expected_int64 := int64(1) + result, err = Convert(1, reflect.Int64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(int64); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_int64 { + t.Errorf("Expected %d, but got %d", expected_int64, got) + return + } + + // Test Convert with type uint + expected_uint := uint(1) + result, err = Convert(1, reflect.Uint) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(uint); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_uint { + t.Errorf("Expected %d, but got %d", expected_uint, got) + return + } + + // Test Convert with type int8 + expected_uint8 := uint8(1) + result, err = Convert(1, reflect.Uint8) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(uint8); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_uint8 { + t.Errorf("Expected %d, but got %d", expected_uint8, got) + return + } + + // Test Convert with type int16 + expected_uint16 := uint16(1) + result, err = Convert(1, reflect.Uint16) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(uint16); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_uint16 { + t.Errorf("Expected %d, but got %d", expected_uint16, got) + return + } + + // Test Convert with type uint32 + expected_uint32 := uint32(1) + result, err = Convert(1, reflect.Uint32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(uint32); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_uint32 { + t.Errorf("Expected %d, but got %d", expected_uint32, got) + return + } + + // Test Convert with type uint64 + expected_uint64 := uint64(1) + result, err = Convert(1, reflect.Uint64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(uint64); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_uint64 { + t.Errorf("Expected %d, but got %d", expected_uint64, got) + return + } + + // Test Convert with type float32 + expected_float32 := float32(1.0) + result, err = Convert(1, reflect.Float32) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(float32); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_float32 { + t.Errorf("Expected %f, but got %f", expected_float32, got) + return + } + + // Test Convert with type float64 + expected_float64 := float64(1.0) + result, err = Convert(1, reflect.Float64) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(float64); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_float64 { + t.Errorf("Expected %f, but got %f", expected_float64, got) + return + } + + // Test Convert with type bool + expected_bool := true + result, err = Convert(true, reflect.Bool) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(bool); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_bool { + t.Errorf("Expected %t, but got %t", expected_bool, got) + return + } + + // Test Convert with type string + expected_string := "hello" + result, err = Convert("hello", reflect.String) + if err != nil { + t.Errorf("There should be no error, but now there is one:%s", err) + return + } + if got, ok := result.(string); !ok { + t.Errorf("The type should be int, but now it's not.") + return + } else if got != expected_string { + t.Errorf("Expected %s, but got %s", expected_string, got) + return + } +} diff --git a/trunk/goutil/typeUtil/mapData.go b/trunk/goutil/typeUtil/mapData.go new file mode 100644 index 0000000..c56ec77 --- /dev/null +++ b/trunk/goutil/typeUtil/mapData.go @@ -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 +} diff --git a/trunk/goutil/typeUtil/mapData_test.go b/trunk/goutil/typeUtil/mapData_test.go new file mode 100644 index 0000000..bd507a9 --- /dev/null +++ b/trunk/goutil/typeUtil/mapData_test.go @@ -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, + } +} diff --git a/trunk/goutil/validationUtil/regexpValidation.go b/trunk/goutil/validationUtil/regexpValidation.go new file mode 100644 index 0000000..4331c53 --- /dev/null +++ b/trunk/goutil/validationUtil/regexpValidation.go @@ -0,0 +1,48 @@ +package validationUtil + +import "regexp" + +var ( + // 身份证号 + re_idno = regexp.MustCompile("^[1-9]{1}[0-9]{14}$|^[1-9]{1}[0-9]{16}([0-9]|[xX])$") + // 邮箱 + re_email = regexp.MustCompile(`^[0-9A-Za-z][\.-_0-9A-Za-z]*@[0-9A-Za-z]+(\.[0-9A-Za-z]+)+$`) + // 中国手机号验证 + re_chinesePhoneNum = regexp.MustCompile(`^1(3[0-9]|4[57]|5[0-35-9]|7[0135678]|8[0-9])\d{8}$`) +) + +// 验证身份证号码 +// idno:身份证号 +// 返回值: +// bool: true:成功 false:不合法 +func IsValideIdno(idno string) bool { + if re_idno.MatchString(idno) { + return true + } + + return false +} + +// 验证邮箱 +// email:邮箱 +// 返回值: +// bool: true:成功 false:不合法 +func IsValideEmail(email string) bool { + if re_email.MatchString(email) { + return true + } + + return false +} + +// 验证中国的手机号码 +// phoneNum:待验证的手机号码 +// 返回值: +// bool: true:成功 false:不合法 +func IsValideChinesePhoneNum(phoneNum string) bool { + if re_chinesePhoneNum.MatchString(phoneNum) { + return true + } + + return false +} diff --git a/trunk/goutil/validationUtil/regexpValidation_test.go b/trunk/goutil/validationUtil/regexpValidation_test.go new file mode 100644 index 0000000..4edafce --- /dev/null +++ b/trunk/goutil/validationUtil/regexpValidation_test.go @@ -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() + } +} diff --git a/trunk/goutil/validationUtil/validationUtil.go b/trunk/goutil/validationUtil/validationUtil.go new file mode 100644 index 0000000..7186ea6 --- /dev/null +++ b/trunk/goutil/validationUtil/validationUtil.go @@ -0,0 +1,87 @@ +package validationUtil + +import ( + "errors" + + "goutil/stringUtil" +) + +const ( + MaxInt32 int = 0x7fffffff + MinInt32 int = -0x7fffffff +) + +// 检查数值范围 +// errList:错误列表 +// val:待检查的值 +// min:最小值 +// max:最大值 +// msg:错误提示 +// 返回值: +// bool:是否在范围内 +func CheckIntRange(errList *([]error), val int, min int, max int, msg string) bool { + if val < min || val > max { + if errList != nil { + *errList = append(*errList, errors.New(msg)) + } + + return true + } + + return false +} + +// 检查数值范围 +// errList:错误列表 +// val:待检查的值 +// min:最小值 +// max:最大值 +// msg:错误提示 +// 返回值: +// bool:是否在范围内 +func CheckFloatRange(errList *([]error), val float64, min float64, max float64, msg string) bool { + if val < min || val > max { + if errList != nil { + *errList = append(*errList, errors.New(msg)) + } + + return true + } + + return false +} + +// 检查字符串是否为空 +// errList:错误列表 +// val:待检查的值 +// msg:错误提示 +// 返回值: +// bool:是否在范围内 +func Require(errList *([]error), val string, msg string) bool { + if stringUtil.IsEmpty(val) { + if errList != nil { + *errList = append(*errList, errors.New(msg)) + } + + return true + } + + return false +} + +// 检查数据是否存在重复项(此函数效率较低,如有效率要求请不要调用此函数) +func IsDistinct(count int, isEqualFunc func(i, j int) bool) bool { + for i := 0; i < count; i++ { + for j := 0; j < count; j++ { + if i == j { + continue + } + + if isEqualFunc(i, j) { + return false + } + } + } + + return true +} diff --git a/trunk/goutil/vsmsUtil/qcloud/qcloud.go b/trunk/goutil/vsmsUtil/qcloud/qcloud.go new file mode 100644 index 0000000..c73ae18 --- /dev/null +++ b/trunk/goutil/vsmsUtil/qcloud/qcloud.go @@ -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 +} diff --git a/trunk/goutil/vsmsUtil/qcloud/response.go b/trunk/goutil/vsmsUtil/qcloud/response.go new file mode 100644 index 0000000..5a03253 --- /dev/null +++ b/trunk/goutil/vsmsUtil/qcloud/response.go @@ -0,0 +1,8 @@ +package qcloud + +type response struct { + Result int `json: "result"` + ErrMsg string `json:"errmsg"` + CallId string `json:"callid"` + Ext string `json:"ext"` +} diff --git a/trunk/goutil/vsmsUtil/qcloud/telField.go b/trunk/goutil/vsmsUtil/qcloud/telField.go new file mode 100644 index 0000000..f5c8059 --- /dev/null +++ b/trunk/goutil/vsmsUtil/qcloud/telField.go @@ -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, + } +} diff --git a/trunk/goutil/webUtil/client.go b/trunk/goutil/webUtil/client.go new file mode 100644 index 0000000..f74b1ba --- /dev/null +++ b/trunk/goutil/webUtil/client.go @@ -0,0 +1,216 @@ +// 适用于大量http(s)请求,连接复用 +package webUtil + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptrace" + "net/url" + "time" +) + +type Client struct { + traceCtx context.Context + client *http.Client +} + +type TransportOPT struct { + //超时时间 + Timeout time.Duration + + //代理字符串,如"http://127.0.0.1:1080" + ProxyString string + + //最大保持连接数 + MaxIdleConns int + + //每个主机的最大保持连接数 + MaxIdleConnsPerHost int + + //单主机最大连接数 + MaxConnsPerHost int + + //保持连接的超时时间 + IdleConnTimeout time.Duration + + //禁止保持连接 + DisableKeepAlives bool +} + +// GET请求 +func (c *Client) Get(urlStr string, header map[string]string) (result []byte, err error) { + req, err := http.NewRequestWithContext(c.traceCtx, http.MethodGet, urlStr, nil) + if err != nil { + //fmt.Println(err) + return + } + + // 处理头部(包括默认头部,以及传入的头部集合) + if header == nil { + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + } else { + for k, v := range header { + req.Header.Add(k, v) + } + } + + res, err := c.client.Do(req) + if err != nil { + //fmt.Println(err) + return + } + defer res.Body.Close() + + result, err = ioutil.ReadAll(res.Body) + + return +} + +// POST请求 +func (c *Client) PostWithMap(urlStr string, data map[string]string, header map[string]string) (result []byte, err error) { + // 组装POST数据 + postValues := url.Values{} + for key, value := range data { + postValues.Set(key, value) + } + postDataStr := postValues.Encode() + byteData := []byte(postDataStr) + + var req *http.Request + req, err = http.NewRequestWithContext(c.traceCtx, http.MethodPost, urlStr, bytes.NewReader(byteData)) + if err != nil { + //fmt.Println(err) + return + } + + // 处理头部(包括默认头部,以及传入的头部集合) + if header == nil { + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + } else { + for k, v := range header { + req.Header.Add(k, v) + } + } + + var res *http.Response + res, err = c.client.Do(req) + if err != nil { + fmt.Println(err) + return + } + defer res.Body.Close() + + result, err = ioutil.ReadAll(res.Body) + + return +} + +// POST请求 +func (c *Client) PostWithByte(urlStr string, data []byte, header map[string]string) (result []byte, err error) { + var req *http.Request + req, err = http.NewRequestWithContext(c.traceCtx, http.MethodPost, urlStr, bytes.NewReader(data)) + if err != nil { + //fmt.Println(err) + return + } + + // 处理头部(包括默认头部,以及传入的头部集合) + if header == nil { + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + } else { + for k, v := range header { + req.Header.Add(k, v) + } + } + + var res *http.Response + res, err = c.client.Do(req) + if err != nil { + fmt.Println(err) + return + } + defer res.Body.Close() + + result, err = ioutil.ReadAll(res.Body) + + return +} + +// 新建Client对象 +// transportOPT参数说明 +// +// Timeout - 连接绝对超时时间 +// ProxyString - 代理字符串 +// MaxIdleConns - 最大空闲连接数 +// MaxIdleConnsPerHost - 每个目标主机的最大空闲连接数 +// MaxConnsPerHost - 每个目标主机的最大连接数 +// IdleConnTimeout - 空闲连接超时时间 +// DisableKeepAlives - 禁用连接保持(要使用连接复用,此值不传入或传入false;否则连接可能不会复用) +func NewClient(transportOPT *TransportOPT) (c *Client) { + c = &Client{} + + //代理 + getProxy := func() func(*http.Request) (*url.URL, error) { + if (transportOPT != nil) && len(transportOPT.ProxyString) > 0 { + uri, _ := url.Parse(transportOPT.ProxyString) + return http.ProxyURL(uri) + } + + return nil + } + + // 默认参数 + timeout := 30 * time.Second + maxIdleConns := 60000 + maxIdleConnsPerHost := 30000 + maxConnsPerHost := 30000 + idleConnTimeout := time.Minute * 1 + disableKeepAlives := false + + if transportOPT != nil { + // 根据传入参数修改默认参数 + if transportOPT.Timeout > 0 { + timeout = transportOPT.Timeout + } + if transportOPT.MaxIdleConns > 0 { + maxIdleConns = transportOPT.MaxIdleConns + } + if transportOPT.MaxIdleConnsPerHost > 0 { + maxIdleConnsPerHost = transportOPT.MaxIdleConnsPerHost + } + if transportOPT.MaxConnsPerHost > 0 { + maxConnsPerHost = transportOPT.MaxConnsPerHost + } + if transportOPT.IdleConnTimeout > 0 { + idleConnTimeout = transportOPT.IdleConnTimeout + } + + disableKeepAlives = transportOPT.DisableKeepAlives + } + + clientTrace := &httptrace.ClientTrace{ + //GotConn: func(gci httptrace.GotConnInfo) { + // fmt.Printf("conn was reused: %t\n", gci.Reused) + //}, + } + + c.traceCtx = httptrace.WithClientTrace(context.Background(), clientTrace) + c.client = &http.Client{ + Timeout: timeout, + + Transport: &http.Transport{ + Proxy: getProxy(), + MaxIdleConns: maxIdleConns, + MaxIdleConnsPerHost: maxIdleConnsPerHost, + MaxConnsPerHost: maxConnsPerHost, + IdleConnTimeout: idleConnTimeout, + DisableKeepAlives: disableKeepAlives, + // ForceAttemptHTTP2: true, + }, + } + + return +} diff --git a/trunk/goutil/webUtil/client_test.go b/trunk/goutil/webUtil/client_test.go new file mode 100644 index 0000000..422b34c --- /dev/null +++ b/trunk/goutil/webUtil/client_test.go @@ -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)) +} diff --git a/trunk/goutil/webUtil/doc.go b/trunk/goutil/webUtil/doc.go new file mode 100644 index 0000000..8b6b210 --- /dev/null +++ b/trunk/goutil/webUtil/doc.go @@ -0,0 +1,4 @@ +/* +Web访问助手包 +*/ +package webUtil diff --git a/trunk/goutil/webUtil/get.go b/trunk/goutil/webUtil/get.go new file mode 100644 index 0000000..1cd3267 --- /dev/null +++ b/trunk/goutil/webUtil/get.go @@ -0,0 +1,182 @@ +package webUtil + +import ( + "fmt" + "io/ioutil" + "net/http" + "strings" +) + +// GET数据 +// weburl:远程服务器地址 +// header:包头集合 +// 返回值: +// 返回的字节 +// 错误对象 +func GetWebData(weburl string, header map[string]string) (result []byte, err error) { + // 构造请求对象 + var request *http.Request + request, err = http.NewRequest("GET", weburl, nil) + if err != nil { + return + } + + // 处理包头 + if header != nil { + for k, v := range header { + request.Header.Add(k, v) + } + } + + // 构造transport对象 + transport := NewTransport() + transport.DisableKeepAlives = true + transport = GetTimeoutTransport(transport, 30) + + // 构造httpClient对象 + client := &http.Client{Transport: transport} + + // 发送数据 + var response *http.Response + response, err = client.Do(request) + if err != nil { + return + } + defer response.Body.Close() + + // 读取数据 + if response.StatusCode == http.StatusOK { + result, err = ioutil.ReadAll(response.Body) + if err != nil { + return + } + } + + return +} + +// GET数据(新方法) +// weburl:远程服务器地址 +// data:数据集合 +// header:包头内容 +// transport:transport对象 +// 返回值 +// http StatusCode +// 字节数组 +// 错误对象 +func GetWebData2(weburl string, data map[string]string, header map[string]string, transport *http.Transport) (statusCode int, result []byte, err error) { + // 处理data,将data以key=value的形式拼接到weburl后,形成一个完整的url + dataStr := "" + count := 0 + for k, v := range data { + if count == len(data)-1 { + dataStr += fmt.Sprintf("%s=%s", k, v) + } else { + dataStr += fmt.Sprintf("%s=%s&", k, v) + } + count += 1 + } + + if dataStr != "" { + if strings.Contains(weburl, "?") { + weburl = fmt.Sprintf("%s&%s", weburl, dataStr) + } else { + weburl = fmt.Sprintf("%s?%s", weburl, dataStr) + } + } + + // 构造请求对象 + var request *http.Request + request, err = http.NewRequest("GET", weburl, nil) + if err != nil { + return + } + + // 处理头部 + if header != nil { + for k, v := range header { + request.Header.Add(k, v) + } + } + + // 构造transport对象 + if transport == nil { + transport = NewTransport() + transport.DisableKeepAlives = true + transport = GetTimeoutTransport(transport, 30) + } + + // 构造httpClient对象 + client := &http.Client{Transport: transport} + + // 发送数据 + var response *http.Response + response, err = client.Do(request) + if err != nil { + return + } + defer response.Body.Close() + + // 获取返回值 + statusCode = response.StatusCode + result, err = ioutil.ReadAll(response.Body) + + return +} + +// GET数据(新方法) +// weburl:远程服务器地址 +// dataStr:参数字符串 +// header:包头内容 +// transport:transport对象 +// 返回值 +// http StatusCode +// 字节数组 +// 错误对象 +func GetWebData3(weburl string, dataStr string, header map[string]string, transport *http.Transport) (statusCode int, result []byte, err error) { + if dataStr != "" { + if strings.Contains(weburl, "?") { + weburl = fmt.Sprintf("%s&%s", weburl, dataStr) + } else { + weburl = fmt.Sprintf("%s?%s", weburl, dataStr) + } + } + + // 构造请求对象 + var request *http.Request + request, err = http.NewRequest("GET", weburl, nil) + if err != nil { + return + } + + // 处理头部 + if header != nil { + for k, v := range header { + request.Header.Add(k, v) + } + } + + // 构造transport对象 + if transport == nil { + transport = NewTransport() + transport.DisableKeepAlives = true + transport = GetTimeoutTransport(transport, 30) + } + + // 构造httpClient对象 + client := &http.Client{Transport: transport} + + // 发送数据 + var response *http.Response + response, err = client.Do(request) + if err != nil { + return + } + defer response.Body.Close() + + // 获取返回值 + statusCode = response.StatusCode + result, err = ioutil.ReadAll(response.Body) + + return +} diff --git a/trunk/goutil/webUtil/get_test.go b/trunk/goutil/webUtil/get_test.go new file mode 100644 index 0000000..b2e1aaa --- /dev/null +++ b/trunk/goutil/webUtil/get_test.go @@ -0,0 +1,41 @@ +package webUtil + +import ( + "testing" +) + +func TestGetWebData(t *testing.T) { + weburl := "http://www.baidu.com" + resp, err := GetWebData(weburl, nil) + if err != nil { + t.Errorf("测试错误,返回的结果为:%s", err) + } + + if len(resp) == 0 { + t.Errorf("返回的数据为空,期望不为空") + } +} + +func TestGetWebData2(t *testing.T) { + weburl := "http://www.baidu.com" + data := make(map[string]string) + status, resp, err := GetWebData2(weburl, data, nil, nil) + if status != 200 || err != nil { + t.Errorf("Test failed. status:%d, err:%s", status, err) + } + + if len(resp) == 0 { + t.Errorf("The result is empty, but we expect not empty") + } + + data["Name"] = "Jordan" + data["Age"] = "32" + status, resp, err = GetWebData2(weburl, data, nil, nil) + if status != 200 || err != nil { + t.Errorf("Test failed. status:%d, err:%s", status, err) + } + + if len(resp) == 0 { + t.Errorf("The result is empty, but we expect not empty") + } +} diff --git a/trunk/goutil/webUtil/post.go b/trunk/goutil/webUtil/post.go new file mode 100644 index 0000000..b82cb50 --- /dev/null +++ b/trunk/goutil/webUtil/post.go @@ -0,0 +1,313 @@ +package webUtil + +import ( + "bytes" + "crypto/tls" + "io/ioutil" + "net/http" + "net/url" +) + +func GetFormHeader() map[string]string { + return map[string]string{"Content-Type": "application/x-www-form-urlencoded"} +} + +func GetJsonHeader() map[string]string { + return map[string]string{"Content-Type": "application/json;charset=UTF-8"} +} + +// POST数据 +// weburl:远程服务器地址 +// data:post的数据集合 +// header:包头集合 +// 返回值: +// 返回的字节 +// 错误对象 +func PostWebData(weburl string, data map[string]string, header map[string]string) (result []byte, err error) { + // 组装POST数据 + postValues := url.Values{} + for key, value := range data { + postValues.Set(key, value) + } + postDataStr := postValues.Encode() + byteData := []byte(postDataStr) + + // 调用发送Byte数组的方法 + result, err = PostByteData(weburl, byteData, header) + + return +} + +// POST Byte数组 +// weburl:远程服务器地址 +// data:post的Byte数组 +// header:包头集合 +// 返回值: +// 返回的字节 +// 错误对象 +func PostByteData(weburl string, data []byte, header map[string]string) (result []byte, err error) { + // 组装POST数据 + reader := bytes.NewReader(data) + + // 构造请求对象 + var request *http.Request + request, err = http.NewRequest("POST", weburl, reader) + if err != nil { + return + } + + // 处理头部(包括默认头部,以及传入的头部集合) + if header == nil { + for k, v := range GetFormHeader() { + request.Header.Add(k, v) + } + } else { + for k, v := range header { + request.Header.Add(k, v) + } + } + + // 构造transport对象 + transport := NewTransport() + transport.DisableKeepAlives = true + + transport = GetTimeoutTransport(transport, 30) + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + + // 构造httpClient对象 + client := &http.Client{Transport: transport} + + // 发送数据 + var response *http.Response + response, err = client.Do(request) + if err != nil { + return + } + defer response.Body.Close() + + // 读取数据 + result, err = ioutil.ReadAll(response.Body) + + return +} + +// POST Byte数组 +// weburl:远程服务器地址 +// data:post的Byte数组 +// header:包头集合 +// transport: transport对象 +// 返回值: +// 返回的字节 +// 错误对象 +func PostByteDataWithTransport(weburl string, data []byte, header map[string]string, transport *http.Transport) (result *[]byte, err error) { + // 组装POST数据 + reader := bytes.NewReader(data) + + // 构造请求对象 + var request *http.Request + request, err = http.NewRequest("POST", weburl, reader) + if err != nil { + return + } + + // 处理头部 + if header != nil { + for k, v := range header { + request.Header.Add(k, v) + } + } + + // 构造transport对象 + if transport == nil { + transport = NewTransport() + transport.DisableKeepAlives = true + transport = GetTimeoutTransport(transport, 30) + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } + + // 构造httpClient对象 + client := &http.Client{Transport: transport} + + // 发送数据 + var response *http.Response + response, err = client.Do(request) + if err != nil { + return + } + defer response.Body.Close() + + body, err1 := ioutil.ReadAll(response.Body) + if err1 != nil { + err = err1 + return + } + + result = &body + + return +} + +// POST map类型的数据 +// weburl:远程服务器地址 +// data:数据 +// header:包头内容 +// transport:transport对象 +// 返回值 +// http StatusCode +// 字节数组 +// 错误对象 +func PostMapData(weburl string, data map[string]string, header map[string]string, transport *http.Transport) (statusCode int, result []byte, err error) { + // 组装POST数据 + postValues := url.Values{} + for key, value := range data { + postValues.Set(key, value) + } + postDataStr := postValues.Encode() + byteData := []byte(postDataStr) + + statusCode, result, err = PostByteData2(weburl, byteData, header, transport) + return +} + +// POST byte类型的数据(新方法) +// weburl:远程服务器地址 +// data:数据 +// header:包头内容 +// transport:transport对象 +// 返回值 +// http StatusCode +// 字节数组 +// 错误对象 +func PostByteData2(weburl string, data []byte, header map[string]string, transport *http.Transport) (statusCode int, result []byte, err error) { + // 组装POST数据 + reader := bytes.NewReader(data) + + // 构造请求对象 + var request *http.Request + request, err = http.NewRequest("POST", weburl, reader) + if err != nil { + return + } + + // 处理头部 + if header != nil { + for k, v := range header { + request.Header.Add(k, v) + } + } + + // 构造transport对象 + if transport == nil { + transport = NewTransport() + transport.DisableKeepAlives = true + transport = GetTimeoutTransport(transport, 30) + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //关闭证书校验 + } + + // 构造httpClient对象 + client := &http.Client{Transport: transport} + + // 发送数据 + var response *http.Response + response, err = client.Do(request) + if err != nil { + return + } + defer response.Body.Close() + + // 获取返回值 + statusCode = response.StatusCode + result, err = ioutil.ReadAll(response.Body) + + return +} + +// POST Byte数组 +// weburl:远程服务器地址 +// data:post的Byte数组 +// header:包头集合 +// 返回值: +// 返回的字节 +// 错误对象 +func PostByteData3(weburl string, data []byte, header map[string]string) (result []byte, err error) { + // 组装POST数据 + reader := bytes.NewReader(data) + + // 构造请求对象 + var request *http.Request + request, err = http.NewRequest("POST", weburl, reader) + if err != nil { + return + } + + request.Header.Set("Content-Type", "application/json;charset=UTF-8") + + // 构造transport对象 + transport := NewTransport() + transport.DisableKeepAlives = true + transport = GetTimeoutTransport(transport, 30) + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + + // 构造httpClient对象 + client := &http.Client{Transport: transport} + + // 发送数据 + var response *http.Response + response, err = client.Do(request) + if err != nil { + return + } + defer response.Body.Close() + + // 读取数据 + result, err = ioutil.ReadAll(response.Body) + + return +} + +// POST Byte数组 +// weburl:远程服务器地址 +// data:post的Byte数组 +// header:包头集合 +// timeout:超时时间 +// 返回值: +// 返回的字节 +// 错误对象 +func PostByteData4(weburl string, data []byte, header map[string]string, timeout int) (result []byte, err error) { + // 组装POST数据 + reader := bytes.NewReader(data) + + // 构造请求对象 + var request *http.Request + request, err = http.NewRequest("POST", weburl, reader) + if err != nil { + return + } + + request.Header.Set("Content-Type", "application/json;charset=UTF-8") + + // 构造transport对象 + if timeout <= 0 { + timeout = 30 + } + transport := NewTransport() + transport.DisableKeepAlives = true + transport = GetTimeoutTransport(transport, timeout) + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + + // 构造httpClient对象 + client := &http.Client{Transport: transport} + + // 发送数据 + var response *http.Response + response, err = client.Do(request) + if err != nil { + return + } + defer response.Body.Close() + + // 读取数据 + result, err = ioutil.ReadAll(response.Body) + + return +} diff --git a/trunk/goutil/webUtil/post_test.go b/trunk/goutil/webUtil/post_test.go new file mode 100644 index 0000000..182d95f --- /dev/null +++ b/trunk/goutil/webUtil/post_test.go @@ -0,0 +1,26 @@ +package webUtil + +import ( + "goutil/zlibUtil" + "testing" +) + +func TestPostWebData(t *testing.T) { + weburl := "https://managecenterapitest-sd3.7qule.com/API/ServerActivate.ashx" + postDict := make(map[string]string, 0) + postDict["ServerGroupID"] = "20002" + resp, err := PostWebData(weburl, postDict, nil) + if err != nil { + t.Errorf("测试错误,返回的结果为:%s", err) + } + + if len(resp) == 0 { + t.Errorf("返回的数据为空,期望不为空") + } + + // 将收到的数据进行zlib解压缩 + _, err = zlibUtil.Decompress(resp) + if err != nil { + t.Errorf("Error:%s", err) + } +} diff --git a/trunk/goutil/webUtil/web.go b/trunk/goutil/webUtil/web.go new file mode 100644 index 0000000..b5c3d5a --- /dev/null +++ b/trunk/goutil/webUtil/web.go @@ -0,0 +1,104 @@ +package webUtil + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "sort" + "time" + + "goutil/netUtil" +) + +// 获取请求的IP(obsolete, 直接调用netUtil.GetHttpAddr) +// 返回值: +// 请求的IP +func GetRequestIP(r *http.Request) string { + /* + http中调用JoinHostPort来给RemoteAddr赋值;它的规则如下: + JoinHostPort combines host and port into a network address of the + form "host:port" or, if host contains a colon or a percent sign, + "[host]:port". + + 所以现在要将RemoteAddr解析成host和port,则需要找到最后一个:,前面的部分则是host; + 如果host包含[],则需要去除 + */ + + return netUtil.GetHttpAddr(r).Host +} + +func NewTransport() *http.Transport { + return &http.Transport{} +} + +// timeout 超时时间 +func GetTimeoutTransport(transport *http.Transport, timeout int) *http.Transport { + transport.Dial = func(netw, addr string) (net.Conn, error) { + deadline := time.Now().Add(time.Duration(timeout) * time.Second) + c, err := net.DialTimeout(netw, addr, time.Second*time.Duration(timeout)) + if err != nil { + return nil, err + } + c.SetDeadline(deadline) + return c, nil + } + + return transport +} + +// b 表示是否需要验证http证书 +func GetTLSTransport(transport *http.Transport, b bool) *http.Transport { + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: b} + + return transport +} + +// 组装请求字符串 +// requestMap:请求数据集合 +func AssembleRequestParam(requestMap map[string]string) (dataStr string) { + count := 0 + for k, v := range requestMap { + if count == len(requestMap)-1 { + dataStr += fmt.Sprintf("%s=%s", k, v) + } else { + dataStr += fmt.Sprintf("%s=%s&", k, v) + } + count += 1 + } + + return +} + +// 按照参数名称升序组装请求字符串 +// requestMap:请求数据集合 +// asc:是否升序 +func AssembleRequestParamSort(requestMap map[string]string, asc bool) (dataStr string) { + finalKeys := make([]string, 0, len(requestMap)) + + // 按key进行排序 + sortKeys := make([]string, 0, len(requestMap)) + for k, _ := range requestMap { + sortKeys = append(sortKeys, k) + } + sort.Strings(sortKeys) + + // 排序是升序还是降序 + if asc { + finalKeys = sortKeys + } else { + for i := len(sortKeys) - 1; i >= 0; i-- { + finalKeys = append(finalKeys, sortKeys[i]) + } + } + + for index, key := range finalKeys { + if index == len(finalKeys)-1 { + dataStr += fmt.Sprintf("%s=%s", key, requestMap[key]) + } else { + dataStr += fmt.Sprintf("%s=%s&", key, requestMap[key]) + } + } + + return +} diff --git a/trunk/goutil/websocketUtil/model.go b/trunk/goutil/websocketUtil/model.go new file mode 100644 index 0000000..21eb159 --- /dev/null +++ b/trunk/goutil/websocketUtil/model.go @@ -0,0 +1,156 @@ +package websocketUtil + +import ( + "fmt" + "time" + + "github.com/gorilla/websocket" +) + +const ( + // 最大重试次数 + send_max_retry_num int = 60 + // 发送错误计数次数 + send_error_num int32 = 3 +) + +type model struct { + wsurl string + msgChan chan *sendModel + con *websocket.Conn + isclose bool +} + +type sendModel struct { + data []byte + sendType int +} + +// 构造请求对象 +func newModel(wsurl string) (*model, error) { + result := &model{} + result.wsurl = wsurl + result.msgChan = make(chan *sendModel, 1000) + + // 创建连接 + err := result.createCon() + if err != nil { + return nil, err + } + + go result.sendhandler() + + return result, nil +} + +// 发送消息 +func (this *model) send(st int, d []byte) { + sm := &sendModel{ + data: d, + sendType: st, + } + this.msgChan <- sm +} + +// 发送处理器 +func (this *model) sendhandler() { + defer func() { + if rerr := recover(); rerr != nil { + errLog("url:%s sendhandler recover:%s", this.wsurl, rerr) + } + }() + + // 下面逻辑需要考虑这些情况: + // 1.对端重启 -> 需要尽可能保证消息投递成功 + // 2.对端永久关闭 -> 需要尽快释放channel里面的消息,以免堵塞channel,增加内占用 + var err error + var errCount int32 + for { + if this.isclose { + return + } + + sm := <-this.msgChan + isOk := false + for i := 0; i < send_max_retry_num; i++ { + if this.isclose { + return + } + + if this.checkcon() == false { + time.Sleep(time.Second * 1) + debugLog("url:%s channel msgchen 数量达到%v", this.wsurl, len(this.msgChan)) + continue + } + + err = this.con.WriteMessage(sm.sendType, sm.data) + if err == nil { + isOk = true + break + } + + // 记录错误日志 + errLog(fmt.Sprintf("wsurl:%s 发送数据错误 err:%s", this.wsurl, err)) + this.con = nil + + // 如果错误计数到达闸值,则不再重试 + if errCount >= send_error_num { + break + } else { + time.Sleep(time.Second) + continue + } + } + + // 修改错误计数 + if isOk == false { + errCount = errCount + 1 + } else { + errCount = 0 + } + } +} + +func (this *model) closeHandler(code int, text string) error { + debugLog("websocketUtil.model 连接关闭 wsurl:%s code:%v text:%s", this.wsurl, code, text) + this.con = nil + return nil +} + +// 创建ws连接 +func (this *model) createCon() error { + dd := &websocket.Dialer{ + Proxy: websocket.DefaultDialer.Proxy, + HandshakeTimeout: 3 * time.Second, + } + ws, _, err := dd.Dial(this.wsurl, nil) + if err != nil { + return err + } + + ws.SetCloseHandler(this.closeHandler) + this.con = ws + + return nil +} + +// 校验con是否可用 +func (this *model) checkcon() bool { + if this.con == nil { + if err := this.createCon(); err != nil { + errLog("wsurl:%s checkcon err:%s", this.wsurl, err) + return false + } + } + + return true +} + +// close +// @Description: 关闭连接 +// @receiver this *model +func (this *model) close() { + close(this.msgChan) + this.isclose = true + debugLog("websocketUtil.model close wsurl:%s", this.wsurl) +} diff --git a/trunk/goutil/websocketUtil/websocketUtil.go b/trunk/goutil/websocketUtil/websocketUtil.go new file mode 100644 index 0000000..ebc34cb --- /dev/null +++ b/trunk/goutil/websocketUtil/websocketUtil.go @@ -0,0 +1,71 @@ +package websocketUtil + +import ( + "errors" + "fmt" + "sync" +) + +var ( + wsmap sync.Map + errLog func(format string, args ...interface{}) + debugLog func(format string, args ...interface{}) +) + +// SetLog +// @Description:设置日志回调函数信息 +// @param errLogFun 错误日志回调函数 +// @param debugLogFun debug日志回调函数 +func SetLog(errLogFun, debugLogFun func(format string, args ...interface{})) { + errLog = errLogFun + debugLog = debugLogFun +} + +// Send +// @Description: 发送数据 +// @param wsurl 发送地址 +// @param st 发送类型,使用websocket下的定义 +// @param data 发送数据 +// @return error +func Send(wsurl string, st int, data []byte) error { + if debugLog == nil || errLog == nil { + return errors.New("websocketUtil未设置errLog,debugLog回调函数,请调用websocketUtil.Setlog函数设置相关信息") + } + + m, err := getOrAdd(wsurl) + if err != nil { + errLog(fmt.Sprintf("websocketUtil.Send error:%s", err)) + return err + } + + m.send(st, data) + + return nil +} + +// getOrAdd +// @Description:获取或者添加对象 +// @param url string +// @return *model +// @return error +func getOrAdd(url string) (*model, error) { + var m *model + var err error + + v, isOk := wsmap.Load(url) + if isOk { + m = v.(*model) + return m, nil + } + + // 不存在,创建对应的socket连接 + m, err = newModel(url) + if err != nil { + return nil, err + } + + // 保存到map + wsmap.Store(url, m) + + return m, err +} diff --git a/trunk/goutil/xmlUtil/doc.go b/trunk/goutil/xmlUtil/doc.go new file mode 100644 index 0000000..8b9f99a --- /dev/null +++ b/trunk/goutil/xmlUtil/doc.go @@ -0,0 +1,12 @@ +/* +xml操作工具类: +此操作工具类来源于:https://github.com/antchfx/xquery +根据实际情况。我去掉了对golang.org/x/net/html/charset的依赖,并添加了各种xml加载函数 + +由于go默认编码的原因,如果文本编码不是utf-8 将会出现乱码 + +使用方式 + root := xmlUtil.LoadFromString(xml) + nodes:= root.SelectElements(xpath) +*/ +package xmlUtil diff --git a/trunk/goutil/xmlUtil/gxpath/README.md b/trunk/goutil/xmlUtil/gxpath/README.md new file mode 100644 index 0000000..59af09c --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/README.md @@ -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. diff --git a/trunk/goutil/xmlUtil/gxpath/internal/build/build.go b/trunk/goutil/xmlUtil/gxpath/internal/build/build.go new file mode 100644 index 0000000..4b80a3b --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/internal/build/build.go @@ -0,0 +1,301 @@ +package build + +import ( + "errors" + "fmt" + + "goutil/xmlUtil/gxpath/internal/parse" + "goutil/xmlUtil/gxpath/internal/query" + "goutil/xmlUtil/gxpath/xpath" +) + +type flag int + +const ( + noneFlag flag = iota + filterFlag +) + +// builder provides building an XPath expressions. +type builder struct { + depth int + flag flag + firstInput query.Query +} + +// axisPredicate creates a predicate to predicating for this axis node. +func axisPredicate(root *parse.AxisNode) func(xpath.NodeNavigator) bool { + // get current axix node type. + typ := xpath.ElementNode + if root.AxeType == "attribute" { + typ = xpath.AttributeNode + } else { + switch root.Prop { + case "comment": + typ = xpath.CommentNode + case "text": + typ = xpath.TextNode + // case "processing-instruction": + // typ = xpath.ProcessingInstructionNode + case "node": + typ = xpath.ElementNode + } + } + predicate := func(n xpath.NodeNavigator) bool { + if typ == n.NodeType() { + if root.LocalName == "" || (root.LocalName == n.LocalName() && root.Prefix == n.Prefix()) { + return true + } + } + return false + } + + return predicate +} + +// processAxisNode buildes a query for the XPath axis node. +func (b *builder) processAxisNode(root *parse.AxisNode) (query.Query, error) { + var ( + err error + qyInput query.Query + qyOutput query.Query + predicate = axisPredicate(root) + ) + + if root.Input == nil { + qyInput = &query.ContextQuery{} + } else { + if b.flag&filterFlag == 0 { + if root.AxeType == "child" && (root.Input.Type() == parse.NodeAxis) { + if input := root.Input.(*parse.AxisNode); input.AxeType == "descendant-or-self" { + var qyGrandInput query.Query + if input.Input != nil { + qyGrandInput, _ = b.processNode(input.Input) + } else { + qyGrandInput = &query.ContextQuery{} + } + qyOutput = &query.DescendantQuery{Input: qyGrandInput, Predicate: predicate, Self: true} + return qyOutput, nil + } + } + } + qyInput, err = b.processNode(root.Input) + if err != nil { + return nil, err + } + } + + switch root.AxeType { + case "ancestor": + qyOutput = &query.AncestorQuery{Input: qyInput, Predicate: predicate} + case "ancestor-or-self": + qyOutput = &query.AncestorQuery{Input: qyInput, Predicate: predicate, Self: true} + case "attribute": + qyOutput = &query.AttributeQuery{Input: qyInput, Predicate: predicate} + case "child": + filter := func(n xpath.NodeNavigator) bool { + v := predicate(n) + switch root.Prop { + case "text": + v = v && n.NodeType() == xpath.TextNode + case "node": + v = v && (n.NodeType() == xpath.ElementNode || n.NodeType() == xpath.TextNode) + case "comment": + v = v && n.NodeType() == xpath.CommentNode + } + return v + } + qyOutput = &query.ChildQuery{Input: qyInput, Predicate: filter} + case "descendant": + qyOutput = &query.DescendantQuery{Input: qyInput, Predicate: predicate} + case "descendant-or-self": + qyOutput = &query.DescendantQuery{Input: qyInput, Predicate: predicate, Self: true} + case "following": + qyOutput = &query.FollowingQuery{Input: qyInput, Predicate: predicate} + case "following-sibling": + qyOutput = &query.FollowingQuery{Input: qyInput, Predicate: predicate, Sibling: true} + case "parent": + qyOutput = &query.ParentQuery{Input: qyInput, Predicate: predicate} + case "preceding": + qyOutput = &query.PrecedingQuery{Input: qyInput, Predicate: predicate} + case "preceding-sibling": + qyOutput = &query.PrecedingQuery{Input: qyInput, Predicate: predicate, Sibling: true} + case "self": + qyOutput = &query.SelfQuery{Input: qyInput, Predicate: predicate} + case "namespace": + // haha,what will you do someting?? + default: + err = fmt.Errorf("unknown axe type: %s", root.AxeType) + return nil, err + } + return qyOutput, nil +} + +// processFilterNode builds query.Query for the XPath filter predicate. +func (b *builder) processFilterNode(root *parse.FilterNode) (query.Query, error) { + b.flag |= filterFlag + + qyInput, err := b.processNode(root.Input) + if err != nil { + return nil, err + } + qyCond, err := b.processNode(root.Condition) + if err != nil { + return nil, err + } + qyOutput := &query.FilterQuery{Input: qyInput, Predicate: qyCond} + return qyOutput, nil +} + +// processFunctionNode buildes query.Query for the XPath function node. +func (b *builder) processFunctionNode(root *parse.FunctionNode) (query.Query, error) { + var qyOutput query.Query + switch root.FuncName { + case "starts-with": + arg1, err := b.processNode(root.Args[0]) + if err != nil { + return nil, err + } + arg2, err := b.processNode(root.Args[1]) + if err != nil { + return nil, err + } + startwith := &startwithFunc{arg1, arg2} + qyOutput = &query.XPathFunction{Input: b.firstInput, Func: startwith.do} + case "substring": + //substring( string , start [, length] ) + if len(root.Args) < 2 { + return nil, errors.New("xpath: substring function must have at least two parameter") + } + var ( + arg1, arg2, arg3 query.Query + err error + ) + if arg1, err = b.processNode(root.Args[0]); err != nil { + return nil, err + } + if arg2, err = b.processNode(root.Args[1]); err != nil { + return nil, err + } + if len(root.Args) == 3 { + if arg3, err = b.processNode(root.Args[2]); err != nil { + return nil, err + } + } + substring := &substringFunc{arg1, arg2, arg3} + qyOutput = &query.XPathFunction{Input: b.firstInput, Func: substring.do} + case "normalize-space": + if len(root.Args) == 0 { + return nil, errors.New("xpath: normalize-space function must have at least one parameter") + } + argQuery, err := b.processNode(root.Args[0]) + if err != nil { + return nil, err + } + qyOutput = &query.XPathFunction{Input: argQuery, Func: normalizespaceFunc} + case "name": + qyOutput = &query.XPathFunction{Input: b.firstInput, Func: nameFunc} + case "last": + qyOutput = &query.XPathFunction{Input: b.firstInput, Func: lastFunc} + case "position": + qyOutput = &query.XPathFunction{Input: b.firstInput, Func: positionFunc} + case "count": + if b.firstInput == nil { + return nil, errors.New("xpath: expression must evaluate to node-set") + } + if len(root.Args) == 0 { + return nil, fmt.Errorf("xpath: count(node-sets) function must with have parameters node-sets") + } + argQuery, err := b.processNode(root.Args[0]) + if err != nil { + return nil, err + } + qyOutput = &query.XPathFunction{Input: argQuery, Func: countFunc} + default: + return nil, fmt.Errorf("not yet support this function %s()", root.FuncName) + } + return qyOutput, nil +} + +func (b *builder) processOperatorNode(root *parse.OperatorNode) (query.Query, error) { + left, err := b.processNode(root.Left) + if err != nil { + return nil, err + } + right, err := b.processNode(root.Right) + if err != nil { + return nil, err + } + var qyOutput query.Query + switch root.Op { + case "+", "-", "div", "mod": // Numeric operator + var exprFunc func(interface{}, interface{}) interface{} + switch root.Op { + case "+": + exprFunc = plusFunc + case "-": + exprFunc = minusFunc + case "div": + exprFunc = divFunc + case "mod": + exprFunc = modFunc + } + qyOutput = &query.NumericExpr{Left: left, Right: right, Do: exprFunc} + case "=", ">", ">=", "<", "<=", "!=": + var exprFunc func(query.Iterator, interface{}, interface{}) interface{} + switch root.Op { + case "=": + exprFunc = eqFunc + case ">": + exprFunc = gtFunc + case ">=": + exprFunc = geFunc + case "<": + exprFunc = ltFunc + case "<=": + exprFunc = leFunc + case "!=": + exprFunc = neFunc + } + qyOutput = &query.LogicalExpr{Left: left, Right: right, Do: exprFunc} + case "or", "and", "|": + isOr := false + if root.Op == "or" || root.Op == "|" { + isOr = true + } + qyOutput = &query.BooleanExpr{Left: left, Right: right, IsOr: isOr} + } + return qyOutput, nil +} + +func (b *builder) processNode(root parse.Node) (q query.Query, err error) { + if b.depth = b.depth + 1; b.depth > 1024 { + err = errors.New("the xpath expressions is too complex") + return + } + + switch root.Type() { + case parse.NodeConstantOperand: + n := root.(*parse.OperandNode) + q = &query.XPathConstant{Val: n.Val} + case parse.NodeRoot: + q = &query.ContextQuery{Root: true} + case parse.NodeAxis: + q, err = b.processAxisNode(root.(*parse.AxisNode)) + b.firstInput = q + case parse.NodeFilter: + q, err = b.processFilterNode(root.(*parse.FilterNode)) + case parse.NodeFunction: + q, err = b.processFunctionNode(root.(*parse.FunctionNode)) + case parse.NodeOperator: + q, err = b.processOperatorNode(root.(*parse.OperatorNode)) + } + return +} + +// Build builds a specified XPath expressions expr. +func Build(expr string) (query.Query, error) { + root := parse.Parse(expr) + b := &builder{} + return b.processNode(root) +} diff --git a/trunk/goutil/xmlUtil/gxpath/internal/build/expr.go b/trunk/goutil/xmlUtil/gxpath/internal/build/expr.go new file mode 100644 index 0000000..88ad604 --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/internal/build/expr.go @@ -0,0 +1,295 @@ +package build + +import ( + "fmt" + "reflect" + "strconv" + + "goutil/xmlUtil/gxpath/internal/query" +) + +// valueType is a return value type. +type valueType int + +const ( + booleanType valueType = iota + numberType + stringType + nodeSetType +) + +func getValueType(i interface{}) valueType { + v := reflect.ValueOf(i) + switch v.Kind() { + case reflect.Float64: + return numberType + case reflect.String: + return stringType + case reflect.Bool: + return booleanType + default: + if _, ok := i.(query.Query); ok { + return nodeSetType + } + } + panic(fmt.Errorf("xpath unknown value type: %v", v.Kind())) +} + +type logical func(query.Iterator, string, interface{}, interface{}) bool + +var logicalFuncs = [][]logical{ + []logical{cmpBoolean_Boolean, nil, nil, nil}, + []logical{nil, cmpNumeric_Numeric, cmpNumeric_String, cmpNumeric_NodeSet}, + []logical{nil, cmpString_Numeric, cmpString_String, cmpString_NodeSet}, + []logical{nil, cmpNodeSet_Numeric, cmpNodeSet_String, cmpNodeSet_NodeSet}, +} + +// number vs number +func cmpNumberNumberF(op string, a, b float64) bool { + switch op { + case "=": + return a == b + case ">": + return a > b + case "<": + return a < b + case ">=": + return a >= b + case "<=": + return a <= b + case "!=": + return a != b + } + return false +} + +// string vs string +func cmpStringStringF(op string, a, b string) bool { + switch op { + case "=": + return a == b + case ">": + return a > b + case "<": + return a < b + case ">=": + return a >= b + case "<=": + return a <= b + case "!=": + return a != b + } + return false +} + +func cmpBooleanBooleanF(op string, a, b bool) bool { + switch op { + case "or": + return a || b + case "and": + return a && b + } + return false +} + +func cmpNumeric_Numeric(t query.Iterator, op string, m, n interface{}) bool { + a := m.(float64) + b := n.(float64) + return cmpNumberNumberF(op, a, b) +} + +func cmpNumeric_String(t query.Iterator, op string, m, n interface{}) bool { + a := m.(float64) + b := n.(string) + num, err := strconv.ParseFloat(b, 64) + if err != nil { + panic(err) + } + return cmpNumberNumberF(op, a, num) +} + +func cmpNumeric_NodeSet(t query.Iterator, op string, m, n interface{}) bool { + a := m.(float64) + b := n.(query.Query) + + for { + node := b.Select(t) + if node == nil { + break + } + num, err := strconv.ParseFloat(node.Value(), 64) + if err != nil { + panic(err) + } + if cmpNumberNumberF(op, a, num) { + return true + } + } + return false +} + +func cmpNodeSet_Numeric(t query.Iterator, op string, m, n interface{}) bool { + a := m.(query.Query) + b := n.(float64) + for { + node := a.Select(t) + if node == nil { + break + } + num, err := strconv.ParseFloat(node.Value(), 64) + if err != nil { + panic(err) + } + if cmpNumberNumberF(op, num, b) { + return true + } + } + return false +} + +func cmpNodeSet_String(t query.Iterator, op string, m, n interface{}) bool { + a := m.(query.Query) + b := n.(string) + for { + node := a.Select(t) + if node == nil { + break + } + if cmpStringStringF(op, b, node.Value()) { + return true + } + } + return false +} + +func cmpNodeSet_NodeSet(t query.Iterator, op string, m, n interface{}) bool { + return false +} + +func cmpString_Numeric(t query.Iterator, op string, m, n interface{}) bool { + a := m.(string) + b := n.(float64) + num, err := strconv.ParseFloat(a, 64) + if err != nil { + panic(err) + } + return cmpNumberNumberF(op, b, num) +} + +func cmpString_String(t query.Iterator, op string, m, n interface{}) bool { + a := m.(string) + b := n.(string) + return cmpStringStringF(op, a, b) +} + +func cmpString_NodeSet(t query.Iterator, op string, m, n interface{}) bool { + a := m.(string) + b := n.(query.Query) + for { + node := b.Select(t) + if node == nil { + break + } + if cmpStringStringF(op, a, node.Value()) { + return true + } + } + return false +} + +func cmpBoolean_Boolean(t query.Iterator, op string, m, n interface{}) bool { + a := m.(bool) + b := n.(bool) + return cmpBooleanBooleanF(op, a, b) +} + +// eqFunc is an `=` operator. +func eqFunc(t query.Iterator, m, n interface{}) interface{} { + t1 := getValueType(m) + t2 := getValueType(n) + return logicalFuncs[t1][t2](t, "=", m, n) +} + +// gtFunc is an `>` operator. +func gtFunc(t query.Iterator, m, n interface{}) interface{} { + t1 := getValueType(m) + t2 := getValueType(n) + return logicalFuncs[t1][t2](t, ">", m, n) +} + +// geFunc is an `>=` operator. +func geFunc(t query.Iterator, m, n interface{}) interface{} { + t1 := getValueType(m) + t2 := getValueType(n) + return logicalFuncs[t1][t2](t, ">=", m, n) +} + +// ltFunc is an `<` operator. +func ltFunc(t query.Iterator, m, n interface{}) interface{} { + t1 := getValueType(m) + t2 := getValueType(n) + return logicalFuncs[t1][t2](t, "<", m, n) +} + +// leFunc is an `<=` operator. +func leFunc(t query.Iterator, m, n interface{}) interface{} { + t1 := getValueType(m) + t2 := getValueType(n) + return logicalFuncs[t1][t2](t, "<=", m, n) +} + +// neFunc is an `!=` operator. +func neFunc(t query.Iterator, m, n interface{}) interface{} { + t1 := getValueType(m) + t2 := getValueType(n) + return logicalFuncs[t1][t2](t, "!=", m, n) +} + +// orFunc is an `or` operator. +var orFunc = func(t query.Iterator, m, n interface{}) interface{} { + t1 := getValueType(m) + t2 := getValueType(n) + return logicalFuncs[t1][t2](t, "or", m, n) +} + +func numericExpr(m, n interface{}, cb func(float64, float64) float64) float64 { + typ := reflect.TypeOf(float64(0)) + a := reflect.ValueOf(m).Convert(typ) + b := reflect.ValueOf(n).Convert(typ) + return cb(a.Float(), b.Float()) +} + +// plusFunc is an `+` operator. +var plusFunc = func(m, n interface{}) interface{} { + return numericExpr(m, n, func(a, b float64) float64 { + return a + b + }) +} + +// minusFunc is an `-` operator. +var minusFunc = func(m, n interface{}) interface{} { + return numericExpr(m, n, func(a, b float64) float64 { + return a - b + }) +} + +// mulFunc is an `*` operator. +var mulFunc = func(m, n interface{}) interface{} { + return numericExpr(m, n, func(a, b float64) float64 { + return a * b + }) +} + +// divFunc is an `DIV` operator. +var divFunc = func(m, n interface{}) interface{} { + return numericExpr(m, n, func(a, b float64) float64 { + return a / b + }) +} + +// modFunc is an 'MOD' operator. +var modFunc = func(m, n interface{}) interface{} { + return numericExpr(m, n, func(a, b float64) float64 { + return float64(int(a) % int(b)) + }) +} diff --git a/trunk/goutil/xmlUtil/gxpath/internal/build/func.go b/trunk/goutil/xmlUtil/gxpath/internal/build/func.go new file mode 100644 index 0000000..1e841c9 --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/internal/build/func.go @@ -0,0 +1,160 @@ +package build + +import ( + "errors" + "strings" + + "goutil/xmlUtil/gxpath/internal/query" + "goutil/xmlUtil/gxpath/xpath" +) + +func predicate(q query.Query) func(xpath.NodeNavigator) bool { + type Predicater interface { + Test(xpath.NodeNavigator) bool + } + if p, ok := q.(Predicater); ok { + return p.Test + } + return func(xpath.NodeNavigator) bool { return true } +} + +// positionFunc is a XPath Node Set functions postion(). +var positionFunc = func(q query.Query, t query.Iterator) interface{} { + var ( + count = 1 + node = t.Current() + ) + test := predicate(q) + for node.MoveToPrevious() { + if test(node) { + count++ + } + } + return float64(count) +} + +// lastFunc is a XPath Node Set functions last(). +var lastFunc = func(q query.Query, t query.Iterator) interface{} { + var ( + count = 0 + node = t.Current() + ) + node.MoveToFirst() + test := predicate(q) + for { + if test(node) { + count++ + } + if !node.MoveToNext() { + break + } + } + return float64(count) +} + +// countFunc is a XPath Node Set functions count(node-set). +var countFunc = func(q query.Query, t query.Iterator) interface{} { + var ( + count = 0 + node = t.Current() + ) + node.MoveToFirst() + test := predicate(q) + for { + if test(node) { + count++ + } + if !node.MoveToNext() { + break + } + } + return float64(count) +} + +// nameFunc is a XPath functions name([node-set]). +var nameFunc = func(q query.Query, t query.Iterator) interface{} { + return t.Current().LocalName() +} + +// startwithFunc is a XPath functions starts-with(string, string). +type startwithFunc struct { + arg1, arg2 query.Query +} + +func (s *startwithFunc) do(q query.Query, t query.Iterator) interface{} { + var ( + m, n string + ok bool + ) + switch typ := s.arg1.Evaluate(t).(type) { + case string: + m = typ + case query.Query: + node := typ.Select(t) + if node == nil { + return false + } + m = node.Value() + default: + panic(errors.New("starts-with() function argument type must be string")) + } + n, ok = s.arg2.Evaluate(t).(string) + if !ok { + panic(errors.New("starts-with() function argument type must be string")) + } + return strings.HasPrefix(m, n) +} + +// normalizespaceFunc is XPath functions normalize-space(string?) +var normalizespaceFunc = func(q query.Query, t query.Iterator) interface{} { + var m string + switch typ := q.Evaluate(t).(type) { + case string: + m = typ + case query.Query: + node := typ.Select(t) + if node == nil { + return false + } + m = node.Value() + } + return strings.TrimSpace(m) +} + +// substringFunc is XPath functions substring function returns a part of a given string. +type substringFunc struct { + arg1, arg2, arg3 query.Query +} + +func (f *substringFunc) do(q query.Query, t query.Iterator) interface{} { + var m string + switch typ := f.arg1.Evaluate(t).(type) { + case string: + m = typ + case query.Query: + node := typ.Select(t) + if node == nil { + return false + } + m = node.Value() + } + + var start, length float64 + var ok bool + + if start, ok = f.arg2.Evaluate(t).(float64); !ok { + panic(errors.New("substring() function first argument type must be int")) + } + if f.arg3 != nil { + if length, ok = f.arg3.Evaluate(t).(float64); !ok { + panic(errors.New("substring() function second argument type must be int")) + } + } + if (len(m) - int(start)) < int(length) { + panic(errors.New("substring() function start and length argument out of range")) + } + if length > 0 { + return m[int(start):int(length+start)] + } + return m[int(start):] +} diff --git a/trunk/goutil/xmlUtil/gxpath/internal/parse/char.go b/trunk/goutil/xmlUtil/gxpath/internal/parse/char.go new file mode 100644 index 0000000..4ff1735 --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/internal/parse/char.go @@ -0,0 +1,315 @@ +package parse + +import "unicode" + +var first = &unicode.RangeTable{ + R16: []unicode.Range16{ + {0x003A, 0x003A, 1}, + {0x0041, 0x005A, 1}, + {0x005F, 0x005F, 1}, + {0x0061, 0x007A, 1}, + {0x00C0, 0x00D6, 1}, + {0x00D8, 0x00F6, 1}, + {0x00F8, 0x00FF, 1}, + {0x0100, 0x0131, 1}, + {0x0134, 0x013E, 1}, + {0x0141, 0x0148, 1}, + {0x014A, 0x017E, 1}, + {0x0180, 0x01C3, 1}, + {0x01CD, 0x01F0, 1}, + {0x01F4, 0x01F5, 1}, + {0x01FA, 0x0217, 1}, + {0x0250, 0x02A8, 1}, + {0x02BB, 0x02C1, 1}, + {0x0386, 0x0386, 1}, + {0x0388, 0x038A, 1}, + {0x038C, 0x038C, 1}, + {0x038E, 0x03A1, 1}, + {0x03A3, 0x03CE, 1}, + {0x03D0, 0x03D6, 1}, + {0x03DA, 0x03E0, 2}, + {0x03E2, 0x03F3, 1}, + {0x0401, 0x040C, 1}, + {0x040E, 0x044F, 1}, + {0x0451, 0x045C, 1}, + {0x045E, 0x0481, 1}, + {0x0490, 0x04C4, 1}, + {0x04C7, 0x04C8, 1}, + {0x04CB, 0x04CC, 1}, + {0x04D0, 0x04EB, 1}, + {0x04EE, 0x04F5, 1}, + {0x04F8, 0x04F9, 1}, + {0x0531, 0x0556, 1}, + {0x0559, 0x0559, 1}, + {0x0561, 0x0586, 1}, + {0x05D0, 0x05EA, 1}, + {0x05F0, 0x05F2, 1}, + {0x0621, 0x063A, 1}, + {0x0641, 0x064A, 1}, + {0x0671, 0x06B7, 1}, + {0x06BA, 0x06BE, 1}, + {0x06C0, 0x06CE, 1}, + {0x06D0, 0x06D3, 1}, + {0x06D5, 0x06D5, 1}, + {0x06E5, 0x06E6, 1}, + {0x0905, 0x0939, 1}, + {0x093D, 0x093D, 1}, + {0x0958, 0x0961, 1}, + {0x0985, 0x098C, 1}, + {0x098F, 0x0990, 1}, + {0x0993, 0x09A8, 1}, + {0x09AA, 0x09B0, 1}, + {0x09B2, 0x09B2, 1}, + {0x09B6, 0x09B9, 1}, + {0x09DC, 0x09DD, 1}, + {0x09DF, 0x09E1, 1}, + {0x09F0, 0x09F1, 1}, + {0x0A05, 0x0A0A, 1}, + {0x0A0F, 0x0A10, 1}, + {0x0A13, 0x0A28, 1}, + {0x0A2A, 0x0A30, 1}, + {0x0A32, 0x0A33, 1}, + {0x0A35, 0x0A36, 1}, + {0x0A38, 0x0A39, 1}, + {0x0A59, 0x0A5C, 1}, + {0x0A5E, 0x0A5E, 1}, + {0x0A72, 0x0A74, 1}, + {0x0A85, 0x0A8B, 1}, + {0x0A8D, 0x0A8D, 1}, + {0x0A8F, 0x0A91, 1}, + {0x0A93, 0x0AA8, 1}, + {0x0AAA, 0x0AB0, 1}, + {0x0AB2, 0x0AB3, 1}, + {0x0AB5, 0x0AB9, 1}, + {0x0ABD, 0x0AE0, 0x23}, + {0x0B05, 0x0B0C, 1}, + {0x0B0F, 0x0B10, 1}, + {0x0B13, 0x0B28, 1}, + {0x0B2A, 0x0B30, 1}, + {0x0B32, 0x0B33, 1}, + {0x0B36, 0x0B39, 1}, + {0x0B3D, 0x0B3D, 1}, + {0x0B5C, 0x0B5D, 1}, + {0x0B5F, 0x0B61, 1}, + {0x0B85, 0x0B8A, 1}, + {0x0B8E, 0x0B90, 1}, + {0x0B92, 0x0B95, 1}, + {0x0B99, 0x0B9A, 1}, + {0x0B9C, 0x0B9C, 1}, + {0x0B9E, 0x0B9F, 1}, + {0x0BA3, 0x0BA4, 1}, + {0x0BA8, 0x0BAA, 1}, + {0x0BAE, 0x0BB5, 1}, + {0x0BB7, 0x0BB9, 1}, + {0x0C05, 0x0C0C, 1}, + {0x0C0E, 0x0C10, 1}, + {0x0C12, 0x0C28, 1}, + {0x0C2A, 0x0C33, 1}, + {0x0C35, 0x0C39, 1}, + {0x0C60, 0x0C61, 1}, + {0x0C85, 0x0C8C, 1}, + {0x0C8E, 0x0C90, 1}, + {0x0C92, 0x0CA8, 1}, + {0x0CAA, 0x0CB3, 1}, + {0x0CB5, 0x0CB9, 1}, + {0x0CDE, 0x0CDE, 1}, + {0x0CE0, 0x0CE1, 1}, + {0x0D05, 0x0D0C, 1}, + {0x0D0E, 0x0D10, 1}, + {0x0D12, 0x0D28, 1}, + {0x0D2A, 0x0D39, 1}, + {0x0D60, 0x0D61, 1}, + {0x0E01, 0x0E2E, 1}, + {0x0E30, 0x0E30, 1}, + {0x0E32, 0x0E33, 1}, + {0x0E40, 0x0E45, 1}, + {0x0E81, 0x0E82, 1}, + {0x0E84, 0x0E84, 1}, + {0x0E87, 0x0E88, 1}, + {0x0E8A, 0x0E8D, 3}, + {0x0E94, 0x0E97, 1}, + {0x0E99, 0x0E9F, 1}, + {0x0EA1, 0x0EA3, 1}, + {0x0EA5, 0x0EA7, 2}, + {0x0EAA, 0x0EAB, 1}, + {0x0EAD, 0x0EAE, 1}, + {0x0EB0, 0x0EB0, 1}, + {0x0EB2, 0x0EB3, 1}, + {0x0EBD, 0x0EBD, 1}, + {0x0EC0, 0x0EC4, 1}, + {0x0F40, 0x0F47, 1}, + {0x0F49, 0x0F69, 1}, + {0x10A0, 0x10C5, 1}, + {0x10D0, 0x10F6, 1}, + {0x1100, 0x1100, 1}, + {0x1102, 0x1103, 1}, + {0x1105, 0x1107, 1}, + {0x1109, 0x1109, 1}, + {0x110B, 0x110C, 1}, + {0x110E, 0x1112, 1}, + {0x113C, 0x1140, 2}, + {0x114C, 0x1150, 2}, + {0x1154, 0x1155, 1}, + {0x1159, 0x1159, 1}, + {0x115F, 0x1161, 1}, + {0x1163, 0x1169, 2}, + {0x116D, 0x116E, 1}, + {0x1172, 0x1173, 1}, + {0x1175, 0x119E, 0x119E - 0x1175}, + {0x11A8, 0x11AB, 0x11AB - 0x11A8}, + {0x11AE, 0x11AF, 1}, + {0x11B7, 0x11B8, 1}, + {0x11BA, 0x11BA, 1}, + {0x11BC, 0x11C2, 1}, + {0x11EB, 0x11F0, 0x11F0 - 0x11EB}, + {0x11F9, 0x11F9, 1}, + {0x1E00, 0x1E9B, 1}, + {0x1EA0, 0x1EF9, 1}, + {0x1F00, 0x1F15, 1}, + {0x1F18, 0x1F1D, 1}, + {0x1F20, 0x1F45, 1}, + {0x1F48, 0x1F4D, 1}, + {0x1F50, 0x1F57, 1}, + {0x1F59, 0x1F5B, 0x1F5B - 0x1F59}, + {0x1F5D, 0x1F5D, 1}, + {0x1F5F, 0x1F7D, 1}, + {0x1F80, 0x1FB4, 1}, + {0x1FB6, 0x1FBC, 1}, + {0x1FBE, 0x1FBE, 1}, + {0x1FC2, 0x1FC4, 1}, + {0x1FC6, 0x1FCC, 1}, + {0x1FD0, 0x1FD3, 1}, + {0x1FD6, 0x1FDB, 1}, + {0x1FE0, 0x1FEC, 1}, + {0x1FF2, 0x1FF4, 1}, + {0x1FF6, 0x1FFC, 1}, + {0x2126, 0x2126, 1}, + {0x212A, 0x212B, 1}, + {0x212E, 0x212E, 1}, + {0x2180, 0x2182, 1}, + {0x3007, 0x3007, 1}, + {0x3021, 0x3029, 1}, + {0x3041, 0x3094, 1}, + {0x30A1, 0x30FA, 1}, + {0x3105, 0x312C, 1}, + {0x4E00, 0x9FA5, 1}, + {0xAC00, 0xD7A3, 1}, + }, +} + +var second = &unicode.RangeTable{ + R16: []unicode.Range16{ + {0x002D, 0x002E, 1}, + {0x0030, 0x0039, 1}, + {0x00B7, 0x00B7, 1}, + {0x02D0, 0x02D1, 1}, + {0x0300, 0x0345, 1}, + {0x0360, 0x0361, 1}, + {0x0387, 0x0387, 1}, + {0x0483, 0x0486, 1}, + {0x0591, 0x05A1, 1}, + {0x05A3, 0x05B9, 1}, + {0x05BB, 0x05BD, 1}, + {0x05BF, 0x05BF, 1}, + {0x05C1, 0x05C2, 1}, + {0x05C4, 0x0640, 0x0640 - 0x05C4}, + {0x064B, 0x0652, 1}, + {0x0660, 0x0669, 1}, + {0x0670, 0x0670, 1}, + {0x06D6, 0x06DC, 1}, + {0x06DD, 0x06DF, 1}, + {0x06E0, 0x06E4, 1}, + {0x06E7, 0x06E8, 1}, + {0x06EA, 0x06ED, 1}, + {0x06F0, 0x06F9, 1}, + {0x0901, 0x0903, 1}, + {0x093C, 0x093C, 1}, + {0x093E, 0x094C, 1}, + {0x094D, 0x094D, 1}, + {0x0951, 0x0954, 1}, + {0x0962, 0x0963, 1}, + {0x0966, 0x096F, 1}, + {0x0981, 0x0983, 1}, + {0x09BC, 0x09BC, 1}, + {0x09BE, 0x09BF, 1}, + {0x09C0, 0x09C4, 1}, + {0x09C7, 0x09C8, 1}, + {0x09CB, 0x09CD, 1}, + {0x09D7, 0x09D7, 1}, + {0x09E2, 0x09E3, 1}, + {0x09E6, 0x09EF, 1}, + {0x0A02, 0x0A3C, 0x3A}, + {0x0A3E, 0x0A3F, 1}, + {0x0A40, 0x0A42, 1}, + {0x0A47, 0x0A48, 1}, + {0x0A4B, 0x0A4D, 1}, + {0x0A66, 0x0A6F, 1}, + {0x0A70, 0x0A71, 1}, + {0x0A81, 0x0A83, 1}, + {0x0ABC, 0x0ABC, 1}, + {0x0ABE, 0x0AC5, 1}, + {0x0AC7, 0x0AC9, 1}, + {0x0ACB, 0x0ACD, 1}, + {0x0AE6, 0x0AEF, 1}, + {0x0B01, 0x0B03, 1}, + {0x0B3C, 0x0B3C, 1}, + {0x0B3E, 0x0B43, 1}, + {0x0B47, 0x0B48, 1}, + {0x0B4B, 0x0B4D, 1}, + {0x0B56, 0x0B57, 1}, + {0x0B66, 0x0B6F, 1}, + {0x0B82, 0x0B83, 1}, + {0x0BBE, 0x0BC2, 1}, + {0x0BC6, 0x0BC8, 1}, + {0x0BCA, 0x0BCD, 1}, + {0x0BD7, 0x0BD7, 1}, + {0x0BE7, 0x0BEF, 1}, + {0x0C01, 0x0C03, 1}, + {0x0C3E, 0x0C44, 1}, + {0x0C46, 0x0C48, 1}, + {0x0C4A, 0x0C4D, 1}, + {0x0C55, 0x0C56, 1}, + {0x0C66, 0x0C6F, 1}, + {0x0C82, 0x0C83, 1}, + {0x0CBE, 0x0CC4, 1}, + {0x0CC6, 0x0CC8, 1}, + {0x0CCA, 0x0CCD, 1}, + {0x0CD5, 0x0CD6, 1}, + {0x0CE6, 0x0CEF, 1}, + {0x0D02, 0x0D03, 1}, + {0x0D3E, 0x0D43, 1}, + {0x0D46, 0x0D48, 1}, + {0x0D4A, 0x0D4D, 1}, + {0x0D57, 0x0D57, 1}, + {0x0D66, 0x0D6F, 1}, + {0x0E31, 0x0E31, 1}, + {0x0E34, 0x0E3A, 1}, + {0x0E46, 0x0E46, 1}, + {0x0E47, 0x0E4E, 1}, + {0x0E50, 0x0E59, 1}, + {0x0EB1, 0x0EB1, 1}, + {0x0EB4, 0x0EB9, 1}, + {0x0EBB, 0x0EBC, 1}, + {0x0EC6, 0x0EC6, 1}, + {0x0EC8, 0x0ECD, 1}, + {0x0ED0, 0x0ED9, 1}, + {0x0F18, 0x0F19, 1}, + {0x0F20, 0x0F29, 1}, + {0x0F35, 0x0F39, 2}, + {0x0F3E, 0x0F3F, 1}, + {0x0F71, 0x0F84, 1}, + {0x0F86, 0x0F8B, 1}, + {0x0F90, 0x0F95, 1}, + {0x0F97, 0x0F97, 1}, + {0x0F99, 0x0FAD, 1}, + {0x0FB1, 0x0FB7, 1}, + {0x0FB9, 0x0FB9, 1}, + {0x20D0, 0x20DC, 1}, + {0x20E1, 0x3005, 0x3005 - 0x20E1}, + {0x302A, 0x302F, 1}, + {0x3031, 0x3035, 1}, + {0x3099, 0x309A, 1}, + {0x309D, 0x309E, 1}, + {0x30FC, 0x30FE, 1}, + }, +} diff --git a/trunk/goutil/xmlUtil/gxpath/internal/parse/node.go b/trunk/goutil/xmlUtil/gxpath/internal/parse/node.go new file mode 100644 index 0000000..7a53b55 --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/internal/parse/node.go @@ -0,0 +1,132 @@ +package parse + +import ( + "bytes" + "fmt" +) + +// A Node is an element in the parse tree. +type Node interface { + Type() NodeType + String() string +} + +// NodeType identifies the type of a parse tree node. +type NodeType int + +func (t NodeType) Type() NodeType { + return t +} + +const ( + NodeRoot NodeType = iota + NodeAxis + NodeFilter + NodeFunction + NodeOperator + NodeVariable + NodeConstantOperand +) + +// RootNode holds a top-level node of tree. +type RootNode struct { + NodeType + slash string +} + +func (r *RootNode) String() string { + return r.slash +} + +// OperatorNode holds two Nodes operator. +type OperatorNode struct { + NodeType + Op string + Left Node + Right Node +} + +func (o *OperatorNode) String() string { + return fmt.Sprintf("%v%s%v", o.Left, o.Op, o.Right) +} + +// AxisNode holds a location step. +type AxisNode struct { + NodeType + Input Node + Prop string // node-test name.[comment|text|processing-instruction|node] + AxeType string // name of the axes.[attribute|ancestor|child|....] + LocalName string // local part name of node. + Prefix string // prefix name of node. +} + +func (a *AxisNode) String() string { + var b bytes.Buffer + if a.AxeType != "" { + b.Write([]byte(a.AxeType + "::")) + } + if a.Prefix != "" { + b.Write([]byte(a.Prefix + ":")) + } + b.Write([]byte(a.LocalName)) + if a.Prop != "" { + b.Write([]byte("/" + a.Prop + "()")) + } + return b.String() +} + +// OperandNode holds a constant operand. +type OperandNode struct { + NodeType + Val interface{} +} + +func (o *OperandNode) String() string { + return fmt.Sprintf("%v", o.Val) +} + +// FilterNode holds a condition filter. +type FilterNode struct { + NodeType + Input, Condition Node +} + +func (f *FilterNode) String() string { + return fmt.Sprintf("%s[%s]", f.Input, f.Condition) +} + +// VariableNode holds a variable. +type VariableNode struct { + NodeType + Name, Prefix string +} + +func (v *VariableNode) String() string { + if v.Prefix == "" { + return v.Name + } + return fmt.Sprintf("%s:%s", v.Prefix, v.Name) +} + +// FunctionNode holds a function call. +type FunctionNode struct { + NodeType + Args []Node + Prefix string + FuncName string // function name +} + +func (f *FunctionNode) String() string { + var b bytes.Buffer + // fun(arg1, ..., argn) + b.Write([]byte(f.FuncName)) + b.Write([]byte("(")) + for i, arg := range f.Args { + if i > 0 { + b.Write([]byte(",")) + } + b.Write([]byte(fmt.Sprintf("%s", arg))) + } + b.Write([]byte(")")) + return b.String() +} diff --git a/trunk/goutil/xmlUtil/gxpath/internal/parse/parse.go b/trunk/goutil/xmlUtil/gxpath/internal/parse/parse.go new file mode 100644 index 0000000..cfbbec0 --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/internal/parse/parse.go @@ -0,0 +1,447 @@ +// https://www.w3.org/TR/xpath/ + +package parse + +import "fmt" + +type parser struct { + r *scanner + d int +} + +// newOperatorNode returns new operator node OperatorNode. +func newOperatorNode(op string, left, right Node) Node { + return &OperatorNode{NodeType: NodeOperator, Op: op, Left: left, Right: right} +} + +// newOperand returns new constant operand node OperandNode. +func newOperandNode(v interface{}) Node { + return &OperandNode{NodeType: NodeConstantOperand, Val: v} +} + +// newAxisNode returns new axis node AxisNode. +func newAxisNode(axeTyp, localName, prefix, prop string, n Node) Node { + return &AxisNode{ + NodeType: NodeAxis, + LocalName: localName, + Prefix: prefix, + AxeType: axeTyp, + Prop: prop, + Input: n, + } +} + +// newVariableNode returns new variable node VariableNode. +func newVariableNode(prefix, name string) Node { + return &VariableNode{NodeType: NodeVariable, Name: name, Prefix: prefix} +} + +// newFilterNode returns a new filter node FilterNode. +func newFilterNode(n, m Node) Node { + return &FilterNode{NodeType: NodeFilter, Input: n, Condition: m} +} + +// newRootNode returns a root node. +func newRootNode(s string) Node { + return &RootNode{NodeType: NodeRoot, slash: s} +} + +// newFunctionNode returns function call node. +func newFunctionNode(name, prefix string, args []Node) Node { + return &FunctionNode{NodeType: NodeFunction, Prefix: prefix, FuncName: name, Args: args} +} + +// testOp reports whether current item name is an operand op. +func testOp(r *scanner, op string) bool { + return r.typ == itemName && r.prefix == "" && r.name == op +} + +func isPrimaryExpr(r *scanner) bool { + switch r.typ { + case itemString, itemNumber, itemDollar, itemLParens: + return true + case itemName: + return r.canBeFunc && !isNodeType(r) + } + return false +} + +func isNodeType(r *scanner) bool { + switch r.name { + case "node", "text", "processing-instruction", "comment": + return r.prefix == "" + } + return false +} + +func isStep(item itemType) bool { + switch item { + case itemDot, itemDotDot, itemAt, itemAxe, itemStar, itemName: + return true + } + return false +} + +func checkItem(r *scanner, typ itemType) { + if r.typ != typ { + panic(fmt.Sprintf("%s has an invalid token", r.text)) + } +} + +// parseExpression parsing the expression with input Node n. +func (p *parser) parseExpression(n Node) Node { + if p.d = p.d + 1; p.d > 200 { + panic("the xpath query is too complex(depth > 200)") + } + n = p.parseOrExpr(n) + p.d-- + return n +} + +// next scanning next item on forward. +func (p *parser) next() bool { + return p.r.nextItem() +} + +func (p *parser) skipItem(typ itemType) { + checkItem(p.r, typ) + p.next() +} + +// OrExpr ::= AndExpr | OrExpr 'or' AndExpr +func (p *parser) parseOrExpr(n Node) Node { + opnd := p.parseAndExpr(n) + for { + if !testOp(p.r, "or") { + break + } + p.next() + opnd = newOperatorNode("or", opnd, p.parseAndExpr(n)) + } + return opnd +} + +// AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr +func (p *parser) parseAndExpr(n Node) Node { + opnd := p.parseEqualityExpr(n) + for { + if !testOp(p.r, "and") { + break + } + p.next() + opnd = newOperatorNode("and", opnd, p.parseEqualityExpr(n)) + } + return opnd +} + +// EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr | EqualityExpr '!=' RelationalExpr +func (p *parser) parseEqualityExpr(n Node) Node { + opnd := p.parseRelationalExpr(n) +Loop: + for { + var op string + switch p.r.typ { + case itemEq: + op = "=" + case itemNe: + op = "!=" + default: + break Loop + } + p.next() + opnd = newOperatorNode(op, opnd, p.parseRelationalExpr(n)) + } + return opnd +} + +// RelationalExpr ::= AdditiveExpr | RelationalExpr '<' AdditiveExpr | RelationalExpr '>' AdditiveExpr +// | RelationalExpr '<=' AdditiveExpr +// | RelationalExpr '>=' AdditiveExpr +func (p *parser) parseRelationalExpr(n Node) Node { + opnd := p.parseAdditiveExpr(n) +Loop: + for { + var op string + switch p.r.typ { + case itemLt: + op = "<" + case itemGt: + op = ">" + case itemLe: + op = "<=" + case itemGe: + op = ">=" + default: + break Loop + } + p.next() + opnd = newOperatorNode(op, opnd, p.parseAdditiveExpr(n)) + } + return opnd +} + +// AdditiveExpr ::= MultiplicativeExpr | AdditiveExpr '+' MultiplicativeExpr | AdditiveExpr '-' MultiplicativeExpr +func (p *parser) parseAdditiveExpr(n Node) Node { + opnd := p.parseMultiplicativeExpr(n) +Loop: + for { + var op string + switch p.r.typ { + case itemPlus: + op = "+" + case itemMinus: + op = "-" + default: + break Loop + } + p.next() + opnd = newOperatorNode(op, opnd, p.parseMultiplicativeExpr(n)) + } + return opnd +} + +// MultiplicativeExpr ::= UnaryExpr | MultiplicativeExpr MultiplyOperator(*) UnaryExpr +// | MultiplicativeExpr 'div' UnaryExpr | MultiplicativeExpr 'mod' UnaryExpr +func (p *parser) parseMultiplicativeExpr(n Node) Node { + opnd := p.parseUnaryExpr(n) +Loop: + for { + var op string + if p.r.typ == itemStar { + op = "*" + } else if testOp(p.r, "div") || testOp(p.r, "mod") { + op = p.r.name + } else { + break Loop + } + p.next() + opnd = newOperatorNode(op, opnd, p.parseUnaryExpr(n)) + } + return opnd +} + +// UnaryExpr ::= UnionExpr | '-' UnaryExpr +func (p *parser) parseUnaryExpr(n Node) Node { + minus := false + // ignore '-' sequence + for p.r.typ == itemMinus { + p.next() + minus = !minus + } + opnd := p.parseUnionExpr(n) + if minus { + opnd = newOperatorNode("*", opnd, newOperandNode(float64(-1))) + } + return opnd +} + +// UnionExpr ::= PathExpr | UnionExpr '|' PathExpr +func (p *parser) parseUnionExpr(n Node) Node { + opnd := p.parsePathExpr(n) +Loop: + for { + if p.r.typ != itemUnion { + break Loop + } + p.next() + opnd2 := p.parsePathExpr(n) + // Checking the node type that must be is node set type? + opnd = newOperatorNode("|", opnd, opnd2) + } + return opnd +} + +// PathExpr ::= LocationPath | FilterExpr | FilterExpr '/' RelativeLocationPath | FilterExpr '//' RelativeLocationPath +func (p *parser) parsePathExpr(n Node) Node { + var opnd Node + if isPrimaryExpr(p.r) { + opnd = p.parseFilterExpr(n) + switch p.r.typ { + case itemSlash: + p.next() + opnd = p.parseRelativeLocationPath(opnd) + case itemSlashSlash: + p.next() + opnd = p.parseRelativeLocationPath(newAxisNode("descendant-or-self", "", "", "", opnd)) + } + } else { + opnd = p.parseLocationPath(nil) + } + return opnd +} + +// FilterExpr ::= PrimaryExpr | FilterExpr Predicate +func (p *parser) parseFilterExpr(n Node) Node { + opnd := p.parsePrimaryExpr(n) + if p.r.typ == itemLBracket { + opnd = newFilterNode(opnd, p.parsePredicate(opnd)) + } + return opnd +} + +// Predicate ::= '[' PredicateExpr ']' +func (p *parser) parsePredicate(n Node) Node { + p.skipItem(itemLBracket) + opnd := p.parseExpression(n) + p.skipItem(itemRBracket) + return opnd +} + +// LocationPath ::= RelativeLocationPath | AbsoluteLocationPath +func (p *parser) parseLocationPath(n Node) (opnd Node) { + switch p.r.typ { + case itemSlash: + p.next() + opnd = newRootNode("/") + if isStep(p.r.typ) { + opnd = p.parseRelativeLocationPath(opnd) // ?? child:: or self ?? + } + case itemSlashSlash: + p.next() + opnd = newRootNode("//") + opnd = p.parseRelativeLocationPath(newAxisNode("descendant-or-self", "", "", "", opnd)) + default: + opnd = p.parseRelativeLocationPath(n) + } + return opnd +} + +// RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | AbbreviatedRelativeLocationPath +func (p *parser) parseRelativeLocationPath(n Node) Node { + opnd := n +Loop: + for { + opnd = p.parseStep(opnd) + switch p.r.typ { + case itemSlashSlash: + p.next() + opnd = newAxisNode("descendant-or-self", "", "", "", opnd) + case itemSlash: + p.next() + default: + break Loop + } + } + return opnd +} + +// Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep +func (p *parser) parseStep(n Node) Node { + axeTyp := "child" // default axes value. + if p.r.typ == itemDot || p.r.typ == itemDotDot { + if p.r.typ == itemDot { + axeTyp = "self" + } else { + axeTyp = "parent" + } + p.next() + return newAxisNode(axeTyp, "", "", "", n) + } + switch p.r.typ { + case itemAt: + p.next() + axeTyp = "attribute" + case itemAxe: + axeTyp = p.r.name + p.next() + } + opnd := p.parseNodeTest(n, axeTyp) + if p.r.typ == itemLBracket { + opnd = newFilterNode(opnd, p.parsePredicate(opnd)) + } + return opnd +} + +// NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' +func (p *parser) parseNodeTest(n Node, axeTyp string) (opnd Node) { + switch p.r.typ { + case itemName: + if p.r.canBeFunc && isNodeType(p.r) { + var prop string + switch p.r.name { + case "comment", "text", "processing-instruction", "node": + prop = p.r.name + } + var name string + p.next() + p.skipItem(itemLParens) + if prop == "processing-instruction" && p.r.typ != itemRParens { + checkItem(p.r, itemString) + name = p.r.strval + p.next() + } + p.skipItem(itemRParens) + opnd = newAxisNode(axeTyp, name, "", prop, n) + } else { + prefix := p.r.prefix + name := p.r.name + p.next() + if p.r.name == "*" { + name = "" + } + opnd = newAxisNode(axeTyp, name, prefix, "", n) + } + case itemStar: + opnd = newAxisNode(axeTyp, "", "", "", n) + p.next() + default: + panic("expression must evaluate to a node-set") + } + return opnd +} + +// PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall +func (p *parser) parsePrimaryExpr(n Node) (opnd Node) { + switch p.r.typ { + case itemString: + opnd = newOperandNode(p.r.strval) + p.next() + case itemNumber: + opnd = newOperandNode(p.r.numval) + p.next() + case itemDollar: + p.next() + checkItem(p.r, itemName) + opnd = newVariableNode(p.r.prefix, p.r.name) + p.next() + case itemLParens: + p.next() + opnd = p.parseExpression(n) + p.skipItem(itemRParens) + case itemName: + if p.r.canBeFunc && !isNodeType(p.r) { + opnd = p.parseMethod(nil) + } + } + return opnd +} + +// FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument )* )? ')' +func (p *parser) parseMethod(n Node) Node { + var args []Node + name := p.r.name + prefix := p.r.prefix + + p.skipItem(itemName) + p.skipItem(itemLParens) + if p.r.typ != itemRParens { + for { + args = append(args, p.parseExpression(n)) + if p.r.typ == itemRParens { + break + } + p.skipItem(itemComma) + } + } + p.skipItem(itemRParens) + return newFunctionNode(name, prefix, args) +} + +// Parse parsing the XPath express string expr and returns a tree Node. +func Parse(expr string) Node { + r := &scanner{text: expr} + r.nextChar() + r.nextItem() + p := &parser{r: r} + return p.parseExpression(nil) +} diff --git a/trunk/goutil/xmlUtil/gxpath/internal/parse/scan.go b/trunk/goutil/xmlUtil/gxpath/internal/parse/scan.go new file mode 100644 index 0000000..8020dbe --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/internal/parse/scan.go @@ -0,0 +1,284 @@ +package parse + +import ( + "errors" + "fmt" + "strconv" + "unicode" +) + +type itemType int + +const ( + itemComma itemType = iota // ',' + itemSlash // '/' + itemAt // '@' + itemDot // '.' + itemLParens // '(' + itemRParens // ')' + itemLBracket // '[' + itemRBracket // ']' + itemStar // '*' + itemPlus // '+' + itemMinus // '-' + itemEq // '=' + itemLt // '<' + itemGt // '>' + itemBang // '!' + itemDollar // '$' + itemApos // '\'' + itemQuote // '"' + itemUnion // '|' + itemNe // '!=' + itemLe // '<=' + itemGe // '>=' + itemAnd // '&&' + itemOr // '||' + itemDotDot // '..' + itemSlashSlash // '//' + itemName // XML Name + itemString // Quoted string constant + itemNumber // Number constant + itemAxe // Axe (like child::) + itemEof // END +) + +type scanner struct { + text, name, prefix string + + pos int + curr rune + typ itemType + strval string // text value at current pos + numval float64 // number value at current pos + canBeFunc bool +} + +func (s *scanner) nextChar() bool { + if s.pos >= len(s.text) { + s.curr = rune(0) + return false + } + s.curr = rune(s.text[s.pos]) + s.pos += 1 + return true +} + +func (s *scanner) nextItem() bool { + s.skipSpace() + switch s.curr { + case 0: + s.typ = itemEof + return false + case ',', '@', '(', ')', '|', '*', '[', ']', '+', '-', '=', '#', '$': + s.typ = asItemType(s.curr) + s.nextChar() + case '<': + s.typ = itemLt + s.nextChar() + if s.curr == '=' { + s.typ = itemLe + s.nextChar() + } + case '>': + s.typ = itemGt + s.nextChar() + if s.curr == '=' { + s.typ = itemGe + s.nextChar() + } + case '!': + s.typ = itemBang + s.nextChar() + if s.curr == '=' { + s.typ = itemNe + s.nextChar() + } + case '.': + s.typ = itemDot + s.nextChar() + if s.curr == '.' { + s.typ = itemDotDot + s.nextChar() + } else if isDigit(s.curr) { + s.typ = itemNumber + s.numval = s.scanFraction() + } + case '/': + s.typ = itemSlash + s.nextChar() + if s.curr == '/' { + s.typ = itemSlashSlash + s.nextChar() + } + case '"', '\'': + s.typ = itemString + s.strval = s.scanString() + default: + if isDigit(s.curr) { + s.typ = itemNumber + s.numval = s.scanNumber() + } else if isName(s.curr) { + s.typ = itemName + s.name = s.scanName() + s.prefix = "" + // "foo:bar" is one itemem not three because it doesn't allow spaces in between + // We should distinct it from "foo::" and need process "foo ::" as well + if s.curr == ':' { + s.nextChar() + // can be "foo:bar" or "foo::" + if s.curr == ':' { + // "foo::" + s.nextChar() + s.typ = itemAxe + } else { // "foo:*", "foo:bar" or "foo: " + s.prefix = s.name + if s.curr == '*' { + s.nextChar() + s.name = "*" + } else if isName(s.curr) { + s.name = s.scanName() + } else { + panic(fmt.Sprintf("%s has an invalid qualified name.", s.text)) + } + } + } else { + s.skipSpace() + if s.curr == ':' { + s.nextChar() + // it can be "foo ::" or just "foo :" + if s.curr == ':' { + s.nextChar() + s.typ = itemAxe + } else { + panic(fmt.Sprintf("%s has an invalid qualified name.", s.text)) + } + } + } + s.skipSpace() + s.canBeFunc = s.curr == '(' + } else { + panic(fmt.Sprintf("%s has an invalid token.", s.text)) + } + } + return true +} + +func (s *scanner) skipSpace() { +Loop: + for { + if !unicode.IsSpace(s.curr) || !s.nextChar() { + break Loop + } + } +} + +func (s *scanner) scanFraction() float64 { + var ( + i = s.pos - 2 + c = 1 // '.' + ) + for isDigit(s.curr) { + s.nextChar() + c++ + } + v, err := strconv.ParseFloat(s.text[i:i+c], 64) + if err != nil { + panic(fmt.Errorf("xpath: scanFraction parse float got error: %v", err)) + } + return v +} + +func (s *scanner) scanNumber() float64 { + var ( + c int + i = s.pos - 1 + ) + for isDigit(s.curr) { + s.nextChar() + c++ + } + if s.curr == '.' { + s.nextChar() + c++ + for isDigit(s.curr) { + s.nextChar() + c++ + } + } + v, err := strconv.ParseFloat(s.text[i:i+c], 64) + if err != nil { + panic(fmt.Errorf("xpath: scanNumber parse float got error: %v", err)) + } + return v +} + +func (s *scanner) scanString() string { + var ( + c = 0 + end = s.curr + ) + s.nextChar() + i := s.pos - 1 + for s.curr != end { + if !s.nextChar() { + panic(errors.New("xpath: scanString got unclosed string")) + } + c++ + } + s.nextChar() + return s.text[i : i+c] +} + +func (s *scanner) scanName() string { + var ( + c int + i = s.pos - 1 + ) + for isName(s.curr) { + c++ + if !s.nextChar() { + break + } + } + return s.text[i : i+c] +} + +func isName(r rune) bool { + return string(r) != ":" && string(r) != "/" && + (unicode.Is(first, r) || unicode.Is(second, r) || string(r) == "*") +} + +func isDigit(r rune) bool { + return unicode.IsDigit(r) +} + +func asItemType(r rune) itemType { + switch r { + case ',': + return itemComma + case '@': + return itemAt + case '(': + return itemLParens + case ')': + return itemRParens + case '|': + return itemUnion + case '*': + return itemStar + case '[': + return itemLBracket + case ']': + return itemRBracket + case '+': + return itemPlus + case '-': + return itemMinus + case '=': + return itemEq + case '$': + return itemDollar + } + panic(fmt.Errorf("unknown item: %v", r)) +} diff --git a/trunk/goutil/xmlUtil/gxpath/internal/query/iter.go b/trunk/goutil/xmlUtil/gxpath/internal/query/iter.go new file mode 100644 index 0000000..5fce316 --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/internal/query/iter.go @@ -0,0 +1,7 @@ +package query + +import "goutil/xmlUtil/gxpath/xpath" + +type Iterator interface { + Current() xpath.NodeNavigator +} diff --git a/trunk/goutil/xmlUtil/gxpath/internal/query/query.go b/trunk/goutil/xmlUtil/gxpath/internal/query/query.go new file mode 100644 index 0000000..29de99f --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/internal/query/query.go @@ -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 +} diff --git a/trunk/goutil/xmlUtil/gxpath/select.go b/trunk/goutil/xmlUtil/gxpath/select.go new file mode 100644 index 0000000..99a713b --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/select.go @@ -0,0 +1,40 @@ +package gxpath + +import ( + "goutil/xmlUtil/gxpath/internal/build" + "goutil/xmlUtil/gxpath/internal/query" + "goutil/xmlUtil/gxpath/xpath" +) + +// NodeIterator holds all matched Node object. +type NodeIterator struct { + node xpath.NodeNavigator + query query.Query +} + +// Current returns current node which matched. +func (t *NodeIterator) Current() xpath.NodeNavigator { + return t.node +} + +// MoveNext moves Navigator to the next match node. +func (t *NodeIterator) MoveNext() bool { + n := t.query.Select(t) + if n != nil { + if !t.node.MoveTo(n) { + t.node = n.Copy() + } + return true + } + return false +} + +// Select selects a node set using the specified XPath expression. +func Select(root xpath.NodeNavigator, expr string) *NodeIterator { + qy, err := build.Build(expr) + if err != nil { + panic(err) + } + t := &NodeIterator{query: qy, node: root} + return t +} diff --git a/trunk/goutil/xmlUtil/gxpath/select_test.go b/trunk/goutil/xmlUtil/gxpath/select_test.go new file mode 100644 index 0000000..d86ebff --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/select_test.go @@ -0,0 +1,496 @@ +package gxpath + +import ( + "bytes" + "strings" + "testing" + + "goutil/xmlUtil/gxpath/xpath" +) + +var html *TNode = example() + +/* +testXPath2(t, html, "//title/node()", 1) still have some error,will fix in future. +testXPath(t, html, "//*[count(*)=3]", "ul") +*/ + +func TestSelf(t *testing.T) { + testXPath(t, html, ".", "html") + testXPath(t, html.FirstChild, ".", "head") + testXPath(t, html, "self::*", "html") + testXPath(t, html.LastChild, "self::body", "body") + testXPath2(t, html, "//body/./ul/li/a", 3) +} + +func TestParent(t *testing.T) { + testXPath(t, html.LastChild, "..", "html") + testXPath(t, html.LastChild, "parent::*", "html") + a := selectNode(html, "//li/a") + testXPath(t, a, "parent::*", "li") + testXPath(t, html, "//title/parent::head", "head") +} + +func TestAttribute(t *testing.T) { + testXPath(t, html, "@lang='en'", "html") + testXPath2(t, html, "@lang='zh'", 0) + testXPath2(t, html, "//@href", 3) + testXPath2(t, html, "//a[@*]", 3) +} + +func TestRelativePath(t *testing.T) { + testXPath(t, html, "head", "head") + testXPath(t, html, "/head", "head") + testXPath(t, html, "body//li", "li") + testXPath(t, html, "/head/title", "title") + + testXPath2(t, html, "/body/ul/li/a", 3) + testXPath(t, html, "//title", "title") + testXPath(t, html, "//title/..", "head") + testXPath(t, html, "//title/../..", "html") + testXPath2(t, html, "//a[@href]", 3) + testXPath(t, html, "//ul/../footer", "footer") +} + +func TestChild(t *testing.T) { + testXPath(t, html, "/child::head", "head") + testXPath(t, html, "/child::head/child::title", "title") + testXPath(t, html, "//title/../child::title", "title") + testXPath(t, html.Parent, "//child::*", "html") +} + +func TestDescendant(t *testing.T) { + testXPath2(t, html, "descendant::*", 15) + testXPath2(t, html, "/head/descendant::*", 2) + testXPath2(t, html, "//ul/descendant::*", 7) //
  • + + testXPath2(t, html, "//ul/descendant::li", 4) //
  • +} + +func TestAncestor(t *testing.T) { + testXPath2(t, html, "/body/footer/ancestor::*", 2) // body>html + testXPath2(t, html, "/body/ul/li/a/ancestor::li", 3) +} + +func TestFollowingSibling(t *testing.T) { + var list []*TNode + list = selectNodes(html, "//li/following-sibling::*") + for _, n := range list { + if n.Data != "li" { + t.Fatalf("expected node is li,but got:%s", n.Data) + } + } + + list = selectNodes(html, "//ul/following-sibling::*") // p,footer + for _, n := range list { + if n.Data != "p" && n.Data != "footer" { + t.Fatal("expected node is not one of the following nodes: [p,footer]") + } + } + testXPath(t, html, "//ul/following-sibling::footer", "footer") + list = selectNodes(html, "//h1/following::*") // ul>li>a,p,footer + if list[0].Data != "ul" { + t.Fatal("expected node is not ul") + } + if list[1].Data != "li" { + t.Fatal("expected node is not li") + } + if list[len(list)-1].Data != "footer" { + t.Fatal("expected node is not footer") + } +} + +func TestPrecedingSibling(t *testing.T) { + testXPath(t, html, "/body/footer/preceding-sibling::*", "p") + testXPath2(t, html, "/body/footer/preceding-sibling::*", 3) // p,ul,h1 + list := selectNodes(html, "//h1/preceding::*") // head>title>meta + if list[0].Data != "head" { + t.Fatal("expected is not head") + } + if list[1].Data != "title" { + t.Fatal("expected is not title") + } + if list[2].Data != "meta" { + t.Fatal("expected is not meta") + } +} + +func TestStarWide(t *testing.T) { + testXPath(t, html, "/head/*", "title") + testXPath2(t, html, "//ul/*", 4) + testXPath(t, html, "@*", "html") + testXPath2(t, html, "/body/h1/*", 0) + testXPath2(t, html, `//ul/*/a`, 3) +} + +func TestNodeTestType(t *testing.T) { + testXPath(t, html, "//title/text()", "Hello") + testXPath(t, html, "//a[@href='/']/text()", "Home") + testXPath2(t, html, "//head/node()", 2) + testXPath2(t, html, "//ul/node()", 4) +} + +func TestPosition(t *testing.T) { + testXPath3(t, html, "/head[1]", html.FirstChild) // compare to 'head' element + ul := selectNode(html, "//ul") + testXPath3(t, html, "/head[last()]", html.FirstChild) + testXPath3(t, html, "//li[1]", ul.FirstChild) + testXPath3(t, html, "//li[4]", ul.LastChild) + testXPath3(t, html, "//li[last()]", ul.LastChild) +} + +func TestPredicate(t *testing.T) { + testXPath(t, html.Parent, "html[@lang='en']", "html") + testXPath(t, html, "//a[@href='/']", "a") + testXPath(t, html, "//meta[@name]", "meta") + ul := selectNode(html, "//ul") + testXPath3(t, html, "//li[position()=4]", ul.LastChild) + testXPath3(t, html, "//li[position()=1]", ul.FirstChild) + testXPath2(t, html, "//li[position()>0]", 4) + testXPath3(t, html, "//a[text()='Home']", selectNode(html, "//a[1]")) +} + +func TestOr_And(t *testing.T) { + list := selectNodes(html, "//h1|//footer") + if len(list) == 0 { + t.Fatal("//h1|//footer no any node found") + } + if list[0].Data != "h1" { + t.Fatalf("expected first node of node-set is h1,but got %s", list[0].Data) + } + if list[1].Data != "footer" { + t.Fatalf("expected first node of node-set is footer,but got %s", list[1].Data) + } + + list = selectNodes(html, "//a[@id=1 or @id=2]") + if list[0] != selectNode(html, "//a[@id=1]") { + t.Fatal("node is not equal") + } + if list[1] != selectNode(html, "//a[@id=2]") { + t.Fatal("node is not equal") + } + testXPath3(t, html, "//a[@id=1 and @href='/']", selectNode(html, "//a[1]")) + testXPath3(t, html, "//a[text()='Home' and @id='1']", selectNode(html, "//a[1]")) +} + +func TestFunction(t *testing.T) { + testXPath2(t, html, "//*[name()='a']", 3) + testXPath(t, html, "//*[starts-with(name(),'h1')]", "h1") + testXPath2(t, html, "//*[starts-with(@href,'/a')]", 2) // a links: `/account`,`/about` + testXPath3(t, html, "//h1[normalize-space(text())='This is a H1']", selectNode(html, "//h1")) + testXPath3(t, html, "//title[substring(.,0)='Hello']", selectNode(html, "//title")) + testXPath3(t, html, "//title[substring(text(),0,4)='Hell']", selectNode(html, "//title")) +} + +func TestOperationOrLogical(t *testing.T) { + testXPath3(t, html, "//li[1+1]", selectNode(html, "//li[2]")) + testXPath3(t, html, "//li[5 div 2]", selectNode(html, "//li[2]")) + testXPath3(t, html, "//li[3 mod 2]", selectNode(html, "//li[1]")) + testXPath3(t, html, "//li[3 - 2]", selectNode(html, "//li[1]")) + testXPath2(t, html, "//li[position() mod 2 = 0 ]", 2) // //li[2],li[4] + testXPath2(t, html, "//a[@id>=1]", 3) // //a[@id>=1] == a[1],a[2],a[3] + testXPath2(t, html, "//a[@id<2]", 1) // //a[@id>=1] == a[1] + testXPath2(t, html, "//a[@id!=2]", 2) // //a[@id>=1] == a[1],a[3] + testXPath2(t, html, "//a[@id=1 or @id=3]", 2) // //a[@id>=1] == a[1],a[3] + testXPath3(t, html, "//a[@id=1 and @href='/']", selectNode(html, "//a[1]")) +} + +func testXPath(t *testing.T, root *TNode, expr string, expected string) { + node := selectNode(root, expr) + if node == nil { + t.Fatalf("`%s` returns node is nil", expr) + } + if node.Data != expected { + t.Fatalf("`%s` expected node is %s,but got %s", expr, expected, node.Data) + } +} + +func testXPath2(t *testing.T, root *TNode, expr string, expected int) { + list := selectNodes(root, expr) + if len(list) != expected { + t.Fatalf("`%s` expected node numbers is %d,but got %d", expr, expected, len(list)) + } +} + +func testXPath3(t *testing.T, root *TNode, expr string, expected *TNode) { + node := selectNode(root, expr) + if node == nil { + t.Fatalf("`%s` returns node is nil", expr) + } + if node != expected { + t.Fatalf("`%s` %s != %s", expr, node.Value(), expected.Value()) + } +} + +func selectNode(root *TNode, expr string) (n *TNode) { + t := Select(createNavigator(root), expr) + if t.MoveNext() { + n = (t.Current().(*TNodeNavigator)).curr + } + return n +} + +func selectNodes(root *TNode, expr string) []*TNode { + t := Select(createNavigator(root), expr) + var nodes []*TNode + for t.MoveNext() { + node := (t.Current().(*TNodeNavigator)).curr + nodes = append(nodes, node) + } + return nodes +} + +func createNavigator(n *TNode) *TNodeNavigator { + return &TNodeNavigator{curr: n, root: n, attr: -1} +} + +type Attribute struct { + Key, Value string +} + +type TNode struct { + Parent, FirstChild, LastChild, PrevSibling, NextSibling *TNode + + Type xpath.NodeType + Data string + Attr []Attribute +} + +func (n *TNode) Value() string { + if n.Type == xpath.TextNode { + return n.Data + } + + var buff bytes.Buffer + var output func(*TNode) + output = func(node *TNode) { + if node.Type == xpath.TextNode { + buff.WriteString(node.Data) + } + for child := node.FirstChild; child != nil; child = child.NextSibling { + output(child) + } + } + output(n) + return buff.String() +} + +// TNodeNavigator is for navigating TNode. +type TNodeNavigator struct { + curr, root *TNode + attr int +} + +func (n *TNodeNavigator) NodeType() xpath.NodeType { + if n.curr.Type == xpath.ElementNode && n.attr != -1 { + return xpath.AttributeNode + } + return n.curr.Type +} + +func (n *TNodeNavigator) LocalName() string { + if n.attr != -1 { + return n.curr.Attr[n.attr].Key + } + return n.curr.Data +} + +func (n *TNodeNavigator) Prefix() string { + return "" +} + +func (n *TNodeNavigator) Value() string { + switch n.curr.Type { + case xpath.CommentNode: + return n.curr.Data + case xpath.ElementNode: + if n.attr != -1 { + return n.curr.Attr[n.attr].Value + } + var buf bytes.Buffer + node := n.curr.FirstChild + for node != nil { + if node.Type == xpath.TextNode { + buf.WriteString(strings.TrimSpace(node.Data)) + } + node = node.NextSibling + } + return buf.String() + case xpath.TextNode: + return n.curr.Data + } + return "" +} + +func (n *TNodeNavigator) Copy() xpath.NodeNavigator { + n2 := *n + return &n2 +} + +func (n *TNodeNavigator) MoveToRoot() { + n.curr = n.root +} + +func (n *TNodeNavigator) MoveToParent() bool { + if node := n.curr.Parent; node != nil { + n.curr = node + return true + } + return false +} + +func (n *TNodeNavigator) MoveToNextAttribute() bool { + if n.attr >= len(n.curr.Attr)-1 { + return false + } + n.attr++ + return true +} + +func (n *TNodeNavigator) MoveToChild() bool { + if node := n.curr.FirstChild; node != nil { + n.curr = node + return true + } + return false +} + +func (n *TNodeNavigator) MoveToFirst() bool { + if n.curr.PrevSibling == nil { + return false + } + for { + node := n.curr.PrevSibling + if node == nil { + break + } + n.curr = node + } + return true +} + +func (n *TNodeNavigator) String() string { + return n.Value() +} + +func (n *TNodeNavigator) MoveToNext() bool { + if node := n.curr.NextSibling; node != nil { + n.curr = node + return true + } + return false +} + +func (n *TNodeNavigator) MoveToPrevious() bool { + if node := n.curr.PrevSibling; node != nil { + n.curr = node + return true + } + return false +} + +func (n *TNodeNavigator) MoveTo(other xpath.NodeNavigator) bool { + node, ok := other.(*TNodeNavigator) + if !ok || node.root != n.root { + return false + } + + n.curr = node.curr + n.attr = node.attr + return true +} + +func createNode(data string, typ xpath.NodeType) *TNode { + return &TNode{Data: data, Type: typ, Attr: make([]Attribute, 0)} +} + +func (t *TNode) createChildNode(data string, typ xpath.NodeType) *TNode { + n := createNode(data, typ) + n.Parent = t + if t.FirstChild == nil { + t.FirstChild = n + } else { + t.LastChild.NextSibling = n + n.PrevSibling = t.LastChild + } + t.LastChild = n + return n +} + +func (t *TNode) appendNode(data string, typ xpath.NodeType) *TNode { + n := createNode(data, typ) + n.Parent = t.Parent + t.NextSibling = n + n.PrevSibling = t + if t.Parent != nil { + t.Parent.LastChild = n + } + return n +} + +func (t *TNode) addAttribute(k, v string) { + t.Attr = append(t.Attr, Attribute{k, v}) +} + +func example() *TNode { + /* + + + Hello + + + +

    This is a H1

    +
    +

    + Hello,This is an example for gxpath. +

    +
    footer script
    + + + */ + doc := createNode("", xpath.RootNode) + xhtml := doc.createChildNode("html", xpath.ElementNode) + xhtml.addAttribute("lang", "en") + + // The HTML head section. + head := xhtml.createChildNode("head", xpath.ElementNode) + n := head.createChildNode("title", xpath.ElementNode) + n = n.createChildNode("Hello", xpath.TextNode) + n = head.createChildNode("meta", xpath.ElementNode) + n.addAttribute("name", "language") + n.addAttribute("content", "en") + // The HTML body section. + body := xhtml.createChildNode("body", xpath.ElementNode) + n = body.createChildNode("h1", xpath.ElementNode) + n = n.createChildNode(" This is a H1 ", xpath.TextNode) + ul := body.createChildNode("ul", xpath.ElementNode) + n = ul.createChildNode("li", xpath.ElementNode) + n = n.createChildNode("a", xpath.ElementNode) + n.addAttribute("id", "1") + n.addAttribute("href", "/") + n = n.createChildNode("Home", xpath.TextNode) + n = ul.createChildNode("li", xpath.ElementNode) + n = n.createChildNode("a", xpath.ElementNode) + n.addAttribute("id", "2") + n.addAttribute("href", "/about") + n = n.createChildNode("about", xpath.TextNode) + n = ul.createChildNode("li", xpath.ElementNode) + n = n.createChildNode("a", xpath.ElementNode) + n.addAttribute("id", "3") + n.addAttribute("href", "/account") + n = n.createChildNode("login", xpath.TextNode) + n = ul.createChildNode("li", xpath.ElementNode) + + n = body.createChildNode("p", xpath.ElementNode) + n = n.createChildNode("Hello,This is an example for gxpath.", xpath.TextNode) + + n = body.createChildNode("footer", xpath.ElementNode) + n = n.createChildNode("footer script", xpath.TextNode) + + return xhtml +} diff --git a/trunk/goutil/xmlUtil/gxpath/xpath/node.go b/trunk/goutil/xmlUtil/gxpath/xpath/node.go new file mode 100644 index 0000000..d8a5245 --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/xpath/node.go @@ -0,0 +1,25 @@ +package xpath + +// A Node is an element node that can navigating to +// an node attribute and another node. +//type Node interface{} + +// A type of XPath node. +type NodeType int + +const ( + // A root node of the XML document or node tree. + RootNode NodeType = iota + + // An element, such as . + ElementNode + + // An attribute, such as id='123'. + AttributeNode + + // The text content of a node. + TextNode + + // A comment node, such as + CommentNode +) diff --git a/trunk/goutil/xmlUtil/gxpath/xpath/xpath.go b/trunk/goutil/xmlUtil/gxpath/xpath/xpath.go new file mode 100644 index 0000000..870c1f8 --- /dev/null +++ b/trunk/goutil/xmlUtil/gxpath/xpath/xpath.go @@ -0,0 +1,43 @@ +package xpath + +// NodeNavigator provides cursor model for navigating XML data. +type NodeNavigator interface { + // NodeType returns the XPathNodeType of the current node. + NodeType() NodeType + + // LocalName gets the Name of the current node. + LocalName() string + + // Prefix returns namespace prefix associated with the current node. + Prefix() string + + // Value gets the value of current node. + Value() string + + // Copy does a deep copy of the NodeNavigator and all its components. + Copy() NodeNavigator + + // MoveToRoot moves the NodeNavigator to the root node of the current node. + MoveToRoot() + + // MoveToParent moves the NodeNavigator to the parent node of the current node. + MoveToParent() bool + + // MoveToNextAttribute moves the NodeNavigator to the next attribute on current node. + MoveToNextAttribute() bool + + // MoveToChild moves the NodeNavigator to the first child node of the current node. + MoveToChild() bool + + // MoveToFirst moves the NodeNavigator to the first sibling node of the current node. + MoveToFirst() bool + + // MoveToNext moves the NodeNavigator to the next sibling node of the current node. + MoveToNext() bool + + // MoveToPrevious moves the NodeNavigator to the previous sibling node of the current node. + MoveToPrevious() bool + + // MoveTo moves the NodeNavigator to the same position as the specified NodeNavigator. + MoveTo(NodeNavigator) bool +} diff --git a/trunk/goutil/xmlUtil/load.go b/trunk/goutil/xmlUtil/load.go new file mode 100644 index 0000000..334d04c --- /dev/null +++ b/trunk/goutil/xmlUtil/load.go @@ -0,0 +1,46 @@ +package xmlUtil + +import ( + "bytes" + "io/ioutil" + "regexp" +) + +// 从文件加载 +// filePath:文件路径 +// 返回值: +// *Node:根节点对象 +// error:错误信息 +func LoadFromFile(filePath string) (*Node, error) { + data, errMsg := ioutil.ReadFile(filePath) + if errMsg != nil { + return nil, errMsg + } + + return LoadFromByte(data) +} + +// 从字节数组加载 +// data:文档数据 +// 返回值: +// *Node:根节点对象 +// error:错误信息 +func LoadFromByte(data []byte) (*Node, error) { + return LoadFromString(string(data)) +} + +// 文档字符串 +// doc:文档字符串 +// 返回值: +// *Node:根节点对象 +// error:错误信息 +func LoadFromString(doc string) (*Node, error) { + // xml.Decoder doesn't properly handle whitespace in some doc + // see songTextString.xml test case ... + reg, _ := regexp.Compile("[ \t\n\r]*<") + doc = reg.ReplaceAllString(doc, "<") + + b := bytes.NewBufferString(doc) + + return LoadFromReader(b) +} diff --git a/trunk/goutil/xmlUtil/load_test.go b/trunk/goutil/xmlUtil/load_test.go new file mode 100644 index 0000000..3041337 --- /dev/null +++ b/trunk/goutil/xmlUtil/load_test.go @@ -0,0 +1,62 @@ +package xmlUtil + +import ( + "fmt" + "strings" + "testing" +) + +// 测试从文件加载 +func TestLoadFromFile(t *testing.T) { + root, errMsg := LoadFromFile("sample.xml") + if errMsg != nil { + t.Error("文件加载失败:", errMsg) + t.Fail() + return + } + + node := root.SelectElement("html/head/title") + if node == nil { + t.Error("读取节点失败:", "html/head/title") + } + + fmt.Println("节点值:", strings.TrimSpace(node.InnerText())) +} + +// 测试从字符串加载 +func TestLoadFromString(t *testing.T) { + var xml string = ` + + + Hello + + + +

    This is a H1

    + +

    + Hello,This is an example for gxpath. +

    +
    footer script
    + + +` + root, errMsg := LoadFromString(xml) + if errMsg != nil { + t.Error("文件加载失败:", errMsg) + t.Fail() + return + } + + node := root.SelectElement("html/head/title") + if node == nil { + t.Error("读取节点失败:", "html/head/title") + } + + fmt.Println("节点值:", strings.TrimSpace(node.InnerText())) +} diff --git a/trunk/goutil/xmlUtil/node.go b/trunk/goutil/xmlUtil/node.go new file mode 100644 index 0000000..50e10d2 --- /dev/null +++ b/trunk/goutil/xmlUtil/node.go @@ -0,0 +1,345 @@ +package xmlUtil + +import ( + "bytes" + "container/list" + "encoding/xml" + "errors" + "fmt" + "io" + "strings" +) + +// A NodeType is the type of a Node. +type NodeType uint + +const ( + // 文档对象节点(根节点) + DocumentNode NodeType = iota + + // 头不声明节点 + DeclarationNode + + // 元素节点 + ElementNode + + // 节点文本 + TextNode + + // 注释 + CommentNode +) + +// xml节点对象 +type Node struct { + Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node + + Type NodeType + NodeName string + Namespace string + Attr []xml.Attr + + level int // node level in the tree +} + +// InnerText returns the text between the start and end tags of the object. +func (n *Node) InnerText() string { + if n.Type == TextNode || n.Type == CommentNode { + return n.NodeName + } + + var buf bytes.Buffer + for child := n.FirstChild; child != nil; child = child.NextSibling { + // filt commentnode + if child.Type == CommentNode { + continue + } + + buf.WriteString(child.InnerText()) + } + + return buf.String() +} + +func outputXML(buf *bytes.Buffer, n *Node) { + if n.Type == TextNode || n.Type == CommentNode { + buf.WriteString(strings.TrimSpace(n.NodeName)) + return + } + buf.WriteString("<" + n.NodeName) + for _, attr := range n.Attr { + if attr.Name.Space != "" { + buf.WriteString(fmt.Sprintf(` %s:%s="%s"`, attr.Name.Space, attr.Name.Local, attr.Value)) + } else { + buf.WriteString(fmt.Sprintf(` %s="%s"`, attr.Name.Local, attr.Value)) + } + } + buf.WriteString(">") + for child := n.FirstChild; child != nil; child = child.NextSibling { + outputXML(buf, child) + } + buf.WriteString(fmt.Sprintf("", n.NodeName)) +} + +// OutputXML returns the text that including tags name. +func (n *Node) OutputXML() string { + var buf bytes.Buffer + outputXML(&buf, n) + return buf.String() +} + +// get all children +func (n *Node) Children() []*Node { + childrenList := make([]*Node, 0) + nowChild := n.FirstChild + for { + if nowChild == nil { + break + } + + childrenList = append(childrenList, nowChild) + if nowChild == n.LastChild { + break + } + + nowChild = nowChild.NextSibling + } + + return childrenList +} + +// get children len +func (n *Node) ChildrenLen() int { + var count int = 0 + nowChild := n.FirstChild + for { + if nowChild == nil { + break + } + + count += 1 + if nowChild == n.LastChild { + break + } + + nowChild = nowChild.NextSibling + } + + return count +} + +// get all attribute +func (n *Node) ALLAttribute() []xml.Attr { + if n.Attr == nil { + return nil + } + + return n.Attr[:] +} + +// 获取属性个数 +func (n *Node) AttributeLen() int { + if n.Attr == nil { + return 0 + } + + return len(n.Attr) +} + +// 输出所有(主要用于测试) +func (this *Node) OutALL() { + stack := list.New() + tmpItem := this + for { + if tmpItem != nil { + stack.PushBack(tmpItem) + tmpItem = tmpItem.NextSibling + } + + break + } + + for { + if stack.Len() <= 0 { + break + } + + nowNode := stack.Front().Value.(*Node) + stack.Remove(stack.Front()) + for _, item := range nowNode.Children() { + stack.PushFront(item) + } + + fmt.Println("name:", nowNode.NodeName, " level: ", nowNode.level, " attr:", nowNode.Attr) + } +} + +// SelectElements finds child elements with the specified name. +func (n *Node) SelectElements(name string) []*Node { + return Find(n, name) +} + +// SelectElements finds child elements with the specified name. +func (n *Node) SelectElement(name string) *Node { + return FindOne(n, name) +} + +// SelectAttr returns the attribute value with the specified name. +func (n *Node) SelectAttr(name string) (string, bool) { + var local, space string + local = name + if i := strings.Index(name, ":"); i > 0 { + space = name[:i] + local = name[i+1:] + } + for _, attr := range n.Attr { + if attr.Name.Local == local && attr.Name.Space == space { + return attr.Value, true + } + } + return "", false +} + +// 给节点添加属性值 +func addAttr(n *Node, key, val string) { + var attr xml.Attr + if i := strings.Index(key, ":"); i > 0 { + attr = xml.Attr{ + Name: xml.Name{Space: key[:i], Local: key[i+1:]}, + Value: val, + } + } else { + attr = xml.Attr{ + Name: xml.Name{Local: key}, + Value: val, + } + } + + n.Attr = append(n.Attr, attr) +} + +// 给节点添加子节点 +func addChild(parent, n *Node) { + n.Parent = parent + if parent.FirstChild == nil { + parent.FirstChild = n + } else { + parent.LastChild.NextSibling = n + n.PrevSibling = parent.LastChild + } + + parent.LastChild = n +} + +// 给节点添加兄弟节点 +func addSibling(sibling, n *Node) { + n.Parent = sibling.Parent + sibling.NextSibling = n + n.PrevSibling = sibling + if sibling.Parent != nil { + sibling.Parent.LastChild = n + } +} + +// 从reader里面加载xml文档 +func LoadFromReader(r io.Reader) (*Node, error) { + var ( + decoder = xml.NewDecoder(r) //// xml解码对象 + doc = &Node{Type: DocumentNode} + level = 0 + declared = false + ) + var prev *Node = doc + for { + tok, err := decoder.Token() + switch { + case err == io.EOF: + goto quit + case err != nil: + return nil, err + } + + switch tok := tok.(type) { + case xml.StartElement: + //if !declared { // if have no xml node,we also need add children + // return nil, errors.New("xml: document is invalid") + //} + // if there is no xml node.then create it + if declared == false { + level++ + + tmpNode := &Node{Type: DeclarationNode, level: level} + addAttr(tmpNode, "version", "1.0") + addAttr(tmpNode, "encoding", "UTF-8") + declared = true + if level == prev.level { + addSibling(prev, tmpNode) + } else if level > prev.level { + addChild(prev, tmpNode) + } + + prev = tmpNode + } + node := &Node{ + Type: ElementNode, + NodeName: tok.Name.Local, + Namespace: tok.Name.Space, + Attr: tok.Attr, + level: level, + } + //fmt.Println(fmt.Sprintf("start > %s : %d", node.Data, level)) + if level == prev.level { + addSibling(prev, node) + } else if level > prev.level { + addChild(prev, node) + } else if level < prev.level { + for i := prev.level - level; i > 1; i-- { + prev = prev.Parent + } + addSibling(prev.Parent, node) + } + prev = node + level++ + case xml.EndElement: + level-- + case xml.CharData: + node := &Node{Type: TextNode, NodeName: string(tok), level: level} + if level == prev.level { + addSibling(prev, node) + } else if level > prev.level { + addChild(prev, node) + } + case xml.Comment: + node := &Node{Type: CommentNode, NodeName: string(tok), level: level} + if level == prev.level { + addSibling(prev, node) + } else if level > prev.level { + addChild(prev, node) + } + case xml.ProcInst: // Processing Instruction + if declared || (!declared && tok.Target != "xml") { + return nil, errors.New("xml: document is invalid") + } + level++ + node := &Node{Type: DeclarationNode, level: level} + pairs := strings.Split(string(tok.Inst), " ") + for _, pair := range pairs { + pair = strings.TrimSpace(pair) + if i := strings.Index(pair, "="); i > 0 { + addAttr(node, pair[:i], strings.Trim(pair[i+1:], `"`)) + } + } + declared = true + if level == prev.level { + addSibling(prev, node) + } else if level > prev.level { + addChild(prev, node) + } + prev = node + case xml.Directive: + } + + } +quit: + return doc, nil +} diff --git a/trunk/goutil/xmlUtil/node_test.go b/trunk/goutil/xmlUtil/node_test.go new file mode 100644 index 0000000..1de6919 --- /dev/null +++ b/trunk/goutil/xmlUtil/node_test.go @@ -0,0 +1,186 @@ +package xmlUtil + +import ( + "strings" + "testing" +) + +func findNode(root *Node, name string) *Node { + node := root.FirstChild + for { + if node == nil || node.NodeName == name { + break + } + node = node.NextSibling + } + return node +} + +func childNodes(root *Node, name string) []*Node { + var list []*Node + node := root.FirstChild + for { + if node == nil { + break + } + if node.NodeName == name { + list = append(list, node) + } + node = node.NextSibling + } + return list +} + +func testNode(t *testing.T, n *Node, expected string) { + if n.NodeName != expected { + t.Fatalf("expected node name is %s,but got %s", expected, n.NodeName) + } +} + +func testAttr(t *testing.T, n *Node, name, expected string) { + for _, attr := range n.Attr { + if attr.Name.Local == name && attr.Value == expected { + return + } + } + t.Fatalf("not found attribute %s in the node %s", name, n.NodeName) +} + +func testValue(t *testing.T, val, expected string) { + if val != expected { + t.Fatalf("expected value is %s,but got %s", expected, val) + } +} + +func TestParse(t *testing.T) { + s := ` + + + Harry Potter + 29.99 + + + Learning XML + 39.95 + +` + root, err := LoadFromReader(strings.NewReader(s)) + if err != nil { + t.Error(err) + } + if root.Type != DocumentNode { + t.Fatal("top node of tree is not DocumentNode") + } + + declarNode := root.FirstChild + if declarNode.Type != DeclarationNode { + t.Fatal("first child node of tree is not DeclarationNode") + } + + if declarNode.Attr[0].Name.Local != "version" && declarNode.Attr[0].Value != "1.0" { + t.Fatal("version attribute not expected") + } + + bookstore := root.LastChild + if bookstore.NodeName != "bookstore" { + t.Fatal("bookstore elem not found") + } + if bookstore.FirstChild.NodeName != "\n" { + t.Fatal("first child node of bookstore is not empty node(\n)") + } + books := childNodes(bookstore, "book") + if len(books) != 2 { + t.Fatalf("expected book element count is 2, but got %d", len(books)) + } + // first book element + testNode(t, findNode(books[0], "title"), "title") + testAttr(t, findNode(books[0], "title"), "lang", "en") + testValue(t, findNode(books[0], "price").InnerText(), "29.99") + testValue(t, findNode(books[0], "title").InnerText(), "Harry Potter") + + // second book element + testNode(t, findNode(books[1], "title"), "title") + testAttr(t, findNode(books[1], "title"), "lang", "en") + testValue(t, findNode(books[1], "price").InnerText(), "39.95") + + testValue(t, books[0].OutputXML(), `Harry Potter29.99`) +} + +func TestTooNested(t *testing.T) { + s := ` + + + + + + + + + + + + + + + + + + + ` + root, err := LoadFromReader(strings.NewReader(s)) + if err != nil { + t.Error(err) + } + aaa := findNode(root, "AAA") + if aaa == nil { + t.Fatal("AAA node not exists") + } + ccc := aaa.LastChild + if ccc.NodeName != "CCC" { + t.Fatalf("expected node is CCC,but got %s", ccc.NodeName) + } + bbb := ccc.PrevSibling + if bbb.NodeName != "BBB" { + t.Fatalf("expected node is bbb,but got %s", bbb.NodeName) + } + ddd := findNode(bbb, "DDD") + testNode(t, ddd, "DDD") + testNode(t, ddd.LastChild, "CCC") +} + +func TestSelectElement(t *testing.T) { + s := ` + + + + + + + + + ` + root, err := LoadFromReader(strings.NewReader(s)) + if err != nil { + t.Error(err) + } + version, _ := root.FirstChild.SelectAttr("version") + if version != "1.0" { + t.Fatal("version!=1.0") + } + aaa := findNode(root, "AAA") + var n *Node + n = aaa.SelectElement("BBB") + if n == nil { + t.Fatalf("n is nil") + } + n = aaa.SelectElement("CCC") + if n == nil { + t.Fatalf("n is nil") + } + + var ns []*Node + ns = aaa.SelectElements("CCC") + if len(ns) != 2 { + t.Fatalf("len(ns)!=2") + } +} diff --git a/trunk/goutil/xmlUtil/query.go b/trunk/goutil/xmlUtil/query.go new file mode 100644 index 0000000..6f8a0b1 --- /dev/null +++ b/trunk/goutil/xmlUtil/query.go @@ -0,0 +1,193 @@ +package xmlUtil + +import ( + "fmt" + + "goutil/xmlUtil/gxpath" + "goutil/xmlUtil/gxpath/xpath" +) + +// xml节点查找结构(用于遍历xml节点) +type xmlNodeNavigator struct { + root, curr *Node + attr int +} + +// 节点类型 +func (x *xmlNodeNavigator) NodeType() xpath.NodeType { + switch x.curr.Type { + case CommentNode: + return xpath.CommentNode + case TextNode: + return xpath.TextNode + case DeclarationNode, DocumentNode: + return xpath.RootNode + case ElementNode: + if x.attr != -1 { + return xpath.AttributeNode + } + return xpath.ElementNode + } + panic(fmt.Sprintf("unknown XML node type: %v", x.curr.Type)) +} + +// 当前查找的节点名或属性名 +func (x *xmlNodeNavigator) LocalName() string { + if x.attr != -1 { + return x.curr.Attr[x.attr].Name.Local + } + return x.curr.NodeName + +} + +// 名节点前缀 +func (x *xmlNodeNavigator) Prefix() string { + return x.curr.Namespace +} + +// 节点值或属性值 +func (x *xmlNodeNavigator) Value() string { + switch x.curr.Type { + case CommentNode: + return x.curr.NodeName + case ElementNode: + if x.attr != -1 { + return x.curr.Attr[x.attr].Value + } + return x.curr.InnerText() + case TextNode: + return x.curr.NodeName + } + return "" +} + +// 创建一个拷贝对象 +func (x *xmlNodeNavigator) Copy() xpath.NodeNavigator { + n := *x + return &n +} + +// 移动到根节点 +func (x *xmlNodeNavigator) MoveToRoot() { + x.curr = x.root +} + +// 移动到父节点 +func (x *xmlNodeNavigator) MoveToParent() bool { + if node := x.curr.Parent; node != nil { + x.curr = node + return true + } + return false +} + +// 移动到下一个属性 +func (x *xmlNodeNavigator) MoveToNextAttribute() bool { + if x.attr >= len(x.curr.Attr)-1 { + return false + } + x.attr++ + return true +} + +// 移动到子节点 +func (x *xmlNodeNavigator) MoveToChild() bool { + if node := x.curr.FirstChild; node != nil { + x.curr = node + return true + } + return false +} + +// 移动到第一个节点 +func (x *xmlNodeNavigator) MoveToFirst() bool { + if x.curr.PrevSibling == nil { + return false + } + for { + node := x.curr.PrevSibling + if node == nil { + break + } + x.curr = node + } + return true +} + +// 节点的值 +func (x *xmlNodeNavigator) String() string { + return x.Value() +} + +// 移动到下一个兄弟节点 +func (x *xmlNodeNavigator) MoveToNext() bool { + if node := x.curr.NextSibling; node != nil { + x.curr = node + return true + } + return false +} + +// 移动到上一个兄弟节点 +func (x *xmlNodeNavigator) MoveToPrevious() bool { + if node := x.curr.PrevSibling; node != nil { + x.curr = node + return true + } + return false +} + +// 移动到指定节点 +func (x *xmlNodeNavigator) MoveTo(other xpath.NodeNavigator) bool { + node, ok := other.(*xmlNodeNavigator) + if !ok || node.root != x.root { + return false + } + + x.curr = node.curr + x.attr = node.attr + return true +} + +// CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node. +func CreateXPathNavigator(top *Node) xpath.NodeNavigator { + return &xmlNodeNavigator{curr: top, root: top, attr: -1} +} + +// 按照xpath查找所有匹配的节点 +// top:根节点 +// expr:xpath表达式 +// 返回值: +// []*Node:结果 +func Find(top *Node, expr string) []*Node { + t := gxpath.Select(CreateXPathNavigator(top), expr) + var elems []*Node + for t.MoveNext() { + elems = append(elems, (t.Current().(*xmlNodeNavigator)).curr) + } + return elems +} + +// 按照xpath查找第一个匹配的节点 +// top:根节点 +// expr:xpath表达式 +// 返回值: +// *Node:查找到的第一个节点 +func FindOne(top *Node, expr string) *Node { + t := gxpath.Select(CreateXPathNavigator(top), expr) + var elem *Node + if t.MoveNext() { + elem = (t.Current().(*xmlNodeNavigator)).curr + } + return elem +} + +// FindEach searches the html.Node and calls functions cb. +func FindEach(top *Node, expr string, cb func(int, *Node)) { + t := gxpath.Select(CreateXPathNavigator(top), expr) + var i int + for t.MoveNext() { + cb(i, (t.Current().(*xmlNodeNavigator)).curr) + i++ + } +} diff --git a/trunk/goutil/xmlUtil/query_test.go b/trunk/goutil/xmlUtil/query_test.go new file mode 100644 index 0000000..e76855e --- /dev/null +++ b/trunk/goutil/xmlUtil/query_test.go @@ -0,0 +1,153 @@ +package xmlUtil + +import ( + "strings" + "testing" +) + +var doc = loadXml() + +func TestXPath(t *testing.T) { + if list := Find(doc, "//book"); len(list) != 12 { + t.Fatal("count(//book) != 12") + } + if node := FindOne(doc, "//book[@id='bk101']"); node == nil { + t.Fatal("//book[@id='bk101] is not found") + } + if node := FindOne(doc, "//book[price>=44.95]"); node == nil { + t.Fatal("//book/price>=44.95 is not found") + } + if list := Find(doc, "//book[genre='Fantasy']"); len(list) != 4 { + t.Fatal("//book[genre='Fantasy'] items count is not equal 4") + } +} + +func loadXml() *Node { + // https://msdn.microsoft.com/en-us/library/ms762271(v=vs.85).aspx + s := ` + + + + Gambardella, Matthew + XML Developer's Guide + Computer + 44.95 + 2000-10-01 + An in-depth look at creating applications + with XML. + + + Ralls, Kim + Midnight Rain + Fantasy + 5.95 + 2000-12-16 + A former architect battles corporate zombies, + an evil sorceress, and her own childhood to become queen + of the world. + + + Corets, Eva + Maeve Ascendant + Fantasy + 5.95 + 2000-11-17 + After the collapse of a nanotechnology + society in England, the young survivors lay the + foundation for a new society. + + + Corets, Eva + Oberon's Legacy + Fantasy + 5.95 + 2001-03-10 + In post-apocalypse England, the mysterious + agent known only as Oberon helps to create a new life + for the inhabitants of London. Sequel to Maeve + Ascendant. + + + Corets, Eva + The Sundered Grail + Fantasy + 5.95 + 2001-09-10 + The two daughters of Maeve, half-sisters, + battle one another for control of England. Sequel to + Oberon's Legacy. + + + Randall, Cynthia + Lover Birds + Romance + 4.95 + 2000-09-02 + When Carla meets Paul at an ornithology + conference, tempers fly as feathers get ruffled. + + + Thurman, Paula + Splish Splash + Romance + 4.95 + 2000-11-02 + A deep sea diver finds true love twenty + thousand leagues beneath the sea. + + + Knorr, Stefan + Creepy Crawlies + Horror + 4.95 + 2000-12-06 + An anthology of horror stories about roaches, + centipedes, scorpions and other insects. + + + Kress, Peter + Paradox Lost + Science Fiction + 6.95 + 2000-11-02 + After an inadvertant trip through a Heisenberg + Uncertainty Device, James Salway discovers the problems + of being quantum. + + + O'Brien, Tim + Microsoft .NET: The Programming Bible + Computer + 36.95 + 2000-12-09 + Microsoft's .NET initiative is explored in + detail in this deep programmer's reference. + + + O'Brien, Tim + MSXML3: A Comprehensive Guide + Computer + 36.95 + 2000-12-01 + The Microsoft MSXML3 parser is covered in + detail, with attention to XML DOM interfaces, XSLT processing, + SAX and more. + + + Galos, Mike + Visual Studio 7: A Comprehensive Guide + Computer + 49.95 + 2001-04-16 + Microsoft Visual Studio 7 is explored in depth, + looking at how Visual Basic, Visual C++, C#, and ASP+ are + integrated into a comprehensive development + environment. + +` + node, err := LoadFromReader(strings.NewReader(s)) + if err != nil { + panic(err) + } + return node +} diff --git a/trunk/goutil/xmlUtil/sample.xml b/trunk/goutil/xmlUtil/sample.xml new file mode 100644 index 0000000..6012895 --- /dev/null +++ b/trunk/goutil/xmlUtil/sample.xml @@ -0,0 +1,20 @@ + + + <!-- 阿斯大三大四的 --> + 这里有中文哦 + + + +

    This is a H1

    + +

    + Hello,This is an example for gxpath. +

    + + + \ No newline at end of file diff --git a/trunk/goutil/yamlUtil/config.yaml b/trunk/goutil/yamlUtil/config.yaml new file mode 100644 index 0000000..100c1b0 --- /dev/null +++ b/trunk/goutil/yamlUtil/config.yaml @@ -0,0 +1,54 @@ +# 配置根节点 +root: + # 是否是调试模式 + debug: true + + # Web服务监听地址和端口 + web_server_address: "192.168.50.85:10051" + + # 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 \ No newline at end of file diff --git a/trunk/goutil/yamlUtil/load.go b/trunk/goutil/yamlUtil/load.go new file mode 100644 index 0000000..f9104f4 --- /dev/null +++ b/trunk/goutil/yamlUtil/load.go @@ -0,0 +1,18 @@ +package yamlUtil + +import ( + "io/ioutil" + "log" +) + +// LoadFromFile 加载配置文件(这里不反序列化) +func LoadFromFile(filePath string) ([]byte, error) { + // 读取 YAML 文件 + yamlFile, err := ioutil.ReadFile(filePath) + if err != nil { + log.Fatalf("Error reading config.yaml file: %v", err) + return nil, err + } + + return yamlFile, nil +} diff --git a/trunk/goutil/yamlUtil/load_test.go b/trunk/goutil/yamlUtil/load_test.go new file mode 100644 index 0000000..c07a12b --- /dev/null +++ b/trunk/goutil/yamlUtil/load_test.go @@ -0,0 +1,14 @@ +package yamlUtil + +import ( + "fmt" + "goutil/jsonUtil" + "testing" +) + +func TestLoadYaml(t *testing.T) { + config, _ := LoadFromFile("config.yaml") + configStr, _ := jsonUtil.DeepClone(config) + + fmt.Print(configStr) +} diff --git a/trunk/goutil/zlibUtil/doc.go b/trunk/goutil/zlibUtil/doc.go new file mode 100644 index 0000000..164e080 --- /dev/null +++ b/trunk/goutil/zlibUtil/doc.go @@ -0,0 +1,4 @@ +/* +zlib压缩、解压缩相关的助手包 +*/ +package zlibUtil diff --git a/trunk/goutil/zlibUtil/zlib.go b/trunk/goutil/zlibUtil/zlib.go new file mode 100644 index 0000000..1822149 --- /dev/null +++ b/trunk/goutil/zlibUtil/zlib.go @@ -0,0 +1,48 @@ +package zlibUtil + +import ( + "bytes" + "compress/zlib" + "io" +) + +// 压缩 +// in:待压缩数据 +// level:压缩等级 +// 返回值: +// 压缩后数据 +// 对应的错误 +func Compress(data []byte, level int) ([]byte, error) { + var buffer bytes.Buffer + compressor, err := zlib.NewWriterLevelDict(&buffer, level, nil) + if err != nil { + return nil, err + } + + compressor.Write(data) + compressor.Close() + + return buffer.Bytes(), nil +} + +// 解压缩 +// in:待解压缩数据 +// 返回值: +// 解压缩后数据 +// 对应的错误 +func Decompress(data []byte) ([]byte, error) { + dataReader := bytes.NewReader(data) + zlibReader, err := zlib.NewReader(dataReader) + if err != nil { + return nil, err + } + defer zlibReader.Close() + + var buffer bytes.Buffer + _, err = io.Copy(&buffer, zlibReader) + if err != nil { + return nil, err + } + + return buffer.Bytes(), nil +} diff --git a/trunk/goutil/zlibUtil/zlib_test.go b/trunk/goutil/zlibUtil/zlib_test.go new file mode 100644 index 0000000..b44d8af --- /dev/null +++ b/trunk/goutil/zlibUtil/zlib_test.go @@ -0,0 +1,77 @@ +package zlibUtil + +import ( + "compress/zlib" + "strings" + "testing" +) + +var ( + InitString = `{"Code":4,"Message":"IPNotAuthorized","Data":null}` + CompressBytes []byte +) + +func TestCompress(t *testing.T) { + data := ([]byte)(InitString) + result, _ := Compress(data, zlib.DefaultCompression) + // if isEqual(result, CompressBytes) == false { + // t.Errorf("压缩失败,期待%v,实际%v", InitBytes, result) + // } + + CompressBytes = result +} + +func TestDecompress(t *testing.T) { + data, _ := Decompress(CompressBytes) + result := string(data) + if result != InitString { + t.Errorf("解压缩失败,期待%s,实际%s", InitString, result) + } +} + +func isEqual(a, b []byte) bool { + if a == nil && b == nil { + return true + } else if a == nil || b == nil { + return false + } + + if len(a) != len(b) { + return false + } + + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + + return true +} + +//BenchmarkCompress-12 10989 107509 ns/op +func BenchmarkCompress(b *testing.B) { + toCompress := []byte(strings.Repeat(InitString, 100)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _,err :=Compress(toCompress, zlib.DefaultCompression) + if err!=nil{ + b.Log(err) + } + } + b.StopTimer() +} + +//BenchmarkDeCompress-12 99153 10802 ns/op +func BenchmarkDeCompress(b *testing.B) { + toCompress := []byte(strings.Repeat(InitString, 100)) + compressedData,_ := Compress(toCompress, zlib.DefaultCompression) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _,err := Decompress(compressedData) + if err!=nil{ + b.Log(err) + } + } + b.StopTimer() +} \ No newline at end of file