Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/main/member/cmd:all-srcs",
"//app/admin/main/member/conf:all-srcs",
"//app/admin/main/member/dao:all-srcs",
"//app/admin/main/member/http:all-srcs",
"//app/admin/main/member/model:all-srcs",
"//app/admin/main/member/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,414 @@
# member-admin
# v1.27.1
1. 修复查询官方认证时的空指针 bug
# v1.27.0
1. 官方认证查询接口增加使用相同社会信用代码的mid列表
# v1.26.3
1. 使用 grpc 请求account-service
# v1.26.2
1. 官方认证审核排序以最后一次提交时间来排序
# v1.26.1
1. 移除 user_detail 表相关代码
2. 向后兼容 birthday
# v1.25.4
1. 修复ai重新审核头像的执行任务的间隔时间
# v1.25.3
1. 头像48小时未审核自动通过逻辑加入操作人与理由
2. ai审核每小时重新审核一下6小时内的头像
# v1.25.2
1. 下线行为日志2版本的接口,改为v3
# v1.25.1
1. 通过用户ID或证件号码查询实名认证查询时无需指定时间范围
# v1.25.0
1. 仅导出认证通过的实名认证信息
# v1.24.0
1. 官方认证增加官网和注册资金字段
2. 官方认证审核页面增加实名认证相关信息
# v1.23.5
1. 实名认证绿色通道写入老数据库
# v1.23.3
1. 添加证待审核数据的发送消息成功的日志
# v1.23.2
1. 移除阿里实名认证待审核数据的上报
# v1.23.1
1. 修复上报实名认证待审核数据
# v1.23.0
1. 实名认证待审核信息的数据通知
2. 实名认证待审核信息变更的数据图表
# v1.22.5
1. 实名认证取消绑定时将申请一并取消掉
2. 按 chunk 向 passport 查询用户信息
# v1.22.3
1. 用户个人信息审核被删除的头像URL需要重新签名
# v1.22.2
1. 实名认证驳回理由权限区分
2. 实名认证查询列表增加排序
# v1.22.1
1. 缓存压缩图片后输出
# v1.21.4
1. 监控用户查询时包含被删除的用户
2. 不再压缩小于 512KB 的图片
3. 优化批量实名认证导出
# v1.21.0
1. 实名认证压缩图片后传输
# v1.20.5
1. 实名认证内部接口
# v1.20.4
1. 实名搜索证件号优先取realname_info
# v1.20.3
1. 实名认证绿色通道功能添加备注
2. 补发经验接口添加权限
# v1.20.2
1. 修复通过身份证搜索芝麻实名,可能无法搜到的问题
# v1.20.1
1. 优化经验修改
# v1.20.0
1. 实名认证绿色通道功能
# v1.19.5
1. 修改 GOMAXPROCS 优化文件读写
# v1.19.2
1. 实名认证导出身份证号防止转义
# v1.19.0
1. 增加官方认证提交来源字段
# v1.18.1
1. 导出时翻译证件类型
# v1.18.0
1. 根据mid列表批量导出与用户身份相关的信息
# v1.17.3
1. 增加封禁类型(恶意冒充他人)
# v1.17.2
Added
- 新增实名列表 state=all 筛选
Changed
- 变更实名使用索引时,允许不传时间
# v1.17.1
Fixed
- 修复芝麻认证列表图像URL问题
- 修复实名证件在未通过时依然显示的问题
# v1.17.0
Added
- 审核列表新增channel入参
- 允许芝麻实名认证人工驳回
- /profile 会员信息查询新增实名信息
Changed
- 将实名审核列表从时间倒序改为正序排列
# v1.16.0
1. 增加修改经验、硬币备注
2. 头像审核页面增加粉丝数
# v1.15.4
1. 用户备注功能
# v1.15.3
1. 修复头像/监控用户信息变更审核的数据图表
# v1.15.2
1. 头像/监控用户信息变更审核的数据图表每5分钟上报一次
# v1.15.1
1. 头像/监控用户信息变更审核的数据图表
# v1.15.0
1. 头像/监控用户信息变更审核的数据通知
# v1.14.0
1. 头像审核封禁时 reason 为必要条件
# v1.13.0
1. 增加 /realname/image/preview
2. 增加 /realname/search/card
# v1.12.1
1. 修复潜在的内存泄漏
# v1.12.0
1. 优化 block mgr 命名
2. 兼容b+封禁显示
3. 优化封禁notify
# v1.11.6
1. 修复实名驳回消息理由
# v1.11.5
1. /realname/list tsFrom 默认为1周前
2. /realname/list/pending 仅返回7天内的审核列表
2. 实名相关读接口,使用从库
# v1.11.4
1. 头像审核封禁全部作为小黑屋封禁
# v1.11.3
1. 读取 csv 时移除 bom 头
# v1.11.0
1. 增加实名管理后台
2. 升级hbase sdk --> v2
# v1.10.2
1. 修复批量转正用户文件上传
# v1.10.1
1. upgrade hbase client
2. refine habse client
# v1.10.0
1. 批量转正用户
# v1.9.4
1. 修复头像驳回增加封禁逻辑
# v1.9.3
1. 头像驳回增加封禁逻辑
# v1.9.2
1. 自动初始化节操
# v1.9.1
1. 修复 db tx 时序问题
# v1.9.0
1. 合并block-admin
# v1.8.1
1. 使用 member service 来获取封禁状态
# v1.8.0
1. 增加经验,节操等修改接口
# v1.7.8
1. 去除官方认证年审线程
# v1.7.7
1. 修正编辑官方认证日志状态
# v1.7.6
1. 去除 hbase 查日志
# v1.7.5
1. 更换Context中的RemoteIP方法为从metadata中获取
# v1.7.4
1. 修改每日凌晨用户头像审核从DB查询为手动选择是否强制从DB查询
# v1.7.3
1. 添加经验消息补发功能
# v1.7.2
1. 修改用户监控权限点
# v1.7.1
1. 修复时间检查逻辑
2. 强制读主库
# v1.7.0
1. 每天凌晨头像审核强制从 DB 查询
# v1.6.4
1. 优化用户官方认证状态
# v1.6.3
1. 昵称查询接口like等级设置为low
# v1.6.2
1. 个人信息审核mid搜索添加大于200报错功能
# v1.6.1
1. 修复审核错误日志
# v1.6.0
1. 后台认证中添加spy block信息字段
# v1.5.0
1. 增加删除签名接口
# v1.4.3
1. 修改昵称判断extra字段
# v1.4.2
1. 自动通过查询,走搜索接口
# v1.4.1
1. 审核搜索由数据库过滤,替换成搜索接口
# v1.4.0
1. 个人信息审核
# v1.3.3
1. 昵称索引接口替换成新的
# v1.3.2
1. 头像审核读取从库
# v1.3.0
1. 头像自动审核
# v1.2.22
1. reviewAudit 操作失败, 记录错误到remark
# v1.2.21
1. 将归档图片移动到私有bucket
# v1.2.20
1. 修正驳回头像迁移逻辑
# v1.2.19
1. 修正驳回头像审核员
# v1.2.18
1. 增加监控用户昵称审核逻辑
# v1.2.17
1. 修复部分驳回图片不显示问题
# v1.2.16
1. 修正分页索引查询 SQL
# v1.2.15
1. 修正审核查询范围 SQL
# v1.2.14
1. 修正审核查询范围 SQL
# v1.2.13
1. 优化审核查询范围
# v1.2.12
1. 优化审核查询 SQL
# v1.2.11
1. 修复头像审核默认值显示错误问题
# v1.2.9
1. fix buildUrl
# v1.2.8
1. 头像正常用户审核增加新的的接口
2. 增加头像审核处理
# v1.2.7
1. 不处理非监控用户签名审核单
# v1.2.6
1. 签名审核不通过发消息
# v1.2.5
1. 监控用户,用户信息审核加入 name 字段
# v1.2.4
1. 加入节操日志
# v1.2.3
1. 头像历史直接出所有
# v1.2.2
1. 官方认证改名字
# v1.2.1
1. 监控用户假删除逻辑
# v1.2.0
1. 用户信息审核,签名审核
# v1.1.32
1. 官方认证提交接口
# v1.1.31
1. 官方认证对外接口
# v1.1.30
1. 监控用户管理
# v1.1.29
1. 官方认证优化
# v1.1.28
1. 私有头像带 token
# v1.1.27
1. 修正官方认证 URL
# v1.1.26
1. 头像历史优化
# v1.1.12
1. 头像历史出完整 URL
# v1.1.11
1. 头像历史 API 支持分页
# v1.1.10
1. 头像历史默认出所有 status 记录
# v1.1.9
1. 头像历史全 filter
# v1.1.8
1. 增加机构类型字段
# v1.1.7
1. 官方认证审核顺便改用户名
# v1.1.6
1. 修正用户头像老头像 key
# v1.1.5
1. 用户头像历史
# v1.1.4
1. 头像历史过滤与排序
# v1.1.3
1. 头像历史
# v1.1.2
1. 认证后缀可选
# v1.1.1
1. 官方认证增加拒绝理由
# v1.1.0
1. 官方认证管理
# v1.0.0
1. 支持查询用户信息
2. 支持查询用户信息分页

View File

@@ -0,0 +1,11 @@
# Owner
zhoujiahui
chenjianrong
zhaogangtao
# Author
zhoujiahui
muyang
# Reviewer
zhoujiahui

View File

@@ -0,0 +1,16 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- chenjianrong
- muyang
- zhaogangtao
- zhoujiahui
labels:
- admin
- admin/main/member
- main
options:
no_parent_owners: true
reviewers:
- muyang
- zhoujiahui

View File

@@ -0,0 +1,10 @@
# member-admin
# 项目简介
1. 下一代用户信息后台服务
# 编译环境
# 依赖包
# 编译执行

View File

@@ -0,0 +1,55 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["member-admin-test.toml"],
importpath = "go-common/app/admin/main/member/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/http:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus/report:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,52 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/http"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/net/trace"
"go-common/library/queue/databus/report"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
// init log
log.Init(conf.Conf.Log)
defer log.Close()
// report log
report.InitManager(conf.Conf.ManagerReport)
log.Info("member start")
// init trace
trace.Init(conf.Conf.Tracer)
defer trace.Close()
// ecode init
ecode.Init(conf.Conf.Ecode)
// service init
http.Init(conf.Conf)
// init pprof conf.Conf.Perf
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("member get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("member exit")
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,308 @@
# This is a TOML document. Boom
version = "1.0.0"
user = "nobody"
pid = "/tmp/member.pid"
dir = "./"
[log]
dir = "/data/log/member"
stdout=true
[bm]
addr = "0.0.0.0:7711"
maxListen = 10
timeout = "1s"
[identify]
whiteAccessKey = ""
whiteMid = 0
[identify.app]
key = "6a29f8ed87407c11"
secret = "d3c5a85f5b895a03735b5d20a273bc57"
[identify.memcache]
name = "go-business/identify"
proto = "tcp"
addr = "172.16.33.54:11211"
active = 5
idle = 5
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[identify.host]
auth = "http://passport.bilibili.com"
secret = "http://open.bilibili.com"
[identify.httpClient]
key = "f022126a8a365e20"
secret = "b7b86838145d634b487e67b811b8fab2"
dial = "30ms"
timeout = "100ms"
keepAlive = "60s"
[identify.httpClient.breaker]
window = "10s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[identify.httpClient.url]
"http://passport.bilibili.co/intranet/auth/tokenInfo" = {timeout = "100ms"}
"http://passport.bilibili.co/intranet/auth/cookieInfo" = {timeout = "100ms"}
"http://open.bilibili.co/api/getsecret" = {timeout = "500ms"}
[orm]
[orm.member]
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_member?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
idleTimeout = "4h"
[orm.memberRead]
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_member?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
idleTimeout = "4h"
[orm.account]
dsn = "account:wx2U1MwXRyWEuURw@tcp(172.16.33.205:3306)/account?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
idleTimeout = "4h"
[host]
#search = "http://uat-bili-search.bilibili.co"
message = "http://uat-message.bilibili.co"
passport = "http://uat-passport.bilibili.co"
merak = "http://merak.bilibili.co"
[httpClient]
[httpClient.read]
key = "c1a1cb2d89c33794"
secret = "dda47eeca111e03e6845017505baea13"
dial = "1s"
timeout = "3s"
keepAlive = "60s"
timer = 16
[httpClient.read.breaker]
window = "10s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[httpClient.write]
key = "c1a1cb2d89c33794"
secret = "dda47eeca111e03e6845017505baea13"
dial = "100ms"
timeout = "800ms"
keepAlive = "60s"
timer = 16
[httpClient.write.breaker]
window = "10s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[httpClient.Passport]
key = "c1a1cb2d89c33794"
secret = "dda47eeca111e03e6845017505baea13"
dial = "100ms"
timeout = "800ms"
keepAlive = "60s"
timer = 16
[hbase]
master = ""
meta = ""
dialTimeout = "1s"
readTimeout = "10s"
readsTimeout = "10s"
writeTimeout = "10s"
writesTimeout = "10s"
[hbase.zookeeper]
root = ""
addrs =["172.18.33.75:2181"]
timeout = "30s"
[expMsgDatabus]
key = "4ba46ba31f9a44ef"
secret = "e4c5a7fce28695209e6b4f0af8cf91c5"
group = "AccountExp-MainAccount-P"
topic = "AccountExp-T"
action = "pub"
name = "member-admin/databus"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "60s"
writeTimeout = "1s"
idleTimeout = "10s"
[auth]
managerHost = "http://uat-manager.bilibili.co"
dashboardHost = "http://uat-dashboard-mng.bilibili.co"
dashboardCaller = "manager-go"
[auth.DsHTTPClient]
key = "manager-go"
secret = "949bbb2dd3178252638c2407578bc7ad"
dial = "1s"
timeout = "1s"
keepAlive = "60s"
[auth.DsHTTPClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[auth.MaHTTPClient]
key = "f6433799dbd88751"
secret = "36f8ddb1806207fe07013ab6a77a3935"
dial = "1s"
timeout = "1s"
keepAlive = "60s"
[auth.MaHTTPClient.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[auth.session]
sessionIDLength = 32
cookieLifeTime = 1800
cookieName = "mng-go"
domain = ".bilibili.co"
[auth.session.Memcache]
name = "go-business/auth"
proto = "tcp"
addr = "172.16.33.54:11211"
active = 5
idle = 5
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[fhbyophbase]
master = ""
meta = ""
dialTimeout = "1s"
readTimeout = "10s"
readsTimeout = "10s"
writeTimeout = "10s"
writesTimeout = "10s"
[fhbyophbase.zookeeper]
root = ""
addrs =["bigdata-test-02:2181","bigdata-test-03:2181","bigdata-test-04:2181"]
timeout = "30s"
[fhbymidhbase]
master = ""
meta = ""
dialTimeout = "1s"
readTimeout = "10s"
readsTimeout = "10s"
writeTimeout = "10s"
writesTimeout = "10s"
[fhbymidhbase.zookeeper]
root = ""
addrs =["bigdata-test-02:2181","bigdata-test-03:2181","bigdata-test-04:2181"]
timeout = "30s"
[facepribfs]
timeout="500ms"
maxFileSize=8388608
url="http://bfs.bilibili.co/bfs/"
bucket="facepri"
key="8923aff2e1124bb2"
secret="b237e8927823cc2984aee980123cb0"
[facebfs]
timeout="500ms"
url="http://bfs.bilibili.co/bfs/"
bucket="face"
key="792d70f16e958493"
secret="08525fe929bdb26c3a6009afd7318f"
[rpcClient]
[rpcClient.coin]
timeout = "1s"
[blockMemcache]
name = "block-admin"
proto = "tcp"
addr = "172.16.33.54:11214"
idle = 10
active = 10
dialTimeout = "2s"
readTimeout = "2s"
writeTimeout = "2s"
idleTimeout = "7h"
[expire]
expire = "200s"
[blockMysql]
addr = "127.0.0.1:3306"
dsn = "root:54985498@tcp(127.0.0.1:3306)/bilibili_block?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
queryTimeout = "1s"
execTimeout = "1s"
tranTimeout = "1s"
[mysql.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[accountNotify]
key = "4ba46ba31f9a44ef"
secret = "99985eb4451cfb1b899ca0fbe3c4bdc8"
group = "AccountNotify-MainAccount-P"
topic = "AccountNotify-T"
action = "pub"
buffer = 128
name = "usersuit-admin/databus"
proto = "tcp"
addr = "172.16.33.158:6205"
active = 1
idle = 1
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[blockProperty]
blackHouseURL = "http://api.bilibili.co/x/internal/credit/blocked/info/batch/add"
msgURL = "http://message.bilibili.co/api/notify/send.user.notify.do"
telURL = "http://passport.bilibili.co/intranet/acc/telInfo/mid"
mailURL = "http://passport.bilibili.co/intranet/user/mail"
[realname]
imageURLTemplate = "http://uat-manager.bilibili.co/x/admin/member/realname/image?token=%s"
dataDir = "/data/storage/realname"
[reviewNotify]
users = []
[redis]
name = "member-admin"
proto = "tcp"
addr = "127.0.0.1:6379"
active = 10
idle = 5
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[memcache]
name = "member-admin"
proto = "tcp"
addr = "127.0.0.1:11211"
active = 10
idle = 5
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"

View File

@@ -0,0 +1,56 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/admin/main/member/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/elastic:go_default_library",
"//library/database/hbase.v2:go_default_library",
"//library/database/orm:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/permit:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/trace:go_default_library",
"//library/queue/databus:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,191 @@
package conf
import (
"errors"
"flag"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/elastic"
"go-common/library/database/hbase.v2"
"go-common/library/database/orm"
"go-common/library/database/sql"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
"go-common/library/net/rpc"
"go-common/library/net/rpc/warden"
"go-common/library/net/trace"
"go-common/library/queue/databus"
"go-common/library/time"
"github.com/BurntSushi/toml"
)
// global var
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config config set
type Config struct {
Log *log.Config
BM *bm.ServerConfig
Tracer *trace.Config
ORM *ORM
ORMRead *ORM
Ecode *ecode.Config
Host *Host
HTTPClient *HTTPClient
HBase *hbase.Config
Auth *permit.Config
FHByOPHBase *hbase.Config
FHByMidHBase *hbase.Config
FacePriBFS *BFS
FaceBFS *BFS
ManagerReport *databus.Config
ExpMsgDatabus *databus.Config
RPCClient *RPC
ES *elastic.Config
Realname *Realname
ReviewNotify *ReviewNotify
Redis *redis.Config
Memcache *memcache.Config
// block
AccountNotify *databus.Config
BlockProperty *Property
BlockMemcache *memcache.Config
BlockMySQL *sql.Config
}
// Host is Host config
type Host struct {
Message string
Passport string
Merak string
}
// Property .
type Property struct {
BlackHouseURL string
MSGURL string
TelURL string
MailURL string
}
// RPC config
type RPC struct {
Coin *rpc.ClientConfig
Account *warden.ClientConfig
Figure *rpc.ClientConfig
Member *rpc.ClientConfig
Spy *rpc.ClientConfig
Relation *rpc.ClientConfig
}
// ORM is database config
type ORM struct {
Member *orm.Config
MemberRead *orm.Config
Account *orm.Config
}
// BFS bfs config
type BFS struct {
Timeout time.Duration
MaxFileSize int
Bucket string
URL string
Key string
Secret string
}
// Realname conf
type Realname struct {
ImageURLTemplate string
DataDir string
RsaPub []byte
RsaPriv []byte
}
// HTTPClient http client
type HTTPClient struct {
Read *bm.ClientConfig
Passport *bm.ClientConfig
}
// ReviewNotify notify users
type ReviewNotify struct {
Users []string
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init conf
func Init() error {
if confPath != "" {
return local()
}
return remote()
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
if err = load(); err != nil {
return
}
go func() {
for range client.Event() {
log.Info("config reload")
if load() != nil {
log.Error("config reload error (%v)", err)
}
}
}()
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
pub, priv := loadRealnameKey()
tmpConf.Realname.RsaPriv = []byte(priv)
tmpConf.Realname.RsaPub = []byte(pub)
*Conf = *tmpConf
return
}
func loadRealnameKey() (string, string) {
priv, ok := client.Value("realname.rsa.priv")
if !ok {
panic("Failed to load realname private key")
}
pub, ok := client.Value("realname.rsa.pub")
if !ok {
panic("Failed to load realname pubic key")
}
return pub, priv
}

View File

@@ -0,0 +1,111 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"bfs_test.go",
"dao_test.go",
"face_history_test.go",
"member_test.go",
"merak_test.go",
"message_test.go",
"monitor_test.go",
"official_test.go",
"passport_test.go",
"realname_test.go",
"redis_test.go",
"review_test.go",
"search_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/model:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
"//vendor/github.com/tsuna/gohbase/hrpc:go_default_library",
"//vendor/gopkg.in/h2non/gock.v1:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"bfs.go",
"dao.go",
"face_history.go",
"member.go",
"merak.go",
"message.go",
"monitor.go",
"official.go",
"passport.go",
"realname.go",
"realname_old.go",
"redis.go",
"review.go",
"search.go",
],
importpath = "go-common/app/admin/main/member/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/dao/block:go_default_library",
"//app/admin/main/member/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/elastic:go_default_library",
"//library/database/hbase.v2:go_default_library",
"//library/database/orm:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/ip:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
"//vendor/github.com/jinzhu/gorm:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/tsuna/gohbase/hrpc:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/main/member/dao/block:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,120 @@
package dao
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"hash"
"io/ioutil"
"net/http"
"strconv"
"time"
"go-common/app/admin/main/member/conf"
"go-common/library/ecode"
"github.com/pkg/errors"
)
// UploadImage upload bfs.
func (d *Dao) UploadImage(c context.Context, fileType string, bs []byte, bfsConf *conf.BFS) (location string, err error) {
url := bfsConf.URL + bfsConf.Bucket + "/"
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(bs))
if err != nil {
err = errors.Wrap(err, " UploadImage http.NewRequest() failed")
return
}
expire := time.Now().Unix()
authorization := authorize(bfsConf.Key, bfsConf.Secret, "PUT", bfsConf.Bucket, "", expire)
req.Header.Set("Host", url)
req.Header.Add("Date", fmt.Sprint(expire))
req.Header.Add("Authorization", authorization)
req.Header.Add("Content-Type", fileType)
// timeout
ctx, cancel := context.WithTimeout(c, time.Duration(bfsConf.Timeout))
req = req.WithContext(ctx)
defer cancel()
resp, err := d.bfsclient.Do(req)
if err != nil {
err = errors.Wrapf(ecode.BfsUploadServiceUnavailable, "d.bfsclient.Do error(%v) | url(%s)", err, url)
return
}
if resp.StatusCode != http.StatusOK {
err = errors.Errorf("UploadImage failed,http code:%d", resp.StatusCode)
return
}
header := resp.Header
code := header.Get("Code")
if code != strconv.Itoa(http.StatusOK) {
err = errors.Wrap(ecode.String(code), "UploadImage failed")
return
}
location = header.Get("Location")
return
}
// DelImage del bfs image.
func (d *Dao) DelImage(c context.Context, fileName string, bfsConf *conf.BFS) (err error) {
url := bfsConf.URL + bfsConf.Bucket + "/" + fileName
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
err = errors.Wrap(err, "DelImage http.NewRequest() failed")
return
}
expire := time.Now().Unix()
authorization := authorize(bfsConf.Key, bfsConf.Secret, "DELETE", bfsConf.Bucket, fileName, expire)
req.Header.Set("Host", url)
req.Header.Add("Date", fmt.Sprint(expire))
req.Header.Add("Authorization", authorization)
// timeout
ctx, cancel := context.WithTimeout(c, time.Duration(bfsConf.Timeout))
req = req.WithContext(ctx)
defer cancel()
resp, err := d.bfsclient.Do(req)
if err != nil {
err = errors.Wrapf(ecode.BfsUploadServiceUnavailable, "d.bfsclient.Do error(%v) | url(%s)", err, url)
return
}
if resp.StatusCode != http.StatusOK {
err = errors.Errorf("DelImage failed,http code:%d", resp.StatusCode)
return
}
header := resp.Header
code := header.Get("Code")
if code != strconv.Itoa(http.StatusOK) {
err = errors.Errorf("DelImage failed,res code:%s", code)
return
}
return
}
// authorize returns authorization for upload file to bfs
func authorize(key, secret, method, bucket, fileName string, expire int64) (authorization string) {
var (
content string
mac hash.Hash
signature string
)
content = fmt.Sprintf("%s\n%s\n%s\n%d\n", method, bucket, fileName, expire)
mac = hmac.New(sha1.New, []byte(secret))
mac.Write([]byte(content))
signature = base64.StdEncoding.EncodeToString(mac.Sum(nil))
authorization = fmt.Sprintf("%s:%s:%d", key, signature, expire)
return
}
//Image image.
func (d *Dao) Image(url string) ([]byte, error) {
resp, err := d.bfsclient.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("Image url(%s) resp.StatusCode code(%v)", url, resp.StatusCode)
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}

View File

@@ -0,0 +1,18 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestDao_DelImage(t *testing.T) {
Convey("DelImage", t, func() {
Convey("when not exist, err should not nil", func() {
// FIXME : 该UT不可重入
err := d.DelImage(context.Background(), "130d0e4aa718fcab4abd3ad756ef57017e42bcf4.png", d.c.FaceBFS)
So(err, ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,63 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"http_test.go",
"mc_test.go",
"mysql_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/model/block:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/database/sql:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"http.go",
"mc.go",
"mysql.go",
],
importpath = "go-common/app/admin/main/member/dao/block",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/model/block:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,53 @@
package block
import (
"context"
"go-common/app/admin/main/member/conf"
"go-common/library/cache/memcache"
xsql "go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
"github.com/pkg/errors"
)
// Dao .
type Dao struct {
conf *conf.Config
mc *memcache.Pool
db *xsql.DB
httpClient *bm.Client
}
// New init mysql db
func New(conf *conf.Config, client *bm.Client, mc *memcache.Pool, db *xsql.DB) (dao *Dao) {
dao = &Dao{
conf: conf,
mc: mc,
db: db,
httpClient: client,
}
return
}
// BeginTX .
func (d *Dao) BeginTX(c context.Context) (tx *xsql.Tx, err error) {
if tx, err = d.db.Begin(c); err != nil {
err = errors.WithStack(err)
}
return
}
// Close close the resource.
func (d *Dao) Close() {
d.mc.Close()
d.db.Close()
}
// Ping dao ping
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.db.Ping(c); err != nil {
return
}
return
}

View File

@@ -0,0 +1,39 @@
package block
import (
"flag"
"os"
"testing"
"go-common/app/admin/main/member/conf"
"go-common/library/cache/memcache"
xsql "go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account.member-admin")
flag.Set("conf_token", "c18eac2285e4e4a75a8672139c30d464")
flag.Set("tree_id", "2135")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../../cmd/member-admin-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
hc := bm.NewClient(conf.Conf.HTTPClient.Read)
d = New(conf.Conf, hc, memcache.NewPool(conf.Conf.BlockMemcache), xsql.NewMySQL(conf.Conf.BlockMySQL))
os.Exit(m.Run())
}

View File

@@ -0,0 +1,203 @@
package block
import (
"context"
"fmt"
"net/url"
"strings"
"time"
model "go-common/app/admin/main/member/model/block"
"go-common/library/ecode"
"github.com/pkg/errors"
)
// BlackhouseBlock .
func (d *Dao) BlackhouseBlock(c context.Context, p *model.ParamBatchBlock) (err error) {
midStrs := make([]string, len(p.MIDs))
for i, mid := range p.MIDs {
midStrs[i] = fmt.Sprintf("%d", mid)
}
params := url.Values{}
params.Set("mids", strings.Join(midStrs, ","))
params.Set("oper_id", fmt.Sprintf("%d", p.AdminID))
params.Set("operator_name", p.AdminName)
params.Set("blocked_days", fmt.Sprintf("%d", p.Duration))
switch p.Action {
case model.BlockActionForever:
params.Set("blocked_forever", "1")
params.Set("punish_type", "3")
default:
params.Set("blocked_forever", "0")
params.Set("punish_type", "2")
}
params.Set("blocked_remark", p.Comment)
params.Set("origin_type", fmt.Sprintf("%d", p.Area))
params.Set("punish_time", fmt.Sprintf("%d", time.Now().Unix()))
params.Set("reason_type", fmt.Sprintf("%d", parseReasonType(p.Reason)))
var res struct {
Code int `json:"code"`
}
if err = d.httpClient.Post(c, d.conf.BlockProperty.BlackHouseURL, "", params, &res); err != nil {
err = errors.WithStack(err)
return
}
if res.Code != 0 {
err = errors.WithStack(ecode.Int(res.Code))
return
}
return
}
func parseReasonType(msg string) (t int) {
switch msg {
case "刷屏":
t = 1
case "抢楼":
t = 2
case "发布色情低俗信息":
t = 3
case "发布赌博诈骗信息":
t = 4
case "发布违禁相关信息", "发布违禁信息":
t = 5
case "发布垃圾广告信息":
t = 6
case "发布人身攻击言论":
t = 7
case "发布侵犯他人隐私信息":
t = 8
case "发布引战言论":
t = 9
case "发布剧透信息":
t = 10
case "恶意添加无关标签":
t = 11
case "恶意删除他人标签":
t = 12
case "发布色情信息":
t = 13
case "发布低俗信息":
t = 14
case "发布暴力血腥信息":
t = 15
case "涉及恶意投稿行为":
t = 16
case "发布非法网站信息":
t = 17
case "发布传播不实信息":
t = 18
case "发布怂恿教唆信息":
t = 19
case "恶意刷屏":
t = 20
case "账号违规":
t = 21
case "恶意抄袭":
t = 22
case "冒充自制原创":
t = 23
case "发布青少年不良内容":
t = 24
case "破坏网络安全":
t = 25
case "发布虚假误导信息":
t = 26
case "仿冒官方认证账号":
t = 27
case "发布不适宜内容":
t = 28
case "违反运营规则":
t = 29
case "恶意创建话题":
t = 30
case "发布违规抽奖":
t = 31
case "恶意冒充他人":
t = 32
default:
t = 0
}
return
}
// SendSysMsg send sys msg.
func (d *Dao) SendSysMsg(c context.Context, code string, mids []int64, title string, content string, remoteIP string) (err error) {
params := url.Values{}
params.Set("mc", code)
params.Set("title", title)
params.Set("data_type", "4")
params.Set("context", content)
params.Set("mid_list", midsToParam(mids))
var res struct {
Code int `json:"code"`
Data *struct {
Status int8 `json:"status"`
Remark string `json:"remark"`
} `json:"data"`
}
if err = d.httpClient.Post(c, d.conf.BlockProperty.MSGURL, remoteIP, params, &res); err != nil {
err = errors.WithStack(err)
return
}
if res.Code != 0 {
err = errors.WithStack(ecode.Int(res.Code))
return
}
return
}
func midsToParam(mids []int64) (str string) {
strs := make([]string, 0, len(mids))
for _, mid := range mids {
strs = append(strs, fmt.Sprintf("%d", mid))
}
return strings.Join(strs, ",")
}
// TelInfo tel info.
func (d *Dao) TelInfo(c context.Context, mid int64) (tel string, err error) {
params := url.Values{}
params.Set("mid", fmt.Sprintf("%d", mid))
var resp struct {
Code int `json:"code"`
Data struct {
Mid int64 `json:"mid"`
Tel string `json:"tel"`
JoinIP string `json:"join_ip"`
JoinTime int64 `json:"join_time"`
} `json:"data"`
}
if err = d.httpClient.Get(c, d.conf.BlockProperty.TelURL, "", params, &resp); err != nil {
err = errors.Wrapf(err, "telinfo : %d", mid)
return
}
if resp.Code != 0 {
err = errors.Errorf("telinfo url(%s) res(%+v) err(%+v)", d.conf.BlockProperty.TelURL+"?"+params.Encode(), resp, ecode.Int(resp.Code))
return
}
tel = resp.Data.Tel
return
}
// MailInfo .
func (d *Dao) MailInfo(c context.Context, mid int64) (mail string, err error) {
params := url.Values{}
params.Set("mid", fmt.Sprintf("%d", mid))
var resp struct {
Code int `json:"code"`
Data string `json:"data"`
}
if err = d.httpClient.Get(c, d.conf.BlockProperty.MailURL, "", params, &resp); err != nil {
err = errors.Wrapf(err, "mailinfo : %d", mid)
return
}
if resp.Code != 0 {
err = errors.Errorf("mailinfo url(%s) res(%+v) err(%+v)", d.conf.BlockProperty.MailURL+"?"+params.Encode(), resp, ecode.Int(resp.Code))
return
}
mail = resp.Data
return
}

View File

@@ -0,0 +1,103 @@
package block
import (
"context"
model "go-common/app/admin/main/member/model/block"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestBlockBlackhouseBlock(t *testing.T) {
convey.Convey("BlackhouseBlock", t, func(ctx convey.C) {
var (
c = context.Background()
p = &model.ParamBatchBlock{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.BlackhouseBlock(c, p)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockparseReasonType(t *testing.T) {
convey.Convey("parseReasonType", t, func(ctx convey.C) {
var (
msg = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
no := parseReasonType(msg)
ctx.Convey("Then no should not be nil.", func(ctx convey.C) {
ctx.So(no, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockSendSysMsg(t *testing.T) {
convey.Convey("SendSysMsg", t, func(ctx convey.C) {
var (
c = context.Background()
code = ""
mids = []int64{}
title = ""
content = ""
remoteIP = ""
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.SendSysMsg(c, code, mids, title, content, remoteIP)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockmidsToParam(t *testing.T) {
convey.Convey("midsToParam", t, func(ctx convey.C) {
var (
mids = []int64{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
str := midsToParam(mids)
ctx.Convey("Then str should not be nil.", func(ctx convey.C) {
ctx.So(str, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockTelInfo(t *testing.T) {
convey.Convey("TelInfo", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
tel, err := d.TelInfo(c, mid)
ctx.Convey("Then err should be nil.tel should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(tel, convey.ShouldEqual, "")
})
})
})
}
func TestBlockMailInfo(t *testing.T) {
convey.Convey("MailInfo", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(0)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
mail, err := d.MailInfo(c, mid)
ctx.Convey("Then err should be nil.mail should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(mail, convey.ShouldEqual, "")
})
})
})
}

View File

@@ -0,0 +1,49 @@
package block
import (
"context"
"fmt"
"go-common/library/cache/memcache"
"github.com/pkg/errors"
)
func userKey(mid int64) (key string) {
key = fmt.Sprintf("u_%d", mid)
return
}
func userDetailKey(mid int64) (key string) {
key = fmt.Sprintf("ud_%d", mid)
return
}
// DeleteUserCache .
func (d *Dao) DeleteUserCache(c context.Context, mid int64) (err error) {
var (
key = userKey(mid)
conn = d.mc.Get(c)
)
defer conn.Close()
if err = conn.Delete(key); err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
err = errors.Wrapf(err, "key : %s", key)
return
}
return
}
// DeleteUserDetailCache delete user detail cache
func (d *Dao) DeleteUserDetailCache(c context.Context, mid int64) (err error) {
var (
key = userDetailKey(mid)
conn = d.mc.Get(c)
)
defer conn.Close()
err = conn.Delete(key)
return
}

View File

@@ -0,0 +1,37 @@
package block
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestBlockuserKey(t *testing.T) {
convey.Convey("userKey", t, func(ctx convey.C) {
var (
mid = int64(46333)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
key := userKey(mid)
ctx.Convey("Then key should not be nil.", func(ctx convey.C) {
ctx.So(key, convey.ShouldEqual, "u_46333")
})
})
})
}
func TestBlockDeleteUserCache(t *testing.T) {
convey.Convey("DeleteUserCache", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(1)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DeleteUserCache(c, mid)
ctx.Convey("test DeleteUserCache", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,180 @@
package block
import (
"context"
"fmt"
"strings"
model "go-common/app/admin/main/member/model/block"
xsql "go-common/library/database/sql"
"github.com/pkg/errors"
)
const (
_user = `SELECT id,mid,status,ctime,mtime FROM block_user WHERE mid=? LIMIT 1`
_users = `SELECT id,mid,status,ctime,mtime FROM block_user WHERE mid IN (%s)`
_upsertUser = `INSERT INTO block_user (mid,status) VALUES (?,?) ON DUPLICATE KEY UPDATE status=?`
_userDetails = `SELECT id,mid,block_count,ctime,mtime FROM block_user_detail WHERE mid IN (%s)`
_addAddBlockCount = `INSERT INTO block_user_detail (mid,block_count) VALUES (?,1) ON DUPLICATE KEY UPDATE block_count=block_count+1`
_history = `SELECT id,mid,admin_id,admin_name,source,area,reason,comment,action,start_time,
duration,notify,ctime,mtime FROM block_history_%d WHERE mid=? ORDER BY mtime %s LIMIT ?,? `
_historyCount = `SELECT count(*) FROM block_history_%d WHERE mid=? LIMIT 1`
_insertHistory = `INSERT INTO block_history_%d (mid,admin_id,admin_name,source,area,reason,comment,action,start_time,duration,notify) VALUES (?,?,?,?,?,?,?,?,?,?,?)`
)
func historyIdx(mid int64) int64 {
return mid % 10
}
// User .
func (d *Dao) User(c context.Context, mid int64) (user *model.DBUser, err error) {
user = &model.DBUser{}
row := d.db.QueryRow(c, _user, mid)
if err = row.Scan(&user.ID, &user.MID, &user.Status, &user.CTime, &user.MTime); err != nil {
err = nil
user = nil
return
}
return
}
// Users .
func (d *Dao) Users(c context.Context, mids []int64) (users []*model.DBUser, err error) {
if len(mids) == 0 {
return
}
var (
sql = fmt.Sprintf(_users, strings.Join(intsToStrs(mids), ","))
rows *xsql.Rows
)
if rows, err = d.db.Query(c, sql); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
user := &model.DBUser{}
if err = rows.Scan(&user.ID, &user.MID, &user.Status, &user.CTime, &user.MTime); err != nil {
err = errors.WithStack(err)
return
}
users = append(users, user)
}
if err = rows.Err(); err != nil {
err = errors.WithStack(err)
}
return
}
// TxUpdateUser .
func (d *Dao) TxUpdateUser(c context.Context, tx *xsql.Tx, mid int64, status model.BlockStatus) (err error) {
if _, err = tx.Exec(_upsertUser, mid, status, status); err != nil {
err = errors.WithStack(err)
return
}
return
}
// UserDetails .
func (d *Dao) UserDetails(c context.Context, mids []int64) (users []*model.DBUserDetail, err error) {
if len(mids) == 0 {
return
}
var (
sql = fmt.Sprintf(_userDetails, strings.Join(intsToStrs(mids), ","))
rows *xsql.Rows
)
if rows, err = d.db.Query(c, sql); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
user := &model.DBUserDetail{}
if err = rows.Scan(&user.ID, &user.MID, &user.BlockCount, &user.CTime, &user.MTime); err != nil {
err = errors.WithStack(err)
return
}
users = append(users, user)
}
if err = rows.Err(); err != nil {
err = errors.WithStack(err)
}
return
}
// UpdateAddBlockCount .
func (d *Dao) UpdateAddBlockCount(c context.Context, mid int64) (err error) {
if _, err = d.db.Exec(c, _addAddBlockCount, mid); err != nil {
err = errors.WithStack(err)
return
}
return
}
// History 获得mid历史封禁记录
func (d *Dao) History(c context.Context, mid int64, start, limit int, desc bool) (history []*model.DBHistory, err error) {
var (
rows *xsql.Rows
)
order := "ASC"
if desc {
order = "DESC"
}
sql := fmt.Sprintf(_history, historyIdx(mid), order)
if rows, err = d.db.Query(c, sql, mid, start, limit); err != nil {
err = errors.WithStack(err)
return
}
defer rows.Close()
for rows.Next() {
h := &model.DBHistory{}
if err = rows.Scan(&h.ID, &h.MID, &h.AdminID, &h.AdminName, &h.Source, &h.Area, &h.Reason, &h.Comment, &h.Action, &h.StartTime, &h.Duration, &h.Notify, &h.CTime, &h.MTime); err != nil {
err = errors.WithStack(err)
return
}
history = append(history, h)
}
if err = rows.Err(); err != nil {
return
}
return
}
// HistoryCount 获得历史记录总长度
func (d *Dao) HistoryCount(c context.Context, mid int64) (total int, err error) {
var (
row *xsql.Row
sql = fmt.Sprintf(_historyCount, historyIdx(mid))
)
row = d.db.QueryRow(c, sql, mid)
if err = row.Scan(&total); err != nil {
err = errors.WithStack(err)
return
}
return
}
// TxInsertHistory .
func (d *Dao) TxInsertHistory(c context.Context, tx *xsql.Tx, h *model.DBHistory) (err error) {
var (
sql = fmt.Sprintf(_insertHistory, historyIdx(h.MID))
)
if _, err = tx.Exec(sql, h.MID, h.AdminID, h.AdminName, h.Source, h.Area, h.Reason, h.Comment, h.Action, h.StartTime, h.Duration, h.Notify); err != nil {
err = errors.WithStack(err)
return
}
return
}
func intsToStrs(ints []int64) (strs []string) {
for _, i := range ints {
strs = append(strs, fmt.Sprintf("%d", i))
}
return
}

View File

@@ -0,0 +1,174 @@
package block
import (
"context"
model "go-common/app/admin/main/member/model/block"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestBlockhistoryIdx(t *testing.T) {
convey.Convey("historyIdx", t, func(ctx convey.C) {
var (
mid = int64(46333)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := historyIdx(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockUser(t *testing.T) {
convey.Convey("User", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
user, err := d.User(c, mid)
ctx.Convey("Then err should be nil.user should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(user, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockUsers(t *testing.T) {
convey.Convey("Users", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{46333}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
users, err := d.Users(c, mids)
ctx.Convey("Then err should be nil.users should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(users, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockTxUpdateUser(t *testing.T) {
convey.Convey("TxUpdateUser", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTX(c)
mid = int64(46333)
status model.BlockStatus = model.BlockStatusFalse
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.TxUpdateUser(c, tx, mid, status)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
func TestBlockUserDetails(t *testing.T) {
convey.Convey("UserDetails", t, func(ctx convey.C) {
var (
c = context.Background()
mids = []int64{46333}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
users, err := d.UserDetails(c, mids)
ctx.Convey("Then err should be nil.users should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(users, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockUpdateAddBlockCount(t *testing.T) {
convey.Convey("UpdateAddBlockCount", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.UpdateAddBlockCount(c, mid)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestBlockHistory(t *testing.T) {
convey.Convey("History", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
start = int(0)
limit = int(10)
desc bool
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
history, err := d.History(c, mid, start, limit, desc)
ctx.Convey("Then err should be nil.history should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(history, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockHistoryCount(t *testing.T) {
convey.Convey("HistoryCount", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(46333)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
total, err := d.HistoryCount(c, mid)
ctx.Convey("Then err should be nil.total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
})
})
})
}
func TestBlockTxInsertHistory(t *testing.T) {
convey.Convey("TxInsertHistory", t, func(ctx convey.C) {
var (
c = context.Background()
tx, _ = d.BeginTX(c)
h = &model.DBHistory{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.TxInsertHistory(c, tx, h)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Reset(func() {
tx.Commit()
})
})
}
func TestBlockintsToStrs(t *testing.T) {
convey.Convey("intsToStrs", t, func(ctx convey.C) {
var (
ints = []int64{46333}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
strs := intsToStrs(ints)
ctx.Convey("Then strs should not be nil.", func(ctx convey.C) {
ctx.So(strs, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,100 @@
package dao
import (
"context"
xhttp "net/http"
"time"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/dao/block"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/database/elastic"
"go-common/library/database/orm"
"go-common/library/database/sql"
bm "go-common/library/net/http/blademaster"
"go-common/library/queue/databus"
"go-common/library/database/hbase.v2"
"github.com/jinzhu/gorm"
)
// Dao dao
type Dao struct {
c *conf.Config
// db
member *gorm.DB
memberRead *gorm.DB
account *gorm.DB
block *block.Dao
fhbyophbase *hbase.Client
fhbymidhbase *hbase.Client
httpClient *bm.Client
passportClient *bm.Client
bfsclient *xhttp.Client
upUnameURL string
queryByMidsURL string
msgURL string
expMsgDatabus *databus.Databus
es *elastic.Elastic
redis *redis.Pool
memcache *memcache.Pool
merakURL string
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
member: orm.NewMySQL(c.ORM.Member),
memberRead: orm.NewMySQL(c.ORM.MemberRead),
account: orm.NewMySQL(c.ORM.Account), // account-php 库
fhbyophbase: hbase.NewClient(c.FHByOPHBase),
fhbymidhbase: hbase.NewClient(c.FHByMidHBase),
httpClient: bm.NewClient(c.HTTPClient.Read),
passportClient: bm.NewClient(c.HTTPClient.Passport),
bfsclient: &xhttp.Client{Timeout: time.Duration(c.FacePriBFS.Timeout)},
msgURL: c.Host.Message + _msgURL,
upUnameURL: c.Host.Passport + _updateUname,
queryByMidsURL: c.Host.Passport + _queryByMids,
merakURL: c.Host.Merak + "/",
expMsgDatabus: databus.New(c.ExpMsgDatabus),
es: elastic.NewElastic(c.ES),
redis: redis.NewPool(c.Redis),
memcache: memcache.NewPool(c.Memcache),
}
dao.block = block.New(c, dao.httpClient, memcache.NewPool(c.BlockMemcache), sql.NewMySQL(c.BlockMySQL))
dao.initORM()
return
}
// Close close the resource.
func (d *Dao) Close() {
if d.member != nil {
d.member.Close()
}
if d.redis != nil {
d.redis.Close()
}
}
func (d *Dao) initORM() {
d.member.LogMode(true)
}
// Ping dao ping
func (d *Dao) Ping(ctx context.Context) (err error) {
if err = d.member.DB().PingContext(ctx); err != nil {
return
}
if err = d.pingRedis(ctx); err != nil {
return
}
return
}
// BlockImpl is
func (d *Dao) BlockImpl() *block.Dao {
return d.block
}

View File

@@ -0,0 +1,43 @@
package dao
import (
"flag"
"os"
"strings"
"testing"
"go-common/app/admin/main/member/conf"
"gopkg.in/h2non/gock.v1"
)
var d *Dao
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.account.member-admin")
flag.Set("conf_token", "c18eac2285e4e4a75a8672139c30d464")
flag.Set("tree_id", "2135")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../cmd/member-admin-test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
d.httpClient.SetTransport(gock.DefaultTransport)
os.Exit(m.Run())
}
func httpMock(method, url string) *gock.Request {
r := gock.New(url)
r.Method = strings.ToUpper(method)
return r
}

View File

@@ -0,0 +1,233 @@
package dao
import (
"context"
"fmt"
"io"
"math"
"strconv"
"strings"
"sync"
"go-common/app/admin/main/member/model"
"go-common/library/log"
"go-common/library/sync/errgroup"
xtime "go-common/library/time"
"github.com/tsuna/gohbase/hrpc"
)
var (
tableFaceByMidHistory = "account:user_face"
tableFaceByOPHistory = "account:user_face_admin"
)
// reverse returns its argument string reversed rune-wise left to right.
func reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
func rpad(s string, c string, l int) string {
dt := l - len(s)
if dt <= 0 {
return s
}
return s + strings.Repeat(c, dt)
}
// func lpad(s string, c string, l int) string {
// dt := l - len(s)
// if dt <= 0 {
// return s
// }
// return strings.Repeat(c, dt) + s
// }
func midKey(mid int64, time xtime.Time) string {
suf := rpad(strconv.FormatInt(math.MaxInt64-int64(time), 10), "0", 10)
return fmt.Sprintf("%s%s", rpad(reverse(strconv.FormatInt(mid, 10)), "0", 10), string(suf[len(suf)-10:]))
}
func operatorKey(operator string, time xtime.Time) string {
suf := rpad(strconv.FormatInt(math.MaxInt64-int64(time), 10), "0", 10)
return fmt.Sprintf("%s%s", rpad(operator, "-", 20), string(suf[len(suf)-10:]))
}
func scanTimes(tDuration xtime.Time) int {
times := int(tDuration)/(60*60) + 5
if times > 50 {
times = 50
}
return times
}
// FaceHistoryByMid is.
func (d *Dao) FaceHistoryByMid(ctx context.Context, arg *model.ArgFaceHistory) (model.FaceRecordList, error) {
size := arg.ETime - arg.STime
stimes := scanTimes(size)
chunk := xtime.Time(size / xtime.Time(stimes))
if chunk == 0 {
chunk = size
}
rendKey, rstartKey := midKey(arg.Mid, arg.STime), midKey(arg.Mid, arg.ETime)
eg := errgroup.Group{}
lock := sync.RWMutex{}
records := make(model.FaceRecordList, 0)
for i := 0; i < stimes; i++ {
times := i + 1
stime := arg.STime + (chunk * xtime.Time(i))
etime := arg.STime + (chunk * xtime.Time(i+1))
eg.Go(func() error {
endKey, startKey := midKey(arg.Mid, stime), midKey(arg.Mid, etime)
log.Info("FaceHistoryByMid: range: start: %s end: %s with times: %d scaning key: start: %s end: %s",
rstartKey, rendKey, times, startKey, endKey)
scanner, err := d.fhbymidhbase.ScanRangeStr(ctx, tableFaceByMidHistory, startKey, endKey)
if err != nil {
log.Error("hbase.ScanRangeStr(%s,%+v) error(%v)", tableFaceByMidHistory, arg, err)
return err
}
for {
r, err := scanner.Next()
if err != nil {
if err != io.EOF {
return err
}
break
}
lock.Lock()
records = append(records, toMidFaceRecord(r))
lock.Unlock()
}
return nil
})
if etime >= arg.ETime {
break
}
}
if err := eg.Wait(); err != nil {
return nil, err
}
return records, nil
}
// FaceHistoryByOP is.
func (d *Dao) FaceHistoryByOP(ctx context.Context, arg *model.ArgFaceHistory) (model.FaceRecordList, error) {
size := arg.ETime - arg.STime
stimes := scanTimes(size)
chunk := xtime.Time(size / xtime.Time(stimes))
if chunk == 0 {
chunk = size
}
rendKey, rstartKey := operatorKey(arg.Operator, arg.STime), operatorKey(arg.Operator, arg.ETime)
eg := errgroup.Group{}
lock := sync.RWMutex{}
records := make(model.FaceRecordList, 0)
for i := 0; i < stimes; i++ {
times := i + 1
stime := arg.STime + (chunk * xtime.Time(i))
etime := arg.STime + (chunk * xtime.Time(i+1))
eg.Go(func() error {
endKey, startKey := operatorKey(arg.Operator, stime), operatorKey(arg.Operator, etime)
log.Info("FaceHistoryByOP: range: start: %s end: %s with times: %d scaning key: start: %s end: %s",
rstartKey, rendKey, times, startKey, endKey)
scanner, err := d.fhbyophbase.ScanRangeStr(ctx, tableFaceByOPHistory, startKey, endKey)
if err != nil {
log.Error("hbase.ScanRangeStr(%s,%+v) error(%v)", tableFaceByOPHistory, arg, err)
return err
}
for {
r, err := scanner.Next()
if err != nil {
if err != io.EOF {
return err
}
break
}
lock.Lock()
records = append(records, toOPFaceRecord(r))
lock.Unlock()
}
return nil
})
if etime >= arg.ETime {
break
}
}
if err := eg.Wait(); err != nil {
return nil, err
}
return records, nil
}
func toOPFaceRecord(res *hrpc.Result) *model.FaceRecord {
l := &model.FaceRecord{}
for _, c := range res.Cells {
key := string(c.Row)
qf := string(c.Qualifier)
v := string(c.Value)
log.Info("Retrieving op face record history: key(%s) qualifier(%s) value(%s)", key, qf, v)
// fill fields
switch qf {
case "id":
l.ID, _ = strconv.ParseInt(v, 10, 64)
case "mid":
l.Mid, _ = strconv.ParseInt(v, 10, 64)
case "sta":
l.Status = model.ParseStatus(v)
case "op":
l.Operator = v
case "old":
l.OldFace = v
case "new":
l.NewFace = v
case "apply":
l.ApplyTime = model.ParseApplyTime(v)
case "mtm":
l.ModifyTime, _ = model.ParseLogTime(v)
}
}
return l
}
func toMidFaceRecord(res *hrpc.Result) *model.FaceRecord {
l := &model.FaceRecord{}
for _, c := range res.Cells {
key := string(c.Row)
qf := string(c.Qualifier)
v := string(c.Value)
log.Info("Retrieving mid face record history: key(%s) qualifier(%s) value(%s)", key, qf, v)
// fill fields
switch qf {
// case "id":
// l.ID, _ = strconv.ParseInt(v, 10, 64)
case "mid":
l.Mid, _ = strconv.ParseInt(v, 10, 64)
case "s":
l.Status = model.ParseStatus(v)
case "op":
l.Operator = v
case "of":
l.OldFace = v
case "nf":
l.NewFace = v
case "at":
l.ApplyTime = model.ParseApplyTime(v)
case "mt":
l.ModifyTime, _ = model.ParseLogTime(v)
}
}
return l
}

View File

@@ -0,0 +1,143 @@
package dao
import (
xtime "go-common/library/time"
"testing"
"github.com/smartystreets/goconvey/convey"
"github.com/tsuna/gohbase/hrpc"
)
func TestDaoreverse(t *testing.T) {
convey.Convey("reverse", t, func(ctx convey.C) {
var (
s = "123"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := reverse(s)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldEqual, "321")
})
})
})
}
func TestDaorpad(t *testing.T) {
convey.Convey("rpad", t, func(ctx convey.C) {
var (
s = "123"
c = "a"
l = int(4)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := rpad(s, c, l)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldEqual, "123a")
})
})
})
}
func TestDaomidKey(t *testing.T) {
convey.Convey("midKey", t, func(ctx convey.C) {
var (
mid = int64(1)
time xtime.Time
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := midKey(mid, time)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaooperatorKey(t *testing.T) {
convey.Convey("operatorKey", t, func(ctx convey.C) {
var (
operator = "admin"
time xtime.Time
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := operatorKey(operator, time)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoscanTimes(t *testing.T) {
convey.Convey("scanTimes", t, func(ctx convey.C) {
var (
tDuration xtime.Time
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := scanTimes(tDuration)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoFaceHistoryByMid(t *testing.T) {
convey.Convey("FaceHistoryByMid", t, func(ctx convey.C) {
// var (
// c = context.Background()
// arg = &model.ArgFaceHistory{}
// )
ctx.Convey("When everything goes positive", func(ctx convey.C) {
// p1, err := d.FaceHistoryByMid(c, arg)
// ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(p1, convey.ShouldNotBeNil)
// })
})
})
}
func TestDaoFaceHistoryByOP(t *testing.T) {
convey.Convey("FaceHistoryByOP", t, func(ctx convey.C) {
// var (
// c = context.Background()
// arg = &model.ArgFaceHistory{}
// )
ctx.Convey("When everything goes positive", func(ctx convey.C) {
// p1, err := d.FaceHistoryByOP(c, arg)
// ctx.Convey("Then err should be nil.p1 should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(p1, convey.ShouldNotBeNil)
// })
})
})
}
func TestDaotoOPFaceRecord(t *testing.T) {
convey.Convey("toOPFaceRecord", t, func(ctx convey.C) {
var (
res = &hrpc.Result{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := toOPFaceRecord(res)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestDaotoMidFaceRecord(t *testing.T) {
convey.Convey("toMidFaceRecord", t, func(ctx convey.C) {
var (
res = &hrpc.Result{}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1 := toMidFaceRecord(res)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,147 @@
package dao
import (
"context"
"fmt"
"strconv"
"go-common/app/admin/main/member/model"
"github.com/pkg/errors"
)
var (
_sharding = int64(100)
)
func baseTable(mid int64) string {
return fmt.Sprintf("user_base_%02d", mid%_sharding)
}
func expTable(mid int64) string {
return fmt.Sprintf("user_exp_%02d", mid%_sharding)
}
// Base is.
func (d *Dao) Base(ctx context.Context, mid int64) (*model.Base, error) {
b := &model.Base{}
if err := d.member.Table(baseTable(mid)).Where("mid=?", mid).Find(b).Error; err != nil {
return nil, err
}
b.RandFaceURL()
return b, nil
}
// Bases is.
func (d *Dao) Bases(ctx context.Context, mids []int64) (map[int64]*model.Base, error) {
bs := make(map[int64]*model.Base, len(mids))
for _, mid := range mids {
b, err := d.Base(ctx, mid)
if err != nil {
continue
}
bs[b.Mid] = b
}
return bs, nil
}
// Exp is.
func (d *Dao) Exp(ctx context.Context, mid int64) (*model.Exp, error) {
e := &model.Exp{}
if err := d.member.Table(expTable(mid)).Where("mid=?", mid).Find(e).Error; err != nil {
return nil, err
}
return e, nil
}
// Moral is.
func (d *Dao) Moral(ctx context.Context, mid int64) (*model.Moral, error) {
m := &model.Moral{
Mid: mid,
Moral: 7000,
}
if err := d.member.Table("user_moral").
Where(&model.Moral{Mid: mid}).
FirstOrCreate(m).Error; err != nil {
return nil, err
}
return m, nil
}
// UpName is.
func (d *Dao) UpName(ctx context.Context, mid int64, name string) error {
ups := map[string]string{
"name": name,
}
if err := d.member.Table(baseTable(mid)).Where("mid=?", mid).Updates(ups).Error; err != nil {
err = errors.Wrap(err, "dao update name")
return err
}
return nil
}
// UpSign is.
func (d *Dao) UpSign(ctx context.Context, mid int64, sign string) error {
ups := map[string]string{
"sign": sign,
}
if err := d.member.Table(baseTable(mid)).Where("mid=?", mid).Updates(ups).Error; err != nil {
err = errors.Wrap(err, "dao update sign")
return err
}
return nil
}
// UpFace is.
func (d *Dao) UpFace(ctx context.Context, mid int64, face string) error {
ups := map[string]string{
"face": face,
}
if err := d.member.Table(baseTable(mid)).Where("mid=?", mid).Updates(ups).Error; err != nil {
err = errors.Wrap(err, "dao update face")
return err
}
return nil
}
// BatchUserAddit is.
func (d *Dao) BatchUserAddit(ctx context.Context, mids []int64) (map[int64]*model.UserAddit, error) {
uas := []*model.UserAddit{}
if err := d.member.Table("user_addit").Where("mid in (?)", mids).Find(&uas).Error; err != nil {
return nil, err
}
uasMap := make(map[int64]*model.UserAddit, len(uas))
for _, ua := range uas {
uasMap[ua.Mid] = ua
}
return uasMap, nil
}
// PubExpMsg is.
func (d *Dao) PubExpMsg(ctx context.Context, msg *model.AddExpMsg) error {
return d.expMsgDatabus.Send(ctx, strconv.FormatInt(msg.Mid, 10), msg)
}
// UserAddit is.
func (d *Dao) UserAddit(ctx context.Context, mid int64) (*model.UserAddit, error) {
addit := &model.UserAddit{}
if err := d.member.Table("user_addit").Where("mid=?", mid).Find(addit).Error; err != nil {
return nil, err
}
return addit, nil
}
// UpAdditRemark update remark.
func (d *Dao) UpAdditRemark(ctx context.Context, mid int64, remark string) error {
addit := &model.UserAddit{
Mid: mid,
}
upRemark := map[string]string{
"remark": remark,
}
if err := d.member.Table("user_addit").Where("mid=?", mid).Assign(upRemark).FirstOrCreate(addit).Error; err != nil {
err = errors.Wrap(err, "dao insert or update addit")
return err
}
return nil
}

View File

@@ -0,0 +1,97 @@
package dao
import (
"context"
"fmt"
"testing"
"time"
"go-common/app/admin/main/member/model"
"github.com/smartystreets/goconvey/convey"
)
func TestBase(t *testing.T) {
convey.Convey("Base", t, func() {
base, err := d.Base(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(base, convey.ShouldNotBeNil)
})
}
func TestExp(t *testing.T) {
convey.Convey("Exp", t, func() {
exp, err := d.Exp(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(exp, convey.ShouldNotBeNil)
})
}
func TestMoral(t *testing.T) {
convey.Convey("Moral", t, func() {
moral, err := d.Moral(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(moral, convey.ShouldNotBeNil)
})
}
func TestDao_BatchUserAddit(t *testing.T) {
convey.Convey("BatchUserAddit", t, func() {
userAddits, err := d.BatchUserAddit(context.Background(), []int64{5, 13, 3})
convey.So(err, convey.ShouldBeNil)
convey.So(userAddits, convey.ShouldNotBeNil)
})
}
func TestDao_UpName(t *testing.T) {
convey.Convey("UpName", t, func() {
name := fmt.Sprintf("100_Bili_%v", time.Now().Unix())
err := d.UpName(context.Background(), 100, name)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDao_UpSign(t *testing.T) {
convey.Convey("UpSign", t, func() {
sign := fmt.Sprintf("签名%v", time.Now().Unix())
err := d.UpSign(context.Background(), 100, sign)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDao_UpFace(t *testing.T) {
convey.Convey("UpFace", t, func() {
face := fmt.Sprintf("testFace%v.jpg", time.Now().Unix())
err := d.UpFace(context.Background(), 100, face)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDao_PubExpMsg(t *testing.T) {
convey.Convey("PubExpMsg", t, func() {
msg := &model.AddExpMsg{
Mid: 1,
IP: "127.0.0.1",
Ts: time.Now().Unix(),
Event: "test",
}
err := d.PubExpMsg(context.Background(), msg)
convey.So(err, convey.ShouldBeNil)
})
}
func TestDao_UserAddit(t *testing.T) {
convey.Convey("UserAddit", t, func() {
userAddit, err := d.UserAddit(context.Background(), 5)
convey.So(err, convey.ShouldBeNil)
convey.So(userAddit, convey.ShouldNotBeNil)
})
}
func TestDao_UpAdditRemark(t *testing.T) {
convey.Convey("UpAdditRemark", t, func() {
remark := fmt.Sprintf("remark%v", time.Now().Unix())
err := d.UpAdditRemark(context.Background(), 27515431, remark)
convey.So(err, convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,84 @@
package dao
import (
"bytes"
"context"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"net/http"
"sort"
"strings"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
_privateKey = "37ba757817b4e9c45c7e97f6ed5eee4e1c7bac52"
_publicKey = "71f079db59672ecec5b8d6f252c4b59ab2a8a227mainsite@bilibili.com"
)
// MerakNotify send notify
func (d *Dao) MerakNotify(ctx context.Context, title, content string) error {
params := map[string]string{
"Action": "CreateWechatMessage",
"PublicKey": _publicKey,
"UserName": strings.Join(d.c.ReviewNotify.Users, ","),
"Title": title,
"Content": content,
"TreeId": "",
}
sign, err := MerakSign(params)
if err != nil {
log.Error("Failed to sign params: %+v: %+v", params, err)
return err
}
params["Signature"] = sign
b, err := json.Marshal(params)
if err != nil {
return errors.WithStack(err)
}
req, err := http.NewRequest(http.MethodPost, d.merakURL, bytes.NewReader(b))
if err != nil {
return errors.WithStack(err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
res := struct {
Action string `json:"Action"`
RetCode int `json:"RetCode"`
Data []string `json:"Data"`
}{}
if err = d.httpClient.Do(ctx, req, &res); err != nil {
return err
}
if res.RetCode != 0 {
err := errors.Errorf("Merak error: %d", res.RetCode)
log.Error("Failed to send notify by merak with params: %+v: %+v", string(b), err)
return err
}
return nil
}
// MerakSign is
func MerakSign(params map[string]string) (string, error) {
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
buf := bytes.Buffer{}
for _, k := range keys {
buf.WriteString(k + params[k])
}
h := sha1.New()
if _, err := h.Write(buf.Bytes()); err != nil {
return "", errors.WithStack(err)
}
if _, err := h.Write([]byte(_privateKey)); err != nil {
return "", errors.WithStack(err)
}
sum := h.Sum(nil)
return hex.EncodeToString(sum), nil
}

View File

@@ -0,0 +1,49 @@
package dao
import (
"context"
"strings"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoSendWechat(t *testing.T) {
convey.Convey("SendWechat", t, func(ctx convey.C) {
var (
content = "测试内容"
title = "测试标题"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.MerakNotify(context.Background(), content, title)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoGetMerakSign(t *testing.T) {
convey.Convey("sign", t, func(ctx convey.C) {
var (
params = map[string]string{
"Action": "CreateWechatMessage",
"PublicKey": _publicKey,
//"UserName": strings.Join(d.c.ReviewNotify.Users, ","),
"UserName": strings.Join([]string{"user1", "user2"}, ","),
"Title": "测试标题",
"Content": "测试内容",
"TreeId": "",
}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p1, err := MerakSign(params)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldEqual, "59cd4e74b225a7d326ee7d6c89bf27cf2f6015dc")
})
})
})
}

View File

@@ -0,0 +1,46 @@
package dao
import (
"context"
"net/url"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
"github.com/pkg/errors"
)
const (
_msgURL = "/api/notify/send.user.notify.do"
)
// Message send message.
func (d *Dao) Message(c context.Context, title, msg string, mids []int64) (err error) {
return d.RawMessage(c, "2_2_2", title, msg, mids)
}
// RawMessage send message with mc.
func (d *Dao) RawMessage(c context.Context, mc string, title, msg string, mids []int64) (err error) {
params := url.Values{}
params.Set("type", "json")
params.Set("source", "2")
params.Set("mc", mc)
params.Set("title", title)
params.Set("data_type", "4")
params.Set("context", msg)
params.Set("mid_list", xstr.JoinInts(mids))
var res struct {
Code int `json:"code"`
}
if err = d.httpClient.Post(c, d.msgURL, "", params, &res); err != nil {
err = errors.Wrap(err, "dao send message")
return
}
if res.Code != ecode.OK.Code() {
err = errors.Wrapf(err, "message send failed,mid(%v)", mids)
return
}
log.Info("sendmessage mc:%s, mids:%v, title:%s, msg:%s", mc, mids, title, msg)
return
}

View File

@@ -0,0 +1,45 @@
package dao
import (
"context"
"gopkg.in/h2non/gock.v1"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoMessage(t *testing.T) {
convey.Convey("Message", t, func(ctx convey.C) {
var (
c = context.Background()
title = "abc test"
msg = "abc"
mids = []int64{112}
mc = "2_2_2"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
defer gock.OffAll()
httpMock("POST", d.msgURL).Reply(200).JSON(`{"code":0}`)
err := d.RawMessage(c, mc, title, msg, mids)
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoRawMessage(t *testing.T) {
convey.Convey("RawMessage", t, func(ctx convey.C) {
var (
c = context.Background()
mc = "2_2_2"
title = "abc test"
msg = "abc test"
mids = []int64{112}
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
defer gock.OffAll()
httpMock("POST", d.msgURL).Reply(200).JSON(`{"code":0}`)
err := d.RawMessage(c, mc, title, msg, mids)
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,64 @@
package dao
import (
"context"
"go-common/app/admin/main/member/model"
"go-common/library/database/sql"
"github.com/pkg/errors"
)
const (
_monitorName = "user_monitor"
)
// Monitors is.
func (d *Dao) Monitors(ctx context.Context, mid int64, includeDeleted bool, pn, ps int) (mns []*model.Monitor, total int, err error) {
query := d.member.Table(_monitorName).Order("id DESC")
if !includeDeleted {
query = query.Where("is_deleted=?", false)
}
if mid > 0 {
query = query.Where("mid=?", mid)
}
query = query.Count(&total)
query = query.Offset((pn - 1) * ps).Limit(ps)
if err = query.Find(&mns).Error; err != nil {
if err == sql.ErrNoRows {
return []*model.Monitor{}, 0, nil
}
err = errors.Wrap(err, "monitors")
return
}
return
}
// AddMonitor is.
func (d *Dao) AddMonitor(ctx context.Context, mid int64, operator, remark string) error {
mn := &model.Monitor{
Mid: mid,
}
ups := map[string]interface{}{
"is_deleted": false,
"operator": operator,
"remark": remark,
}
if err := d.member.Table(_monitorName).Where("mid=?", mid).Assign(ups).FirstOrCreate(mn).Error; err != nil {
return errors.Wrap(err, "add monitor")
}
return nil
}
// DelMonitor is.
func (d *Dao) DelMonitor(ctx context.Context, mid int64, operator, remark string) error {
ups := map[string]interface{}{
"is_deleted": true,
"operator": operator,
"remark": remark,
}
if err := d.member.Table(_monitorName).Where("mid=?", mid).UpdateColumns(ups).Error; err != nil {
return errors.Wrap(err, "del monitor")
}
return nil
}

View File

@@ -0,0 +1,60 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoAddMonitor(t *testing.T) {
convey.Convey("AddMonitor", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(1)
operator = "aa"
remark = "aa"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.AddMonitor(c, mid, operator, remark)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoMonitors(t *testing.T) {
convey.Convey("Monitors", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(1)
pn = int(0)
ps = int(10)
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
mns, total, err := d.Monitors(c, mid, false, pn, ps)
ctx.Convey("Then err should be nil.mns,total should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldNotBeNil)
ctx.So(mns, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoDelMonitor(t *testing.T) {
convey.Convey("DelMonitor", t, func(ctx convey.C) {
var (
c = context.Background()
mid = int64(1)
operator = "aa"
remark = "aa"
)
ctx.Convey("When everything goes positive", func(ctx convey.C) {
err := d.DelMonitor(c, mid, operator, remark)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,186 @@
package dao
import (
"context"
"time"
"go-common/app/admin/main/member/model"
"github.com/jinzhu/gorm"
"github.com/pkg/errors"
)
const (
_officialName = "user_official"
_officialDocName = "user_official_doc"
_officialDocAdditName = "user_official_doc_addit"
)
// Official is.
func (d *Dao) Official(ctx context.Context, mid int64) (off *model.Official, err error) {
off = &model.Official{}
if err = d.member.Table(_officialName).Where("mid=?", mid).Find(off).Error; err != nil {
err = errors.Wrap(err, "official docs")
}
return
}
// OfficialEdit is.
func (d *Dao) OfficialEdit(ctx context.Context, mid int64, role int8, title, desc string) (off *model.Official, err error) {
off = &model.Official{
Mid: mid,
}
attrs := map[string]interface{}{
"role": role,
"title": title,
"description": desc,
}
if err = d.member.Table(_officialName).Where("mid=?", mid).Assign(attrs).FirstOrCreate(off).Error; err != nil {
err = errors.Wrap(err, "official edit")
}
return
}
// Officials is.
func (d *Dao) Officials(ctx context.Context, mid int64, roles []int8, stime, etime time.Time, pn, ps int) (offs []*model.Official, total int, err error) {
where := "role in (?) AND ctime>? AND ctime<?"
if mid > 0 {
where = "mid=? AND " + where
err = d.member.Table(_officialName).Order("ctime DESC").Offset((pn-1)*ps).Limit(ps).Where(where, mid, roles, stime, etime).Find(&offs).Error
d.member.Table(_officialName).Where(where, mid, roles, stime, etime).Count(&total)
} else {
err = d.member.Table(_officialName).Order("ctime DESC").Offset((pn-1)*ps).Limit(ps).Where(where, roles, stime, etime).Find(&offs).Error
d.member.Table(_officialName).Where(where, roles, stime, etime).Count(&total)
}
if err != nil {
err = errors.Wrap(err, "official docs")
}
return
}
// OfficialDoc is.
func (d *Dao) OfficialDoc(ctx context.Context, mid int64) (off *model.OfficialDoc, err error) {
off = &model.OfficialDoc{OfficialExtra: &model.OfficialExtra{}}
if err = d.member.Table(_officialDocName).Where("mid=?", mid).First(off).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
err = errors.Wrap(err, "official doc")
off = nil
return
}
off.ParseExtra()
return
}
// OfficialDocs is.
func (d *Dao) OfficialDocs(ctx context.Context, mid int64, roles, states []int8, uname string, stime, etime time.Time, pn, ps int) (offs []*model.OfficialDoc, total int, err error) {
query := d.member.Table(_officialDocName).Order("submit_time DESC")
if uname != "" {
query = query.Where("uname=?", uname)
}
if mid > 0 {
query = query.Where("mid=?", mid)
}
query = query.Where("role IN (?)", roles).
Where("state IN (?)", states).
Where("ctime > ?", stime).
Where("ctime < ?", etime)
query.Count(&total)
if err = query.Offset((pn - 1) * ps).Limit(ps).Find(&offs).Error; err != nil {
err = errors.Wrap(err, "official docs")
return
}
for _, od := range offs {
od.ParseExtra()
}
return
}
// OfficialDocsByMids is.
func (d *Dao) OfficialDocsByMids(ctx context.Context, mids []int64) (map[int64]*model.OfficialDoc, error) {
ofl := make([]*model.OfficialDoc, 0, len(mids))
offs := make(map[int64]*model.OfficialDoc, len(mids))
if err := d.member.Table(_officialDocName).Where("mid IN (?)", mids).Find(&ofl).Error; err != nil {
err = errors.Wrap(err, "official docs")
return nil, err
}
for _, of := range ofl {
offs[of.Mid] = of
}
for _, od := range offs {
od.ParseExtra()
}
return offs, nil
}
// OfficialDocAudit is.
func (d *Dao) OfficialDocAudit(ctx context.Context, mid int64, state int8, uname string, isInternal bool, rejectReason string) (err error) {
ups := map[string]interface{}{
"state": state,
"uname": uname,
"is_internal": isInternal,
"reject_reason": rejectReason,
}
if err = d.member.Table(_officialDocName).Where("mid=?", mid).Updates(ups).Error; err != nil {
err = errors.Wrap(err, "official doc audit")
}
return
}
// OfficialDocEdit is.
func (d *Dao) OfficialDocEdit(ctx context.Context, mid int64, name string, role, state int8, title, desc, extra string, uname string, isInternal bool) (err error) {
off := &model.OfficialDoc{
Mid: mid,
SubmitSource: "admin",
}
ups := map[string]interface{}{
"state": state,
"role": role,
"name": name,
"title": title,
"description": desc,
"extra": extra,
"uname": uname,
"is_internal": isInternal,
}
if err = d.member.Table(_officialDocName).Where("mid=?", mid).Assign(ups).FirstOrCreate(off).Error; err != nil {
err = errors.Wrap(err, "official doc audit")
}
return
}
// OfficialDocSubmit is.
func (d *Dao) OfficialDocSubmit(ctx context.Context, mid int64, name string, role, state int8, title, desc, extra string, uname string, isInternal bool, submitSource string) (err error) {
off := &model.OfficialDoc{
Mid: mid,
}
ups := map[string]interface{}{
"state": state,
"role": role,
"name": name,
"title": title,
"description": desc,
"extra": extra,
"uname": uname,
"is_internal": isInternal,
"submit_source": submitSource,
}
if err = d.member.Table(_officialDocName).Where("mid=?", mid).Assign(ups).FirstOrCreate(off).Error; err != nil {
err = errors.Wrap(err, "official doc audit")
}
return
}
// OfficialDocAddits .
func (d *Dao) OfficialDocAddits(ctx context.Context, property string, vstring string) ([]*model.OfficialDocAddit, error) {
addits := make([]*model.OfficialDocAddit, 0)
err := d.member.Table(_officialDocAdditName).Where("property=? and vstring=?", property, vstring).Find(&addits).Error
if err != nil {
err = errors.Wrap(err, "find official doc addit error")
return nil, err
}
return addits, nil
}

View File

@@ -0,0 +1,80 @@
package dao
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestOfficial(t *testing.T) {
convey.Convey("Official", t, func() {
o, err := d.Official(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
})
}
func TestOfficialEdit(t *testing.T) {
convey.Convey("OfficialEdit", t, func() {
o, err := d.OfficialEdit(context.Background(), 123, 1, "title", "desc")
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
})
}
func TestOfficials(t *testing.T) {
convey.Convey("Officials", t, func() {
o, total, err := d.Officials(context.Background(), 123, []int8{1}, time.Time{}, time.Now(), 1, 20)
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(total, convey.ShouldBeGreaterThan, 0)
})
}
func TestOfficialDoc(t *testing.T) {
convey.Convey("OfficialDoc", t, func() {
o, err := d.OfficialDoc(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
})
}
func TestOfficialDocs(t *testing.T) {
convey.Convey("OfficialDocs", t, func() {
o, total, err := d.OfficialDocs(context.Background(), 123, []int8{1}, []int8{1}, "", time.Time{}, time.Now(), 1, 20)
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(total, convey.ShouldBeGreaterThan, 0)
})
}
func TestOfficialDocAudit(t *testing.T) {
convey.Convey("OfficialDocAudit", t, func() {
err := d.OfficialDocAudit(context.Background(), 123, 1, "guan", true, "")
convey.So(err, convey.ShouldBeNil)
})
}
func TestOfficialDocEdit(t *testing.T) {
convey.Convey("OfficialDocEdit", t, func() {
err := d.OfficialDocEdit(context.Background(), 123, "guan", 1, 1, "title", "desc", "extra", "12121", true)
convey.So(err, convey.ShouldBeNil)
})
}
func TestOfficialDocsByMids(t *testing.T) {
convey.Convey("OfficialDocsByMids", t, func() {
res, err := d.OfficialDocsByMids(context.Background(), []int64{2, 3, 10, 12})
convey.So(err, convey.ShouldBeNil)
convey.So(len(res), convey.ShouldBeGreaterThan, 0)
})
}
func TestOfficialDocSubmit(t *testing.T) {
convey.Convey("OfficialDocSubmit", t, func() {
err := d.OfficialDocSubmit(context.Background(), 123, "guan", 1, 1, "title", "desc", "extra", "12121", true, "admin")
convey.So(err, convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,119 @@
package dao
import (
"context"
"go-common/app/admin/main/member/model"
"net/url"
"strconv"
"sync"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/xstr"
)
const (
_updateUname = "/intranet/acc/updateUname"
_queryByMids = "/intranet/acc/queryByMids"
)
// UpdateUname is.
func (d *Dao) UpdateUname(ctx context.Context, mid int64, name string) error {
ip := metadata.String(ctx, metadata.RemoteIP)
params := url.Values{}
params.Set("mid", strconv.FormatInt(mid, 10))
params.Set("uname", name)
var res struct {
Code int `json:"code"`
}
if err := d.passportClient.Post(ctx, d.upUnameURL, ip, params, &res); err != nil {
return err
}
if res.Code != 0 {
log.Error("Failed to update uname(%+v) code(%+v)", params, res.Code)
return parsePassportEcode(res.Code)
}
return nil
}
func parsePassportEcode(pecode int) error {
switch pecode {
case -618:
return ecode.UpdateUnameRepeated
case -617:
return ecode.UpdateUnameHadLocked
case -601:
return ecode.UpdateUnameFormat
}
log.Error("Unrecognized passport ecode: %d", pecode)
return ecode.Int(pecode)
}
// PassportQueryByMids is.
func (d *Dao) PassportQueryByMids(ctx context.Context, mids []int64) (map[int64]*model.PassportQueryByMidResult, error) {
ip := metadata.String(ctx, metadata.RemoteIP)
params := url.Values{}
params.Set("mids", xstr.JoinInts(mids))
var res struct {
Code int `json:"code"`
Cards map[string]*model.PassportQueryByMidResult `json:"cards"`
}
if err := d.passportClient.Get(ctx, d.queryByMidsURL, ip, params, &res); err != nil {
return nil, err
}
if res.Code != 0 {
log.Error("Failed to QueryByMid(%+v) code(%+v)", params, res.Code)
return nil, ecode.Int(res.Code)
}
result := make(map[int64]*model.PassportQueryByMidResult, len(res.Cards))
for _, card := range res.Cards {
result[card.Mid] = card
}
return result, nil
}
// PassportQueryByMidsChunked is
func (d *Dao) PassportQueryByMidsChunked(ctx context.Context, mids []int64, chunkSize int) (map[int64]*model.PassportQueryByMidResult, error) {
chunkedMids := func() [][]int64 {
divided := make([][]int64, 0)
for i := 0; i < len(mids); i += chunkSize {
end := i + chunkSize
if end > len(mids) {
end = len(mids)
}
divided = append(divided, mids[i:end])
}
return divided
}()
lock := sync.Mutex{}
result := make(map[int64]*model.PassportQueryByMidResult, len(mids))
wg := sync.WaitGroup{}
for _, chunk := range chunkedMids {
wg.Add(1)
go func(chunk []int64) {
defer wg.Done()
res, err := d.PassportQueryByMids(ctx, chunk)
if err != nil {
log.Error("Failed to get passport query by mids: %+v: %+v", chunk, err)
return
}
lock.Lock()
for k, v := range res {
result[k] = v
}
lock.Unlock()
}(chunk)
}
wg.Wait()
return result, nil
}

View File

@@ -0,0 +1,58 @@
package dao
import (
"context"
"fmt"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
"gopkg.in/h2non/gock.v1"
)
func TestDaoUpdateUname(t *testing.T) {
convey.Convey("UpdateUname", t, func(ctx convey.C) {
var (
mid = int64(321)
name = fmt.Sprintf("321testName%v%v", time.Now().Minute(), time.Now().Second())
)
ctx.Convey("UpdateUname success", func(ctx convey.C) {
defer gock.OffAll()
httpMock("POST", d.upUnameURL).Reply(200).JSON(`{"code":0}`)
err := d.UpdateUname(context.Background(), mid, name)
ctx.So(err, convey.ShouldBeNil)
})
ctx.Convey("UpdateUname failed", func(ctx convey.C) {
defer gock.OffAll()
httpMock("POST", d.upUnameURL).Reply(200).JSON(`{"code":500}`)
err := d.UpdateUname(context.Background(), mid, name)
ctx.So(err, convey.ShouldNotBeNil)
})
})
}
func TestDaoPassportQueryByMids(t *testing.T) {
convey.Convey("PassportQueryByMids", t, func(ctx convey.C) {
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p, err := d.PassportQueryByMids(context.Background(), []int64{1, 2, 3})
ctx.Convey("Then err should be nil.p should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoPassportQueryByMidsChunked(t *testing.T) {
convey.Convey("PassportQueryByMidsChunked", t, func(ctx convey.C) {
ctx.Convey("When everything goes positive", func(ctx convey.C) {
p, err := d.PassportQueryByMidsChunked(context.Background(), []int64{1, 2, 3}, 50)
ctx.Convey("Then err should be nil.p should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(p, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,500 @@
package dao
import (
"context"
"encoding/base64"
"fmt"
"strings"
"time"
"go-common/app/admin/main/member/model"
"go-common/library/cache/memcache"
"go-common/library/log"
"github.com/pkg/errors"
)
var (
_reasonKey = "reason"
)
func realnameImageKey(IMGData string) string {
return fmt.Sprintf("realname_image_%s", IMGData)
}
// RealnameInfo is.
func (d *Dao) RealnameInfo(ctx context.Context, mid int64) (info *model.DBRealnameInfo, err error) {
info = &model.DBRealnameInfo{}
if err = d.memberRead.Table(info.TableName()).Where("mid = ?", mid).First(&info).Error; err != nil {
info = nil
err = errors.WithStack(err)
}
return
}
// BatchRealnameInfo is
func (d *Dao) BatchRealnameInfo(ctx context.Context, mids []int64) (map[int64]*model.DBRealnameInfo, error) {
find := make([]*model.DBRealnameInfo, 0, len(mids))
db := d.memberRead.Table("realname_info").Where("mid IN (?)", mids).Where("status=?", model.RealnameApplyStatePassed.DBStatus()).Find(&find)
if err := db.Error; err != nil {
return nil, err
}
result := make(map[int64]*model.DBRealnameInfo, len(find))
for _, ri := range find {
result[ri.MID] = ri
}
return result, nil
}
// RealnameInfoByCardMD5 is.
func (d *Dao) RealnameInfoByCardMD5(ctx context.Context, cardMD5 string, state int, channel uint8) (infos []*model.DBRealnameInfo, err error) {
infos = make([]*model.DBRealnameInfo, 0)
var info *model.DBRealnameInfo
db := d.memberRead.Table(info.TableName()).Where("card_md5 = ?", cardMD5).Where("channel = ?", channel)
if state >= 0 {
db = db.Where("status = ?", state)
}
if err = db.Find(&infos).Error; err != nil {
if !db.RecordNotFound() {
err = errors.Wrapf(err, "RealnameInfoByCardMD5")
return
}
err = nil
return
}
return
}
// UpdateRealnameAlipayApply is.
func (d *Dao) UpdateRealnameAlipayApply(ctx context.Context, id int64, adminID int64, adminName string, state int, reason string) (err error) {
var (
mld *model.DBRealnameAlipayApply
ups = map[string]interface{}{
"operator": adminName,
"operator_id": adminID,
"operator_time": time.Now(),
"status": state,
"reason": reason,
}
)
if err = d.member.Table(mld.TableName()).Where("id = ?", id).Updates(ups).Error; err != nil {
err = errors.Wrapf(err, "UpdateRealnameAlipayApply")
}
return
}
// UpdateRealnameInfo is.
func (d *Dao) UpdateRealnameInfo(ctx context.Context, mid int64, state int, reason string) (err error) {
var (
mld *model.DBRealnameInfo
ups = map[string]interface{}{
"status": state,
"reason": reason,
}
)
if err = d.member.Table(mld.TableName()).Where("mid = ?", mid).Updates(ups).Error; err != nil {
err = errors.Wrapf(err, "UpdateRealnameInfo")
}
return
}
// RealnameMainApply is.
func (d *Dao) RealnameMainApply(ctx context.Context, id int64) (apply *model.DBRealnameApply, err error) {
apply = &model.DBRealnameApply{
Status: model.RealnameApplyStateNone.DBStatus(),
}
if err = d.memberRead.Table(apply.TableName()).Where("id = ?", id).First(&apply).Error; err != nil {
err = errors.WithStack(err)
}
return
}
// RealnameAlipayApply is.
func (d *Dao) RealnameAlipayApply(ctx context.Context, id int64) (apply *model.DBRealnameAlipayApply, err error) {
apply = &model.DBRealnameAlipayApply{
Status: model.RealnameApplyStateNone.DBStatus(),
}
if err = d.memberRead.Table(apply.TableName()).Where("id = ?", id).First(&apply).Error; err != nil {
err = errors.WithStack(err)
}
return
}
// RealnameMainList is.
func (d *Dao) RealnameMainList(ctx context.Context, mids []int64, cardType int, country int, opName string, tsFrom, tsTo int64, state int, pn, ps int, isDesc bool) (list []*model.DBRealnameApply, total int, err error) {
var (
mdl *model.DBRealnameApply
)
db := d.memberRead.Table(mdl.TableName())
if state >= 0 {
db = db.Where("status = ?", state)
}
if len(mids) > 0 {
db = db.Where("mid in (?)", mids)
}
if cardType >= 0 {
db = db.Where("card_type = ?", cardType)
}
if country >= 0 {
db = db.Where("country = ?", country)
}
if opName != "" {
db = db.Where("operator = ?", opName)
}
if tsTo > 0 {
timeTo := time.Unix(tsTo, 0)
db = db.Where("mtime <= ?", timeTo)
}
if tsFrom > 0 {
timeFrom := time.Unix(tsFrom, 0)
db = db.Where("mtime >= ?", timeFrom)
}
if err = db.Count(&total).Error; err != nil {
err = errors.Wrapf(err, "realname apply list count")
return
}
mtimeSort := "mtime ASC"
if isDesc {
mtimeSort = "mtime DESC"
}
db = db.Order(mtimeSort).Offset((pn - 1) * ps).Limit(ps)
if err = db.Find(&list).Error; err != nil {
if !db.RecordNotFound() {
err = errors.Wrapf(err, "realname apply list")
return
}
err = nil
return
}
return
}
// RealnameAlipayList is.
func (d *Dao) RealnameAlipayList(ctx context.Context, mids []int64, tsFrom, tsTo int64, state int, pn, ps int, isDesc bool) (list []*model.DBRealnameAlipayApply, total int, err error) {
var (
mdl *model.DBRealnameAlipayApply
)
db := d.memberRead.Table(mdl.TableName())
if state >= 0 {
db = db.Where("status = ?", state)
}
if len(mids) > 0 {
db = db.Where("mid in (?)", mids)
}
if tsTo > 0 {
timeTo := time.Unix(tsTo, 0)
db = db.Where("mtime <= ?", timeTo)
}
if tsFrom > 0 {
timeFrom := time.Unix(tsFrom, 0)
db = db.Where("mtime >= ?", timeFrom)
}
if err = db.Count(&total).Error; err != nil {
err = errors.Wrapf(err, "realname apply list count")
return
}
mtimeSort := "mtime ASC"
if isDesc {
mtimeSort = "mtime DESC"
}
db = db.Order(mtimeSort).Offset((pn - 1) * ps).Limit(ps)
if err = db.Find(&list).Error; err != nil {
if !db.RecordNotFound() {
err = errors.Wrapf(err, "realname apply list")
return
}
err = nil
return
}
return
}
// RealnameSearchCards is.
func (d *Dao) RealnameSearchCards(ctx context.Context, cardMD5s []string) (list []*model.DBRealnameInfo, err error) {
var (
mdl *model.DBRealnameInfo
)
db := d.memberRead.Table(mdl.TableName()).Where("card_md5 in (?)", cardMD5s)
if err = db.Find(&list).Error; err != nil {
if !db.RecordNotFound() {
err = errors.Wrapf(err, "realname apply list")
return
}
err = nil
return
}
return
}
// UpdateRealnameMainApply .
func (d *Dao) UpdateRealnameMainApply(ctx context.Context, id int, state int, opname string, opid int64, optime time.Time, remark string) (err error) {
var (
mld *model.DBRealnameApply
ups = map[string]interface{}{
"operator": opname,
"operator_id": opid,
"operator_time": optime,
"remark": remark,
"remark_status": 1,
"status": state,
}
)
if err = d.member.Table(mld.TableName()).Where("id = ?", id).Updates(ups).Error; err != nil {
err = errors.Wrapf(err, "UpdateRealnameApply")
}
return
}
// UpdateOldRealnameApply .
func (d *Dao) UpdateOldRealnameApply(ctx context.Context, id int64, state int, opname string, opid int64, optime time.Time, remark string) (err error) {
var (
ups = map[string]interface{}{
"operater": opname,
// "operator_id": opid,
"operater_time": optime.Unix(),
"remark": remark,
"remark_status": 1,
"status": state,
}
)
if err = d.account.Table("dede_identification_card_apply").Where("id = ?", id).Updates(ups).Error; err != nil {
err = errors.Wrapf(err, "UpdateOldRealnameApply")
}
return
}
// RealnameApplyIMG .
func (d *Dao) RealnameApplyIMG(ctx context.Context, ids []int64) (imgMap map[int64]*model.DBRealnameApplyIMG, err error) {
var (
db = d.memberRead.Where("id in (?)", ids)
list []*model.DBRealnameApplyIMG
)
imgMap = make(map[int64]*model.DBRealnameApplyIMG)
if err = db.Find(&list).Error; err != nil {
if !db.RecordNotFound() {
err = errors.Wrapf(err, "RealnameApplyIMG")
return
}
err = nil
}
for _, l := range list {
imgMap[l.ID] = l
}
return
}
// RealnameApplyCount .
func (d *Dao) RealnameApplyCount(ctx context.Context, mid int64) (count int, err error) {
var (
ml *model.DBRealnameApply
aml *model.DBRealnameAlipayApply
tempCount int
db = d.memberRead.Table(ml.TableName()).Where("mid = ?", mid)
db2 = d.memberRead.Table(aml.TableName()).Where("mid = ?", mid)
)
if err = db.Count(&tempCount).Error; err != nil {
err = errors.WithStack(err)
return
}
count += tempCount
if err = db2.Count(&tempCount).Error; err != nil {
err = errors.WithStack(err)
return
}
count += tempCount
return
}
// RealnameReasonList .
func (d *Dao) RealnameReasonList(ctx context.Context) (list []string, total int, err error) {
var (
conf = &model.DBRealnameConfig{}
db = d.memberRead.Where("`key` = ?", _reasonKey).First(&conf)
)
if err = db.Error; err != nil {
if !db.RecordNotFound() {
err = errors.Wrapf(err, "RealnameReasonList")
return
}
err = nil
}
list = decodeReason(conf.Data)
total = len(list)
return
}
// UpdateRealnameReason .
func (d *Dao) UpdateRealnameReason(ctx context.Context, list []string) (err error) {
var (
conf *model.DBRealnameConfig
ups = map[string]interface{}{
"data": encodeReason(list),
}
)
if err = d.member.Table(conf.TableName()).Where("`key` = ?", _reasonKey).Updates(ups).Error; err != nil {
err = errors.Wrapf(err, "UpdateRealnameReason")
}
return
}
func encodeReason(list []string) (data string) {
raw := []byte(strings.Join(list, "(#=_=#)"))
return base64.StdEncoding.EncodeToString(raw)
}
func decodeReason(data string) (list []string) {
var (
raw []byte
err error
)
if raw, err = base64.StdEncoding.DecodeString(data); err != nil {
err = errors.WithStack(err)
log.Error("%+v", err)
return
}
list = strings.Split(string(raw), "(#=_=#)")
return
}
// AddRealnameIMG is
func (d *Dao) AddRealnameIMG(ctx context.Context, img *model.DBRealnameApplyIMG) error {
if err := d.member.Create(img).Error; err != nil {
return errors.WithStack(err)
}
return nil
}
// AddRealnameApply is
func (d *Dao) AddRealnameApply(ctx context.Context, apply *model.DBRealnameApply) error {
if err := d.member.Create(apply).Error; err != nil {
return errors.WithStack(err)
}
return nil
}
// SubmitRealnameInfo is
func (d *Dao) SubmitRealnameInfo(ctx context.Context, info *model.DBRealnameInfo) error {
ups := map[string]interface{}{
"channel": info.Channel,
"realname": info.Realname,
"country": info.Country,
"card_type": info.CardType,
"card": info.Card,
"card_md5": info.CardMD5,
"status": info.Status,
"reason": info.Reason,
}
if err := d.member.Table(info.TableName()).Where("mid=?", info.MID).Assign(ups).FirstOrCreate(info).Error; err != nil {
return errors.WithStack(err)
}
return nil
}
// GetRealnameImageCache is
func (d *Dao) GetRealnameImageCache(ctx context.Context, IMGData string) ([]byte, error) {
key := realnameImageKey(IMGData)
conn := d.memcache.Get(ctx)
defer conn.Close()
item, err := conn.Get(key)
if err != nil {
return nil, err
}
out := []byte{}
if err := conn.Scan(item, &out); err != nil {
return nil, err
}
return out, nil
}
// SetRealnameImageCache is
func (d *Dao) SetRealnameImageCache(ctx context.Context, IMGData string, data []byte) error {
key := realnameImageKey(IMGData)
conn := d.memcache.Get(ctx)
defer conn.Close()
return conn.Set(&memcache.Item{
Key: key,
Value: data,
Flags: memcache.FlagRAW | memcache.FlagGzip,
Expiration: 6 * 3600, // cache for 6 hours
})
}
// RecentRealnameApplyImg is
func (d *Dao) RecentRealnameApplyImg(ctx context.Context, duration time.Duration) ([]*model.DBRealnameApplyIMG, error) {
from := time.Now().Add(-duration)
result := []*model.DBRealnameApplyIMG{}
db := d.memberRead.Where("mtime>=?", from).Order("id desc").Find(&result)
if err := db.Error; err != nil {
return nil, err
}
return result, nil
}
// RejectRealnameMainApply is
func (d *Dao) RejectRealnameMainApply(ctx context.Context, mid int64, opname string, opid int64, remark string) (err error) {
var (
mld *model.DBRealnameApply
ups = map[string]interface{}{
"operator": opname,
"operator_id": opid,
"operator_time": time.Now(),
"remark": remark,
"remark_status": 1,
"status": model.RealnameApplyStateRejective.DBStatus(),
}
)
if err = d.member.Table(mld.TableName()).
Where("mid = ?", mid).
Where("status = ?", model.RealnameApplyStatePassed.DBStatus()).
Updates(ups).Error; err != nil {
err = errors.Wrapf(err, "RejectRealnameMainApply")
}
return
}
// RejectRealnameAlipayApply is
func (d *Dao) RejectRealnameAlipayApply(ctx context.Context, mid int64, opname string, opid int64, reason string) (err error) {
var (
mld *model.DBRealnameAlipayApply
ups = map[string]interface{}{
"operator": opname,
"operator_id": opid,
"operator_time": time.Now(),
"status": model.RealnameApplyStateRejective.DBStatus(),
"reason": reason,
}
)
if err = d.member.Table(mld.TableName()).
Where("mid = ?", mid).
Where("status = ?", model.RealnameApplyStatePassed.DBStatus()).
Updates(ups).Error; err != nil {
err = errors.Wrapf(err, "UpdateRealnameAlipayApply")
}
return
}
// LastPassedRealnameMainApply is
func (d *Dao) LastPassedRealnameMainApply(ctx context.Context, mid int64) (*model.DBRealnameApply, error) {
apply := &model.DBRealnameApply{}
if err := d.member.Table("realname_apply").Where("mid=?", mid).
Where("status=?", model.RealnameApplyStatePassed.DBStatus()).
Order("id DESC").Limit(1).Last(apply).Error; err != nil {
return nil, err
}
return apply, nil
}
// LastPassedRealnameAlipayApply is
func (d *Dao) LastPassedRealnameAlipayApply(ctx context.Context, mid int64) (*model.DBRealnameAlipayApply, error) {
apply := &model.DBRealnameAlipayApply{}
if err := d.member.Table("realname_alipay_apply").Where("mid=?", mid).
Where("status=?", model.RealnameApplyStatePassed.DBStatus()).
Order("id DESC").Limit(1).Last(apply).Error; err != nil {
return nil, err
}
return apply, nil
}

View File

@@ -0,0 +1,25 @@
package dao
import (
"context"
"go-common/app/admin/main/member/model"
"github.com/pkg/errors"
)
// AddOldRealnameIMG is
func (d *Dao) AddOldRealnameIMG(ctx context.Context, img *model.DeDeIdentificationCardApplyImg) error {
if err := d.account.Create(img).Error; err != nil {
return errors.WithStack(err)
}
return nil
}
// AddOldRealnameApply is
func (d *Dao) AddOldRealnameApply(ctx context.Context, apply *model.DeDeIdentificationCardApply) error {
if err := d.account.Create(apply).Error; err != nil {
return errors.WithStack(err)
}
return nil
}

View File

@@ -0,0 +1,169 @@
package dao
import (
"context"
"testing"
"time"
"go-common/app/admin/main/member/model"
. "github.com/smartystreets/goconvey/convey"
)
func TestRealnameList(t *testing.T) {
Convey("Realname list", t, func() {
var (
mids = []int64{46333}
cardType = 0
country = 0
opName = ""
tsFrom = int64(1528023084)
tsTo = time.Now().Unix()
state = 2
pn, ps = 1, 20
)
list, total, err := d.RealnameMainList(context.Background(), mids, cardType, country, opName, tsFrom, tsTo, state, pn, ps, false)
So(err, ShouldBeNil)
So(list, ShouldNotBeNil)
So(total, ShouldNotBeNil)
})
}
func TestRealnameReason(t *testing.T) {
Convey("Realname reason", t, func() {
var (
list = []string{
"+1s",
"蛤",
"苟利国家",
"19260817",
}
)
err := d.UpdateRealnameReason(context.Background(), list)
So(err, ShouldBeNil)
list2, total, err := d.RealnameReasonList(context.Background())
So(err, ShouldBeNil)
So(total, ShouldEqual, len(list))
So(list2, ShouldResemble, list)
})
}
func TestRealnameApplyCount(t *testing.T) {
Convey("Realname apply count", t, func() {
var (
mid = int64(1)
)
count, err := d.RealnameApplyCount(context.Background(), mid)
So(err, ShouldBeNil)
So(count, ShouldNotBeNil)
})
}
func TestRealnameApply(t *testing.T) {
Convey("Realname apply", t, func() {
var (
id = int64(1)
)
apply, err := d.RealnameMainApply(context.Background(), id)
So(err, ShouldBeNil)
So(apply, ShouldNotBeNil)
})
}
func TestRealnameApplyUpdate(t *testing.T) {
Convey("Realname apply update", t, func() {
var (
id = 1
state = 2
opname = "ut"
opid = int64(233)
optime = time.Now()
remark = "ut_reason"
)
err := d.UpdateRealnameMainApply(context.Background(), id, state, opname, opid, optime, remark)
So(err, ShouldBeNil)
})
}
func TestRealnameAlipayApply(t *testing.T) {
Convey("Realname alipay apply", t, func() {
var (
id = int64(1)
)
apply, err := d.RealnameAlipayApply(context.Background(), id)
So(err, ShouldBeNil)
t.Log(apply)
})
}
func TestRealnameUpdateAlipayApply(t *testing.T) {
Convey("Realname update alipay apply", t, func() {
var (
id = int64(1)
)
err := d.UpdateRealnameAlipayApply(context.Background(), id, 1, "someone", 2, "ut")
So(err, ShouldBeNil)
})
}
func TestUpdateRealnameInfo(t *testing.T) {
Convey("Realname update realname info", t, func() {
var (
id = int64(1)
)
err := d.UpdateRealnameInfo(context.Background(), id, 2, "ut")
So(err, ShouldBeNil)
})
}
func TestAddRealnameIMG(t *testing.T) {
Convey("AddRealnameIMG", t, func() {
err := d.AddRealnameIMG(context.Background(), &model.DBRealnameApplyIMG{IMGData: "testing"})
So(err, ShouldBeNil)
})
}
func TestAddRealnameApply(t *testing.T) {
Convey("AddRealnameApply", t, func() {
err := d.AddRealnameApply(context.Background(), &model.DBRealnameApply{MID: 1})
So(err, ShouldBeNil)
})
}
func TestBatchRealnameInfo(t *testing.T) {
Convey("BatchRealnameInfo", t, func() {
res, err := d.BatchRealnameInfo(context.Background(), []int64{1, 2, 3})
So(res, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}
func TestRejectRealnameMainApply(t *testing.T) {
Convey("RejectRealnameMainApply", t, func() {
err := d.RejectRealnameMainApply(context.Background(), 1, "admin", 1, "test")
So(err, ShouldBeNil)
})
}
func TestRejectRealnameAlipayApply(t *testing.T) {
Convey("RejectRealnameAlipayApply", t, func() {
err := d.RejectRealnameAlipayApply(context.Background(), 1, "admin", 1, "test")
So(err, ShouldBeNil)
})
}
func TestAddOldRealnameIMG(t *testing.T) {
Convey("AddOldRealnameIMG", t, func() {
err := d.AddOldRealnameIMG(context.Background(), &model.DeDeIdentificationCardApplyImg{IMGData: "/test"})
So(err, ShouldBeNil)
})
}
func TestAddOldRealnameApply(t *testing.T) {
Convey("AddOldRealnameApply", t, func() {
err := d.AddOldRealnameApply(context.Background(), &model.DeDeIdentificationCardApply{MID: 1})
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,46 @@
package dao
import (
"context"
"fmt"
"time"
"go-common/library/cache/redis"
"go-common/library/log"
"go-common/library/net/ip"
)
func reviewAuditNotifyLockKey(t time.Time) string {
prefix := t.Format("2006-01-02-15")
if t.Minute() < 30 {
return fmt.Sprintf("review_notify_%s_00", prefix)
}
return fmt.Sprintf("review_notify_%s_30", prefix)
}
func (d *Dao) pingRedis(c context.Context) error {
conn := d.redis.Get(c)
defer conn.Close()
_, err := conn.Do("SET", "ping", "pong")
return err
}
// TryLockReviewNotify is
func (d *Dao) TryLockReviewNotify(c context.Context, t time.Time) (bool, error) {
key := reviewAuditNotifyLockKey(t)
conn := d.redis.Get(c)
defer conn.Close()
locked, err := redis.Bool(conn.Do("SETNX", key, fmt.Sprintf("%s::%s", ip.InternalIP(), t)))
if err != nil {
return false, err
}
if !locked {
return false, nil
}
if _, err := conn.Do("EXPIRE", key, 60*60); err != nil {
log.Error("Failed to set expire on key: %s: %+v", key, err)
// return
}
return locked, nil
}

View File

@@ -0,0 +1,47 @@
package dao
import (
"context"
"testing"
"time"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoTryLockReviewNotify(t *testing.T) {
convey.Convey("TryLockReviewNotify", t, func() {
now := time.Now()
no := time.Date(2018, 1, 7, 20, now.Minute(), now.Second(), 651387237, time.UTC)
p1, err := d.TryLockReviewNotify(context.Background(), no)
convey.So(err, convey.ShouldBeNil)
convey.So(p1, convey.ShouldNotBeNil)
})
}
//func TestDaoreviewAuditNotifyLockKey(t *testing.T) {
// convey.Convey("reviewAuditNotifyLockKey", t, func(ctx convey.C) {
// var (
// no = time.Now()
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// p1 := reviewAuditNotifyLockKey(no)
// ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
// ctx.So(p1, convey.ShouldNotBeNil)
// })
// })
// })
//}
//
//func TestDaopingRedis(t *testing.T) {
// convey.Convey("pingRedis", t, func(ctx convey.C) {
// var (
// c = context.Background()
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// err := d.pingRedis(c)
// ctx.Convey("Then err should be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// })
// })
// })
//}

View File

@@ -0,0 +1,238 @@
package dao
import (
"context"
"fmt"
"go-common/app/admin/main/member/model"
"go-common/library/database/sql"
xtime "go-common/library/time"
"github.com/pkg/errors"
)
const (
_reviewName = "user_property_review"
)
// Reviews is. todo delete
func (d *Dao) Reviews(ctx context.Context, mid int64, property []int8, state []int8, isMonitor, isDesc bool, operator string, stime, etime xtime.Time, pn, ps int) ([]*model.UserPropertyReview, int, error) {
from, to, err := d.prepareReviewRange(ctx, stime, etime)
if err != nil {
return nil, 0, err
}
order := "asc"
if isDesc {
order = "desc"
}
rws := []*model.UserPropertyReview{}
total := 0
query := d.member.Table(_reviewName).
Order("id "+order).
Where("id>=?", from).
Where("id<=?", to).
Where("is_monitor=?", isMonitor)
if mid > 0 {
query = query.Where("mid=?", mid)
}
if len(property) > 0 {
query = query.Where("property IN (?)", property)
}
if operator != "" {
query = query.Where("operator=?", operator)
}
if len(state) > 0 {
query = query.Where("state IN (?)", state)
}
query = query.Count(&total)
query = query.Offset((pn - 1) * ps).Limit(ps)
if err := query.Find(&rws).Error; err != nil {
if err == sql.ErrNoRows {
return []*model.UserPropertyReview{}, 0, nil
}
err = errors.Wrap(err, "reviews")
return nil, 0, err
}
for _, rw := range rws {
if rw.Property == model.ReviewPropertyFace {
rw.BuildFaceURL()
}
}
return rws, total, nil
}
func (d *Dao) prepareReviewRange(ctx context.Context, stime, etime xtime.Time) (int64, int64, error) {
// from id
rw := &model.UserPropertyReview{}
if err := d.member.Table(_reviewName).
Select("id").
Where("ctime>?", stime).
Order("ctime asc").
Limit(1).
Find(rw).Error; err != nil {
return 0, 0, err
}
from := rw.ID
// to id
rw = &model.UserPropertyReview{}
if err := d.member.Table(_reviewName).
Select("id").
Where("ctime<?", etime).
Order("ctime desc").
Limit(1).
Find(rw).Error; err != nil {
return 0, 0, err
}
to := rw.ID
return from, to, nil
}
// ReviewAudit is.
func (d *Dao) ReviewAudit(ctx context.Context, id []int64, state int8, remark, operator string) error {
ups := map[string]interface{}{
"state": state,
"remark": remark,
"operator": operator,
}
if err := d.member.Table(_reviewName).Where("id IN (?)", id).Where("state=?", model.ReviewStateWait).UpdateColumns(ups).Error; err != nil {
return errors.Wrap(err, "review audit")
}
return nil
}
// Review is.
func (d *Dao) Review(ctx context.Context, id int64) (*model.UserPropertyReview, error) {
r := &model.UserPropertyReview{}
if err := d.member.Table(_reviewName).Where("id=?", id).First(r).Error; err != nil {
return nil, err
}
return r, nil
}
// ReviewByIDs is.
func (d *Dao) ReviewByIDs(ctx context.Context, ids []int64, state []int8) ([]*model.UserPropertyReview, error) {
rws := []*model.UserPropertyReview{}
query := d.member.Table(_reviewName).Where("id IN (?)", ids)
if len(state) > 0 {
query = query.Where("state in (?)", state)
}
if err := query.Find(&rws).Error; err != nil {
if err == sql.ErrNoRows {
return []*model.UserPropertyReview{}, nil
}
err = errors.Wrap(err, "review by ids")
return nil, err
}
return rws, nil
}
// UpdateReviewFace is.
func (d *Dao) UpdateReviewFace(ctx context.Context, id int64, face string) error {
ups := map[string]interface{}{
"new": face,
}
if err := d.member.Table(_reviewName).
Where("id=?", id).
Where("property=?", model.ReviewPropertyFace).
Update(ups).Error; err != nil {
return errors.WithStack(err)
}
return nil
}
// MvArchivedFaceToPriv mvArchivedFaceToPriv.
func (d *Dao) MvArchivedFaceToPriv(ctx context.Context, face, privFace, operator, remark string) error {
ups := map[string]interface{}{
"new": privFace,
"remark": fmt.Sprintf("将归档图片移动到新bucket, face: %s,remark: %s,operator: %s", face, remark, operator),
}
if err := d.member.Table(_reviewName).
Where("new=?", face).
Where("property=?", model.ReviewPropertyFace).
Where("state=?", model.ReviewStateArchived).
Where("is_monitor=?", false).
Update(ups).Error; err != nil {
return errors.WithStack(err)
}
return nil
}
// IncrFaceReject incrFaceReject.
func (d *Dao) IncrFaceReject(ctx context.Context, mid int64) error {
if err := d.member.Exec("INSERT INTO user_addit (mid,face_reject)VALUES(?,1) ON DUPLICATE KEY UPDATE face_reject=face_reject+1", mid).Error; err != nil {
return errors.Wrapf(err, "mid: %d", mid)
}
return nil
}
// IncrViolationCount is.
func (d *Dao) IncrViolationCount(ctx context.Context, mid int64) error {
if err := d.member.Exec("INSERT INTO user_addit (mid,violation_count)VALUES(?,1) ON DUPLICATE KEY UPDATE violation_count=violation_count+1", mid).Error; err != nil {
return errors.Wrapf(err, "mid: %d", mid)
}
return nil
}
// FaceAutoPass is.
func (d *Dao) FaceAutoPass(ctx context.Context, ids []int64, etime xtime.Time) (err error) {
ups := map[string]interface{}{
"operator": "system/auto",
"remark": "48 小时未处理自动通过",
"state": model.ReviewStatePass,
}
if err = d.member.Table(_reviewName).
Where("state in (?)", []int8{model.ReviewStateWait, model.ReviewStateQueuing}).
Where("id in (?)", ids).
Where("mtime<?", etime).
Update(ups).Error; err != nil {
err = errors.WithStack(err)
return
}
return
}
// UpdateRemark is.
func (d *Dao) UpdateRemark(ctx context.Context, id int64, remark string) (err error) {
ups := map[string]interface{}{
"remark": remark,
}
if err := d.member.Table(_reviewName).
Where("id=?", id).
Update(ups).Error; err != nil {
return errors.WithStack(err)
}
return nil
}
//QueuingFaceReviewsByTime is.
func (d *Dao) QueuingFaceReviewsByTime(c context.Context, stime, etime xtime.Time) ([]*model.UserPropertyReview, error) {
rws := []*model.UserPropertyReview{}
if err := d.member.Table(_reviewName).
Where("ctime>? ", stime).
Where("ctime<? ", etime).
Where("property=?", model.ReviewPropertyFace).
Where("state=?", model.ReviewStateQueuing).
Find(&rws).Error; err != nil {
return nil, err
}
return rws, nil
}
//AuditQueuingFace is
func (d *Dao) AuditQueuingFace(c context.Context, id int64, remark string, state int8) error {
ups := map[string]interface{}{
"remark": remark,
"state": state,
"operator": "system",
}
if err := d.member.Table(_reviewName).
Where("id=?", id).
Where("state=?", model.ReviewStateQueuing).
Update(ups).Error; err != nil {
return errors.WithStack(err)
}
return nil
}

View File

@@ -0,0 +1,115 @@
package dao
import (
"context"
"testing"
"time"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
func TestDao_MvArchivedFaceToPri(t *testing.T) {
Convey("MvArchivedFaceToPri", t, func() {
err := d.MvArchivedFaceToPriv(context.Background(), "/bfs/face/7e68723b9d3664ac3773c1f3c26d5e2bfabc0f23.jpg", "/bfs/face/7e68723b9d3664ac3773c1f3c26d5e2bfabc0f21.jpg", "sys", "")
So(err, ShouldBeNil)
})
}
func TestDao_IncrFaceReject(t *testing.T) {
Convey("IncrFaceReject", t, func() {
err := d.IncrFaceReject(context.Background(), 2)
So(err, ShouldBeNil)
})
}
func TestDao_IncrViolationCount(t *testing.T) {
Convey("IncrViolationCount", t, func() {
err := d.IncrViolationCount(context.Background(), 2)
So(err, ShouldBeNil)
})
}
func TestDao_FaceAutoPass(t *testing.T) {
Convey("FaceAutoPass", t, func() {
t := xtime.Time(time.Now().Unix())
err := d.FaceAutoPass(context.Background(), []int64{1}, t)
So(err, ShouldBeNil)
})
}
func TestDao_prepareReviewRange(t *testing.T) {
Convey("prepareReviewRange", t, func() {
stime := time.Date(0, 0, 0, 0, 0, 0, 0, time.Local).Unix()
etime := time.Now().Unix()
s, e, err := d.prepareReviewRange(context.Background(), xtime.Time(stime), xtime.Time(etime))
So(err, ShouldBeNil)
So(s, ShouldNotBeEmpty)
So(e, ShouldNotBeEmpty)
})
}
func TestDao_UpdateRemark(t *testing.T) {
Convey("UpdateRemark", t, func() {
err := d.UpdateRemark(context.Background(), 1, "12334")
So(err, ShouldBeNil)
})
}
func TestDao_QueuingFaceReviewsByTime(t *testing.T) {
Convey("QueuingFaceReviewsByTime", t, func() {
stime := time.Date(2018, 10, 1, 0, 0, 0, 0, time.Local).Unix()
etime := time.Date(2018, 11, 1, 0, 0, 0, 0, time.Local).Unix()
rws, err := d.QueuingFaceReviewsByTime(context.Background(), xtime.Time(stime), xtime.Time(etime))
So(err, ShouldBeNil)
// FIXME : UAT上全时间段查询不到该类数据
So(rws, ShouldBeEmpty)
})
}
func TestDao_ReviewByIDs(t *testing.T) {
Convey("ReviewByIDs", t, func() {
rws, err := d.ReviewByIDs(context.Background(), []int64{1, 2}, []int8{})
So(err, ShouldBeNil)
So(rws, ShouldNotBeEmpty)
})
}
func TestDao_Reviews(t *testing.T) {
Convey("Reviews", t, func() {
rws, total, err := d.Reviews(context.Background(), 2231365, []int8{1}, []int8{0, 1, 2}, true, true, "", 1530542443, 1540910443, 1, 10)
So(err, ShouldBeNil)
So(total, ShouldNotBeNil)
So(rws, ShouldNotBeNil)
})
}
func TestDao_ReviewAudit(t *testing.T) {
Convey("ReviewAudit", t, func() {
err := d.ReviewAudit(context.Background(), []int64{2231365, 2231365}, 0, "test", "test")
So(err, ShouldBeNil)
})
}
func TestDao_Review(t *testing.T) {
Convey("Review", t, func() {
userPropertyReview, err := d.Review(context.Background(), 1)
So(err, ShouldBeNil)
So(userPropertyReview, ShouldNotBeNil)
})
}
func TestDao_UpdateReviewFace(t *testing.T) {
Convey("UpdateReviewFace", t, func() {
err := d.UpdateReviewFace(context.Background(), 2231365, "face test")
So(err, ShouldBeNil)
})
}
func TestDao_AuditQueuingFace(t *testing.T) {
Convey("Review", t, func() {
err := d.AuditQueuingFace(context.Background(), 2231365, "face test", 0)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,163 @@
package dao
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"go-common/app/admin/main/member/model"
"go-common/library/database/elastic"
"go-common/library/log"
"github.com/pkg/errors"
)
var (
_numPattern = regexp.MustCompile(`^\d+$`)
)
func allnum(s string) bool {
return _numPattern.MatchString(s)
}
// SearchMember is.
func (d *Dao) SearchMember(ctx context.Context, arg *model.ArgList) (*model.SearchMemberResult, error) {
r := d.es.NewRequest("member_user").
Fields("mid", "name").
Index("user_base").
Order("mid", elastic.OrderAsc).
Ps(int(arg.PS)).
Pn(int(arg.PN))
if arg.Mid != 0 {
r.WhereEq("mid", arg.Mid)
}
if arg.Keyword != "" {
fields := []string{"name"}
if allnum(arg.Keyword) {
fields = append(fields, "mid")
}
r.WhereLike(fields, []string{arg.Keyword}, true, elastic.LikeLevelLow)
}
result := &model.SearchMemberResult{}
if err := r.Scan(ctx, &result); err != nil {
return nil, errors.WithStack(err)
}
return result, nil
}
// SearchLog is.
func (d *Dao) SearchLog(ctx context.Context, uid, oid int64, uname, action string) (*model.SearchLogResult, error) {
nowYear := time.Now().Year()
index := []string{
fmt.Sprintf("log_audit_121_%d", nowYear),
fmt.Sprintf("log_audit_121_%d", nowYear-1),
}
r := d.es.NewRequest("log_audit").
Index(index...).
Fields("uid", "uname", "oid", "type", "action", "str_0", "str_1", "str_2", "int_0", "int_1", "int_2", "ctime", "extra_data").
Order("ctime", elastic.OrderDesc)
// 默认查询的是第一页每夜10条记录
if uid > 0 {
r.WhereEq("uid", strconv.FormatInt(uid, 10))
}
if oid > 0 {
r.WhereEq("oid", strconv.FormatInt(oid, 10))
}
if uname != "" {
r.WhereEq("uname", uname)
}
if action != "" {
r.WhereIn("action", strings.Split(action, ","))
}
result := &model.SearchLogResult{}
if err := r.Scan(ctx, result); err != nil {
log.Error("Failed to SearchLog: Scan params(%s) error(%+v)", r.Params(), err)
return nil, err
}
return result, nil
}
// SearchFaceCheckRes is.
func (d *Dao) SearchFaceCheckRes(ctx context.Context, fileName string) (*model.SearchLogResult, error) {
index := fmt.Sprintf("log_audit_161_%s", time.Now().Format("2006_01"))
r := d.es.NewRequest("log_audit").
Index(index).
Fields("uid", "uname", "oid", "type", "action", "str_0", "str_1", "str_2", "int_0", "int_1", "int_2", "ctime", "extra_data").
WhereEq("str_1", "face").
WhereEq("str_2", fileName).
Order("ctime", elastic.OrderDesc).
Pn(1).
Ps(1)
result := &model.SearchLogResult{}
if err := r.Scan(ctx, result); err != nil {
log.Error("Failed to SearchFaceCheckRes: Scan params(%s) error(%+v)", r.Params(), err)
return nil, err
}
return result, nil
}
// SearchUserAuditLog is.
func (d *Dao) SearchUserAuditLog(ctx context.Context, mid int64) (*model.SearchLogResult, error) {
nowYear := time.Now().Year()
index := []string{
fmt.Sprintf("log_audit_121_%d", nowYear),
fmt.Sprintf("log_audit_121_%d", nowYear-1),
}
r := d.es.NewRequest("log_audit").
Index(index...).
Fields("uid", "uname", "oid", "type", "action", "str_0", "str_1", "str_2", "int_0", "int_1", "int_2", "ctime", "extra_data").
WhereEq("action", "base_audit").
WhereEq("oid", strconv.FormatInt(mid, 10)).
Order("ctime", elastic.OrderDesc).
Pn(1).
Ps(5)
result := &model.SearchLogResult{}
if err := r.Scan(ctx, result); err != nil {
log.Error("Failed to SearchUserAuditLog: Scan params(%s) error(%+v)", r.Params(), err)
return nil, err
}
return result, nil
}
// SearchUserPropertyReview is.
func (d *Dao) SearchUserPropertyReview(ctx context.Context, mid int64, property []int, state []int, isMonitor, isDesc bool, operator, stime, etime string, pn, ps int) (*model.SearchUserPropertyReviewResult, error) {
order := elastic.OrderAsc
if isDesc {
order = elastic.OrderDesc
}
monitor := 0
if isMonitor {
monitor = 1
}
r := d.es.NewRequest("user_property_review").
Fields("id").
Index("user_property_review").
WhereEq("is_monitor", monitor).
WhereRange("ctime", stime, etime, elastic.RangeScopeLcRc).
Order("id", order).
Ps(ps).
Pn(pn)
if mid > 0 {
r.WhereEq("mid", mid)
}
if operator != "" {
r.WhereEq("operator", operator)
}
if len(property) > 0 {
r.WhereIn("property", property)
}
if len(state) > 0 {
r.WhereIn("state", state)
}
result := &model.SearchUserPropertyReviewResult{}
if err := r.Scan(ctx, result); err != nil {
return nil, errors.WithStack(err)
}
return result, nil
}

View File

@@ -0,0 +1,53 @@
package dao
import (
"context"
"testing"
"time"
"go-common/app/admin/main/member/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoSearchMember(t *testing.T) {
convey.Convey("SearchMember", t, func() {
result, err := d.SearchMember(context.Background(), &model.ArgList{Mid: 627272, PN: 1, PS: 10})
convey.So(err, convey.ShouldBeNil)
convey.So(result, convey.ShouldNotBeNil)
})
}
func TestSearchLog(t *testing.T) {
convey.Convey("SearchLog", t, func() {
result, err := d.SearchLog(context.Background(), 1164, 0, "", "")
convey.So(err, convey.ShouldBeNil)
convey.So(result, convey.ShouldNotBeNil)
})
}
func TestDao_SearchFaceCheckRes(t *testing.T) {
convey.Convey("SearchFaceCheckRes", t, func() {
result, err := d.SearchFaceCheckRes(context.Background(), "2879cd5fb8518f7c6da75887994c1b2a7fe670bd.png")
convey.So(err, convey.ShouldBeNil)
convey.So(result, convey.ShouldNotBeNil)
})
}
func TestDao_SearchUserAuditLog(t *testing.T) {
convey.Convey("SearchUserAuditLog", t, func() {
result, err := d.SearchUserAuditLog(context.Background(), 1164)
convey.So(err, convey.ShouldBeNil)
convey.So(result, convey.ShouldNotBeNil)
})
}
func TestDao_SearchUserPropertyReview(t *testing.T) {
convey.Convey("SearchFaceCheckRes", t, func() {
etime := time.Now().Format("2006-01-02 15:04:05")
stime := time.Now().AddDate(0, 0, -26).Format("2006-01-02 15:04:05")
result, err := d.SearchUserPropertyReview(context.Background(), 0, []int{1}, []int{1, 2}, false, false, "", stime, etime, 1, 10)
convey.So(err, convey.ShouldBeNil)
convey.So(result, convey.ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,61 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"http.go",
"member.go",
"monitor.go",
"official.go",
"realname.go",
"review.go",
],
importpath = "go-common/app/admin/main/member/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/http/block:go_default_library",
"//app/admin/main/member/model:go_default_library",
"//app/admin/main/member/service:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/permit:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/metadata:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/main/member/http/block:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,51 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["http_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/block/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"block.go",
"http.go",
],
importpath = "go-common/app/admin/main/member/http/block",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/model/block:go_default_library",
"//app/admin/main/member/service/block:go_default_library",
"//library/ecode:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/permit:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,50 @@
package block
import (
model "go-common/app/admin/main/member/model/block"
bm "go-common/library/net/http/blademaster"
)
func blockSearch(c *bm.Context) {
var (
err error
v = &model.ParamSearch{}
)
if err = bind(c, v); err != nil {
return
}
c.JSON(svc.Search(c, v.MIDs))
}
func history(c *bm.Context) {
var (
err error
v = &model.ParamHistory{}
)
if err = bind(c, v); err != nil {
return
}
c.JSON(svc.History(c, v.MID, v.PS, v.PN, v.Desc))
}
func batchBlock(c *bm.Context) {
var (
err error
v = &model.ParamBatchBlock{}
)
if err = bind(c, v); err != nil {
return
}
c.JSON(nil, svc.BatchBlock(c, v))
}
func batchRemove(c *bm.Context) {
var (
err error
v = &model.ParamBatchRemove{}
)
if err = bind(c, v); err != nil {
return
}
c.JSON(nil, svc.BatchRemove(c, v))
}

View File

@@ -0,0 +1,46 @@
package block
import (
model "go-common/app/admin/main/member/model/block"
service "go-common/app/admin/main/member/service/block"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
"github.com/pkg/errors"
)
var (
svc *service.Service
)
// Setup is.
func Setup(blockSvc *service.Service, e *bm.Engine, authSvc *permit.Permit) {
svc = blockSvc
cb := e.Group("/x/admin/block", authSvc.Permit("BLOCK_SEARCH"))
{
cb.POST("/search", blockSearch)
cb.GET("/history", history)
}
cb = e.Group("/x/admin/block", authSvc.Permit("BLOCK_BLOCK"))
{
cb.POST("", batchBlock)
}
cb = e.Group("/x/admin/block", authSvc.Permit("BLOCK_REMOVE"))
{
cb.POST("/remove", batchRemove)
}
}
func bind(c *bm.Context, v model.ParamValidator) (err error) {
if err = c.Bind(v); err != nil {
err = errors.WithStack(err)
return
}
if !v.Validate() {
err = ecode.RequestErr
c.JSON(nil, ecode.RequestErr)
return
}
return
}

View File

@@ -0,0 +1,25 @@
package block
import (
"flag"
"testing"
"go-common/app/admin/main/block/conf"
. "github.com/smartystreets/goconvey/convey"
)
func TestMain(m *testing.M) {
flag.Set("conf", "../cmd/block-admin-test.toml")
var err error
if err = conf.Init(); err != nil {
panic(err)
}
m.Run()
}
func TestTools(t *testing.T) {
Convey("tools", t, func() {
})
}

View File

@@ -0,0 +1,144 @@
package http
import (
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/http/block"
"go-common/app/admin/main/member/service"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
authSvc *permit.Permit
vfySvc *verify.Verify
svc *service.Service
)
// Init init
func Init(c *conf.Config) {
initService(c)
// init router
engine := bm.DefaultServer(c.BM)
initRouter(engine)
block.Setup(svc.BlockImpl(), engine, authSvc)
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
// initService init services.
func initService(c *conf.Config) {
authSvc = permit.New(c.Auth)
vfySvc = verify.New(nil)
svc = service.New(c)
}
// outerRouter init outer router api path.
func initRouter(e *bm.Engine) {
// init api
e.Ping(ping)
e.Register(register)
mg := e.Group("/x/admin/member")
{
mg.GET("/list", authSvc.Permit("ACCOUNT_REVIEW"), members)
mg.POST("/exp/set", authSvc.Permit("ACCOUNT_REVIEW_SET_EXP"), expSet)
mg.POST("/moral/set", authSvc.Permit("ACCOUNT_REVIEW"), moralSet)
mg.POST("/rank/set", authSvc.Permit("ACCOUNT_REVIEW_SET_RANK"), rankSet)
mg.POST("/coin/set", authSvc.Permit("ACCOUNT_REVIEW"), coinSet)
mg.POST("/addit/remark/set", authSvc.Permit("ACCOUNT_REVIEW"), additRemarkSet)
mg.GET("/profile", authSvc.Permit("ACCOUNT_REVIEW"), memberProfile)
mg.GET("/exp/log", authSvc.Permit("ACCOUNT_EXP_LOG"), expLog)
mg.GET("/face/history", authSvc.Permit("ACCOUNT_FACE_HISTORY"), faceHistory)
mg.POST("/batch/formal", authSvc.Permit("ACCOUNT_BATCH_FORMAL"), batchFormal)
mg.GET("/moral/log", moralLog)
mg.POST("/sign/del", delSign)
mg.POST("/pub/exp/msg", authSvc.Permit("ACCOUNT_PUB_EXP_MSG"), pubExpMsg)
//个人信息审核
mg.GET("/base/review", authSvc.Permit("ACCOUNT_REVIEW_AUDIT"), baseReview)
mg.POST("/face/clear", authSvc.Permit("ACCOUNT_REVIEW_AUDIT"), clearFace)
mg.POST("/sign/clear", authSvc.Permit("ACCOUNT_REVIEW_AUDIT"), clearSign)
mg.POST("/name/clear", authSvc.Permit("ACCOUNT_REVIEW_AUDIT"), clearName)
og := mg.Group("/official")
{
og.GET("/list", authSvc.Permit("OFFICIAL_VIEW"), officials)
og.GET("/list/excel", authSvc.Permit("OFFICIAL_VIEW"), officialsExcel)
og.GET("/doc", authSvc.Permit("OFFICIAL_VIEW"), officialDoc)
og.GET("/docs", authSvc.Permit("OFFICIAL_VIEW"), officialDocs)
og.GET("/docs/excel", authSvc.Permit("OFFICIAL_VIEW"), officialDocsExcel)
og.GET("/doc/audit", authSvc.Permit("OFFICIAL_AUDIT"), officialDocAudit)
og.GET("/doc/edit", authSvc.Permit("OFFICIAL_MNG"), officialDocEdit)
ex := og.Group("/internal")
ex.GET("/doc", vfySvc.Verify, officialDoc)
ex.POST("/doc/audit", vfySvc.Verify, officialDocAudit)
ex.POST("/doc/submit", vfySvc.Verify, officialDocSubmit)
}
rw := mg.Group("/review")
{
rw.GET("", authSvc.Permit("ACCOUNT_PROPERTY_REVIEW_AUDIT"), review)
rw.GET("/list", authSvc.Permit("ACCOUNT_PROPERTY_REVIEW_AUDIT"), reviewList)
rw.POST("/audit", authSvc.Permit("ACCOUNT_PROPERTY_REVIEW_AUDIT"), reviewAudit)
rw.GET("/face/list", authSvc.Permit("ACCOUNT_PROPERTY_REVIEW_FACE_AUDIT"), reviewFaceList)
rw.POST("/face/audit", authSvc.Permit("ACCOUNT_PROPERTY_REVIEW_FACE_AUDIT"), reviewAudit)
}
mn := mg.Group("/monitor")
{
mn.GET("/list", authSvc.Permit("ACCOUNT_MONITOR_REVIEW"), monitors)
mn.POST("/add", authSvc.Permit("ACCOUNT_MONITOR_MNG"), addMonitor)
mn.POST("/del", authSvc.Permit("ACCOUNT_MONITOR_MNG"), delMonitor)
}
rn := mg.Group("/realname")
{
rn.GET("/list", authSvc.Permit("REALNAME_QUERY"), realnameList)
rn.GET("/list/pending", authSvc.Permit("REALNAME_AUDIT"), realnamePendingList)
rn.GET("/image", authSvc.Permit("REALNAME_AUDIT"), realnameImage)
rn.GET("/image/preview", realnameImagePreview)
rn.POST("/apply/audit", authSvc.Permit("REALNAME_AUDIT"), realnameAuditApply)
rn.GET("/reason", authSvc.Permit("REALNAME_AUDIT"), realnameReasonList)
rn.POST("/reason/set", authSvc.Permit("REALNAME_SET_REASON"), realnameSetReason)
rn.POST("/search/card", vfySvc.Verify, realnameSearchCard)
rn.POST("/unbind", authSvc.Permit("ACCOUNT_REVIEW"), realnameUnbind)
rn.GET("/excel", authSvc.Permit("REALNAME_EXPORT"), realnameExport)
rn.POST("/file/upload", authSvc.Permit("REALNAME_SUBMIT"), realnameFileUpload)
rn.POST("/submit", authSvc.Permit("REALNAME_SUBMIT"), realnameSubmit)
ex := rn.Group("/internal", vfySvc.Verify)
ex.GET("/list", realnameList)
ex.GET("/list/pending", realnamePendingList)
ex.GET("/image", realnameImage)
ex.GET("/image/preview", realnameImagePreview)
ex.POST("/apply/audit", realnameAuditApply)
ex.GET("/reason", realnameReasonList)
ex.POST("/reason/set", realnameSetReason)
ex.POST("/search/card", realnameSearchCard)
ex.POST("/unbind", realnameUnbind)
ex.GET("/excel", realnameExport)
ex.POST("/file/upload", realnameFileUpload)
ex.POST("/submit", realnameSubmit)
}
}
}
// ping check server ok.
func ping(c *bm.Context) {
if err := svc.Ping(c); err != nil {
log.Error("Failed to ping service: %+v", err)
c.AbortWithStatus(ecode.Cause(err).Code())
return
}
c.AbortWithStatus(200)
}
func register(c *bm.Context) {
c.JSON(nil, nil)
}

View File

@@ -0,0 +1,196 @@
package http
import (
"io/ioutil"
"time"
"go-common/app/admin/main/member/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
xtime "go-common/library/time"
)
func members(ctx *bm.Context) {
arg := &model.ArgList{}
if err := ctx.Bind(arg); err != nil {
return
}
if arg.PN <= 0 {
arg.PN = 1
}
if arg.PS <= 0 {
arg.PS = 10
}
ctx.JSON(svc.Members(ctx, arg))
}
func memberProfile(ctx *bm.Context) {
arg := &model.ArgMid{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(svc.MemberProfile(ctx, arg.Mid))
}
func delSign(ctx *bm.Context) {
arg := &model.ArgMids{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(nil, svc.DelSign(ctx, arg))
}
// pubExpMsg is.
func pubExpMsg(ctx *bm.Context) {
arg := &model.ArgPubExpMsg{}
if err := ctx.Bind(arg); err != nil {
return
}
if arg.IP == "" {
arg.IP = metadata.String(ctx, metadata.RemoteIP)
}
if arg.Ts == 0 {
arg.Ts = time.Now().Unix()
}
ctx.JSON(nil, svc.PubExpMsg(ctx, arg))
}
func expSet(ctx *bm.Context) {
arg := &model.ArgExpSet{}
if err := ctx.Bind(arg); err != nil {
return
}
arg.IP = metadata.String(ctx, metadata.RemoteIP)
ctx.JSON(nil, svc.SetExp(ctx, arg))
}
func moralSet(ctx *bm.Context) {
arg := &model.ArgMoralSet{}
if err := ctx.Bind(arg); err != nil {
return
}
arg.IP = metadata.String(ctx, metadata.RemoteIP)
ctx.JSON(nil, svc.SetMoral(ctx, arg))
}
func coinSet(ctx *bm.Context) {
arg := &model.ArgCoinSet{}
if err := ctx.Bind(arg); err != nil {
return
}
arg.IP = metadata.String(ctx, metadata.RemoteIP)
ctx.JSON(nil, svc.SetCoin(ctx, arg))
}
func rankSet(ctx *bm.Context) {
arg := &model.ArgRankSet{}
if err := ctx.Bind(arg); err != nil {
return
}
arg.IP = metadata.String(ctx, metadata.RemoteIP)
ctx.JSON(nil, svc.SetRank(ctx, arg))
}
func additRemarkSet(ctx *bm.Context) {
arg := &model.ArgAdditRemarkSet{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(nil, svc.SetAdditRemark(ctx, arg))
}
func baseReview(ctx *bm.Context) {
arg := &model.ArgBaseReview{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(svc.BaseReview(ctx, arg))
}
func clearFace(ctx *bm.Context) {
arg := &model.ArgMids{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(svc.ClearFace(ctx, arg), nil)
}
func clearSign(ctx *bm.Context) {
arg := &model.ArgMids{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(svc.ClearSign(ctx, arg), nil)
}
func clearName(ctx *bm.Context) {
arg := &model.ArgMids{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(svc.ClearName(ctx, arg), nil)
}
func expLog(ctx *bm.Context) {
arg := &model.ArgMid{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(svc.ExpLog(ctx, arg.Mid))
}
func faceHistory(ctx *bm.Context) {
arg := &model.ArgFaceHistory{}
if err := ctx.Bind(arg); err != nil {
return
}
if ctx.Request.Form.Get("status") == "" {
// 0:队列中 1:待审核 2:通过 3:驳回
arg.Status = []int8{0, 1, 2, 3}
}
if arg.PN <= 0 {
arg.PN = 1
}
if arg.PS <= 0 {
arg.PS = 50
}
if arg.ETime <= 0 {
arg.ETime = xtime.Time(time.Now().Unix())
}
ctx.JSON(svc.FaceHistory(ctx, arg))
}
func moralLog(ctx *bm.Context) {
arg := &model.ArgMid{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(svc.MoralLog(ctx, arg.Mid))
}
func batchFormal(ctx *bm.Context) {
arg := &model.ArgBatchFormal{}
if err := ctx.Bind(arg); err != nil {
return
}
defer ctx.Request.Form.Del("file") // 防止日志不出现
ctx.Request.ParseMultipartForm(32 << 20)
fd, _, err := ctx.Request.FormFile("file")
if err != nil {
log.Warn("Failed to parse form file: %+v", err)
ctx.JSON(nil, ecode.RequestErr)
return
}
defer fd.Close()
file, err := ioutil.ReadAll(fd)
if err != nil {
log.Warn("Failed to read form file: %+v", err)
ctx.JSON(nil, ecode.RequestErr)
return
}
log.Info("Succeeded to parse file data: file-length: %d", len(file))
arg.FileData = file
ctx.JSON(nil, svc.BatchFormal(ctx, arg))
}

View File

@@ -0,0 +1,60 @@
package http
import (
"go-common/app/admin/main/member/model"
bm "go-common/library/net/http/blademaster"
)
func monitors(ctx *bm.Context) {
arg := &model.ArgMonitor{}
if err := ctx.Bind(arg); err != nil {
return
}
if arg.Pn <= 0 {
arg.Pn = 1
}
if arg.Ps <= 0 {
arg.Ps = 10
}
mns, total, err := svc.Monitors(ctx, arg)
if err != nil {
ctx.JSON(nil, err)
return
}
res := map[string]interface{}{
"monitors": mns,
"page": map[string]int{
"num": arg.Pn,
"size": arg.Ps,
"total": total,
},
}
ctx.JSON(res, nil)
}
func addMonitor(ctx *bm.Context) {
arg := &model.ArgAddMonitor{}
if err := ctx.Bind(arg); err != nil {
return
}
arg.OperatorID = operatorID(ctx)
ctx.JSON(nil, svc.AddMonitor(ctx, arg))
}
func delMonitor(ctx *bm.Context) {
arg := &model.ArgDelMonitor{}
if err := ctx.Bind(arg); err != nil {
return
}
arg.OperatorID = operatorID(ctx)
ctx.JSON(nil, svc.DelMonitor(ctx, arg))
}
func operatorID(ctx *bm.Context) int64 {
uidI, ok := ctx.Get("uid")
if !ok {
return 0
}
uid, _ := uidI.(int64)
return uid
}

View File

@@ -0,0 +1,211 @@
package http
import (
"bytes"
"encoding/csv"
"fmt"
"strconv"
"go-common/app/admin/main/member/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
func officials(ctx *bm.Context) {
arg := &model.ArgOfficial{}
if err := ctx.Bind(arg); err != nil {
return
}
if arg.Pn <= 0 {
arg.Pn = 1
}
if arg.Ps <= 0 {
arg.Ps = 10
}
os, total, err := svc.Officials(ctx, arg)
if err != nil {
ctx.JSON(nil, err)
return
}
res := map[string]interface{}{
"officials": os,
"page": map[string]int{
"num": arg.Pn,
"size": arg.Ps,
"total": total,
},
}
ctx.JSON(res, nil)
}
func officialsExcel(ctx *bm.Context) {
arg := &model.ArgOfficial{}
if err := ctx.Bind(arg); err != nil {
return
}
if arg.Pn <= 0 {
arg.Pn = 1
}
if arg.Ps <= 0 {
arg.Ps = 10
}
os, _, err := svc.Officials(ctx, arg)
if err != nil {
ctx.JSON(nil, err)
return
}
data := make([][]string, 0, len(os))
data = append(data, []string{"完成认证时间", "用户ID", "昵称", "认证类型", "认证称号", "称号后缀"})
for _, of := range os {
fields := []string{
of.CTime.Time().Format("2006-01-02 15:04:05"),
strconv.FormatInt(of.Mid, 10),
of.Name,
model.OfficialRoleName(of.Role),
of.Title,
of.Desc,
}
data = append(data, fields)
}
buf := bytes.NewBuffer(nil)
w := csv.NewWriter(buf)
for _, record := range data {
if err := w.Write(record); err != nil {
ctx.JSON(nil, err)
return
}
}
w.Flush()
res := buf.Bytes()
ctx.Writer.Header().Set("Content-Type", "application/csv")
ctx.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.csv", "官方认证名单"))
ctx.Writer.Write([]byte("\xEF\xBB\xBF")) // 写 UTF-8 的 BOM 头,别删,删了客服就找上门了
ctx.Writer.Write(res)
}
func officialDocs(ctx *bm.Context) {
arg := &model.ArgOfficialDoc{}
if err := ctx.Bind(arg); err != nil {
return
}
if arg.Pn <= 0 {
arg.Pn = 1
}
if arg.Ps <= 0 {
arg.Ps = 10
}
ods, total, err := svc.OfficialDocs(ctx, arg)
if err != nil {
ctx.JSON(nil, err)
return
}
res := map[string]interface{}{
"officials": ods,
"page": map[string]int{
"num": arg.Pn,
"size": arg.Ps,
"total": total,
},
}
ctx.JSON(res, nil)
}
func officialDocsExcel(ctx *bm.Context) {
arg := &model.ArgOfficialDoc{}
if err := ctx.Bind(arg); err != nil {
return
}
if arg.Pn <= 0 {
arg.Pn = 1
}
if arg.Ps <= 0 {
arg.Ps = 10
}
ods, _, err := svc.OfficialDocs(ctx, arg)
if err != nil {
ctx.JSON(nil, err)
return
}
data := make([][]string, 0, len(ods))
data = append(data, []string{"申请时间", "认证类型", "用户ID", "用户昵称", "审核状态", "认证称号", "称号后缀", "操作人"})
for _, od := range ods {
fields := []string{
od.CTime.Time().Format("2006-01-02 15:04:05"),
model.OfficialRoleName(od.Role),
strconv.FormatInt(od.Mid, 10),
od.Name,
model.OfficialStateName(od.State),
od.Title,
od.Desc,
od.Uname,
}
data = append(data, fields)
}
buf := bytes.NewBuffer(nil)
w := csv.NewWriter(buf)
for _, record := range data {
if err := w.Write(record); err != nil {
ctx.JSON(nil, err)
return
}
}
w.Flush()
res := buf.Bytes()
ctx.Writer.Header().Set("Content-Type", "application/csv")
ctx.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.csv", "官方认证审核记录"))
ctx.Writer.Write([]byte("\xEF\xBB\xBF")) // 写 UTF-8 的 BOM 头,别删,删了客服就找上门了
ctx.Writer.Write(res)
}
func officialDoc(ctx *bm.Context) {
arg := &model.ArgMid{}
if err := ctx.Bind(arg); err != nil {
return
}
od, logs, block, spys, rn, sameCreditCodeMids, err := svc.OfficialDoc(ctx, arg.Mid)
if err != nil {
ctx.JSON(nil, err)
return
}
res := map[string]interface{}{
"official": od,
"logs": logs.Result,
"block": block,
"spys": spys,
"realname": rn,
"same_credit_code_mids": sameCreditCodeMids,
}
ctx.JSON(res, nil)
}
func officialDocAudit(ctx *bm.Context) {
arg := &model.ArgOfficialAudit{}
if err := ctx.Bind(arg); err != nil {
return
}
if arg.State == model.OfficialStateNoPass && arg.Reason == "" {
ctx.JSON(nil, ecode.RequestErr)
return
}
if arg.Source != "" {
arg.Reason = fmt.Sprintf("%s来源%s", arg.Reason, arg.Source)
}
ctx.JSON(nil, svc.OfficialDocAudit(ctx, arg))
}
func officialDocEdit(ctx *bm.Context) {
arg := &model.ArgOfficialEdit{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(nil, svc.OfficialDocEdit(ctx, arg))
}
func officialDocSubmit(ctx *bm.Context) {
arg := &model.ArgOfficialSubmit{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(nil, svc.OfficialDocSubmit(ctx, arg))
}

View File

@@ -0,0 +1,357 @@
package http
import (
"bytes"
"encoding/csv"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"time"
"go-common/app/admin/main/member/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"github.com/pkg/errors"
)
func realnameList(ctx *bm.Context) {
var (
arg = &model.ArgRealnameList{}
list []*model.RespRealnameApply
total int
err error
)
if err = ctx.Bind(arg); err != nil {
return
}
if arg.PN <= 0 {
arg.PN = 1
}
if arg.PS <= 0 || arg.PS > 100 {
arg.PS = 10
}
// 没有指定实名认证申请状态State默认查询所有
if arg.State == "" {
arg.State = model.RealnameApplyStateAll
}
// 如果没有使用 mid || card 则取默认7天前作为 TSFrom
if arg.MID == 0 && arg.Card == "" {
if arg.TSFrom <= 0 {
arg.TSFrom = time.Now().Add(-time.Hour * 24 * 7).Unix()
}
}
// 如果使用 mid 或 card 进行查询,则查询所有时间段对应的数据
if arg.MID != 0 || arg.Card != "" {
arg.TSFrom = 0
arg.TSTo = 0
}
if list, total, err = svc.RealnameList(ctx, arg); err != nil {
ctx.JSON(nil, err)
return
}
res := map[string]interface{}{
"list": list,
"page": map[string]int{
"num": arg.PN,
"size": arg.PS,
"total": total,
},
}
ctx.JSON(res, nil)
}
func realnamePendingList(ctx *bm.Context) {
var (
arg = &model.ArgRealnamePendingList{}
list []*model.RespRealnameApply
total int
err error
)
if err = ctx.Bind(arg); err != nil {
return
}
if arg.PN <= 0 {
arg.PN = 1
}
if arg.PS <= 0 || arg.PS > 100 {
arg.PS = 10
}
if list, total, err = svc.RealnamePendingList(ctx, arg); err != nil {
ctx.JSON(nil, err)
return
}
res := map[string]interface{}{
"list": list,
"page": map[string]int{
"num": arg.PN,
"size": arg.PS,
"total": total,
},
}
ctx.JSON(res, nil)
}
func realnameAuditApply(ctx *bm.Context) {
var (
arg = &model.ArgRealnameAuditApply{}
adminName string
adminID int64
data interface{}
ok bool
err error
)
if err = ctx.Bind(arg); err != nil {
return
}
// 驳回必须有reason
if arg.Action == model.RealnameActionReject && arg.Reason == "" {
ctx.JSON(nil, ecode.RequestErr)
return
}
if data, ok = ctx.Get("username"); !ok {
ctx.JSON(nil, ecode.AccessDenied)
return
}
if adminName, ok = data.(string); !ok {
ctx.JSON(nil, ecode.ServerErr)
return
}
if data, ok = ctx.Get("uid"); !ok {
ctx.JSON(nil, ecode.AccessDenied)
return
}
if adminID, ok = data.(int64); !ok {
ctx.JSON(nil, ecode.ServerErr)
return
}
ctx.JSON(nil, svc.RealnameAuditApply(ctx, arg, adminName, adminID))
}
func realnameReasonList(ctx *bm.Context) {
var (
arg = &model.ArgRealnameReasonList{}
list []string
total int
err error
)
if err = ctx.Bind(arg); err != nil {
return
}
if arg.PN <= 0 {
arg.PN = 1
}
if arg.PS <= 0 || arg.PS > 100 {
arg.PS = 20
}
if list, total, err = svc.RealnameReasonList(ctx, arg); err != nil {
ctx.JSON(nil, err)
return
}
res := map[string]interface{}{
"list": list,
"page": map[string]int{
"num": arg.PN,
"size": arg.PS,
"total": total,
},
}
ctx.JSON(res, nil)
}
func realnameSetReason(ctx *bm.Context) {
var (
arg = &model.ArgRealnameSetReason{}
)
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(nil, svc.RealnameSetReason(ctx, arg))
}
func realnameImage(ctx *bm.Context) {
var (
arg = &model.ArgRealnameImage{}
img []byte
err error
)
if err = ctx.Bind(arg); err != nil {
return
}
if img, err = svc.FetchRealnameImage(ctx, arg.Token); err != nil {
ctx.JSON(nil, err)
return
}
ctx.Bytes(http.StatusOK, http.DetectContentType(img), img)
}
func realnameImagePreview(ctx *bm.Context) {
var (
arg = &model.ArgRealnameImagePreview{}
img []byte
err error
)
if err = ctx.Bind(arg); err != nil {
return
}
if arg.BorderSize <= 0 {
arg.BorderSize = 1000
}
if img, err = svc.RealnameImagePreview(ctx, arg.Token, arg.BorderSize); err != nil {
ctx.JSON(nil, err)
return
}
ctx.Bytes(http.StatusOK, http.DetectContentType(img), img)
}
func realnameSearchCard(ctx *bm.Context) {
var (
arg = &model.ArgRealnameSearchCard{}
err error
)
if err = ctx.Bind(arg); err != nil {
return
}
if len(arg.Cards) > 50 {
err = ecode.RequestErr
return
}
ctx.JSON(svc.RealnameSearchCard(ctx, arg.Cards, arg.CardType, arg.Country))
}
func realnameUnbind(ctx *bm.Context) {
var (
arg = &model.ArgMid{}
adminName string
adminID int64
data interface{}
ok bool
err error
)
if err = ctx.Bind(arg); err != nil {
return
}
if data, ok = ctx.Get("username"); !ok {
ctx.JSON(nil, ecode.AccessDenied)
return
}
if adminName, ok = data.(string); !ok {
ctx.JSON(nil, ecode.ServerErr)
return
}
if data, ok = ctx.Get("uid"); !ok {
ctx.JSON(nil, ecode.AccessDenied)
return
}
if adminID, ok = data.(int64); !ok {
ctx.JSON(nil, ecode.ServerErr)
return
}
ctx.JSON(nil, svc.RealnameUnbind(ctx, arg.Mid, adminName, adminID))
}
func realnameExport(ctx *bm.Context) {
arg := &model.ArgMids{}
if err := ctx.Bind(arg); err != nil {
return
}
if len(arg.Mid) > 1000 {
ctx.JSON(nil, ecode.RequestErr)
return
}
realnameExports, err := svc.RealnameExcel(ctx, arg.Mid)
if err != nil {
ctx.JSON(nil, err)
return
}
data := make([][]string, 0, len(realnameExports))
data = append(data, []string{"用户uid", "用户名", "昵称", "姓名", "手机号", "证件类型", "证件号"})
for _, user := range realnameExports {
fields := []string{
strconv.FormatInt(user.Mid, 10),
user.UserID,
user.Uname,
user.Realname,
user.Tel,
model.CardTypeString(user.CardType),
avoidescape(user.CardNum),
}
data = append(data, fields)
}
buf := bytes.NewBuffer(nil)
w := csv.NewWriter(buf)
for _, record := range data {
if err := w.Write(record); err != nil {
ctx.JSON(nil, err)
return
}
}
w.Flush()
res := buf.Bytes()
ctx.Writer.Header().Set("Content-Type", "application/csv")
ctx.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.csv", "实名认证导出"))
ctx.Writer.Write([]byte("\xEF\xBB\xBF")) // 写 UTF-8 的 BOM 头,别删,删了客服就找上门了
ctx.Writer.Write(res)
}
func avoidescape(in string) string {
return fmt.Sprintf("%s,", in)
}
func realnameSubmit(ctx *bm.Context) {
arg := &model.ArgRealnameSubmit{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(nil, svc.RealnameSubmit(ctx, arg))
}
func realnameFileUpload(c *bm.Context) {
arg := &model.ArgMid{}
if err := c.Bind(arg); err != nil {
return
}
mid := arg.Mid
defer c.Request.Form.Del("img") // 防止日志不出现
c.Request.ParseMultipartForm(32 << 20)
imgBytes, err := func() ([]byte, error) {
img := c.Request.FormValue("img")
if img != "" {
log.Info("Succeeded to parse img file from form value: mid: %d, length: %d", mid, len(img))
return []byte(img), nil
}
log.Warn("Failed to parse img file from form value, fallback to form file: mid: %d", mid)
f, _, err := c.Request.FormFile("img")
if err != nil {
return nil, errors.Wrapf(err, "parse img form file: mid: %d", mid)
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, errors.Wrapf(err, "read img form file: mid: %d", mid)
}
if len(data) <= 0 {
return nil, errors.Wrapf(err, "form file data: mid: %d, length: %d", mid, len(data))
}
log.Info("Succeeded to parse file from form file: mid: %d, length: %d", mid, len(data))
return data, nil
}()
if err != nil {
log.Error("Failed to parse realname upload file: mid: %d: %+v", mid, err)
c.JSON(nil, ecode.RequestErr)
return
}
var resData struct {
SRC string `json:"token"`
}
if resData.SRC, err = svc.RealnameFileUpload(c, arg.Mid, imgBytes); err != nil {
log.Error("%+v", err)
c.JSON(nil, err)
return
}
c.JSON(resData, nil)
}

View File

@@ -0,0 +1,92 @@
package http
import (
"go-common/app/admin/main/member/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
func review(ctx *bm.Context) {
arg := &model.ArgReview{}
if err := ctx.Bind(arg); err != nil {
return
}
ctx.JSON(svc.Review(ctx, arg))
}
func reviewList(ctx *bm.Context) {
arg := &model.ArgReviewList{}
if err := ctx.Bind(arg); err != nil {
return
}
arg.IsMonitor = true
if arg.Pn <= 0 {
arg.Pn = 1
}
if arg.Ps <= 0 {
arg.Ps = 10
}
rws, total, err := svc.Reviews(ctx, arg)
if err != nil {
ctx.JSON(nil, err)
return
}
res := map[string]interface{}{
"reviews": rws,
"page": map[string]int{
"num": arg.Pn,
"size": arg.Ps,
"total": total,
},
}
ctx.JSON(res, nil)
}
func reviewFaceList(ctx *bm.Context) {
arg := &model.ArgReviewList{}
if err := ctx.Bind(arg); err != nil {
return
}
arg.IsMonitor = false
arg.Property = []int8{model.ReviewPropertyFace}
if arg.Pn <= 0 {
arg.Pn = 1
}
if arg.Ps <= 0 {
arg.Ps = 10
}
rws, total, err := svc.Reviews(ctx, arg)
if err != nil {
ctx.JSON(nil, err)
return
}
res := map[string]interface{}{
"reviews": rws,
"page": map[string]int{
"num": arg.Pn,
"size": arg.Ps,
"total": total,
},
}
ctx.JSON(res, nil)
}
func reviewAudit(ctx *bm.Context) {
arg := &model.ArgReviewAudit{}
if err := ctx.Bind(arg); err != nil {
return
}
if arg.BlockUser {
blockArg := model.ArgBatchBlock{}
if err := ctx.Bind(&blockArg); err != nil {
return
}
// yuzheng: 头像这里的封禁都作为小黑屋封禁
blockArg.Source = 2
if !blockArg.Validate() {
ctx.JSON(nil, ecode.RequestErr)
return
}
arg.ArgBatchBlock = blockArg
}
ctx.JSON(nil, svc.ReviewAudit(ctx, arg))
}

View File

@@ -0,0 +1,71 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"block.go",
"member.go",
"monitor.go",
"official.go",
"pagination.go",
"params.go",
"realname.go",
"realname_old.go",
"review.go",
"search.go",
],
importpath = "go-common/app/admin/main/member/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/model/block:go_default_library",
"//app/service/main/member/model:go_default_library",
"//app/service/main/member/model/block:go_default_library",
"//library/log:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/main/member/model/block:all-srcs",
"//app/admin/main/member/model/bom:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["member_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"],
)

View File

@@ -0,0 +1,13 @@
package model
import (
"go-common/app/service/main/member/model/block"
)
// BlockResult is
type BlockResult struct {
MID int64 `json:"mid"`
BlockStatus block.BlockStatus `json:"block_status"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
}

View File

@@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"http.go",
"model.go",
"notify.go",
],
importpath = "go-common/app/admin/main/member/model/block",
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,46 @@
package block
import (
"time"
)
// DBUser .
type DBUser struct {
ID int64
MID int64
Status BlockStatus
CTime time.Time
MTime time.Time
}
// DBUserDetail .
type DBUserDetail struct {
ID int64
MID int64
BlockCount int
CTime time.Time
MTime time.Time
}
// DBHistory .
type DBHistory struct {
ID int64
MID int64
AdminID int64
AdminName string
Source BlockSource
Area BlockArea
Reason string
Comment string
Action BlockAction
StartTime time.Time
Duration int64
Notify bool
CTime time.Time
MTime time.Time
}
// MCBlockInfo .
type MCBlockInfo struct {
BlockStatus BlockStatus `json:"bs"`
}

View File

@@ -0,0 +1,131 @@
package block
// ParamValidator .
type ParamValidator interface {
Validate() bool
}
// ParamSearch .
type ParamSearch struct {
MIDs []int64 `form:"mids,split"`
}
// Validate .
func (p *ParamSearch) Validate() bool {
p.MIDs = intsSet(p.MIDs)
if len(p.MIDs) == 0 || len(p.MIDs) > 200 {
return false
}
return true
}
// ParamHistory .
type ParamHistory struct {
MID int64 `form:"mid"`
Desc bool `form:"desc"`
PS int `form:"ps"`
PN int `form:"pn"`
}
// Validate .
func (p *ParamHistory) Validate() bool {
if p.MID <= 0 {
return false
}
if p.PS <= 0 || p.PS > 100 {
return false
}
if p.PN <= 0 {
return false
}
return true
}
// ParamBatchBlock .
type ParamBatchBlock struct {
MIDs []int64 `form:"mids,split"`
AdminID int64 `form:"admin_id"`
AdminName string `form:"admin_name"`
Source BlockMgrSource `form:"source"`
Area BlockArea `form:"area"`
Reason string `form:"reason"`
Comment string `form:"comment"`
Action BlockAction `form:"action"`
Duration int64 `form:"duration"` // 单位:天
Notify bool `form:"notify"`
}
// Validate .
func (p *ParamBatchBlock) Validate() bool {
p.MIDs = intsSet(p.MIDs)
if len(p.MIDs) == 0 || len(p.MIDs) > 200 {
return false
}
if p.AdminID <= 0 {
return false
}
if p.AdminName == "" {
return false
}
if p.Source != BlockMgrSourceSys && p.Source != BlockMgrSourceCredit {
return false
}
if !p.Area.Contain() {
return false
}
if p.Comment == "" {
return false
}
if p.Action != BlockActionForever && p.Action != BlockActionLimit {
return false
}
if p.Action == BlockActionLimit {
if p.Duration <= 0 {
return false
}
}
return true
}
// ParamBatchRemove .
type ParamBatchRemove struct {
MIDs []int64 `form:"mids,split"`
AdminID int64 `form:"admin_id"`
AdminName string `form:"admin_name"`
Comment string `form:"comment"`
Notify bool `form:"notify"`
}
// Validate .
func (p *ParamBatchRemove) Validate() bool {
p.MIDs = intsSet(p.MIDs)
if len(p.MIDs) == 0 || len(p.MIDs) > 200 {
return false
}
if p.AdminID <= 0 {
return false
}
if p.AdminName == "" {
return false
}
if p.Comment == "" {
return false
}
return true
}
func intsSet(ints []int64) (intSet []int64) {
if len(ints) == 0 {
return
}
OUTER:
for i := range ints {
for ni := range intSet {
if ints[i] == intSet[ni] {
continue OUTER
}
}
intSet = append(intSet, ints[i])
}
return
}

View File

@@ -0,0 +1,248 @@
package block
import (
"time"
)
// BlockStatus 封禁状态 0. 未封禁 1. 永久封禁 2. 限时封禁
type BlockStatus uint8
const (
// BlockStatusFalse 未封禁
BlockStatusFalse BlockStatus = iota
// BlockStatusForever 永久封禁
BlockStatusForever
// BlockStatusLimit 限时封禁
BlockStatusLimit
// BlockStatusCredit 小黑屋封禁
BlockStatusCredit
)
// BlockSource 封禁来源 1. 小黑屋(小黑屋和manager后台封禁) 2. 系统封禁(反作弊及监控系统上报) 3.解封 (所有后台,用户前台自助的解封)
type BlockSource uint8
// Contain .
func (b BlockSource) Contain() bool {
switch b {
case BlockSourceBlackHouse, BlockSourceSys, BlockSourceManager, BlockSourceBplus:
return true
default:
return false
}
}
const (
// BlockSourceBlackHouse 小黑屋封禁
BlockSourceBlackHouse BlockSource = iota + 1
// BlockSourceSys 系统封禁
BlockSourceSys
// BlockSourceManager 管理后台
BlockSourceManager
// BlockSourceBplus B+相关(动态、im、小视频)
BlockSourceBplus
)
// String .
func (b BlockSource) String() string {
switch b {
case BlockSourceBlackHouse:
return "小黑屋封禁"
case BlockSourceSys:
return "系统封禁"
default:
return ""
}
}
const (
// BlockLogBizID 用户审核日志
BlockLogBizID int = 122
)
// BlockArea 封禁业务
type BlockArea uint8
// Contain .
func (b BlockArea) Contain() bool {
switch b {
case BlockAreaNone, BlockAreaReply, BlockAreaDanmaku, BlockAreaMessage, BlockAreaTag, BlockAreaProfile, BlockAreaArchive, BlockAreaMusic, BlockAreaArticle, BlockAreaSpaceBanner, BlockAreaDynamic, BlockAreaAlbum, BlockAreaQuickVideo:
return true
default:
return false
}
}
func (b BlockArea) String() string {
switch b {
case BlockAreaReply:
return "评论"
case BlockAreaDanmaku:
return "弹幕"
case BlockAreaMessage:
return "私信"
case BlockAreaTag:
return "标签"
case BlockAreaProfile:
return "个人资料"
case BlockAreaArchive:
return "投稿"
case BlockAreaMusic:
return "音频"
case BlockAreaArticle:
return "专栏"
case BlockAreaSpaceBanner:
return "空间头图"
case BlockAreaDynamic:
return "动态"
case BlockAreaAlbum:
return "相册"
case BlockAreaQuickVideo:
return "小视频"
default:
return ""
}
}
// const .
const (
BlockAreaNone BlockArea = iota
BlockAreaReply
BlockAreaDanmaku
BlockAreaMessage
BlockAreaTag
BlockAreaProfile // 个人资料
BlockAreaArchive
BlockAreaMusic
BlockAreaArticle
BlockAreaSpaceBanner // 空间头图
BlockAreaDynamic // 动态
BlockAreaAlbum // 相册
BlockAreaQuickVideo //小视频
)
// BlockAction .
type BlockAction uint8
const (
// BlockActionLimit 限时封禁
BlockActionLimit BlockAction = iota + 1
// BlockActionForever 永久封禁
BlockActionForever
// BlockActionAdminRemove 后台解封
BlockActionAdminRemove
// BlockActionSelfRemove 自动解封
BlockActionSelfRemove
)
// String .
func (b BlockAction) String() string {
switch b {
case BlockActionLimit:
return "限时封禁"
case BlockActionForever:
return "永久封禁"
case BlockActionAdminRemove:
return "后台解封"
case BlockActionSelfRemove:
return "自动解封"
default:
return ""
}
}
// BlockInfo 封禁信息
type BlockInfo struct {
MID int64 `json:"mid"`
Nickname string `json:"nickname"`
Username string `json:"username"` // 注册生成时不可更改的username
Tel string `json:"tel"`
TelStatus int32 `json:"tel_status"`
Mail string `json:"mail"` // 绑定的邮箱
Level int32 `json:"level"`
SpyScore int8 `json:"spy_score"`
FigureRank int8 `json:"figure_rank"`
RegTime int64 `json:"reg_time"`
BlockStatus BlockStatus `json:"block_status"` // blockStatus 封禁状态 0. 未封禁 1. 永久封禁 2. 限时封禁
BlockCount int `json:"block_count"`
}
// ParseStatus .
func (b *BlockInfo) ParseStatus(db *DBUser) {
switch db.Status {
case BlockStatusCredit:
b.BlockStatus = BlockStatusLimit
default:
b.BlockStatus = db.Status
}
}
// BlockHistory 封禁历史
type BlockHistory struct {
Type BlockMgrType `json:"type"`
Operator string `json:"operator"` // 操作人
Reason string `json:"reason"` // 封禁原因
Action BlockAction `json:"action"` // 操作类型
ActionTime int64 `json:"action_time"` // 操作时间
RemoveTime int64 `json:"remove_time"` // 解封时间
Comment string `json:"comment"`
}
// BlockDetail blockDetail.
type BlockDetail struct {
Status BlockStatus `json:"status"`
Total int `json:"total"`
History []*BlockHistory `json:"history"`
}
// ParseDB .
func (b *BlockHistory) ParseDB(data *DBHistory) {
switch data.Action {
case BlockActionForever, BlockActionLimit:
switch data.Source {
case BlockSourceSys, BlockSourceManager:
b.Type = BlockMgrTypeSys
default:
b.Type = BlockMgrTypeCredit
}
case BlockActionSelfRemove, BlockActionAdminRemove:
b.Type = BlockMgrTypeRemove
}
b.Operator = data.AdminName
if data.Area.String() == "" {
b.Reason = data.Reason
} else {
b.Reason = data.Area.String() + " - " + data.Reason
}
b.Action = data.Action
b.ActionTime = data.StartTime.Unix()
if b.Action == BlockActionLimit {
b.RemoveTime = data.StartTime.Add(time.Second * time.Duration(data.Duration)).Unix()
}
b.Comment = data.Comment
}
// BlockMessage 通知消息体
type BlockMessage struct {
MID int64 `json:"mid"` // 用户mid
Area BlockArea `json:"area"` // BlockArea 封禁类型 1. 小黑屋(小黑屋和manager后台封禁) 2. 系统封禁(反作弊及监控系统上报) 3.解封 (所有后台,用户前台自助的解封)
Status BlockStatus `json:"status"` // blockStatus 封禁状态 0. 未封禁 1. 永久封禁 2. 限时封禁
}
// BlockMgrType mgr后台用
type BlockMgrType uint8
// BlockType enum
const (
BlockMgrTypeCredit = iota + 1
BlockMgrTypeSys
BlockMgrTypeRemove
)
// BlockMgrSource mgr后台用
type BlockMgrSource uint8
// BlockMgrSource enum
const (
BlockMgrSourceSys = iota + 1
BlockMgrSourceCredit
)

View File

@@ -0,0 +1,8 @@
package block
// AccountNotify .
type AccountNotify struct {
UID int64 `json:"mid"`
Type string `json:"type"`
Action string `json:"action"`
}

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"bom.go",
"discard_go15.go",
],
importpath = "go-common/app/admin/main/member/model/bom",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_xtest",
srcs = ["bom_test.go"],
tags = ["automanaged"],
deps = [
"//app/admin/main/member/model/bom:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,39 @@
// Package bom is used to clean up UTF-8 Byte Order Marks.
package bom
import (
"bufio"
"io"
)
const (
bom0 = 0xef
bom1 = 0xbb
bom2 = 0xbf
)
// Clean returns b with the 3 byte BOM stripped off the front if it is present.
// If the BOM is not present, then b is returned.
func Clean(b []byte) []byte {
if len(b) >= 3 &&
b[0] == bom0 &&
b[1] == bom1 &&
b[2] == bom2 {
return b[3:]
}
return b
}
// NewReader returns an io.Reader that will skip over initial UTF-8 byte order marks.
func NewReader(r io.Reader) io.Reader {
buf := bufio.NewReader(r)
b, err := buf.Peek(3)
if err != nil {
// not enough bytes
return buf
}
if b[0] == bom0 && b[1] == bom1 && b[2] == bom2 {
discardBytes(buf, 3)
}
return buf
}

View File

@@ -0,0 +1,80 @@
package bom_test
import (
"bytes"
"io/ioutil"
"testing"
"go-common/app/admin/main/member/model/bom"
"github.com/stretchr/testify/assert"
)
var testCases = []struct {
Input []byte
Expected []byte
}{
{
Input: nil,
Expected: nil,
},
{
Input: []byte{},
Expected: []byte{},
},
{
Input: []byte{0xef},
Expected: []byte{0xef},
},
{
Input: []byte{0xef, 0xbb},
Expected: []byte{0xef, 0xbb},
},
{
Input: []byte{0xef, 0xbb, 0xbf},
Expected: []byte{},
},
{
Input: []byte{0xef, 0xbb, 0xbf, 0x41, 0x42, 0x43},
Expected: []byte{0x41, 0x42, 0x43},
},
{
Input: []byte{0xef, 0xbb, 0x41, 0x42, 0x43},
Expected: []byte{0xef, 0xbb, 0x41, 0x42, 0x43},
},
{
Input: []byte{0xef, 0x41, 0x42, 0x43},
Expected: []byte{0xef, 0x41, 0x42, 0x43},
},
{
Input: []byte{0x41, 0x42, 0x43},
Expected: []byte{0x41, 0x42, 0x43},
},
}
func TestClean(t *testing.T) {
assert := assert.New(t)
for _, tc := range testCases {
output := bom.Clean(tc.Input)
assert.Equal(tc.Expected, output)
}
}
func TestReader(t *testing.T) {
assert := assert.New(t)
for _, tc := range testCases {
// An input value of nil works differently to the Clean function.
// In this case it results in an empty buffer, not nil.
expected := tc.Expected
if tc.Input == nil {
expected = []byte{}
}
r1 := bytes.NewReader(tc.Input)
r2 := bom.NewReader(r1)
output, err := ioutil.ReadAll(r2)
assert.NoError(err)
assert.Equal(expected, output)
}
}

View File

@@ -0,0 +1,12 @@
// +build !go1.5
package bom
import "bufio"
func discardBytes(buf *bufio.Reader, n int) {
// cannot use the buf.Discard method as it was introduced in Go 1.5
for i := 0; i < n; i++ {
buf.ReadByte()
}
}

View File

@@ -0,0 +1,10 @@
// +build go1.5
package bom
import "bufio"
func discardBytes(buf *bufio.Reader, n int) {
// the Discard method was introduced in Go 1.5
buf.Discard(n)
}

View File

@@ -0,0 +1,306 @@
package model
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"math/rand"
"net/url"
"path/filepath"
"strconv"
"strings"
"time"
xtime "go-common/library/time"
)
const (
_expMulti = 100
level1 = 1
level2 = 200
level3 = 1500
level4 = 4500
level5 = 10800
level6 = 28800
levelMax = -1
_URLNoFace = "http://static.hdslb.com/images/member/noface.gif"
// ManagerLogID manager log id.
ManagerLogID = 121
//FaceCheckLogID is.
FaceCheckLogID = 161
// bfs facepri bucket
_facepriKeyID = "8923aff2e1124bb2"
_facepriKeySecret = "b237e8927823cc2984aee980123cb0"
)
// base audit type const.
const (
BaseAuditType = iota
BaseAuditTypeFace
BaseAuditTypeSign
BaseAuditTypeName
)
// Base is.
type Base struct {
Mid int64 `json:"mid" gorm:"column:mid"`
Name string `json:"name" gorm:"column:name"`
Sex int64 `json:"sex" gorm:"column:sex"`
Face string `json:"face" gorm:"column:face"`
Sign string `json:"sign" gorm:"column:sign"`
Rank int64 `json:"rank" gorm:"column:rank"`
Birthday xtime.Time `json:"birthday" gorm:"column:birthday"`
CTime xtime.Time `json:"ctime" gorm:"column:ctime"`
MTime xtime.Time `json:"mtime" gorm:"column:mtime"`
}
// RandFaceURL get face URL
func (b *Base) RandFaceURL() {
if b.Face == "" {
b.Face = _URLNoFace
return
}
b.Face = fmt.Sprintf("http://i%d.hdslb.com%s", rand.Int63n(3), b.Face)
}
// Detail is.
type Detail struct {
Mid int64 `json:"mid" gorm:"column:mid"`
Birthday xtime.Time `json:"birthday" gorm:"column:birthday"`
Place int64 `json:"place" gorm:"column:place"`
Marital int64 `json:"marital" gorm:"column:marital"`
Dating int64 `json:"dating" gorm:"column:dating"`
Tags string `json:"tags" gorm:"column:tags"`
CTime xtime.Time `json:"ctime" gorm:"column:ctime"`
MTime xtime.Time `json:"mtime" gorm:"column:mtime"`
}
// Exp is.
type Exp struct {
Mid int64 `json:"mid" gorm:"column:mid"`
Exp int64 `json:"exp" gorm:"column:exp"`
Flag uint32 `json:"flag" gorm:"column:flag"`
AddTime xtime.Time `json:"addtime" gorm:"column:addtime"`
CTime xtime.Time `json:"ctime" gorm:"column:ctime"`
MTime xtime.Time `json:"mtime" gorm:"column:mtime"`
}
// Moral is.
type Moral struct {
Mid int64 `json:"mid" gorm:"column:mid"`
Moral int64 `json:"moral" gorm:"column:moral"`
Added int64 `json:"added" gorm:"column:added"`
Deducted int64 `json:"deducted" gorm:"column:deducted"`
LastRecoverDate xtime.Time `json:"last_recover_date" gorm:"colum:last_recover_date"`
CTime xtime.Time `json:"ctime" gorm:"column:ctime"`
MTime xtime.Time `json:"mtime" gorm:"column:mtime"`
}
// UserAddit is.
type UserAddit struct {
ID int64 `json:"id" gorm:"column:id"`
Mid int64 `json:"mid" gorm:"column:mid"`
FaceReject int64 `json:"face_reject" gorm:"colum:face_reject"`
ViolationCount int64 `json:"violation_count" gorm:"colum:violation_count"`
CTime xtime.Time `json:"ctime" gorm:"column:ctime"`
MTime xtime.Time `json:"mtime" gorm:"column:mtime"`
Remark string `json:"remark" gorm:"column:remark"`
}
// Level is.
type Level struct {
CurrentLevel int32 `json:"current_level"`
CurrentMin int32 `json:"current_min"`
CurrentExp int32 `json:"current_exp"`
NextExp int32 `json:"next_exp"`
}
// Profile is.
type Profile struct {
Base Base `json:"base"`
Detail Detail `json:"detail"`
Exp Exp `json:"exp"`
Level Level `json:"level"`
Moral Moral `json:"moral"`
Official Official `json:"official"`
Coin Coin `json:"coin"`
Addit UserAddit `json:"addit"`
Realanme Realname `json:"realname"`
}
// Coin is.
type Coin struct {
Coins float64 `json:"coins"`
}
// UserLog is.
type UserLog struct {
Mid int64 `json:"mid"`
IP string `json:"ip"`
TS int64 `json:"ts"`
Content map[string]string `json:"content"`
}
// FaceRecord is.
type FaceRecord struct {
ID int64 `json:"id"`
Mid int64 `json:"mid"`
ModifyTime xtime.Time `json:"modify_time"`
ApplyTime xtime.Time `json:"apply_time"`
NewFace string `json:"new_face"`
OldFace string `json:"old_face"`
Operator string `json:"operator"`
Status int8 `json:"status"`
}
// BaseReview is.
type BaseReview struct {
Base
Addit UserAddit `json:"addit"`
Logs []AuditLog `json:"logs"`
}
// AddExpMsg is.
type AddExpMsg struct {
Event string `json:"event,omitempty"`
Mid int64 `json:"mid,omitempty"`
IP string `json:"ip,omitempty"`
Ts int64 `json:"ts,omitempty"`
}
// BuildFaceURL is.
func BuildFaceURL(raw string) string {
if raw == "" {
return _URLNoFace
}
ori, err := url.Parse(raw)
if err != nil {
return raw
}
if ori.Path == "/images/member/noface.gif" {
return _URLNoFace
}
if strings.HasPrefix(ori.Path, "/bfs/facepri") {
token := authorize(_facepriKeyID, _facepriKeySecret, "GET", "facepri", filepath.Base(ori.Path), time.Now().Unix())
p := url.Values{}
p.Set("token", token)
ori.RawQuery = p.Encode()
}
if ori.Hostname() == "" {
ori.Host = fmt.Sprintf("i%d.hdslb.com", rand.Int63n(3))
ori.Scheme = "http"
}
return ori.String()
}
// authorize returns authorization for upload file to bfs
func authorize(key, secret, method, bucket, filename string, expire int64) string {
content := fmt.Sprintf("%s\n%s\n%s\n%d\n", method, bucket, filename, expire)
mac := hmac.New(sha1.New, []byte(secret))
mac.Write([]byte(content))
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return fmt.Sprintf("%s:%s:%d", key, signature, expire)
}
// BuildFaceURL is.
func (fr *FaceRecord) BuildFaceURL() {
fr.NewFace = BuildFaceURL(fr.NewFace)
fr.OldFace = BuildFaceURL(fr.OldFace)
}
// ParseStatus is.
func ParseStatus(s string) int8 {
st, _ := strconv.ParseInt(s, 10, 8)
return int8(st)
}
// ParseLogTime is.
func ParseLogTime(ts string) (xt xtime.Time, err error) {
var (
t time.Time
)
if t, err = time.ParseInLocation("2006-01-02 15:04:05", ts, time.Local); err != nil {
return
}
xt.Scan(t)
return
}
// ParseApplyTime is.
func ParseApplyTime(ts string) xtime.Time {
ti, _ := strconv.ParseInt(ts, 10, 64)
return xtime.Time(ti)
}
// NewProfile is.
func NewProfile() *Profile {
return &Profile{}
}
// FaceRecordList is
type FaceRecordList []*FaceRecord
// Filter is
func (frl FaceRecordList) Filter(con func(*FaceRecord) bool) FaceRecordList {
res := make(FaceRecordList, 0)
for _, fr := range frl {
if con(fr) {
res = append(res, fr)
}
}
return res
}
// Paginate is
func (frl FaceRecordList) Paginate(skip int, size int) FaceRecordList {
if skip > len(frl) {
skip = len(frl)
}
end := skip + size
if end > len(frl) {
end = len(frl)
}
return frl[skip:end]
}
// FromExp is.
func (lv *Level) FromExp(e *Exp) {
exp := e.Exp / _expMulti
switch {
case exp < level1:
lv.CurrentLevel = 0
lv.CurrentMin = 0
lv.NextExp = level1
case exp < level2:
lv.CurrentLevel = 1
lv.CurrentMin = level1
lv.NextExp = level2
case exp < level3:
lv.CurrentLevel = 2
lv.CurrentMin = level2
lv.NextExp = level3
case exp < level4:
lv.CurrentLevel = 3
lv.CurrentMin = level3
lv.NextExp = level4
case exp < level5:
lv.CurrentLevel = 4
lv.CurrentMin = level4
lv.NextExp = level5
case exp < level6:
lv.CurrentLevel = 5
lv.CurrentMin = level5
lv.NextExp = level6
default:
lv.CurrentLevel = 6
lv.CurrentMin = level6
lv.NextExp = levelMax
}
lv.CurrentExp = int32(exp)
}

View File

@@ -0,0 +1,15 @@
package model
import (
"net/url"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBuildFaceURL(t *testing.T) {
face := BuildFaceURL("/bfs/facepri/4a259f715b63157f24f76521231480438058436e.jpg")
u, err := url.Parse(face)
assert.Nil(t, err)
assert.NotNil(t, u.Query().Get("token"))
}

View File

@@ -0,0 +1,19 @@
package model
import (
xtime "go-common/library/time"
)
// Monitor is.
type Monitor struct {
ID int64 `json:"id" gorm:"column:id"`
Mid int64 `json:"mid" gorm:"column:mid"`
Operator string `json:"operator" gorm:"column:operator"`
Remark string `json:"remark" gorm:"column:remark"`
CTime xtime.Time `json:"ctime" gorm:"column:ctime"`
MTime xtime.Time `json:"mtime" gorm:"column:mtime"`
IsDeleted bool `json:"is_deleted" gorm:"column:is_deleted"`
// 昵称,后期拼进来
Name string `json:"name" gorm:"-"`
}

View File

@@ -0,0 +1,173 @@
package model
import (
"encoding/json"
xtime "go-common/library/time"
)
// official role const.
const (
OfficialRoleUnauth = iota
OfficialRoleUp
OfficialRoleIdentify
OfficialRoleBusiness
OfficialRoleGov
OfficialRoleMedia
OfficialRoleOther
)
// OfficialRoleName official role name.
func OfficialRoleName(role int8) string {
switch role {
case OfficialRoleUnauth:
return "未认证"
case OfficialRoleUp:
return "UP主认证"
case OfficialRoleIdentify:
return "身份认证"
case OfficialRoleBusiness:
return "企业认证"
case OfficialRoleGov:
return "政府认证"
case OfficialRoleMedia:
return "媒体认证"
case OfficialRoleOther:
return "其他认证"
}
return ""
}
// official state const.
const (
OfficialStateWait = iota
OfficialStatePass
OfficialStateNoPass
OfficialStateReWait
)
// OfficialStateName official state name.
func OfficialStateName(state int8) string {
switch state {
case OfficialStateWait:
return "待审核"
case OfficialStatePass:
return "审核通过"
case OfficialStateNoPass:
return "审核未通过"
case OfficialStateReWait:
return "待重新审核"
}
return ""
}
// all
var (
AllRoles = []int8{
OfficialRoleUnauth,
OfficialRoleUp,
OfficialRoleIdentify,
OfficialRoleBusiness,
OfficialRoleGov,
OfficialRoleMedia,
OfficialRoleOther,
}
AllStates = []int8{
OfficialStateWait,
OfficialStatePass,
OfficialStateNoPass,
OfficialStateReWait,
}
)
// Official is.
type Official struct {
ID int64 `json:"id" gorm:"column:id"`
Mid int64 `json:"mid" gorm:"column:mid"`
Role int8 `json:"role" gorm:"column:role"`
Title string `json:"title" gorm:"column:title"`
Desc string `json:"desc" gorm:"column:description"`
CTime xtime.Time `json:"ctime" gorm:"column:ctime"`
MTime xtime.Time `json:"mtime" gorm:"column:mtime"`
// 后台展示需求,需要查 official doc 对应的昵称
Name string `json:"name" gorm:"-"`
}
// OfficialDoc is.
type OfficialDoc struct {
ID int64 `json:"id" gorm:"column:id"`
Mid int64 `json:"mid" gorm:"column:mid"`
Name string `json:"name" gorm:"column:name"`
State int8 `json:"state" gorm:"column:state"`
Role int8 `json:"role" gorm:"column:role"`
Title string `json:"title" gorm:"column:title"`
Desc string `json:"desc" gorm:"column:description"`
Uname string `json:"uname" gorm:"column:uname"`
Extra string `json:"-" gorm:"column:extra"`
IsInternal bool `json:"is_internal" gorm:"column:is_internal"`
RejectReason string `json:"reject_reason" gorm:"column:reject_reason"`
SubmitSource string `json:"submit_source" gorm:"column:submit_source"`
SubmitTime xtime.Time `json:"submit_time" gorm:"column:submit_time"`
CTime xtime.Time `json:"ctime" gorm:"column:ctime"`
MTime xtime.Time `json:"mtime" gorm:"column:mtime"`
*OfficialExtra `gorm:"-"`
}
// OfficialExtra extra.
type OfficialExtra struct {
Realname int8 `json:"realname" gorm:"-"`
Operator string `json:"operator" gorm:"-"`
Telephone string `json:"telephone" gorm:"-"`
Email string `json:"email" gorm:"-"`
Address string `json:"address" gorm:"-"`
Company string `json:"company" gorm:"-"`
CreditCode string `json:"credit_code" gorm:"-"` // 社会信用代码
Organization string `json:"organization" gorm:"-"` // 政府或组织名称
OrganizationType string `json:"organization_type" gorm:"-"` // 政府或机构类型
BusinessLicense string `json:"business_license" gorm:"-"` // 企业营业执照
BusinessScale string `json:"business_scale" gorm:"-"` // 企业规模
BusinessLevel string `json:"business_level" gorm:"-"` // 行政级别
BusinessAuth string `json:"business_auth" gorm:"-"` // 企业授权函
Supplement string `json:"supplement" gorm:"-"` // 其他补充材料
Professional string `json:"professional" gorm:"-"` // 专业资质
Identification string `json:"identification" gorm:"-"` // 身份证明
OfficalSite string `json:"official_site" gorm:"-"` // 官方站点
RegisteredCapital string `json:"registered_capital" gorm:"-"` // 注册资本
}
// OfficialDocAddit .
type OfficialDocAddit struct {
Mid int64 `json:"mid" gorm:"mid"`
Property string `json:"property" gorm:"property"`
Vstring string `json:"vstring" gorm:"vstring"`
}
// ParseExtra parse extra.
func (oc *OfficialDoc) ParseExtra() {
oe := &OfficialExtra{}
if len(oc.Extra) > 0 {
json.Unmarshal([]byte(oc.Extra), oe)
}
oc.OfficialExtra = oe
}
// Validate is.
func (oc *OfficialDoc) Validate() bool {
if oc.Mid <= 0 ||
oc.Name == "" ||
oc.Role <= 0 ||
oc.Title == "" {
return false
}
return true
}
// String is
func (oe *OfficialExtra) String() string {
bs, _ := json.Marshal(oe)
if len(bs) == 0 {
bs = []byte("{}")
}
return string(bs)
}

View File

@@ -0,0 +1,25 @@
package model
// Page is.
type Page struct {
Num int `json:"num"`
Size int `json:"size"`
Total int `json:"total"`
}
// CommonPagination is.
type CommonPagination struct {
Page Page `json:"page"`
}
// MemberPagination is.
type MemberPagination struct {
Members interface{} `json:"members"`
*CommonPagination
}
// FaceRecordPagination is.
type FaceRecordPagination struct {
Records interface{} `json:"records"`
*CommonPagination
}

View File

@@ -0,0 +1,343 @@
package model
import (
"go-common/app/admin/main/member/model/block"
xtime "go-common/library/time"
)
// ArgMid is.
type ArgMid struct {
Mid int64 `form:"mid" validate:"min=1,required"`
}
// ArgMids is.
type ArgMids struct {
Mid []int64 `form:"mid,split" validate:"dive,gt=0"`
Operator string `form:"operator"`
OperatorID int64 `form:"operator_id"`
}
// ArgExpSet is.
type ArgExpSet struct {
Mid int64 `form:"mid" validate:"min=1"`
Exp float64 `form:"exp" validate:"required"`
Reason string `form:"reason" validate:"required"`
Operator string `form:"operator"`
OperatorID int64 `form:"operator_id"`
IP string `form:"ip"`
}
// ArgMoralSet is.
type ArgMoralSet struct {
Mid int64 `form:"mid" validate:"min=1"`
Moral float64 `form:"moral" validate:"required"`
Reason string `form:"reason" validate:"required"`
Operator string `form:"operator"`
OperatorID int64 `form:"operator_id"`
IP string `form:"ip"`
}
// ArgRankSet is.
type ArgRankSet struct {
Mid int64 `form:"mid" validate:"min=1"`
Rank int64 `form:"rank" validate:"required"`
Reason string `form:"reason" validate:"required"`
Operator string `form:"operator"`
OperatorID int64 `form:"operator_id"`
IP string `form:"ip"`
}
// ArgCoinSet is.
type ArgCoinSet struct {
Mid int64 `form:"mid" validate:"min=1"`
Coins float64 `form:"coins" validate:"required"`
Reason string `form:"reason" validate:"required"`
Operator string `form:"operator"`
OperatorID int64 `form:"operator_id"`
IP string `form:"ip"`
}
// ArgAdditRemarkSet is.
type ArgAdditRemarkSet struct {
Mid int64 `form:"mid" validate:"min=1"`
Remark string `form:"remark"`
}
// ArgBaseReview is.
type ArgBaseReview struct {
Mid []int64 `form:"mid,split"`
StartMid int64 `form:"start_mid" validate:"min=0"`
EndMid int64 `form:"end_mid" validate:"min=0"`
}
// Mids mid list.
func (amr *ArgBaseReview) Mids() []int64 {
mids := amr.Mid
for i := amr.StartMid; i <= amr.EndMid; i++ {
mids = append(mids, i)
}
return mids
}
// ArgList is.
type ArgList struct {
Mid int64 `form:"mid"`
Keyword string `form:"keyword"`
PN int64 `form:"pn"`
PS int64 `form:"ps"`
}
// ArgOfficial is.
type ArgOfficial struct {
Mid int64 `form:"mid"`
Role []int64 `form:"role,split"`
STime xtime.Time `form:"stime"`
ETime xtime.Time `form:"etime"`
Pn int `form:"pn"`
Ps int `form:"ps"`
}
// ArgOfficialDoc is.
type ArgOfficialDoc struct {
Mid int64 `form:"mid"`
Role []int64 `form:"role,split"`
State []int64 `form:"state,split"`
STime xtime.Time `form:"stime"`
ETime xtime.Time `form:"etime"`
Uname string `form:"uname"`
Pn int `form:"pn"`
Ps int `form:"ps"`
}
// ArgOfficialAudit is.
type ArgOfficialAudit struct {
Mid int64 `form:"mid" validate:"min=1"`
State int8 `form:"state" validate:"min=1"`
UID int64 `form:"uid" validate:"min=1"`
Uname string `form:"uname" validate:"min=1"`
Reason string `form:"reason"`
Source string `form:"source"`
IsInternal bool `form:"is_internal"`
}
// ArgOfficialEdit is.
type ArgOfficialEdit struct {
Mid int64 `form:"mid" validate:"min=1,required"`
Role int8 `form:"role" validate:"min=0"`
Name string `form:"name" validate:"gt=1,required"`
Title string `form:"title" validate:"gt=1,required"`
Desc string `form:"desc"`
// extra
Telephone string `form:"telephone"`
Email string `form:"email"`
Address string `form:"address"`
Supplement string `form:"supplement"`
Company string `form:"company"`
Operator string `form:"operator"`
CreditCode string `form:"credit_code"`
Organization string `form:"organization"`
OrganizationType string `form:"organization_type"`
BusinessLicense string `form:"business_license"`
BusinessScale string `form:"business_scale"`
BusinessLevel string `form:"business_level"`
BusinessAuth string `form:"business_auth"`
OfficalSite string `form:"official_site"`
RegisteredCapital string `form:"registered_capital"`
SendMessage bool `form:"send_msg"`
MessageTitle string `form:"msg_title"`
MessageContent string `form:"msg_content"`
UID int64 `form:"uid" validate:"min=1"`
Uname string `form:"uname" validate:"min=1"`
IsInternal bool `form:"is_internal"`
}
// ArgOfficialSubmit arg submit official doc
type ArgOfficialSubmit struct {
Mid int64 `form:"mid"`
Name string `form:"name"`
Role int8 `form:"role"`
Title string `form:"title"`
Desc string `form:"desc"`
// extra
Realname int8 `form:"realname"`
Operator string `form:"operator"`
Telephone string `form:"telephone"`
Email string `form:"email"`
Address string `form:"address"`
Company string `form:"company"`
CreditCode string `form:"credit_code"` // 社会信用代码
Organization string `form:"organization"` // 政府或组织名称
OrganizationType string `form:"organization_type"` // 组织或机构类型
BusinessLicense string `form:"business_license"` // 企业营业执照
BusinessScale string `form:"business_scale"` // 企业规模
BusinessLevel string `form:"business_level"` // 企业登记
BusinessAuth string `form:"business_auth"` // 企业授权函
Supplement string `form:"supplement"` // 其他补充材料
Professional string `form:"professional"` // 专业资质
Identification string `form:"identification"` // 身份证明
OfficalSite string `form:"official_site"`
RegisteredCapital string `form:"registered_capital"`
UID int64 `form:"uid"`
Uname string `form:"uname"`
IsInternal bool `form:"is_internal"`
SubmitSource string `form:"submit_source"`
}
// ArgFaceHistory is.
type ArgFaceHistory struct {
Mid int64 `form:"mid"`
Operator string `form:"operator"`
Status []int8 `form:"status,split"`
STime xtime.Time `form:"stime" validate:"min=0"`
ETime xtime.Time `form:"etime" validate:"min=0"`
PS int `form:"ps" validate:"min=0,max=50"`
PN int `form:"pn" validate:"min=0"`
}
// ArgMonitor is.
type ArgMonitor struct {
Mid int64 `form:"mid"`
Pn int `form:"pn"`
Ps int `form:"ps"`
}
// ArgAddMonitor is.
type ArgAddMonitor struct {
Mid int64 `form:"mid" validate:"min=1,required"`
Operator string `form:"operator"`
OperatorID int64 `form:"operator_id"`
Remark string `form:"remark"`
}
// ArgDelMonitor is.
type ArgDelMonitor struct {
Mid int64 `form:"mid" validate:"min=1,required"`
Operator string `form:"operator"`
OperatorID int64 `form:"operator_id"`
Remark string `form:"remark"`
}
// ArgReviewList is.
type ArgReviewList struct {
Mid int64 `form:"mid"`
Property []int8 `form:"property,split"`
Operator string `form:"operator"`
State []int8 `form:"state,split"`
IsDesc bool `form:"is_desc"`
IsMonitor bool `form:"is_monitor"`
ForceDB bool `form:"force_db"`
STime xtime.Time `form:"stime" validate:"min=0"`
ETime xtime.Time `form:"etime" validate:"min=0"`
Ps int `form:"ps" validate:"min=0,max=50"`
Pn int `form:"pn" validate:"min=0"`
}
// ArgReviewAudit is.
type ArgReviewAudit struct {
ID []int64 `form:"id,split" validate:"dive,gt=0"`
State int8 `form:"state" validate:"min=1"`
Operator string `form:"operator"`
OperatorID int64 `form:"operator_id"`
Remark string `form:"remark"`
BlockUser bool `form:"block_user"`
//for block
ArgBatchBlock
}
// ArgBatchBlock .
type ArgBatchBlock struct {
Source block.BlockMgrSource `form:"block_source"`
Area block.BlockArea `form:"block_area"`
Reason string `form:"block_reason"`
Comment string `form:"block_comment"`
Action block.BlockAction `form:"block_action"`
Duration int64 `form:"block_duration"` // 单位:天
Notify bool `form:"block_notify"`
}
// Validate .
func (p *ArgBatchBlock) Validate() bool {
// p.MIDs = intsSet(p.MIDs)
// if len(p.MIDs) == 0 || len(p.MIDs) > 200 {
// return false
// }
// if p.AdminID <= 0 {
// return false
// }
// if p.AdminName == "" {
// return false
// }
if p.Source != block.BlockMgrSourceSys && p.Source != block.BlockMgrSourceCredit {
return false
}
if !p.Area.Contain() {
return false
}
if p.Comment == "" {
return false
}
if p.Action != block.BlockActionForever && p.Action != block.BlockActionLimit {
return false
}
if p.Action == block.BlockActionLimit {
if p.Duration <= 0 {
return false
}
}
return true
}
// ArgReview is.
type ArgReview struct {
ID int64 `form:"id" validate:"min=1"`
}
// ArgPubExpMsg is.
type ArgPubExpMsg struct {
Event string `form:"event" validate:"min=1,required"`
Mid int64 `form:"mid" validate:"min=1,required"`
IP string `form:"ip"`
Ts int64 `form:"ts"`
}
// Mode is.
func (a *ArgFaceHistory) Mode() string {
if a.Mid > 0 && a.Operator != "" {
return "op"
}
if a.Mid > 0 {
return "mid"
}
return "op"
}
// ArgBatchFormal is
type ArgBatchFormal struct {
FileData []byte
Operator string `form:"operator"`
OperatorID int64 `form:"operator_id"`
}
// ArgRealnameSubmit is
type ArgRealnameSubmit struct {
Mid int64 `form:"mid" validate:"required"`
Realname string `form:"realname" validate:"required"`
CardType int8 `form:"card_type"`
CardNum string `form:"card_num" validate:"required"`
Country int16 `form:"country"`
FrontImageToken string `form:"front_image_token" validate:"required"`
BackImageToken string `form:"back_image_token" validate:"required"`
HandImageToken string `form:"hand_image_token"`
Operator string `form:"operator" validate:"required"`
OperatorID int64 `form:"operator_id" validate:"required"`
Remark string `form:"remark" validate:"required"`
}

View File

@@ -0,0 +1,612 @@
package model
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"strconv"
"strings"
"time"
"go-common/app/admin/main/member/conf"
memmdl "go-common/app/service/main/member/model"
"go-common/library/log"
xtime "go-common/library/time"
"github.com/pkg/errors"
)
// realname conf var
var (
RealnameSalt = "biliidentification@#$%^&*()(*&^%$#"
RealnameImgPrefix = "/idenfiles/"
RealnameImgSuffix = ".txt"
LogActionRealnameUnbind = "realname_unbind"
LogActionRealnameBack = "realname_back"
LogActionRealnameSubmit = "realname_submit"
RealnameManagerLogID = 251
)
type realnameChannel string
// RealnameChannle
const (
ChannelMain realnameChannel = "main"
ChannelAlipay realnameChannel = "alipay"
)
func (ch realnameChannel) DBChannel() uint8 {
switch ch {
case ChannelMain:
return 0
case ChannelAlipay:
return 1
}
return 0
}
type realnameAction string
// RealnameAction
const (
RealnameActionPass realnameAction = "pass"
RealnameActionReject realnameAction = "reject"
)
// RealnameCardType is.
type RealnameCardType string
const (
cardTypeIdentityCard RealnameCardType = "identity_card"
cardTypeForeignPassport RealnameCardType = "foreign_passport"
//Mainland Travel Permit for Hong Kong and Macao Residents
cardTypeHongkongMacaoPermit RealnameCardType = "hongkong_macao_travel_permit"
//Mainland travel permit for Taiwan residents
cardTypeTaiwanPermit RealnameCardType = "taiwan_travel_permit"
cardTypeChinaPassport RealnameCardType = "china_passport"
//Foreigner's Permanent Residence Card
cardTypeForeignerPermanentResidenceCard RealnameCardType = "foreigner_permanent_residence_card"
cardTypeForeignIdentityCard RealnameCardType = "foreign_identity_card"
)
// RealnameApplyStatus is.
type RealnameApplyStatus string
// RealnameApplyStatus
const (
RealnameApplyStateAll RealnameApplyStatus = "all"
RealnameApplyStatePending RealnameApplyStatus = "pending"
RealnameApplyStatePassed RealnameApplyStatus = "passed"
RealnameApplyStateRejective RealnameApplyStatus = "rejective"
RealnameApplyStateNone RealnameApplyStatus = "none"
)
// DBStatus is.
func (r RealnameApplyStatus) DBStatus() int {
switch r {
case RealnameApplyStatePending:
return 0
case RealnameApplyStatePassed:
return 1
case RealnameApplyStateRejective:
return 2
default:
return -1
}
}
// ArgRealnameList is.
type ArgRealnameList struct {
Channel realnameChannel `form:"channel" validate:"required"`
MID int64 `form:"mid"`
Card string `form:"card"`
CardType RealnameCardType `form:"card_type"`
Country int `form:"country"`
OPName string `form:"op_name"`
TSFrom int64 `form:"ts_from"`
TSTo int64 `form:"ts_to"`
State RealnameApplyStatus `form:"state"`
PS int `form:"ps"`
PN int `form:"pn"`
IsDesc bool `form:"is_desc"`
}
// DBCardType return card_type store in db
func (a *ArgRealnameList) DBCardType() int {
switch a.CardType {
case cardTypeIdentityCard:
return 0
case cardTypeForeignPassport:
return 1
case cardTypeHongkongMacaoPermit:
return 2
case cardTypeTaiwanPermit:
return 3
case cardTypeChinaPassport:
return 4
case cardTypeForeignerPermanentResidenceCard:
return 5
case cardTypeForeignIdentityCard:
return 6
default:
log.Warn("ArgRealnameList : %+v , unknown CardType", a)
return -1
}
}
// DBCountry return country store in db
func (a *ArgRealnameList) DBCountry() int {
if a.CardType == "" {
return -1
}
return a.Country
}
// DBState return state store in db
func (a *ArgRealnameList) DBState() int {
switch a.State {
case RealnameApplyStateAll:
return -1
case RealnameApplyStatePending:
return 0
case RealnameApplyStatePassed:
return 1
case RealnameApplyStateRejective:
return 2
case RealnameApplyStateNone:
return 3
default:
log.Warn("ArgRealnameList : %+v , unknown State", a)
return 0
}
}
// ArgRealnamePendingList is.
type ArgRealnamePendingList struct {
Channel realnameChannel `form:"channel" validate:"required"`
PS int `form:"ps"`
PN int `form:"pn"`
}
// ArgRealnameAuditApply is.
type ArgRealnameAuditApply struct {
ID int64 `form:"id" validate:"required"`
Channel realnameChannel `form:"channel" validate:"required"`
Action realnameAction `form:"action" validate:"required"`
Reason string `form:"reason"`
}
// DBChannel return channel store in db
func (a *ArgRealnameAuditApply) DBChannel() int {
switch a.Channel {
case ChannelMain:
return 0
case ChannelAlipay:
return 1
default:
log.Warn("ArgRealnameAuditApply : %+v , unknown Channel", a)
return 0
}
}
// ArgRealnameReasonList is.
type ArgRealnameReasonList struct {
PS int `form:"ps"`
PN int `form:"pn"`
}
// ArgRealnameSetReason is.
type ArgRealnameSetReason struct {
Reasons []string `form:"reasons,split"`
}
// ArgRealnameImage is.
type ArgRealnameImage struct {
Token string `form:"token" validate:"required"`
}
// ArgRealnameImagePreview is.
type ArgRealnameImagePreview struct {
ArgRealnameImage
BorderSize uint `form:"border_size"` // 图片最大边长度(缩放后)
}
// ArgRealnameSearchCard is.
type ArgRealnameSearchCard struct {
Cards []string `form:"cards,split" validate:"required"`
CardType int `form:"card_type"`
Country int `form:"card_type"`
}
// RespRealnameApply is.
type RespRealnameApply struct {
ID int64 `json:"id"`
Channel realnameChannel `json:"channel"`
MID int64 `json:"mid"`
Nickname string `json:"nickname"`
Times int `json:"times"`
CardType RealnameCardType `json:"card_type"`
Country int16 `json:"country"`
Card string `json:"card"`
Realname string `json:"realname"`
Level int32 `json:"level"`
IMGIDs []int64 `json:"-"`
IMGs []string `json:"imgs"`
State RealnameApplyStatus `json:"state"`
OPName string `json:"op_name"`
OPTS int64 `json:"op_ts"`
OPReason string `json:"op_reason"`
CreateTS int64 `json:"create_ts"`
}
// ParseDBMainApply parse realname_apply from db
func (r *RespRealnameApply) ParseDBMainApply(db *DBRealnameApply) {
var err error
r.ID = db.ID
r.Channel = ChannelMain
r.MID = db.MID
r.CardType = r.convertCardType(db.CardType)
r.Country = db.Country
if db.CardNum != "" {
if r.Card, err = CardDecrypt(db.CardNum); err != nil {
log.Error("%+v", err)
}
}
r.IMGIDs = append(r.IMGIDs, db.HandIMG, db.FrontIMG, db.BackIMG)
r.Realname = db.Realname
r.State = r.ParseStatus(db.Status)
r.OPName = db.Operator
r.OPTS = db.OperatorTime.Unix()
r.OPReason = db.Remark
r.CreateTS = db.CTime.Unix()
}
// ParseDBAlipayApply parse realname_alipay_apply from db
func (r *RespRealnameApply) ParseDBAlipayApply(db *DBRealnameAlipayApply) {
var err error
r.ID = db.ID
r.Channel = ChannelAlipay
r.MID = db.MID
r.CardType = cardTypeIdentityCard // identity_card
r.Country = 0 // china
if db.Card != "" {
if r.Card, err = CardDecrypt(db.Card); err != nil {
log.Error("%+v", err)
}
}
r.ParseDBApplyIMG(db.IMG)
r.Realname = db.Realname
r.State = r.ParseStatus(db.Status)
r.OPName = "alipay"
if db.Operator != "" {
r.OPName = db.Operator
}
r.OPTS = db.OperatorTime.Unix()
r.OPReason = db.Reason
r.CreateTS = db.CTime.Unix()
}
// ParseDBApplyIMG parse apply_img from db
func (r *RespRealnameApply) ParseDBApplyIMG(token string) {
r.IMGs = append(r.IMGs, imgURL(token))
}
// ParseMember parse member info from rpc call
func (r *RespRealnameApply) ParseMember(mem *memmdl.Member) {
r.Nickname = mem.Name
r.Level = mem.LevelInfo.Cur
}
func imgURL(token string) string {
token = strings.TrimPrefix(token, "/idenfiles/")
token = strings.TrimSuffix(token, ".txt")
return fmt.Sprintf(conf.Conf.Realname.ImageURLTemplate, token)
}
// ParseStatus parse status stored in db
func (r *RespRealnameApply) ParseStatus(status int) (s RealnameApplyStatus) {
switch status {
case 0:
return RealnameApplyStatePending
case 1:
return RealnameApplyStatePassed
case 2:
return RealnameApplyStateRejective
default:
log.Warn("RespRealnameApply parse status err , unknown apply status :%d", status)
return RealnameApplyStateNone
}
}
// ConvertCardType convert card_type from db to api
func (r *RespRealnameApply) convertCardType(cardType int8) (t RealnameCardType) {
switch cardType {
case 0:
return cardTypeIdentityCard
case 1:
return cardTypeForeignPassport
case 2:
return cardTypeHongkongMacaoPermit
case 3:
return cardTypeTaiwanPermit
case 4:
return cardTypeChinaPassport
case 5:
return cardTypeForeignerPermanentResidenceCard
case 6:
return cardTypeForeignIdentityCard
default:
log.Warn("RespRealnameApply parse card type err , unknown card type :%d", cardType)
return cardTypeIdentityCard
}
}
// DBRealnameInfo is.
type DBRealnameInfo struct {
ID int64 `gorm:"column:id"`
MID int64 `gorm:"column:mid"`
Channel uint8 `gorm:"column:channel"`
Realname string `gorm:"column:realname"`
Country int16 `gorm:"column:country"`
CardType int8 `gorm:"column:card_type"`
Card string `gorm:"column:card"`
CardMD5 string `gorm:"column:card_md5"`
Status int `gorm:"column:status"`
Reason string `gorm:"column:reason"`
CTime time.Time `gorm:"column:ctime"`
MTime time.Time `gorm:"column:mtime"`
}
// TableName is...
func (d *DBRealnameInfo) TableName() string {
return "realname_info"
}
// DBRealnameApply is.
type DBRealnameApply struct {
ID int64 `gorm:"column:id"`
MID int64 `gorm:"column:mid"`
Realname string `gorm:"column:realname"`
Country int16 `gorm:"column:country"`
CardType int8 `gorm:"column:card_type"`
CardNum string `gorm:"column:card_num"`
CardMD5 string `gorm:"column:card_md5"`
HandIMG int64 `gorm:"column:hand_img"`
FrontIMG int64 `gorm:"column:front_img"`
BackIMG int64 `gorm:"column:back_img"`
Status int `gorm:"column:status"`
Operator string `gorm:"column:operator"`
OperatorID int64 `gorm:"column:operator_id"`
OperatorTime time.Time `gorm:"column:operator_time"`
Remark string `gorm:"column:remark"`
RemarkStatus int8 `gorm:"column:remark_status"`
CTime time.Time `gorm:"column:ctime"`
MTime time.Time `gorm:"column:mtime"`
}
// TableName is...
func (d *DBRealnameApply) TableName() string {
return "realname_apply"
}
// DBRealnameAlipayApply is.
type DBRealnameAlipayApply struct {
ID int64 `gorm:"column:id"`
MID int64 `gorm:"column:mid"`
Realname string `gorm:"column:realname"`
Card string `gorm:"column:card"`
IMG string `gorm:"column:img"`
Status int `gorm:"column:status"`
Reason string `gorm:"column:reason"`
Bizno string `gorm:"column:bizno"`
Operator string `gorm:"column:operator"`
OperatorID int64 `gorm:"column:operator_id"`
OperatorTime time.Time `gorm:"column:operator_time"`
CTime time.Time `gorm:"column:ctime"`
MTime time.Time `gorm:"column:mtime"`
}
// TableName is...
func (d *DBRealnameAlipayApply) TableName() string {
return "realname_alipay_apply"
}
// IsPassed is...
func (d *DBRealnameApply) IsPassed() bool {
return d.Status == 1
}
// DBRealnameApplyIMG is.
type DBRealnameApplyIMG struct {
ID int64 `gorm:"column:id"`
IMGData string `gorm:"column:img_data"`
CTime time.Time `gorm:"column:ctime"`
MTime time.Time `gorm:"column:mtime"`
}
// TableName ...
func (d *DBRealnameApplyIMG) TableName() string {
return "realname_apply_img"
}
// DBRealnameConfig ...
type DBRealnameConfig struct {
ID int64 `gorm:"column:id"`
Key string `gorm:"column:key"`
Data string `gorm:"column:data"`
CTime time.Time `gorm:"column:ctime"`
MTime time.Time `gorm:"column:mtime"`
}
// TableName ...
func (d *DBRealnameConfig) TableName() string {
return "realname_config"
}
// CardDecrypt is
func CardDecrypt(data string) (text string, err error) {
var (
dataBytes = []byte(data)
decryptedData []byte
textBytes []byte
size int
)
decryptedData = make([]byte, base64.StdEncoding.DecodedLen(len(dataBytes)))
if size, err = base64.StdEncoding.Decode(decryptedData, dataBytes); err != nil {
err = errors.Wrapf(err, "base decode %s", data)
return
}
if textBytes, err = rsaDecrypt(decryptedData[:size]); err != nil {
err = errors.Wrapf(err, "rsa decrypt %s , data : %s", decryptedData, data)
return
}
text = string(textBytes)
return
}
func rsaDecrypt(text []byte) (content []byte, err error) {
block, _ := pem.Decode(conf.Conf.Realname.RsaPriv)
if block == nil {
err = errors.New("private key error")
return
}
var (
privateKey *rsa.PrivateKey
)
if privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
err = errors.WithStack(err)
return
}
if content, err = rsa.DecryptPKCS1v15(rand.Reader, privateKey, text); err != nil {
err = errors.WithStack(err)
return
}
return
}
// DBRealnameAuditLog is.
type DBRealnameAuditLog struct {
ID int64 `gorm:"column:id"`
MID int64 `gorm:"column:mid"`
AdminID int64 `gorm:"column:admin_id"`
AdminName string `gorm:"column:admin_name"`
Channel uint8 `gorm:"column:channel"`
FromState int `gorm:"column:from_state"`
ToState int `gorm:"column:to_state"`
CTime time.Time `gorm:"column:ctime"`
MTime time.Time `gorm:"column:mtime"`
}
// Tablename is.
func (d *DBRealnameAuditLog) Tablename() string {
return "realname_audit_log"
}
// Realname is.
type Realname struct {
State RealnameApplyStatus `json:"state"`
Channel realnameChannel `json:"moral"`
Card string `json:"card"`
CardType int8 `json:"card_type"`
Country int16 `json:"country"`
Realname string `json:"realname"`
Images []string `json:"images"`
}
// ParseDBApplyIMG parse apply_img from db
func (r *Realname) ParseDBApplyIMG(token string) {
r.Images = append(r.Images, imgURL(token))
}
// ParseInfo .
func (r *Realname) ParseInfo(info *DBRealnameInfo) {
switch info.Status {
case 0:
r.State = RealnameApplyStatePending
case 1:
r.State = RealnameApplyStatePassed
case 2:
r.State = RealnameApplyStateRejective
default:
log.Warn("Realname status err , unknown info status :%d", info.Status)
r.State = RealnameApplyStateNone
}
switch info.Channel {
case 0:
r.Channel = ChannelMain
case 1:
r.Channel = ChannelAlipay
default:
log.Warn("Realname channel err , unknown info channel :%d", info.Channel)
r.Channel = ChannelMain
}
r.Realname = info.Realname
r.CardType = info.CardType
r.Country = info.Country
var err error
var maskedCard string
if info.Card != "" && r.State == RealnameApplyStatePassed {
if r.Card, err = CardDecrypt(info.Card); err != nil {
log.Error("%+v", err)
}
var (
cStrs = strings.Split(r.Card, "")
)
if len(cStrs) > 0 {
if len(cStrs) == 1 {
maskedCard = "*"
} else if len(cStrs) > 5 {
maskedCard = cStrs[0] + strings.Repeat("*", len(cStrs)-3) + strings.Join(cStrs[len(cStrs)-2:], "")
} else {
maskedCard = cStrs[0] + strings.Repeat("*", len(cStrs)-1)
}
}
r.Card = maskedCard
}
}
// RealnameExport is.
type RealnameExport struct {
Mid int64 `json:"mid" gorm:"column:mid"`
UserID string `json:"userid" gorm:"column:userid"`
Uname string `json:"uname" gorm:"column:uname"`
Realname string `json:"realname" gorm:"column:realname"`
Tel string `json:"tel" gorm:"column:tel"`
CardType int8 `json:"card_type" gorm:"column:card_type"`
CardNum string `json:"card_num" gorm:"column:card_num"`
}
// PassportQueryByMidResult is.
type PassportQueryByMidResult struct {
Mid int64 `json:"mid"`
Name string `json:"name"`
Userid string `json:"userid"`
Email string `json:"email"`
Tel string `json:"tel"`
Jointime xtime.Time `json:"jointime"`
}
var _cardTypeToString = map[int8]string{
0: "身份证",
1: "护照(境外签发)",
2: "港澳居民来往内地通行证",
3: "台湾居民来往大陆通行证",
4: "护照(中国签发)",
5: "外国人永久居留证",
6: "其他国家或地区身份证",
}
// CardTypeString is
func CardTypeString(cardType int8) string {
typeString, ok := _cardTypeToString[cardType]
if !ok {
return strconv.FormatInt(int64(cardType), 10)
}
return typeString
}

View File

@@ -0,0 +1,41 @@
package model
import (
"time"
)
// DeDeIdentificationCardApplyImg is.
type DeDeIdentificationCardApplyImg struct {
ID int64 `gorm:"column:id"`
IMGData string `gorm:"column:img_data"`
AddTime time.Time `gorm:"column:add_time"`
}
// TableName is
func (d *DeDeIdentificationCardApplyImg) TableName() string {
return "dede_identification_card_apply_img"
}
// DeDeIdentificationCardApply is
type DeDeIdentificationCardApply struct {
ID int64 `gorm:"column:id"`
MID int64 `gorm:"column:mid"`
Realname string `gorm:"column:realname"`
Type int8 `gorm:"column:type"`
CardData string `gorm:"column:card_data"`
CardForSearch string `gorm:"column:card_for_search"`
FrontImg int64 `gorm:"column:front_img"`
FrontImg2 int64 `gorm:"column:front_img2"`
BackImg int64 `gorm:"column:back_img"`
ApplyTime int32 `gorm:"column:apply_time"`
Operator string `gorm:"column:operater"`
OperatorTime int32 `gorm:"column:operater_time"`
Status int8 `gorm:"column:status"`
Remark string `gorm:"column:remark"`
RemarkStatus int8 `gorm:"column:remark_status"`
}
// TableName is
func (d *DeDeIdentificationCardApply) TableName() string {
return "dede_identification_card_apply"
}

View File

@@ -0,0 +1,101 @@
package model
import (
"encoding/json"
"fmt"
"go-common/app/admin/main/member/model/block"
"go-common/library/log"
xtime "go-common/library/time"
)
// review state const.
const (
ReviewStateWait = iota
ReviewStatePass
ReviewStateNoPass
ReviewStateArchived
ReviewStateQueuing = 10
)
// review property const.
const (
ReviewProperty = iota
ReviewPropertyFace
ReviewPropertySign
ReviewPropertyName
)
// all
var (
AllReviewStates = []int8{
ReviewStateWait,
ReviewStatePass,
ReviewStateNoPass,
ReviewStateQueuing,
}
)
// UserPropertyReview is
type UserPropertyReview struct {
ID int64 `json:"id" gorm:"column:id"`
Mid int64 `json:"mid" gorm:"column:mid"`
Old string `json:"old" gorm:"column:old"`
New string `json:"new" gorm:"column:new"`
State int8 `json:"state" gorm:"column:state"`
Property int8 `json:"property" gorm:"column:property"`
Remark string `json:"remark" gorm:"column:remark"`
Operator string `json:"operator" gorm:"column:operator"`
IsMonitor bool `json:"is_monitor" gorm:"column:is_monitor"`
Extra string `json:"extra" gorm:"column:extra"`
CTime xtime.Time `json:"ctime" gorm:"column:ctime"`
MTime xtime.Time `json:"mtime" gorm:"column:mtime"`
// 昵称,展示用
Name string `json:"name" gorm:"-"`
FaceReject int64 `json:"face_reject" gorm:"-"`
Block *block.BlockDetail `json:"block" gorm:"-"`
Follower int64 `json:"follower" gorm:"-"`
}
// Extra is.
type Extra struct {
NickFree bool `json:"nick_free"`
}
// NickFree nick free.
func (r *UserPropertyReview) NickFree() bool {
if len(r.Extra) == 0 {
return false
}
ext := Extra{}
if err := json.Unmarshal([]byte(r.Extra), &ext); err != nil {
log.Error("Failed to unmarshal extra, userPropertyReview: %+v error: %v", r, err)
return false
}
return ext.NickFree
}
// FaceCheckRes is.
type FaceCheckRes struct {
Blood float64 `json:"blood,omitempty"`
Violent float64 `json:"violent,omitempty"`
Sex float64 `json:"sex,omitempty"`
Politics float64 `json:"politics,omitempty"`
}
// Valid is.
func (fcr *FaceCheckRes) Valid() bool {
return fcr.Sex < 0.19 && fcr.Politics < 0.5 && fcr.Blood < 0.5 && fcr.Violent < 0.5
}
// String is.
func (fcr *FaceCheckRes) String() string {
return fmt.Sprintf("Sex: %.4f, Politics: %.4f", fcr.Sex, fcr.Politics)
}
//BuildFaceURL buildFaceUrl.
func (r *UserPropertyReview) BuildFaceURL() {
r.Old = BuildFaceURL(r.Old)
r.New = BuildFaceURL(r.New)
}

View File

@@ -0,0 +1,77 @@
package model
// SearchMemberResult is.
type SearchMemberResult struct {
Order string `json:"order"`
Sort string `json:"sort"`
Result []struct {
Mid int64 `json:"mid"`
Name string `json:"name"`
} `json:"result"`
Page Page `json:"page"`
}
// Mids is.
func (r *SearchMemberResult) Mids() []int64 {
mids := make([]int64, 0, len(r.Result))
for _, r := range r.Result {
mids = append(mids, r.Mid)
}
return mids
}
// Pagination is.
func (r *SearchMemberResult) Pagination() *CommonPagination {
return &CommonPagination{
Page: r.Page,
}
}
// SearchUserPropertyReviewResult is.
type SearchUserPropertyReviewResult struct {
Order string `json:"order"`
Sort string `json:"sort"`
Result []struct {
ID int64 `json:"id"`
} `json:"result"`
Page Page `json:"page"`
}
// IDs is.
func (r *SearchUserPropertyReviewResult) IDs() []int64 {
ids := make([]int64, 0, len(r.Result))
for _, r := range r.Result {
ids = append(ids, r.ID)
}
return ids
}
// Total is.
func (r *SearchUserPropertyReviewResult) Total() int {
return r.Page.Total
}
// SearchLogResult is.
type SearchLogResult struct {
Order string `json:"order"`
Sort string `json:"sort"`
Result []AuditLog `json:"result"`
Page Page `json:"page"`
}
// AuditLog is.
type AuditLog struct {
UID int64 `json:"uid"`
Uname string `json:"uname"`
OID int64 `json:"oid"`
Type int8 `json:"type"`
Action string `json:"action"`
Str0 string `json:"str_0"`
Str1 string `json:"str_1"`
Str2 string `json:"str_2"`
Int0 int `json:"int_0"`
Int1 int `json:"int_1"`
Int2 int `json:"int_2"`
Ctime string `json:"ctime"`
Extra string `json:"extra_data"`
}

View File

@@ -0,0 +1,101 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"member_test.go",
"official_test.go",
"review_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/model:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"member.go",
"monitor.go",
"official.go",
"realname.go",
"review.go",
"review_audit.go",
"service.go",
],
importpath = "go-common/app/admin/main/member/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/dao:go_default_library",
"//app/admin/main/member/model:go_default_library",
"//app/admin/main/member/model/block:go_default_library",
"//app/admin/main/member/model/bom:go_default_library",
"//app/admin/main/member/service/block:go_default_library",
"//app/interface/main/account/service/realname/crypto:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/coin/api/gorpc:go_default_library",
"//app/service/main/coin/model:go_default_library",
"//app/service/main/figure/rpc/client:go_default_library",
"//app/service/main/member/api/gorpc:go_default_library",
"//app/service/main/member/model:go_default_library",
"//app/service/main/member/model/block:go_default_library",
"//app/service/main/member/service/crypto:go_default_library",
"//app/service/main/relation/model:go_default_library",
"//app/service/main/relation/rpc/client:go_default_library",
"//app/service/main/spy/model:go_default_library",
"//app/service/main/spy/rpc/client:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/stat/prom:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/nfnt/resize:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/robfig/cron:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/main/member/service/block:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,66 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/model/block:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"audit_log.go",
"block.go",
"msg.go",
"notify.go",
"rpc.go",
"service.go",
],
importpath = "go-common/app/admin/main/member/service/block",
tags = ["automanaged"],
deps = [
"//app/admin/main/member/conf:go_default_library",
"//app/admin/main/member/dao/block:go_default_library",
"//app/admin/main/member/model/block:go_default_library",
"//app/service/main/account/api:go_default_library",
"//app/service/main/figure/model:go_default_library",
"//app/service/main/figure/rpc/client:go_default_library",
"//app/service/main/spy/model:go_default_library",
"//app/service/main/spy/rpc/client:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/queue/databus:go_default_library",
"//library/queue/databus/report:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,48 @@
package block
import (
"context"
"strconv"
"time"
model "go-common/app/admin/main/member/model/block"
"go-common/library/log"
manager "go-common/library/queue/databus/report"
)
// AddAuditLog .
func (s *Service) AddAuditLog(c context.Context, tp model.BlockAction, uid int64, uname string, oids []int64, duration time.Duration, source model.BlockSource, area model.BlockArea, reason, comment string, notify bool, stime time.Time) error {
var (
err error
dur = int64(duration / time.Second)
notifyStr = strconv.FormatBool(notify)
)
for _, oid := range oids {
managerInfo := &manager.ManagerInfo{
UID: uid,
Uname: uname,
Business: model.BlockLogBizID,
Type: int(tp),
Action: tp.String(),
Oid: oid,
Ctime: time.Now(),
Index: []interface{}{dur, uint8(source), uint8(area), reason, comment, notifyStr},
Content: map[string]interface{}{
"duration": dur,
"source": source,
"area": area,
"reason": reason,
"comment": comment,
"notify": notifyStr,
"action_time": stime.Unix(),
"remove_time": stime.Add(time.Second * time.Duration(dur)).Unix(),
},
}
if err = manager.Manager(managerInfo); err != nil {
log.Error("manager.Manager(%+v) error(%+v)", managerInfo, err)
continue
}
log.Info("s.managerSendLog(%+v)", managerInfo)
}
return err
}

View File

@@ -0,0 +1,320 @@
package block
import (
"context"
"sync"
"time"
model "go-common/app/admin/main/member/model/block"
xsql "go-common/library/database/sql"
"go-common/library/log"
"go-common/library/sync/errgroup"
"github.com/pkg/errors"
)
// Search .
func (s *Service) Search(c context.Context, mids []int64) (infos []*model.BlockInfo, err error) {
var (
users []*model.DBUser
userDetails []*model.DBUserDetail
eg errgroup.Group
mapMu sync.Mutex
userMap = make(map[int64]*model.BlockInfo)
)
if users, err = s.dao.Users(c, mids); err != nil {
return
}
if userDetails, err = s.dao.UserDetails(c, mids); err != nil {
return
}
infos = make([]*model.BlockInfo, 0, len(mids))
for _, m := range mids {
mid := m
eg.Go(func() (err error) {
info := &model.BlockInfo{
MID: mid,
}
// 1. account 数据
if info.Nickname, info.TelStatus, info.Level, info.RegTime, err = s.AccountInfo(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
}
if info.Nickname == "" {
log.Info("user mid(%d) not found", info.MID)
return
}
// 2. 封禁状态
for i := range users {
if users[i].MID == mid {
info.ParseStatus(users[i])
break
}
}
// 3. 封禁次数
for i := range userDetails {
if userDetails[i].MID == mid {
info.BlockCount = userDetails[i].BlockCount
break
}
}
// 4. spy 分值
if info.SpyScore, err = s.SpyScore(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.SpyScore = -1
}
// 5. figure 排名
if info.FigureRank, err = s.FigureRank(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.FigureRank = -1
}
// 6. extra 额外账号信息
if info.Tel, err = s.telInfo(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.Tel = "N/A"
}
if info.Mail, err = s.mailInfo(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.Mail = "N/A"
}
if info.Username, err = s.userID(context.Background(), mid); err != nil {
log.Error("%+v", err)
err = nil
info.Username = "N/A"
}
mapMu.Lock()
userMap[info.MID] = info
mapMu.Unlock()
return
})
}
eg.Wait()
for _, mid := range mids {
if info, ok := userMap[mid]; ok {
infos = append(infos, info)
}
}
return
}
// History .
func (s *Service) History(c context.Context, mid int64, ps, pn int, desc bool) (history *model.BlockDetail, err error) {
var (
start = (pn - 1) * ps
limit = ps
dbHistory []*model.DBHistory
dbUser *model.DBUser
)
history = &model.BlockDetail{}
if dbUser, err = s.dao.User(c, mid); err != nil {
return
}
if dbUser != nil {
history.Status = dbUser.Status
}
if history.Total, err = s.dao.HistoryCount(c, mid); err != nil {
return
}
if dbHistory, err = s.dao.History(c, mid, start, limit, desc); err != nil {
return
}
history.History = make([]*model.BlockHistory, 0, len(dbHistory))
for i := range dbHistory {
his := &model.BlockHistory{}
his.ParseDB(dbHistory[i])
history.History = append(history.History, his)
}
return
}
// BatchBlock .
func (s *Service) BatchBlock(c context.Context, p *model.ParamBatchBlock) (err error) {
var (
tx *xsql.Tx
duration = time.Duration(p.Duration) * time.Hour * 24
source model.BlockSource
stime = time.Now()
)
if tx, err = s.dao.BeginTX(c); err != nil {
return
}
if p.Source == model.BlockMgrSourceSys {
// 系统封禁
source = model.BlockSourceSys
} else if p.Source == model.BlockMgrSourceCredit {
// 小黑屋封禁
source = model.BlockSourceBlackHouse
if err = s.dao.BlackhouseBlock(context.Background(), p); err != nil {
return
}
}
for _, mid := range p.MIDs {
if err = s.action(c, tx, mid, p.AdminID, p.AdminName, source, p.Area, p.Reason, p.Comment, p.Action, duration, p.Notify, stime); err != nil {
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
return
}
// 发送站内信
if p.Notify {
s.mission(func() {
if notifyErr := s.notifyMSG(context.Background(), p.MIDs, source, p.Action, p.Area, p.Reason, p.Duration); notifyErr != nil {
log.Error("%+v", notifyErr)
return
}
})
}
s.mission(func() {
s.AddAuditLog(context.Background(), p.Action, p.AdminID, p.AdminName, p.MIDs, duration, source, p.Area, p.Reason, p.Comment, p.Notify, stime)
})
s.asyncPurgeCache(p.MIDs)
return
}
// BatchRemove .
func (s *Service) BatchRemove(c context.Context, p *model.ParamBatchRemove) (err error) {
var (
tx *xsql.Tx
stime = time.Now()
)
if tx, err = s.dao.BeginTX(c); err != nil {
return
}
for _, mid := range p.MIDs {
if err = s.action(c, tx, mid, p.AdminID, p.AdminName, model.BlockSourceManager, model.BlockAreaNone, "", p.Comment, model.BlockActionAdminRemove, 0, p.Notify, stime); err != nil {
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
return
}
// 发送站内信
if p.Notify {
s.mission(func() {
if notifyErr := s.notifyMSG(context.Background(), p.MIDs, model.BlockSourceManager, model.BlockActionAdminRemove, model.BlockAreaNone, "", 0); notifyErr != nil {
log.Error("%+v", notifyErr)
return
}
})
}
s.mission(func() {
s.AddAuditLog(context.Background(), model.BlockActionAdminRemove, p.AdminID, p.AdminName, p.MIDs, 0, model.BlockSourceManager, model.BlockAreaNone, "", p.Comment, p.Notify, stime)
})
s.asyncPurgeCache(p.MIDs)
return
}
// notifyMSG .
func (s *Service) notifyMSG(c context.Context, mids []int64, source model.BlockSource, action model.BlockAction, area model.BlockArea, reason string, days int64) (err error) {
code, title, content := s.MSGInfo(source, action, area, reason, days)
log.Info("block admin title : %s , content : %s , mids : %+v", title, content, mids)
if err = s.dao.SendSysMsg(context.Background(), code, mids, title, content, ""); err != nil {
return
}
return
}
func (s *Service) action(c context.Context, tx *xsql.Tx, mid int64, adminID int64, adminName string, source model.BlockSource, area model.BlockArea, reason, comment string, action model.BlockAction, duration time.Duration, notify bool, stime time.Time) (err error) {
var (
db = &model.DBHistory{
MID: mid,
AdminID: adminID,
AdminName: adminName,
Source: source,
Area: area,
Reason: reason,
Comment: comment,
Action: action,
StartTime: stime,
Duration: int64(duration / time.Second),
Notify: notify,
}
blockStatus model.BlockStatus
)
if err = s.dao.TxInsertHistory(c, tx, db); err != nil {
return
}
switch action {
case model.BlockActionAdminRemove, model.BlockActionSelfRemove:
blockStatus = model.BlockStatusFalse
case model.BlockActionLimit:
switch source {
case model.BlockSourceBlackHouse:
blockStatus = model.BlockStatusCredit
default:
blockStatus = model.BlockStatusLimit
}
s.mission(func() {
if err = s.dao.UpdateAddBlockCount(context.Background(), mid); err != nil {
log.Error("%+v", err)
}
})
case model.BlockActionForever:
blockStatus = model.BlockStatusForever
s.mission(func() {
if err = s.dao.UpdateAddBlockCount(context.Background(), mid); err != nil {
log.Error("%+v", err)
}
})
default:
err = errors.Errorf("unknown block action [%d]", action)
return
}
if err = s.dao.TxUpdateUser(c, tx, mid, blockStatus); err != nil {
return
}
return
}
func (s *Service) userID(c context.Context, mid int64) (id string, err error) {
return "N/A", nil
}
func (s *Service) mailInfo(c context.Context, mid int64) (mail string, err error) {
if mail, err = s.dao.MailInfo(c, mid); mail == "" {
mail = "N/A"
}
return
}
// TelInfo .
func (s *Service) telInfo(c context.Context, mid int64) (tel string, err error) {
if tel, err = s.dao.TelInfo(c, mid); err != nil {
return
}
if len(tel) == 0 {
tel = "N/A"
return
}
if len(tel) < 4 {
tel = tel[:1] + "****"
return
}
tel = tel[:3] + "****" + tel[len(tel)-4:]
return
}
func (s *Service) asyncPurgeCache(mids []int64) {
_ = s.cache.Do(context.Background(), func(ctx context.Context) {
for _, mid := range mids {
if cacheErr := s.dao.DeleteUserCache(ctx, mid); cacheErr != nil {
log.Error("%+v", cacheErr)
}
if cacheErr := s.dao.DeleteUserDetailCache(ctx, mid); cacheErr != nil {
log.Error("%+v", cacheErr)
}
if databusErr := s.accountNotify(ctx, mid); databusErr != nil {
log.Error("%+v", databusErr)
}
}
})
}

View File

@@ -0,0 +1,85 @@
package block
import (
"fmt"
model "go-common/app/admin/main/member/model/block"
"go-common/library/log"
)
const (
_creditLimit = `抱歉,你的账号因“%s%s”现已进行封禁%d天处理账号解封需要满足以下两个条件:1.账号封禁时间已满。2.完成解封答题( #{点击进入解封答题}{"http://www.bilibili.com/blackroom/releaseexame.html"} )全部完成后解封。封禁期间将无法投稿、发送及回复消息,无法发布评论、弹幕,无法对他人评论进行回复、赞踩操作,无法进行投币、编辑标签、添加关注、添加收藏操作。解封后恢复正常,还请遵守社区规范,共同维护良好的社区氛围!`
_creditForever = `抱歉,你的账号因“%s%s”现已进行永久封禁处理。封禁期间将无法投稿、发送及回复消息无法发布评论、弹幕无法对他人评论进行回复、赞踩操作无法进行投币、编辑标签、添加关注、添加收藏操作。解封后恢复正常还请遵守社区规范共同维护良好的社区氛围`
_sysLimit = `抱歉,你的账号因“%s”现已进行封禁%d天处理账号解封需要满足以下两个条件:1.账号封禁时间已满。2.完成解封答题( #{点击进入解封答题}{"http://www.bilibili.com/blackroom/releaseexame.html"} )全部完成后解封。封禁期间将无法投稿、发送及回复消息,无法发布评论、弹幕,无法对他人评论进行回复、赞踩操作,无法进行投币、编辑标签、添加关注、添加收藏操作。解封后恢复正常,还请遵守社区规范,共同维护良好的社区氛围!`
_sysForever = `抱歉,你的账号因“%s”现已进行永久封禁处理。封禁期间将无法投稿、发送及回复消息无法发布评论、弹幕无法对他人评论进行回复、赞踩操作无法进行投币、编辑标签、添加关注、添加收藏操作。解封后恢复正常还请遵守社区规范共同维护良好的社区氛围`
_remove = `你的账号已经解除封禁,封禁期间禁止使用的各项社区功能已经恢复。请遵守社区规范,共同维护良好的社区氛围。`
)
// MSGInfo get msg info
func (s *Service) MSGInfo(source model.BlockSource, action model.BlockAction, area model.BlockArea, reason string, days int64) (code string, title, content string) {
// 小黑屋封禁
if source == model.BlockSourceBlackHouse {
areaStr := area.String()
if areaStr != "" {
areaStr = fmt.Sprintf("在%s中", areaStr)
}
if action == model.BlockActionLimit {
code = "2_3_2"
title = "账号违规处理通知"
content = fmt.Sprintf(_creditLimit, areaStr, s.convertReason(reason), days)
return
}
if action == model.BlockActionForever {
code = "2_3_3"
title = "账号违规处理通知"
content = fmt.Sprintf(_creditForever, areaStr, s.convertReason(reason))
return
}
}
// 系统封禁
if source == model.BlockSourceSys {
if action == model.BlockActionLimit {
code = "2_3_4"
title = "账号违规处理通知"
content = fmt.Sprintf(_sysLimit, s.convertReason(reason), days)
return
}
if action == model.BlockActionForever {
code = "2_3_5"
title = "账号违规处理通知"
content = fmt.Sprintf(_sysForever, s.convertReason(reason))
return
}
}
if action == model.BlockActionAdminRemove || action == model.BlockActionSelfRemove {
code = "2_3_6"
title = "账号封禁解除通知"
content = _remove
return
}
log.Error("s.MSGInfo unkown source[%v] action[%v] area[%v] reason[%s] days[%d]", source, action, area, reason, days)
return
}
func (s *Service) convertReason(reason string) string {
switch reason {
case "账号资料相关违规":
return "账号资料违规"
case "作品投稿违规":
return "作品投稿违规"
case "异常注册账号":
return "异常注册"
case "异常答题账号":
return "异常答题"
case "异常数据行为":
return "异常数据行为"
case "发布违规信息":
return "发布违规信息"
case "其他自动封禁", "手动封禁":
return "违反社区规则"
default:
return reason
}
}

View File

@@ -0,0 +1,18 @@
package block
import (
"context"
"strconv"
"go-common/app/admin/main/member/model/block"
"github.com/pkg/errors"
)
func (s *Service) accountNotify(c context.Context, mid int64) (err error) {
msg := &block.AccountNotify{UID: mid, Action: "blockUser"}
if err = s.accountNotifyPub.Send(c, strconv.FormatInt(msg.UID, 10), msg); err != nil {
err = errors.Errorf("mid(%d) s.accountNotify.Send(%+v) error(%+v)", msg.UID, msg, err)
}
return
}

View File

@@ -0,0 +1,72 @@
package block
import (
"context"
account "go-common/app/service/main/account/api"
mdlfigure "go-common/app/service/main/figure/model"
mdlspy "go-common/app/service/main/spy/model"
"go-common/library/net/metadata"
"github.com/pkg/errors"
)
// SpyScore .
func (s *Service) SpyScore(c context.Context, mid int64) (score int8, err error) {
var (
arg = &mdlspy.ArgUserScore{
Mid: mid,
}
res *mdlspy.UserScore
)
if res, err = s.spyRPC.UserScore(c, arg); err != nil {
err = errors.WithStack(err)
return
}
if res == nil {
return
}
score = res.Score
return
}
// FigureRank .
func (s *Service) FigureRank(c context.Context, mid int64) (rank int8, err error) {
var (
arg = &mdlfigure.ArgUserFigure{
Mid: mid,
}
res *mdlfigure.FigureWithRank
)
if res, err = s.figureRPC.UserFigure(c, arg); err != nil {
err = errors.WithStack(err)
return
}
if res == nil {
rank = 100
return
}
rank = res.Percentage
return
}
// AccountInfo .
func (s *Service) AccountInfo(c context.Context, mid int64) (nickname string, tel int32, level int32, regTime int64, err error) {
var (
arg = &account.MidReq{
Mid: mid,
RealIp: metadata.String(c, metadata.RemoteIP),
}
profileReply *account.ProfileReply
)
if profileReply, err = s.accountClient.Profile3(c, arg); err != nil {
err = errors.WithStack(err)
return
}
res := profileReply.Profile
nickname = res.Name
tel = res.TelStatus
level = res.Level
regTime = int64(res.JoinTime)
return
}

View File

@@ -0,0 +1,75 @@
package block
import (
"context"
"runtime/debug"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/dao/block"
account "go-common/app/service/main/account/api"
rpcfigure "go-common/app/service/main/figure/rpc/client"
rpcspy "go-common/app/service/main/spy/rpc/client"
"go-common/library/log"
"go-common/library/queue/databus"
"go-common/library/sync/pipeline/fanout"
)
// Service struct
type Service struct {
conf *conf.Config
dao *block.Dao
cache *fanout.Fanout
spyRPC *rpcspy.Service
figureRPC *rpcfigure.Service
accountClient account.AccountClient
missch chan func()
accountNotifyPub *databus.Databus
}
// New init
func New(conf *conf.Config, dao *block.Dao, spyRPC *rpcspy.Service, figureRPC *rpcfigure.Service,
accountClient account.AccountClient, accountNotifyPub *databus.Databus) (s *Service) {
s = &Service{
conf: conf,
dao: dao,
cache: fanout.New("memberAdminCache", fanout.Worker(1), fanout.Buffer(10240)),
missch: make(chan func(), 10240),
accountNotifyPub: accountNotifyPub,
spyRPC: spyRPC,
figureRPC: figureRPC,
accountClient: accountClient,
}
go s.missproc()
return s
}
func (s *Service) missproc() {
defer func() {
if x := recover(); x != nil {
log.Error("service.missproc panic(%+v) : %s", x, debug.Stack())
go s.missproc()
}
}()
for {
f := <-s.missch
f()
}
}
func (s *Service) mission(f func()) {
select {
case s.missch <- f:
default:
log.Error("s.missch full")
}
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,65 @@
package block
import (
"context"
"flag"
"os"
"testing"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/model/block"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
c = context.Background()
)
func TestMain(m *testing.M) {
defer os.Exit(0)
flag.Set("conf", "../cmd/member-admin-test.toml")
var err error
if err = conf.Init(); err != nil {
panic(err)
}
m.Run()
}
func TestService(t *testing.T) {
Convey("", t, func() {
s.Ping(c)
s.Close()
})
}
func TestBlock(t *testing.T) {
Convey("block", t, func() {
var (
p = &block.ParamBatchBlock{
MIDs: []int64{1, 2, 3, 4},
AdminID: 233,
AdminName: "233",
Source: 1,
Area: block.BlockAreaNone,
Reason: "test",
Comment: "test",
Action: block.BlockActionLimit,
Duration: 1,
Notify: false,
}
pm = &block.ParamBatchRemove{
MIDs: []int64{1, 2, 3, 4},
AdminID: 233,
AdminName: "233",
Comment: "test",
Notify: false,
}
)
err := s.BatchBlock(c, p)
So(err, ShouldBeNil)
err = s.BatchRemove(c, pm)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,718 @@
package service
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"sort"
"strconv"
"time"
"go-common/app/admin/main/member/model"
"go-common/app/admin/main/member/model/bom"
coin "go-common/app/service/main/coin/model"
member "go-common/app/service/main/member/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/queue/databus/report"
"github.com/pkg/errors"
)
const (
_logActionBaseAudit = "base_audit"
_logActionDeleteSign = "delete_sign"
_logActionRankUpdate = "rank_set"
_logActionCoinUpdate = "coin_set"
_logActionExpUpdate = "exp_set"
_logActionMoralUpdate = "moral_set"
)
func (s *Service) batchBase(ctx context.Context, mids []int64) (map[int64]*model.Base, error) {
bs := make(map[int64]*model.Base, len(mids))
for _, mid := range mids {
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
bs[b.Mid] = b
}
return bs, nil
}
// BaseReview is.
func (s *Service) BaseReview(ctx context.Context, arg *model.ArgBaseReview) ([]*model.BaseReview, error) {
mids := arg.Mids()
if len(mids) == 0 {
return nil, ecode.RequestErr
}
if len(mids) > 200 {
//mids = mids[:200]
return nil, ecode.SearchMidOverLimit
}
var mrs []*model.BaseReview
for _, mid := range mids {
base, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
logs, err := s.dao.SearchUserAuditLog(ctx, mid)
if err != nil {
log.Error("Failed to search user audit log, mid: %d error: %v", mid, err)
continue
}
fixFaceLogs(logs.Result)
mr := &model.BaseReview{
Base: *base,
Logs: logs.Result,
}
mrs = append(mrs, mr)
}
if err := s.reviewAddit(ctx, mids, mrs); err != nil {
log.Error("Failed to fetch review violation count with mids: %+v: %+v", mids, err)
}
return mrs, nil
}
func fixFaceLogs(auditLogs []model.AuditLog) {
for i, v := range auditLogs {
if v.Type != model.BaseAuditTypeFace {
continue
}
// 对头像进行重新签名
ext := new(struct {
Old string `json:"old"`
New string `json:"new"`
})
if err := json.Unmarshal([]byte(v.Extra), &ext); err != nil {
log.Error("Failed to unmarshal extra, additLog: %+v error: %v", v, err)
continue
}
ext.New = model.BuildFaceURL(ext.New)
extraDataBytes, err := json.Marshal(ext)
if err != nil {
log.Error("FaceExtra (%+v) json marshal err(%v)", ext, err)
continue
}
auditLogs[i].Extra = string(extraDataBytes)
}
}
// ClearFace is
func (s *Service) ClearFace(ctx context.Context, arg *model.ArgMids) error {
for _, mid := range arg.Mid {
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
if err = s.dao.UpFace(ctx, mid, ""); err != nil {
log.Error("Failed to clear face mid %d error: %+v", mid, err)
continue
}
privFace, err := s.mvToPrivate(ctx, urlPath(b.Face))
if err != nil {
log.Error("Failed to mv face To private bucket, mid: %d error: %+v", mid, err)
err = nil
}
if err = s.dao.Message(ctx, "违规头像处理通知", "抱歉,由于你的头像涉嫌违规,已被修改。如有疑问请联系客服。", []int64{mid}); err != nil {
log.Error("Failed to send message, mid: %d error: %+v", mid, err)
err = nil
}
if err = s.dao.IncrViolationCount(ctx, mid); err != nil {
log.Error("Failed to increase violation count mid: %d error: %+v", mid, err)
err = nil
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: model.BaseAuditTypeFace,
Oid: mid,
Action: _logActionBaseAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"old": b.Face,
"new": model.BuildFaceURL(privFace),
},
})
}
return nil
}
// ClearSign is
func (s *Service) ClearSign(ctx context.Context, arg *model.ArgMids) error {
for _, mid := range arg.Mid {
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
if err = s.dao.UpSign(ctx, mid, ""); err != nil {
log.Error("Failed to clear sign mid %d error: %+v", mid, err)
continue
}
if err = s.dao.Message(ctx, "违规签名处理通知", "抱歉,由于你的签名涉嫌违规,已被修改。如有疑问请联系客服。", []int64{mid}); err != nil {
log.Error("Failed to send message, mid: %d error: %+v", mid, err)
err = nil
}
if err = s.dao.IncrViolationCount(ctx, mid); err != nil {
log.Error("Failed to increase violation count mid: %d error: %+v", mid, err)
err = nil
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: model.BaseAuditTypeSign,
Oid: mid,
Action: _logActionBaseAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"old": b.Sign,
"new": "",
},
})
}
return nil
}
// ClearName is
func (s *Service) ClearName(ctx context.Context, arg *model.ArgMids) error {
for _, mid := range arg.Mid {
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base by mid: %d: %+v", mid, err)
continue
}
defaultName := fmt.Sprintf("bili_%d", mid)
if err = s.dao.UpdateUname(ctx, mid, defaultName); err != nil {
log.Error("Failed to clear name mid %d error: %+v", mid, err)
continue
}
if err = s.dao.Message(ctx, "违规昵称处理通知", "抱歉,由于你的昵称涉嫌违规,已被修改。如有疑问请联系客服。", []int64{mid}); err != nil {
log.Error("Failed to send message, mid: %d error: %+v", mid, err)
err = nil
}
if err = s.dao.IncrViolationCount(ctx, mid); err != nil {
log.Error("Failed to increase violation count mid: %d error: %+v", mid, err)
err = nil
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: model.BaseAuditTypeName,
Oid: mid,
Action: _logActionBaseAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"old": b.Name,
"new": defaultName,
},
})
}
return nil
}
// Members is.
func (s *Service) Members(ctx context.Context, arg *model.ArgList) (*model.MemberPagination, error) {
searched, err := s.dao.SearchMember(ctx, arg)
if err != nil {
return nil, err
}
mids := searched.Mids()
bs, err := s.batchBase(ctx, mids)
if err != nil {
return nil, err
}
result := make([]*model.Base, 0, len(mids))
for _, mid := range mids {
if b, ok := bs[mid]; ok {
result = append(result, b)
}
}
page := &model.MemberPagination{
CommonPagination: searched.Pagination(),
Members: result,
}
return page, nil
}
// MemberProfile is.
func (s *Service) MemberProfile(ctx context.Context, mid int64) (*model.Profile, error) {
p := model.NewProfile()
// base
b, err := s.dao.Base(ctx, mid)
if err != nil {
log.Error("Failed to retrive user base with mid: %d: %+v", mid, err)
return nil, err
}
p.Base = *b
// detail
// remove later
p.Detail = model.Detail{
Mid: b.Mid,
Birthday: b.Birthday,
}
// exp
e, err := s.dao.Exp(ctx, mid)
if err != nil {
log.Error("Failed to retrive user exp with mid: %d: %+v", mid, err)
}
if e != nil {
p.Exp = *e
p.Level.FromExp(e)
}
// moral
mo, err := s.dao.Moral(ctx, mid)
if err != nil {
log.Error("Failed to retrive user moral with mid: %d: %+v", mid, err)
}
if mo != nil {
p.Moral = *mo
}
// official
of, err := s.dao.Official(ctx, mid)
if err != nil {
log.Error("Failed to retrive user official with mid: %d: %+v", mid, err)
}
if of != nil {
p.Official = *of
}
// coin
co, err := s.coinRPC.UserCoins(ctx, &coin.ArgCoinInfo{Mid: mid})
if err != nil {
log.Error("Failed to retrive user coins with mid: %d: %+v", mid, err)
}
p.Coin = model.Coin{Coins: co}
// addit
ad, err := s.dao.UserAddit(ctx, mid)
if err != nil {
log.Error("Failed to retrive user addit with mid: %d: %+v", mid, err)
}
if ad != nil {
p.Addit = *ad
}
// realname
dr, err := s.dao.RealnameInfo(ctx, mid)
if err != nil {
log.Error("Failed to retrive user realname with mid: %d: %+v", mid, err)
}
if dr != nil {
p.Realanme.ParseInfo(dr)
} else {
p.Realanme.State = model.RealnameApplyStateNone
}
return p, nil
}
// DelSign is.
func (s *Service) DelSign(ctx context.Context, arg *model.ArgMids) error {
for _, mid := range arg.Mid {
err := s.dao.UpSign(ctx, mid, "")
if err != nil {
log.Error("Failed to delete sign mid: %d: %+v", mid, err)
continue
}
if err := s.dao.Message(ctx, "违规签名处理通知", "抱歉,由于你的个性签名内容涉嫌违规,我们已将你的个性签名清空,如有问题请联系客服。", []int64{mid}); err != nil {
log.Error("Failed to send message: mid: %d: %+v", mid, err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: mid,
Action: _logActionDeleteSign,
Ctime: time.Now(),
})
}
return nil
}
// SetMoral is.
func (s *Service) SetMoral(ctx context.Context, arg *model.ArgMoralSet) error {
moral, err := s.dao.Moral(ctx, arg.Mid)
if err != nil {
return errors.WithStack(err)
}
newMoral := int64(arg.Moral * 100)
delta := newMoral - moral.Moral
if err = s.memberRPC.AddMoral(ctx,
&member.ArgUpdateMoral{
Mid: arg.Mid,
Delta: delta,
Operator: arg.Operator,
Reason: arg.Reason,
Remark: "管理后台",
Origin: member.ManualChangeType,
IP: arg.IP}); err != nil {
return errors.WithStack(err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionMoralUpdate,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"new_moral": newMoral,
"delta": delta,
},
})
return nil
}
// SetExp is.
func (s *Service) SetExp(ctx context.Context, arg *model.ArgExpSet) error {
exp, err := func() (*model.Exp, error) {
exp, err := s.dao.Exp(ctx, arg.Mid)
if err != nil {
if err == ecode.NothingFound {
return &model.Exp{Mid: arg.Mid}, nil
}
return nil, errors.WithStack(err)
}
return exp, nil
}()
if err != nil {
return err
}
delta := arg.Exp - float64(exp.Exp/100)
if err = s.memberRPC.UpdateExp(ctx,
&member.ArgAddExp{
Mid: arg.Mid,
Count: delta,
Operate: arg.Operator,
Reason: arg.Reason,
IP: arg.IP}); err != nil {
return errors.WithStack(err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionExpUpdate,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"new_exp": arg.Exp,
"delta": delta,
},
})
return nil
}
// SetRank is.
func (s *Service) SetRank(ctx context.Context, arg *model.ArgRankSet) error {
if err := s.memberRPC.SetRank(ctx,
&member.ArgUpdateRank{
Mid: arg.Mid,
Rank: arg.Rank,
RemoteIP: arg.IP}); err != nil {
return errors.WithStack(err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionRankUpdate,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"new_rank": arg.Rank,
"reason": arg.Reason,
},
})
return nil
}
// SetCoin is.
func (s *Service) SetCoin(ctx context.Context, arg *model.ArgCoinSet) error {
coins, err := s.coinRPC.UserCoins(ctx, &coin.ArgCoinInfo{Mid: arg.Mid, RealIP: arg.IP})
if err != nil {
return errors.WithStack(err)
}
reason := "系统操作"
if arg.Reason != "" {
reason = fmt.Sprintf("系统操作:%s", arg.Reason)
}
delta := arg.Coins - coins
if _, err = s.coinRPC.ModifyCoin(ctx,
&coin.ArgModifyCoin{
Mid: arg.Mid,
Operator: arg.Operator,
Reason: reason,
Count: delta,
IP: arg.IP}); err != nil {
return errors.WithStack(err)
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionCoinUpdate,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"new_coins": arg.Coins,
"delta": delta,
"reason": reason,
},
})
return nil
}
// SetAdditRemark is.
func (s *Service) SetAdditRemark(ctx context.Context, arg *model.ArgAdditRemarkSet) error {
return s.dao.UpAdditRemark(ctx, arg.Mid, arg.Remark)
}
// PubExpMsg is.
func (s *Service) PubExpMsg(ctx context.Context, arg *model.ArgPubExpMsg) (err error) {
msg := &model.AddExpMsg{
Event: arg.Event,
Mid: arg.Mid,
IP: arg.IP,
Ts: arg.Ts,
}
return s.dao.PubExpMsg(ctx, msg)
}
// ExpLog is.
func (s *Service) ExpLog(ctx context.Context, mid int64) ([]*model.UserLog, error) {
return nil, ecode.MethodNotAllowed
}
func filterByStatus(status ...int8) func(*model.FaceRecord) bool {
ss := make(map[int8]struct{}, len(status))
for _, s := range status {
ss[s] = struct{}{}
}
return func(fr *model.FaceRecord) bool {
_, ok := ss[fr.Status]
return ok
}
}
func filterByMid(mid int64) func(*model.FaceRecord) bool {
return func(fr *model.FaceRecord) bool {
return fr.Mid == mid
}
}
func filterByOP(operator string) func(*model.FaceRecord) bool {
return func(fr *model.FaceRecord) bool {
return fr.Operator == operator
}
}
// FaceHistory is.
func (s *Service) FaceHistory(ctx context.Context, arg *model.ArgFaceHistory) (*model.FaceRecordPagination, error) {
list, err := s.faceHistory(ctx, arg)
if err != nil {
return nil, err
}
plist := list.Paginate(arg.PS*(arg.PN-1), arg.PS)
page := &model.FaceRecordPagination{
Records: plist,
CommonPagination: &model.CommonPagination{
Page: model.Page{
Num: arg.PN,
Size: arg.PS,
Total: len(list),
},
},
}
return page, nil
}
func (s *Service) faceHistory(ctx context.Context, arg *model.ArgFaceHistory) (res model.FaceRecordList, err error) {
switch arg.Mode() {
case "op":
res, err = s.dao.FaceHistoryByOP(ctx, arg)
if err != nil {
return nil, err
}
if arg.Mid > 0 {
res = res.Filter(filterByMid(arg.Mid))
}
case "mid":
res, err = s.dao.FaceHistoryByMid(ctx, arg)
if err != nil {
return nil, err
}
if arg.Operator != "" {
res = res.Filter(filterByOP(arg.Operator))
}
}
res = res.Filter(filterByStatus(arg.Status...))
sort.Slice(res, func(i, j int) bool {
return res[i].ModifyTime > res[j].ModifyTime
})
for _, r := range res {
r.BuildFaceURL()
}
return
}
// MoralLog is.
func (s *Service) MoralLog(ctx context.Context, mid int64) ([]*model.UserLog, error) {
return nil, ecode.MethodNotAllowed
}
func (s *Service) reviewAddit(ctx context.Context, mids []int64, mrs []*model.BaseReview) error {
uas, err := s.dao.BatchUserAddit(ctx, mids)
if err != nil {
return err
}
for _, mr := range mrs {
if ua, ok := uas[mr.Mid]; ok {
mr.Addit = *ua
}
}
return nil
}
// BatchFormal is
func (s *Service) BatchFormal(ctx context.Context, arg *model.ArgBatchFormal) error {
fp := bom.NewReader(bytes.NewReader(arg.FileData))
reader := csv.NewReader(fp)
ip := metadata.String(ctx, metadata.RemoteIP)
columns, err := reader.Read()
if err != nil {
log.Error("Failed to read columns from csv: %+v", err)
return ecode.RequestErr
}
findMidPositon := func() (int, error) {
for i, col := range columns {
if col == "mid" {
return i, nil
}
}
return 0, errors.New("No mid column")
}
midPosition, err := findMidPositon()
if err != nil {
log.Error("Failed to find mid column: %+v", err)
return ecode.RequestErr
}
mids := make([]int64, 0)
for {
record, rerr := reader.Read()
if rerr == io.EOF {
break
}
if rerr != nil {
log.Error("Failed to parse csv: %+v", errors.WithStack(rerr))
return ecode.RequestErr
}
if len(record) < midPosition {
log.Warn("Skip record due to no suitable position: %+v", record)
continue
}
mid, perr := strconv.ParseInt(record[midPosition], 10, 64)
if perr != nil {
log.Warn("Failed to parse mid on data: %+v: %+v", record[midPosition], perr)
continue
}
mids = append(mids, mid)
}
bases, err := s.batchBase(ctx, mids)
if err != nil {
log.Error("Failed to query bases with mids: %+v: %+v", mids, err)
return ecode.RequestErr
}
for _, mid := range mids {
base, ok := bases[mid]
if !ok {
log.Warn("No such user with mid: %d", mid)
continue
}
if base.Rank >= 10000 {
log.Warn("Rank already exceeded 10000 on mid: %d: %+v", mid, base)
continue
}
rankArg := &model.ArgRankSet{
Mid: mid,
Rank: 10000,
Operator: arg.Operator,
OperatorID: arg.OperatorID,
IP: ip,
}
if err := s.SetRank(ctx, rankArg); err != nil {
log.Warn("Failed to set rank with mid: %d: %+v", mid, err)
continue
}
// 通过发放一次每日登录的经验奖励消息来使用户等级直升 lv1
expArg := &model.ArgPubExpMsg{
Mid: mid,
IP: ip,
Ts: time.Now().Unix(),
Event: "login",
}
if err := s.PubExpMsg(ctx, expArg); err != nil {
log.Warn("Failed to pub exp message with mid: %d: %+v", mid, err)
continue
}
}
return nil
}

View File

@@ -0,0 +1,36 @@
package service
import (
"context"
"testing"
"go-common/app/admin/main/member/model"
"github.com/smartystreets/goconvey/convey"
)
func TestMemberList(t *testing.T) {
convey.Convey("MemberList", t, func() {
results, err := s.Members(context.Background(), &model.ArgList{
Keyword: "123",
})
convey.So(err, convey.ShouldBeNil)
convey.So(results, convey.ShouldNotBeNil)
})
}
func TestMemberProfile(t *testing.T) {
convey.Convey("MemberProfile", t, func() {
result, err := s.MemberProfile(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(result, convey.ShouldNotBeNil)
})
}
func TestExpLog(t *testing.T) {
convey.Convey("ExpLog", t, func() {
result, err := s.ExpLog(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(result, convey.ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,95 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/admin/main/member/model"
"go-common/library/log"
"go-common/library/queue/databus/report"
)
const (
_logActionMonitorAdd = "monitor_user_add"
_logActionMonitorDel = "monitor_user_del"
)
// Monitors is.
func (s *Service) Monitors(ctx context.Context, arg *model.ArgMonitor) ([]*model.Monitor, int, error) {
includeDeleted := false
if arg.Mid > 0 {
includeDeleted = true
}
mns, total, err := s.dao.Monitors(ctx, arg.Mid, includeDeleted, arg.Pn, arg.Ps)
if err != nil {
return nil, 0, err
}
s.monitorsName(ctx, mns)
return mns, total, nil
}
// AddMonitor is.
func (s *Service) AddMonitor(ctx context.Context, arg *model.ArgAddMonitor) error {
remark := fmt.Sprintf("加入监控列表:%s", arg.Remark)
if err := s.dao.AddMonitor(ctx, arg.Mid, arg.Operator, remark); err != nil {
return err
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionMonitorAdd,
Ctime: time.Now(),
// extra
Index: []interface{}{},
Content: map[string]interface{}{
"remark": remark,
},
})
return nil
}
// DelMonitor is.
func (s *Service) DelMonitor(ctx context.Context, arg *model.ArgDelMonitor) error {
remark := fmt.Sprintf("移出监控列表:%s", arg.Remark)
if err := s.dao.DelMonitor(ctx, arg.Mid, arg.Operator, remark); err != nil {
return err
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionMonitorDel,
Ctime: time.Now(),
// extra
Index: []interface{}{},
Content: map[string]interface{}{
"remark": remark,
},
})
return nil
}
func (s *Service) monitorsName(ctx context.Context, mns []*model.Monitor) {
mids := make([]int64, 0, len(mns))
for _, mn := range mns {
mids = append(mids, mn.Mid)
}
bs, err := s.dao.Bases(ctx, mids)
if err != nil {
log.Error("Failed to fetch bases with mids: %+v: %+v", mids, err)
return
}
for _, mn := range mns {
b, ok := bs[mn.Mid]
if !ok {
continue
}
mn.Name = b.Name
}
}

View File

@@ -0,0 +1,436 @@
package service
import (
"context"
"encoding/json"
"strings"
"time"
"go-common/app/admin/main/member/model"
"go-common/app/service/main/member/model/block"
spymodel "go-common/app/service/main/spy/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/queue/databus/report"
xtime "go-common/library/time"
"github.com/pkg/errors"
)
const (
_logActionAudit = "official_doc_audit"
_logActionEdit = "official_doc_edit"
_logActionEditName = "official_doc_edit_name"
)
func i64Toi8(in []int64) []int8 {
out := make([]int8, 0, len(in))
for _, i := range in {
out = append(out, int8(i))
}
return out
}
func i8Toi64(in []int8) []int64 {
out := make([]int64, 0, len(in))
for _, i := range in {
out = append(out, int64(i))
}
return out
}
func actOr(act ...string) string {
return strings.Join(act, ",")
}
func (s *Service) officialName(ctx context.Context, ofs []*model.Official) {
mids := make([]int64, 0, len(ofs))
for _, o := range ofs {
mids = append(mids, o.Mid)
}
ofds, err := s.dao.OfficialDocsByMids(ctx, mids)
if err != nil {
log.Error("Failed to s.dao.OfficialDocsByMids(%+v): %+v", mids, err)
return
}
for _, o := range ofs {
od, ok := ofds[o.Mid]
if !ok {
continue
}
o.Name = od.Name
}
}
// Officials is.
func (s *Service) Officials(ctx context.Context, arg *model.ArgOfficial) ([]*model.Official, int, error) {
if len(arg.Role) == 0 {
arg.Role = i8Toi64(model.AllRoles)
}
if arg.ETime == 0 {
arg.ETime = xtime.Time(time.Now().Unix())
}
ofs, total, err := s.dao.Officials(ctx, arg.Mid, i64Toi8(arg.Role), arg.STime.Time(), arg.ETime.Time(), arg.Pn, arg.Ps)
if err != nil {
return nil, 0, err
}
// 需要展示昵称
s.officialName(ctx, ofs)
return ofs, total, err
}
func (s *Service) blockResult(ctx context.Context, mid int64) (*model.BlockResult, error) {
info, err := s.memberRPC.BlockInfo(ctx, &block.RPCArgInfo{MID: mid})
if err != nil {
err = errors.Wrapf(err, "%v", mid)
return nil, err
}
block := &model.BlockResult{
MID: info.MID,
BlockStatus: info.BlockStatus,
StartTime: info.StartTime,
EndTime: info.EndTime,
}
return block, nil
}
// OfficialDoc is.
func (s *Service) OfficialDoc(ctx context.Context, mid int64) (od *model.OfficialDoc, logs *model.SearchLogResult, block *model.BlockResult, spys []*spymodel.Statistics, realname *model.Realname, sameCreditCodeMids []int64, err error) {
if od, err = s.dao.OfficialDoc(ctx, mid); err != nil {
return
}
if od == nil {
od = &model.OfficialDoc{
Mid: mid,
OfficialExtra: &model.OfficialExtra{},
}
}
logs, err = s.dao.SearchLog(ctx, 0, mid, "", actOr(_logActionAudit, _logActionEdit))
if err != nil {
log.Error("Failed to s.dao.SearchLog(%+v): %+v", mid, err)
return
}
block, err = s.blockResult(ctx, mid)
if err != nil {
log.Error("Failed to s.blockResult(%+v): %+v", mid, err)
return
}
arg := &spymodel.ArgStat{Mid: mid}
spys, err = s.spyRPC.StatByID(ctx, arg)
if err != nil {
log.Error("Failed to s.spyRPC.StatByID: mid(%d): %+v", od.Mid, err)
return
}
realname, err = s.officialRealname(ctx, mid)
if err != nil {
log.Error("Failed to get official realname with mid: %d: %+v", od.Mid, err)
return
}
// 查询使用相同社会信用代码的mid
sameCreditCodeMids = make([]int64, 0)
if od.OfficialExtra.CreditCode != "" {
func() {
addits, err := s.OfficialDocAddits(ctx, "credit_code", od.OfficialExtra.CreditCode)
if err != nil {
log.Error("Failed to get official addit with mid: %d: %+v", od.Mid, err)
return
}
for _, addit := range addits {
if addit.Mid != od.Mid {
sameCreditCodeMids = append(sameCreditCodeMids, addit.Mid)
}
}
}()
}
return
}
func (s *Service) officialRealname(ctx context.Context, mid int64) (*model.Realname, error) {
realname := &model.Realname{
State: model.RealnameApplyStateNone,
}
dr, err := s.dao.RealnameInfo(ctx, mid)
if err != nil {
log.Error("Failed to get realname info with mid: %d: %+v", mid, err)
return realname, nil
}
if dr != nil {
realname.ParseInfo(dr)
}
imagesByMain := func() {
apply, err := s.dao.LastPassedRealnameMainApply(ctx, mid)
if err != nil {
log.Error("Failed to get last passed realname main apply with mid: %d: %+v", mid, err)
return
}
images, err := s.dao.RealnameApplyIMG(ctx, []int64{apply.HandIMG, apply.FrontIMG, apply.BackIMG})
if err != nil {
log.Error("Failed to get realname apply image by apply: %+v: %+v", apply, err)
return
}
for _, image := range images {
realname.ParseDBApplyIMG(image.IMGData)
}
}
imagesByAlipay := func() {
apply, err := s.dao.LastPassedRealnameAlipayApply(ctx, mid)
if err != nil {
log.Error("Failed to get last passed realname alipay apply with mid: %d: %+v", mid, err)
return
}
realname.ParseDBApplyIMG(apply.IMG)
}
switch dr.Channel {
case model.ChannelMain.DBChannel():
imagesByMain()
case model.ChannelAlipay.DBChannel():
imagesByAlipay()
default:
log.Error("Failed to get realname apply images by realname info: %+v", dr)
}
return realname, nil
}
// OfficialDocs is.
func (s *Service) OfficialDocs(ctx context.Context, arg *model.ArgOfficialDoc) ([]*model.OfficialDoc, int, error) {
if len(arg.Role) == 0 {
arg.Role = i8Toi64(model.AllRoles)
}
if len(arg.State) == 0 {
arg.State = i8Toi64(model.AllStates)
}
if arg.ETime == 0 {
arg.ETime = xtime.Time(time.Now().Unix())
}
return s.dao.OfficialDocs(ctx, arg.Mid, i64Toi8(arg.Role), i64Toi8(arg.State), arg.Uname, arg.STime.Time(), arg.ETime.Time(), arg.Pn, arg.Ps)
}
// OfficialDocAudit is.
func (s *Service) OfficialDocAudit(ctx context.Context, arg *model.ArgOfficialAudit) (err error) {
od, err := s.dao.OfficialDoc(ctx, arg.Mid)
if err != nil {
return
}
if arg.State == model.OfficialStatePass {
if err = s.updateUname(ctx, od.Mid, od.Name, arg.UID, arg.Uname); err != nil {
log.Error("Failed to update uname: mid(%d), name(%s): %+v", od.Mid, od.Name, err)
return
}
}
if err = s.dao.OfficialDocAudit(ctx, arg.Mid, arg.State, arg.Uname, arg.IsInternal, arg.Reason); err != nil {
return
}
od, err = s.dao.OfficialDoc(ctx, arg.Mid)
if err != nil {
return
}
role := int8(model.OfficialRoleUnauth)
cnt := `对不起,您的官方认证申请未通过,未通过原因:"` + arg.Reason + `,重新申请点#{这里}{"https://account.bilibili.com/account/official/home"}`
if arg.State == model.OfficialStatePass {
role = od.Role
cnt = "恭喜您的官方认证申请已经通过啦o(* ̄▽ ̄*)o"
}
if _, err = s.dao.OfficialEdit(ctx, arg.Mid, role, od.Title, od.Desc); err != nil {
return
}
if err = s.dao.Message(ctx, "官方认证审核通知", cnt, []int64{arg.Mid}); err != nil {
log.Error("Failed to send message: %+v", err)
err = nil
}
report.Manager(&report.ManagerInfo{
Uname: arg.Uname,
UID: arg.UID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{arg.State, int64(od.CTime), od.Role, od.Name, od.Title, od.Desc},
Content: map[string]interface{}{
"reason": arg.Reason,
"name": od.Name,
"extra": od.Extra,
"role": od.Role,
"title": od.Title,
"desc": od.Desc,
"state": arg.State,
"doc_ctime": int64(od.CTime),
},
})
return
}
// OfficialDocEdit is.
func (s *Service) OfficialDocEdit(ctx context.Context, arg *model.ArgOfficialEdit) (err error) {
od, _ := s.dao.OfficialDoc(ctx, arg.Mid)
if od == nil {
od = &model.OfficialDoc{
Mid: arg.Mid,
Role: arg.Role,
OfficialExtra: &model.OfficialExtra{},
}
}
od.State = int8(model.OfficialStatePass)
if arg.Role == model.OfficialRoleUnauth {
od.State = int8(model.OfficialStateNoPass)
}
od.Name = arg.Name
od.Uname = arg.Uname
od.Telephone = arg.Telephone
od.Email = arg.Email
od.Address = arg.Address
od.Supplement = arg.Supplement
od.Company = arg.Company
od.Operator = arg.Operator
od.CreditCode = arg.CreditCode
od.Organization = arg.Organization
od.OrganizationType = arg.OrganizationType
od.BusinessLicense = arg.BusinessLicense
od.BusinessLevel = arg.BusinessLevel
od.BusinessScale = arg.BusinessScale
od.BusinessAuth = arg.BusinessAuth
od.OfficalSite = arg.OfficalSite
od.RegisteredCapital = arg.RegisteredCapital
extra, err := json.Marshal(od.OfficialExtra)
if err != nil {
err = errors.Wrap(err, "official doc edit")
return
}
if err = s.updateUname(ctx, arg.Mid, arg.Name, arg.UID, arg.Uname); err != nil {
log.Error("Failed to update uname: mid(%d), name(%s): %+v", arg.Mid, arg.Name, err)
err = ecode.MemberNameFormatErr
return
}
if err = s.dao.OfficialDocEdit(ctx, arg.Mid, arg.Name, arg.Role, od.State, arg.Title, arg.Desc, string(extra), arg.Uname, arg.IsInternal); err != nil {
log.Error("Failed to update official doc: %+v", err)
err = ecode.RequestErr
return
}
if _, err = s.dao.OfficialEdit(ctx, arg.Mid, arg.Role, arg.Title, arg.Desc); err != nil {
return
}
report.Manager(&report.ManagerInfo{
Uname: arg.Uname,
UID: arg.UID,
Business: model.ManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: _logActionEdit,
Ctime: time.Now(),
// extra
Index: []interface{}{od.State, int64(od.CTime), arg.Role, arg.Name, arg.Title, arg.Desc},
Content: map[string]interface{}{
"extra": string(extra),
"name": arg.Name,
"role": arg.Role,
"title": arg.Title,
"desc": arg.Desc,
"state": od.State,
"doc_ctime": int64(od.CTime),
},
})
if arg.SendMessage {
if merr := s.dao.Message(ctx, arg.MessageTitle, arg.MessageContent, []int64{arg.Mid}); merr != nil {
log.Error("Failed to send message: %+v", merr)
}
}
return
}
// OfficialDocSubmit is.
func (s *Service) OfficialDocSubmit(ctx context.Context, arg *model.ArgOfficialSubmit) (err error) {
od := &model.OfficialDoc{
Mid: arg.Mid,
Name: arg.Name,
State: int8(model.OfficialStateWait),
Role: arg.Role,
Title: arg.Title,
Desc: arg.Desc,
Uname: arg.Uname,
IsInternal: arg.IsInternal,
SubmitSource: arg.SubmitSource,
OfficialExtra: &model.OfficialExtra{
Realname: arg.Realname,
Operator: arg.Operator,
Telephone: arg.Telephone,
Email: arg.Email,
Address: arg.Address,
Company: arg.Company,
CreditCode: arg.CreditCode,
Organization: arg.Organization,
OrganizationType: arg.OrganizationType,
BusinessLicense: arg.BusinessLicense,
BusinessScale: arg.BusinessScale,
BusinessLevel: arg.BusinessLevel,
BusinessAuth: arg.BusinessAuth,
Supplement: arg.Supplement,
Professional: arg.Professional,
Identification: arg.Identification,
OfficalSite: arg.OfficalSite,
RegisteredCapital: arg.RegisteredCapital,
},
}
if !od.Validate() {
log.Error("Failed to validate official doc: %+v", od)
err = ecode.RequestErr
return
}
return s.dao.OfficialDocSubmit(ctx, od.Mid, od.Name, od.Role, int8(model.OfficialStateWait), od.Title, od.Desc, od.OfficialExtra.String(), od.Uname, od.IsInternal, od.SubmitSource)
}
func (s *Service) updateUname(ctx context.Context, mid int64, name string, adminID int64, adminName string) error {
b, err := s.dao.Base(ctx, mid)
if err != nil {
return err
}
if b.Name == name {
return nil
}
if err := s.dao.UpdateUname(ctx, mid, name); err != nil {
log.Error("Failed to update uname to aso: mid(%d), name(%s): %+v", mid, name, err)
return err
}
if err := s.dao.UpName(ctx, mid, name); err != nil {
log.Error("Failed to update uname to member: mid(%d), name(%s): %+v", mid, name, err)
return err
}
report.Manager(&report.ManagerInfo{
Uname: adminName,
UID: adminID,
Business: model.ManagerLogID,
Type: 0,
Oid: mid,
Action: _logActionEditName,
Ctime: time.Now(),
// extra
Index: []interface{}{0, 0, 0, "", "", ""},
Content: map[string]interface{}{
"old_name": b.Name,
"new_name": name,
},
})
return nil
}
// OfficialDocAddits find mids by property and value
func (s *Service) OfficialDocAddits(ctx context.Context, property string, vstring string) ([]*model.OfficialDocAddit, error) {
if property == "" {
return nil, ecode.RequestErr
}
addits, err := s.dao.OfficialDocAddits(ctx, property, vstring)
return addits, err
}

View File

@@ -0,0 +1,84 @@
package service
import (
"context"
"testing"
"time"
"go-common/app/admin/main/member/model"
xtime "go-common/library/time"
"github.com/smartystreets/goconvey/convey"
)
func TestOfficials(t *testing.T) {
convey.Convey("Officials", t, func() {
o, total, err := s.Officials(context.Background(), &model.ArgOfficial{
Mid: 123,
Role: []int64{1},
ETime: xtime.Time(time.Now().Unix()),
Pn: 1,
Ps: 20,
})
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(total, convey.ShouldBeGreaterThan, 0)
})
}
func TestOfficialDoc(t *testing.T) {
convey.Convey("OfficialDoc", t, func() {
o, logs, block, spy, realname, mids, err := s.OfficialDoc(context.Background(), 123)
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(logs, convey.ShouldNotBeNil)
convey.So(block, convey.ShouldNotBeNil)
convey.So(spy, convey.ShouldNotBeNil)
convey.So(realname, convey.ShouldNotBeNil)
convey.So(mids, convey.ShouldNotBeNil)
})
}
func TestOfficialDocs(t *testing.T) {
convey.Convey("OfficialDocs", t, func() {
o, total, err := s.OfficialDocs(context.Background(), &model.ArgOfficialDoc{
Mid: 123,
Role: []int64{1},
State: []int64{1},
ETime: xtime.Time(time.Now().Unix()),
Pn: 1,
Ps: 20,
})
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(total, convey.ShouldBeGreaterThan, 0)
})
}
func TestOfficialDocAudit(t *testing.T) {
convey.Convey("OfficialDocAudit", t, func() {
err := s.OfficialDocAudit(context.Background(), &model.ArgOfficialAudit{
Mid: 123,
State: 1,
UID: 111,
Uname: "guan",
Reason: "xxx",
})
convey.So(err, convey.ShouldBeNil)
})
}
func TestOfficialDocEdit(t *testing.T) {
convey.Convey("OfficialDocEdit", t, func() {
err := s.OfficialDocEdit(context.Background(), &model.ArgOfficialEdit{
Mid: 123,
Name: "guan",
Role: 1,
Title: "title",
Desc: "desc",
UID: 111,
Uname: "guan",
})
convey.So(err, convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,772 @@
package service
import (
"bufio"
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"image"
"image/jpeg"
"image/png"
"io/ioutil"
mrand "math/rand"
"os"
"strconv"
"strings"
"syscall"
"time"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/model"
memmdl "go-common/app/service/main/member/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/queue/databus/report"
"github.com/nfnt/resize"
"github.com/pkg/errors"
)
// consts
const (
_512KiloBytes = 512 * 1024
)
// RealnameList .
func (s *Service) RealnameList(ctx context.Context, arg *model.ArgRealnameList) (list []*model.RespRealnameApply, total int, err error) {
list = make([]*model.RespRealnameApply, 0)
switch arg.Channel {
case model.ChannelMain:
return s.realnameMainList(ctx, arg)
case model.ChannelAlipay:
return s.realnameAlipayList(ctx, arg)
}
return
}
func (s *Service) realnameMainList(ctx context.Context, arg *model.ArgRealnameList) (list []*model.RespRealnameApply, total int, err error) {
var (
dl []*model.DBRealnameApply
)
if arg.Card != "" {
// 如果查询证件号, 则通过证件号MD5 realname_info 查询到 对应的 mids
var (
infos []*model.DBRealnameInfo
mids []int64
md5 = cardMD5(arg.Card, arg.DBCardType(), arg.Country)
)
log.Info("realnameMainList card: %s, md5: %s", arg.Card, md5)
if infos, err = s.dao.RealnameInfoByCardMD5(ctx, md5, arg.State.DBStatus(), model.ChannelMain.DBChannel()); err != nil {
return
}
log.Info("realnameMainList infos : %+v", infos)
if len(infos) <= 0 {
return
}
for _, i := range infos {
log.Info("realnameMainList info: %+v", i)
mids = append(mids, i.MID)
}
log.Info("realnameMainList mids: %+v", mids)
if dl, total, err = s.dao.RealnameMainList(ctx, mids, arg.DBCardType(), arg.DBCountry(), arg.OPName, arg.TSFrom, arg.TSTo, arg.DBState(), arg.PN, arg.PS, arg.IsDesc); err != nil {
return
}
} else {
var (
mids []int64
)
if arg.MID > 0 {
mids = append(mids, arg.MID)
}
if dl, total, err = s.dao.RealnameMainList(ctx, mids, arg.DBCardType(), arg.DBCountry(), arg.OPName, arg.TSFrom, arg.TSTo, arg.DBState(), arg.PN, arg.PS, arg.IsDesc); err != nil {
return
}
}
var (
midMap = make(map[int64]int) // map[mid]count
mids []int64
imgIDs []int64
)
// 审核 db 数据解析进 list
for _, d := range dl {
midMap[d.MID] = 0
var (
r = &model.RespRealnameApply{}
)
r.ParseDBMainApply(d)
imgIDs = append(imgIDs, d.HandIMG, d.FrontIMG, d.BackIMG)
list = append(list, r)
}
// 没有数据则返回
if len(midMap) <= 0 {
return
}
// 获取实名申请次数
for mid := range midMap {
if midMap[mid], err = s.dao.RealnameApplyCount(ctx, mid); err != nil {
return
}
mids = append(mids, mid)
}
// 获取mid的昵称 & 等级信息
var (
memsArg = &memmdl.ArgMemberMids{
Mids: mids,
}
memMap map[int64]*memmdl.Member
imgMap map[int64]*model.DBRealnameApplyIMG
)
if memMap, err = s.memberRPC.Members(ctx, memsArg); err != nil {
err = errors.WithStack(err)
return
}
// 获取证件照信息
if imgMap, err = s.dao.RealnameApplyIMG(ctx, imgIDs); err != nil {
return
}
for _, ra := range list {
if mem, ok := memMap[ra.MID]; ok {
ra.ParseMember(mem)
}
for _, id := range ra.IMGIDs {
if img, ok := imgMap[id]; ok {
ra.ParseDBApplyIMG(img.IMGData)
}
}
ra.Times = midMap[ra.MID]
}
return
}
func (s *Service) realnameAlipayList(ctx context.Context, arg *model.ArgRealnameList) (list []*model.RespRealnameApply, total int, err error) {
var (
dl []*model.DBRealnameAlipayApply
)
if arg.Card != "" {
// 如果查询证件号, 则通过证件号MD5 realname_info 查询到 对应的 mids
var (
infos []*model.DBRealnameInfo
mids []int64
md5 = cardMD5(arg.Card, arg.DBCardType(), arg.Country)
)
log.Info("realnameAlipayList card: %s, md5: %s", arg.Card, md5)
if infos, err = s.dao.RealnameInfoByCardMD5(ctx, md5, arg.State.DBStatus(), model.ChannelAlipay.DBChannel()); err != nil {
return
}
log.Info("realnameAlipayList infos : %+v", infos)
if len(infos) <= 0 {
return
}
for _, i := range infos {
log.Info("realnameAlipayList info: %+v", i)
mids = append(mids, i.MID)
}
log.Info("realnameAlipayList mids: %+v", mids)
if dl, total, err = s.dao.RealnameAlipayList(ctx, mids, 0, 0, arg.State.DBStatus(), arg.PN, arg.PS, arg.IsDesc); err != nil {
return
}
} else {
var (
mids []int64
)
if arg.MID > 0 {
mids = append(mids, arg.MID)
}
if dl, total, err = s.dao.RealnameAlipayList(ctx, mids, arg.TSFrom, arg.TSTo, arg.State.DBStatus(), arg.PN, arg.PS, arg.IsDesc); err != nil {
return
}
}
log.Info("realnameAlipayList dl: %+v, total: %d", dl, total)
var (
midMap = make(map[int64]int)
)
// append to list
for _, d := range dl {
midMap[d.MID] = 0
var (
r = &model.RespRealnameApply{}
)
r.ParseDBAlipayApply(d)
list = append(list, r)
}
if len(midMap) <= 0 {
return
}
var mids []int64
for mid := range midMap {
if midMap[mid], err = s.dao.RealnameApplyCount(ctx, mid); err != nil {
return
}
mids = append(mids, mid)
}
var (
memsArg = &memmdl.ArgMemberMids{
Mids: mids,
}
memMap map[int64]*memmdl.Member
)
if memMap, err = s.memberRPC.Members(ctx, memsArg); err != nil {
err = errors.WithStack(err)
return
}
for _, ra := range list {
if mem, ok := memMap[ra.MID]; ok {
ra.ParseMember(mem)
}
ra.Times = midMap[ra.MID]
}
return
}
func cardMD5(card string, cardType int, country int) (res string) {
if card == "" || cardType < 0 || country < 0 {
return
}
var (
lowerCode = strings.ToLower(card)
key = fmt.Sprintf("%s_%s_%d_%d", model.RealnameSalt, lowerCode, cardType, country)
)
return fmt.Sprintf("%x", md5.Sum([]byte(key)))
}
// RealnamePendingList .
func (s *Service) RealnamePendingList(ctx context.Context, arg *model.ArgRealnamePendingList) (list []*model.RespRealnameApply, total int, err error) {
var (
larg = &model.ArgRealnameList{
Channel: arg.Channel,
State: model.RealnameApplyStatePending,
TSFrom: time.Now().Add(-time.Hour * 24 * 7).Unix(),
PS: arg.PS,
PN: arg.PN,
}
)
return s.RealnameList(ctx, larg)
}
// RealnameAuditApply .
func (s *Service) RealnameAuditApply(ctx context.Context, arg *model.ArgRealnameAuditApply, adminName string, adminID int64) (err error) {
var (
mid int64
)
// 1. check the apply state
switch arg.Channel {
case model.ChannelMain:
var apply *model.DBRealnameApply
if apply, err = s.dao.RealnameMainApply(ctx, arg.ID); err != nil {
return
}
if apply.IsPassed() {
return
}
mid = apply.MID
case model.ChannelAlipay:
var apply *model.DBRealnameAlipayApply
if apply, err = s.dao.RealnameAlipayApply(ctx, arg.ID); err != nil {
return
}
if apply.Status == model.RealnameApplyStateNone.DBStatus() || apply.Status == model.RealnameApplyStateRejective.DBStatus() {
return
}
mid = apply.MID
}
var (
state = 0
msgTitle = ""
msgContent = ""
mc = "2_2_1"
expNotify = false
)
switch arg.Action {
case model.RealnameActionPass:
state = model.RealnameApplyStatePassed.DBStatus()
msgTitle = "您提交的实名认证已审核通过"
msgContent = "恭喜,您提交的实名认证已通过审核"
expNotify = true
case model.RealnameActionReject:
state = model.RealnameApplyStateRejective.DBStatus()
msgTitle = "您提交的实名认证未通过审核"
msgContent = fmt.Sprintf(`抱歉,您提交的实名认证未通过审核,驳回原因:%s。请修改后重新提交实名认证。`, arg.Reason)
default:
err = ecode.RequestErr
return
}
// 2. do something
switch arg.Channel {
case model.ChannelMain:
if err = s.dao.UpdateOldRealnameApply(ctx, arg.ID, state, adminName, adminID, time.Now(), arg.Reason); err != nil {
return
}
case model.ChannelAlipay:
if err = s.dao.UpdateRealnameAlipayApply(ctx, arg.ID, adminID, adminName, state, arg.Reason); err != nil {
return
}
if err = s.dao.UpdateRealnameInfo(ctx, mid, state, arg.Reason); err != nil {
return
}
}
go func() {
if err := s.dao.RawMessage(context.Background(), mc, msgTitle, msgContent, []int64{mid}); err != nil {
log.Error("%+v", err)
}
if expNotify {
expMsg := &model.AddExpMsg{
Event: "identify",
Mid: mid,
IP: metadata.String(ctx, metadata.RemoteIP),
Ts: time.Now().Unix(),
}
if err := s.dao.PubExpMsg(ctx, expMsg); err != nil {
log.Error("%+v", err)
}
}
}()
return
}
// RealnameReasonList .
func (s *Service) RealnameReasonList(ctx context.Context, arg *model.ArgRealnameReasonList) (list []string, total int, err error) {
return s.dao.RealnameReasonList(ctx)
}
// RealnameSetReason .
func (s *Service) RealnameSetReason(ctx context.Context, arg *model.ArgRealnameSetReason) (err error) {
return s.dao.UpdateRealnameReason(ctx, arg.Reasons)
}
// RealnameSearchCard .
func (s *Service) RealnameSearchCard(ctx context.Context, cards []string, cardType int, country int) (data map[string]int64, err error) {
var (
hashmap = make(map[string]string) //map[hash]card
hashes = make([]string, 0)
list []*model.DBRealnameInfo
)
for _, card := range cards {
hash := cardMD5(card, cardType, country)
hashmap[hash] = card
hashes = append(hashes, hash)
}
if list, err = s.dao.RealnameSearchCards(ctx, hashes); err != nil {
return
}
data = make(map[string]int64)
for _, l := range list {
if rawCode, ok := hashmap[l.CardMD5]; ok {
data[rawCode] = l.MID
}
}
return
}
// RealnameUnbind is.
func (s *Service) RealnameUnbind(ctx context.Context, mid int64, adminName string, adminID int64) (err error) {
var (
info *model.DBRealnameInfo
)
if info, err = s.dao.RealnameInfo(ctx, mid); err != nil {
return
}
if info == nil {
err = ecode.RealnameAlipayApplyInvalid
return
}
if info.Status != model.RealnameApplyStatePassed.DBStatus() {
return
}
if err = s.dao.UpdateRealnameInfo(ctx, mid, model.RealnameApplyStateRejective.DBStatus(), "管理后台解绑"); err != nil {
return
}
switch info.Channel {
case model.ChannelMain.DBChannel():
if err = s.dao.RejectRealnameMainApply(ctx, mid, adminName, adminID, "管理后台解绑"); err != nil {
return
}
case model.ChannelAlipay.DBChannel():
if err = s.dao.RejectRealnameAlipayApply(ctx, mid, adminName, adminID, "管理后台解绑"); err != nil {
return
}
default:
log.Warn("Failed to reject realname apply: unrecognized channel: %+v", info)
}
go func() {
r := &report.ManagerInfo{
Uname: adminName,
UID: adminID,
Business: model.RealnameManagerLogID,
Type: 0,
Oid: mid,
Action: model.LogActionRealnameUnbind,
Ctime: time.Now(),
}
if err = report.Manager(r); err != nil {
log.Error("Send manager log failed : %+v , report : %+v", err, r)
err = nil
return
}
log.Info("Send manager log success report : %+v", r)
}()
return
}
// RealnameImage return img
func (s *Service) RealnameImage(ctx context.Context, token string) ([]byte, error) {
filePath := fmt.Sprintf("%s/%s.txt", conf.Conf.Realname.DataDir, token)
_, err := os.Stat(filePath)
if os.IsNotExist(err) {
log.Info("file : %s , not found", filePath)
return nil, ecode.RequestErr
}
file, err := os.Open(filePath)
if err != nil {
return nil, errors.WithStack(err)
}
defer file.Close()
img, err := ioutil.ReadAll(file)
if err != nil {
return nil, errors.WithStack(err)
}
return s.mainCryptor.IMGDecrypt(img)
}
// FetchRealnameImage is
func (s *Service) FetchRealnameImage(ctx context.Context, token string) ([]byte, error) {
img, err := s.dao.GetRealnameImageCache(ctx, asIMGData(token))
if err == nil && len(img) > 0 {
return img, nil
}
if err != nil {
log.Warn("Failed to get realname image from cache: %s: %+v", token, err)
}
img, err = s.RealnameImage(ctx, token)
if err != nil {
return nil, err
}
if len(img) <= _512KiloBytes {
return img, nil
}
striped, err := StripImage(img)
if err != nil {
log.Warn("Failed to strip image: %+v", err)
return img, nil
}
return striped, nil
}
// RealnameImagePreview return preview img
func (s *Service) RealnameImagePreview(ctx context.Context, token string, borderSize uint) (data []byte, err error) {
var (
src []byte
)
if src, err = s.RealnameImage(ctx, token); err != nil {
return
}
if len(src) == 0 {
return
}
var (
img image.Image
imgWidth, imgHeight int
imgFormat string
sr = bytes.NewReader(src)
)
if img, imgFormat, err = image.Decode(sr); err != nil {
log.Warn("Failed to decode image: %+v, return origin image data directly", err)
return src, nil
}
imgWidth, imgHeight = img.Bounds().Dx(), img.Bounds().Dy()
log.Info("Decode img : %s , format : %s , width : %d , height : %d ", token, imgFormat, imgWidth, imgHeight)
if imgFormat != "png" && imgFormat != "jpg" && imgFormat != "jpeg" {
return
}
if imgWidth > imgHeight {
img = resize.Resize(borderSize, 0, img, resize.Lanczos3)
} else {
img = resize.Resize(0, borderSize, img, resize.Lanczos3)
}
var (
bb bytes.Buffer
bw = bufio.NewWriter(&bb)
)
switch imgFormat {
case "jpg", "jpeg":
if err = jpeg.Encode(bw, img, nil); err != nil {
err = errors.WithStack(err)
return
}
case "png":
if err = png.Encode(bw, img); err != nil {
err = errors.WithStack(err)
return
}
}
data = bb.Bytes()
return
}
// RealnameExcel export user realname info
func (s *Service) RealnameExcel(ctx context.Context, mids []int64) ([]*model.RealnameExport, error) {
infos, err := s.dao.BatchRealnameInfo(ctx, mids)
if err != nil {
log.Warn("Failed to get realname info with mids: %+v: %+v", mids, err)
// keep an empty infos
infos = make(map[int64]*model.DBRealnameInfo)
}
pinfos, err := s.dao.PassportQueryByMidsChunked(ctx, mids, 100)
if err != nil {
log.Warn("Failed to get passport query by mids: %+v: %+v", mids, err)
// keep an empty infos
pinfos = make(map[int64]*model.PassportQueryByMidResult)
}
res := make([]*model.RealnameExport, 0, len(mids))
for _, mid := range mids {
export := &model.RealnameExport{
Mid: mid,
}
// passport
func() {
p, ok := pinfos[mid]
if !ok {
log.Warn("Failed to get passport info with mid: %d", mid)
return
}
export.UserID = p.Userid
export.Uname = p.Name
export.Tel = p.Tel
}()
// realname
func() {
info, ok := infos[mid]
if !ok {
log.Warn("Failed to get realname info with mid: %d", mid)
return
}
export.Realname = info.Realname
export.CardType = info.CardType
cardDecode, err := model.CardDecrypt(info.Card)
if err != nil {
log.Error("Failed to decrypt card: %s: %+v", info.Card, err)
return
}
export.CardNum = cardDecode
}()
res = append(res, export)
}
return res, nil
}
// RealnameSubmit is
func (s *Service) RealnameSubmit(ctx context.Context, arg *model.ArgRealnameSubmit) error {
encryptedCardNum, err := s.realnameCrypto.CardEncrypt([]byte(arg.CardNum))
if err != nil {
return err
}
_ = func() error {
front := &model.DBRealnameApplyIMG{IMGData: asIMGData(arg.FrontImageToken)}
if err := s.dao.AddRealnameIMG(ctx, front); err != nil {
return err
}
back := &model.DBRealnameApplyIMG{IMGData: asIMGData(arg.BackImageToken)}
if err := s.dao.AddRealnameIMG(ctx, back); err != nil {
return err
}
apply := &model.DBRealnameApply{
MID: arg.Mid,
Realname: arg.Realname,
Country: arg.Country,
CardType: arg.CardType,
CardNum: string(encryptedCardNum),
CardMD5: cardMD5(arg.CardNum, int(arg.CardType), int(arg.Country)),
FrontIMG: front.ID,
BackIMG: back.ID,
Status: model.RealnameApplyStatePassed.DBStatus(),
Operator: arg.Operator,
OperatorID: arg.OperatorID,
OperatorTime: time.Now(),
}
if arg.HandImageToken != "" {
hand := &model.DBRealnameApplyIMG{IMGData: asIMGData(arg.HandImageToken)}
if err := s.dao.AddRealnameIMG(ctx, hand); err != nil {
return err
}
apply.HandIMG = hand.ID
}
if err := s.dao.AddRealnameApply(ctx, apply); err != nil {
return err
}
info := &model.DBRealnameInfo{
MID: apply.MID,
Channel: model.ChannelMain.DBChannel(),
Realname: apply.Realname,
Country: apply.Country,
CardType: apply.CardType,
Card: apply.CardNum,
CardMD5: apply.CardMD5,
Status: model.RealnameApplyStatePassed.DBStatus(),
Reason: fmt.Sprintf("管理后台提交:%s", arg.Remark),
}
return s.dao.SubmitRealnameInfo(ctx, info)
}
toOld := func() error {
front := &model.DeDeIdentificationCardApplyImg{IMGData: asIMGData(arg.FrontImageToken)}
if err := s.dao.AddOldRealnameIMG(ctx, front); err != nil {
return err
}
back := &model.DeDeIdentificationCardApplyImg{IMGData: asIMGData(arg.BackImageToken)}
if err := s.dao.AddOldRealnameIMG(ctx, back); err != nil {
return err
}
apply := &model.DeDeIdentificationCardApply{
MID: arg.Mid,
Realname: arg.Realname,
Type: arg.CardType,
CardData: string(encryptedCardNum),
CardForSearch: cardMD5(arg.CardNum, int(arg.CardType), int(arg.Country)),
FrontImg: front.ID,
BackImg: back.ID,
ApplyTime: int32(time.Now().Unix()),
Operator: arg.Operator,
OperatorTime: int32(time.Now().Unix()),
Status: int8(model.RealnameApplyStatePassed.DBStatus()),
Remark: fmt.Sprintf("管理后台提交:%s", arg.Remark),
}
if arg.HandImageToken != "" {
hand := &model.DeDeIdentificationCardApplyImg{IMGData: asIMGData(arg.HandImageToken)}
if err := s.dao.AddOldRealnameIMG(ctx, hand); err != nil {
return err
}
apply.FrontImg2 = hand.ID
}
if err := s.dao.AddOldRealnameApply(ctx, apply); err != nil {
return err
}
info := &model.DBRealnameInfo{
MID: apply.MID,
Channel: model.ChannelMain.DBChannel(),
Realname: apply.Realname,
Country: arg.Country,
CardType: arg.CardType,
Card: apply.CardData,
CardMD5: apply.CardForSearch,
Status: model.RealnameApplyStatePassed.DBStatus(),
Reason: fmt.Sprintf("管理后台提交:%s", arg.Remark),
}
return s.dao.SubmitRealnameInfo(ctx, info)
}
if err := toOld(); err != nil {
return err
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.RealnameManagerLogID,
Type: 0,
Oid: arg.Mid,
Action: model.LogActionRealnameSubmit,
Ctime: time.Now(),
})
return nil
}
// RealnameFileUpload is
func (s *Service) RealnameFileUpload(ctx context.Context, mid int64, data []byte) (src string, err error) {
var (
md5Engine = md5.New()
hashMID string
hashRand string
fileName string
dirPath string
dateStr string
)
md5Engine.Write([]byte(strconv.FormatInt(mid, 10)))
hashMID = hex.EncodeToString(md5Engine.Sum(nil))
md5Engine.Reset()
md5Engine.Write([]byte(strconv.FormatInt(time.Now().Unix(), 10)))
md5Engine.Write([]byte(strconv.FormatInt(mrand.Int63n(1000000), 10)))
hashRand = hex.EncodeToString(md5Engine.Sum(nil))
fileName = fmt.Sprintf("%s_%s.txt", hashMID[:6], hashRand)
dateStr = time.Now().Format("20060102")
dirPath = fmt.Sprintf("%s/%s/", s.c.Realname.DataDir, dateStr)
var (
dataFile *os.File
writeFileSize int
encrptedData []byte
)
_, err = os.Stat(dirPath)
if os.IsNotExist(err) {
mask := syscall.Umask(0)
defer syscall.Umask(mask)
if err = os.MkdirAll(dirPath, 0777); err != nil {
err = errors.WithStack(err)
return
}
}
if encrptedData, err = s.mainCryptor.IMGEncrypt(data); err != nil {
err = errors.WithStack(err)
return
}
if dataFile, err = os.Create(dirPath + fileName); err != nil {
err = errors.Wrapf(err, "create file %s failed", dirPath+fileName)
return
}
defer dataFile.Close()
if writeFileSize, err = dataFile.Write(encrptedData); err != nil {
err = errors.Wrapf(err, "write file %s size %d failed", dirPath+fileName, len(encrptedData))
return
}
if writeFileSize != len(encrptedData) {
err = errors.Errorf("Write file data to %s , expected %d actual %d", dirPath+fileName, len(encrptedData), writeFileSize)
return
}
src = fmt.Sprintf("%s/%s", dateStr, strings.TrimSuffix(fileName, ".txt"))
return
}
func asIMGData(imgToken string) string {
return model.RealnameImgPrefix + imgToken + model.RealnameImgSuffix
}
func asIMGToken(IMGData string) string {
token := strings.TrimPrefix(IMGData, "/idenfiles/")
token = strings.TrimSuffix(token, ".txt")
return token
}
// StripImage is
func StripImage(raw []byte) ([]byte, error) {
i, format, err := image.Decode(bytes.NewReader(raw))
if err != nil {
return nil, errors.WithStack(err)
}
out := &bytes.Buffer{}
switch format {
case "jpg", "jpeg":
if err := jpeg.Encode(out, i, &jpeg.Options{Quality: jpeg.DefaultQuality}); err != nil {
return nil, errors.WithStack(err)
}
default:
return nil, errors.Errorf("Unsupported type: %s", format)
}
return out.Bytes(), nil
}

View File

@@ -0,0 +1,242 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/admin/main/member/model"
"go-common/app/admin/main/member/model/block"
relation "go-common/app/service/main/relation/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/queue/databus/report"
xtime "go-common/library/time"
)
const (
_logActionUserPropertyAudit = "user_property_review_audit"
)
// Reviews is.
func (s *Service) Reviews(ctx context.Context, arg *model.ArgReviewList) ([]*model.UserPropertyReview, int, error) {
bySearch := func() ([]*model.UserPropertyReview, int, error) {
stime := arg.STime.Time().Format("2006-01-02 15:04:05")
if arg.ETime == 0 {
arg.ETime = xtime.Time(time.Now().Unix())
}
etime := arg.ETime.Time().Format("2006-01-02 15:04:05")
property := int8ToInt(arg.Property)
state := int8ToInt(arg.State)
result, err := s.dao.SearchUserPropertyReview(ctx, arg.Mid, property, state, arg.IsMonitor, arg.IsDesc, arg.Operator, stime, etime, arg.Pn, arg.Ps)
if err != nil {
return nil, 0, err
}
ids := result.IDs()
rws, err := s.dao.ReviewByIDs(ctx, ids, arg.State)
if err != nil {
return nil, 0, err
}
rws = arrange(rws, ids)
return rws, result.Total(), nil
}
byDB := func() ([]*model.UserPropertyReview, int, error) {
return s.dao.Reviews(ctx, arg.Mid, arg.Property, arg.State, arg.IsMonitor, arg.IsDesc, arg.Operator, arg.STime, arg.ETime, arg.Pn, arg.Ps)
}
rws, total, err := bySearch()
if arg.ForceDB {
log.Info("Force user property review query to db")
rws, total, err = byDB()
}
if err != nil {
return nil, 0, err
}
for _, rw := range rws {
if rw.Property == model.ReviewPropertyFace {
rw.BuildFaceURL()
}
}
s.reviewsName(ctx, rws)
s.reviewsFaceReject(ctx, rws)
s.reviewsRelationStat(ctx, rws)
return rws, total, err
}
func (s *Service) onReviewSuccess(ctx context.Context, waitRws []*model.UserPropertyReview, arg *model.ArgReviewAudit) error {
if !arg.BlockUser {
return nil
}
blockArg := &block.ParamBatchBlock{
MIDs: waitRwMids(waitRws),
AdminName: arg.Operator,
AdminID: arg.OperatorID,
Source: arg.Source,
Area: arg.Area,
Reason: arg.Reason,
Comment: arg.Comment,
Action: arg.Action,
Duration: arg.Duration,
Notify: arg.Notify,
}
if !blockArg.Validate() {
log.Error("Failed to validate block parama, arg: %v", blockArg)
return ecode.RequestErr
}
if err := s.block.BatchBlock(ctx, blockArg); err != nil {
log.Error("Failed to batch block, error: %v, arg: %v", err, blockArg)
return err
}
return nil
}
// ReviewAudit is.
func (s *Service) ReviewAudit(ctx context.Context, arg *model.ArgReviewAudit) error {
waitRws, err := s.dao.ReviewByIDs(ctx, arg.ID, []int8{model.ReviewStateWait})
if err != nil {
return err
}
if err := s.dao.ReviewAudit(ctx, arg.ID, arg.State, arg.Remark, arg.Operator); err != nil {
return err
}
for _, r := range waitRws {
ak := auditKey(r.Property, r.IsMonitor)
handler, ok := s.auditHandlers[ak]
if !ok {
log.Warn("Unable to handle property update: review: %+v audit: %+v", r, arg)
continue
}
if err := handler(ctx, r, arg); err != nil {
log.Error("Failed to trigger review audit event: review: %+v error: %+v", r, err)
remark := fmt.Sprintf("操作异常:%s, 备注: %s", ecode.Cause(err).Message(), arg.Remark)
if err = s.dao.UpdateRemark(ctx, r.ID, remark); err != nil {
log.Error("Failed to update remark error: %v", err)
}
}
report.Manager(&report.ManagerInfo{
Uname: arg.Operator,
UID: arg.OperatorID,
Business: model.ManagerLogID,
Type: 0,
Oid: r.Mid,
Action: _logActionUserPropertyAudit,
Ctime: time.Now(),
// extra
Index: []interface{}{r.ID, arg.State, 0, arg.Remark, "", ""},
Content: map[string]interface{}{
"remark": arg.Remark,
"state": arg.State,
"id": r.ID,
"mid": r.Mid,
},
})
}
s.onReviewSuccess(ctx, waitRws, arg)
return nil
}
// Review is.
func (s *Service) Review(ctx context.Context, arg *model.ArgReview) (*model.UserPropertyReview, error) {
r, err := s.dao.Review(ctx, arg.ID)
if err != nil {
return nil, err
}
r.Block, err = s.block.History(ctx, r.Mid, 2, 1, true)
if err != nil {
log.Error("Failed to get block review info, error: %v, mid: %v", err, r.Mid)
err = nil
}
return r, nil
}
func (s *Service) reviewsName(ctx context.Context, rws []*model.UserPropertyReview) {
mids := make([]int64, 0, len(rws))
for _, rw := range rws {
mids = append(mids, rw.Mid)
}
bs, err := s.dao.Bases(ctx, mids)
if err != nil {
log.Error("Failed to fetch bases with mids: %+v: %+v", mids, err)
return
}
for _, rw := range rws {
b, ok := bs[rw.Mid]
if !ok {
continue
}
rw.Name = b.Name
}
}
func (s *Service) reviewsFaceReject(ctx context.Context, rws []*model.UserPropertyReview) {
mids := make([]int64, 0, len(rws))
for _, rw := range rws {
mids = append(mids, rw.Mid)
}
frs, err := s.dao.BatchUserAddit(ctx, mids)
if err != nil {
log.Error("Failed to fetch FaceRejects with mids: %+v: %+v", mids, err)
return
}
for _, rw := range rws {
if fr, ok := frs[rw.Mid]; ok {
rw.FaceReject = fr.FaceReject
}
}
}
func (s *Service) reviewsRelationStat(ctx context.Context, rws []*model.UserPropertyReview) {
mids := make([]int64, 0, len(rws))
for _, rw := range rws {
mids = append(mids, rw.Mid)
}
stats, err := s.relationRPC.Stats(ctx, &relation.ArgMids{
Mids: mids,
RealIP: metadata.String(ctx, metadata.RemoteIP),
})
if err != nil {
log.Error("Failed to fetch relation stat with mids: %+v: %+v", mids, err)
return
}
for _, rw := range rws {
stat, ok := stats[rw.Mid]
if !ok {
continue
}
rw.Follower = stat.Follower
}
}
func int8ToInt(in []int8) []int {
res := []int{}
for _, i := range in {
res = append(res, int(i))
}
return res
}
func arrange(rws []*model.UserPropertyReview, ids []int64) []*model.UserPropertyReview {
res := []*model.UserPropertyReview{}
tmp := make(map[int64]*model.UserPropertyReview, len(ids))
for _, rw := range rws {
tmp[rw.ID] = rw
}
for _, id := range ids {
if rw, ok := tmp[id]; ok {
res = append(res, rw)
}
}
return res
}
func waitRwMids(waitRws []*model.UserPropertyReview) []int64 {
mids := make([]int64, 0, len(waitRws))
for _, w := range waitRws {
mids = append(mids, w.Mid)
}
return mids
}

View File

@@ -0,0 +1,151 @@
package service
import (
"context"
"fmt"
"math/rand"
"net/http"
"net/url"
"path"
"go-common/app/admin/main/member/model"
comodel "go-common/app/service/main/coin/model"
"go-common/library/ecode"
"go-common/library/log"
)
var upNameCostCoins = 6.0
type auditHandler func(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error
func (s *Service) initAuditHandler() {
s.auditHandlers[auditKey(model.ReviewPropertySign, true)] = s.onSignAudit
s.auditHandlers[auditKey(model.ReviewPropertyName, true)] = s.onNameAudit
s.auditHandlers[auditKey(model.ReviewPropertyFace, true)] = s.onFaceMonitorAudit
s.auditHandlers[auditKey(model.ReviewPropertyFace, false)] = s.onFaceAudit
}
func auditKey(property int8, isMonitor bool) string {
return fmt.Sprintf("%d-%t", property, isMonitor)
}
func (s *Service) onSignAudit(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
switch arg.State {
case model.ReviewStatePass:
return s.dao.UpSign(ctx, origin.Mid, origin.New)
case model.ReviewStateNoPass:
if err := s.dao.Message(ctx, "违规签名处理通知", "抱歉,由于你的签名涉嫌违规,系统已将你的签名回退。如有疑问请联系客服。", []int64{origin.Mid}); err != nil {
log.Error("Failed to send message: mid: %d: %+v", origin.Mid, err)
}
return nil
}
return nil
}
func (s *Service) onNameAudit(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
switch arg.State {
case model.ReviewStatePass:
if origin.NickFree() {
return s.dao.UpdateUname(ctx, origin.Mid, origin.New)
}
coins, err := s.coinRPC.UserCoins(ctx, &comodel.ArgCoinInfo{Mid: origin.Mid})
if err != nil {
return err
}
if coins < upNameCostCoins {
return ecode.UpdateUnameMoneyIsNot
}
if err := s.dao.UpdateUname(ctx, origin.Mid, origin.New); err != nil {
log.Error("faild to update Name, mid: %d, name: %s, %+v", origin.Mid, origin.New, err)
return err
}
arg := &comodel.ArgModifyCoin{
Mid: origin.Mid,
Count: -upNameCostCoins,
Reason: fmt.Sprintf("UPDATE:NICK:%s=>%s", origin.Old, origin.New),
}
if _, err := s.coinRPC.ModifyCoin(ctx, arg); err != nil {
log.Error("faild to modify coin, arg: %+v, %+v", arg, err)
return err
}
case model.ReviewStateNoPass:
if err := s.dao.Message(ctx, "违规昵称处理通知", "抱歉,由于你的昵称涉嫌违规,系统已将你的昵称回退。如有疑问请联系客服。", []int64{origin.Mid}); err != nil {
log.Error("Failed to send message: mid: %d: %+v", origin.Mid, err)
}
return nil
}
return nil
}
func (s *Service) onFaceAudit(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
switch arg.State {
case model.ReviewStateNoPass:
if err := s.dao.UpFace(ctx, origin.Mid, ""); err != nil {
return err
}
return s.faceReject(ctx, origin, arg)
}
return nil
}
func (s *Service) onFaceMonitorAudit(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
switch arg.State {
case model.ReviewStatePass:
return s.dao.UpFace(ctx, origin.Mid, origin.New)
case model.ReviewStateNoPass:
return s.faceReject(ctx, origin, arg)
}
return nil
}
func (s *Service) mvToPrivate(ctx context.Context, face string) (string, error) {
file, err := s.dao.Image(buildURL(face))
if err != nil {
return "", err
}
ftype := http.DetectContentType(file)
privURL, err := s.dao.UploadImage(ctx, ftype, file, s.c.FacePriBFS)
if err != nil {
return "", err
}
if err := s.dao.DelImage(ctx, path.Base(privURL), s.c.FaceBFS); err != nil {
log.Error("s.dao.DelImage(%v) error(%+v)", privURL, err)
}
return urlPath(privURL), nil
}
func buildURL(path string) string {
return fmt.Sprintf("http://i%d.hdslb.com%s", rand.Int63n(3), path)
}
func urlPath(in string) string {
URL, err := url.Parse(in)
if err != nil {
return ""
}
return URL.Path
}
func (s *Service) faceReject(ctx context.Context, origin *model.UserPropertyReview, arg *model.ArgReviewAudit) error {
privFace, err := s.mvToPrivate(ctx, origin.New)
if err != nil {
log.Error("s.mvToPrivate(%d) error(%+v)", origin.Mid, err)
return err
}
if err := s.dao.UpdateReviewFace(ctx, origin.ID, privFace); err != nil {
log.Error("Failed to update review face: id: %d face: %s: %+v", origin.ID, privFace, err)
return err
}
if err := s.dao.MvArchivedFaceToPriv(ctx, origin.New, privFace, arg.Operator, arg.Remark); err != nil {
log.Error("mv archived face to private bucket mid(%d) error(%v)", origin.Mid, err)
return err
}
if err := s.dao.Message(ctx, "违规头像处理通知", "抱歉,由于你的头像涉嫌违规,系统已将你的头像回退。如有疑问请联系客服。", []int64{origin.Mid}); err != nil {
log.Error("Failed to send message: mid: %d: %+v", origin.Mid, err)
}
if err := s.dao.IncrFaceReject(ctx, origin.Mid); err != nil {
log.Error("IncrFaceReject faild: mid: %d: %+v", origin.Mid, err)
}
return nil
}

View File

@@ -0,0 +1,19 @@
package service
import (
"context"
"testing"
"go-common/app/admin/main/member/model"
"github.com/smartystreets/goconvey/convey"
)
func TestService_Reviews(t *testing.T) {
convey.Convey("Reviews", t, func() {
o, logs, err := s.Reviews(context.Background(), &model.ArgReviewList{Property: []int8{1}, IsDesc: true, STime: 10000, Pn: 1, Ps: 10})
convey.So(err, convey.ShouldBeNil)
convey.So(o, convey.ShouldNotBeNil)
convey.So(logs, convey.ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,405 @@
package service
import (
"context"
"encoding/json"
"fmt"
"path"
"time"
"go-common/app/admin/main/member/conf"
"go-common/app/admin/main/member/dao"
"go-common/app/admin/main/member/model"
"go-common/app/admin/main/member/service/block"
acccrypto "go-common/app/interface/main/account/service/realname/crypto"
account "go-common/app/service/main/account/api"
coinrpc "go-common/app/service/main/coin/api/gorpc"
rpcfigure "go-common/app/service/main/figure/rpc/client"
memberrpc "go-common/app/service/main/member/api/gorpc"
"go-common/app/service/main/member/service/crypto"
rpcrelation "go-common/app/service/main/relation/rpc/client"
rpcspy "go-common/app/service/main/spy/rpc/client"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/queue/databus"
"go-common/library/stat/prom"
xtime "go-common/library/time"
"github.com/robfig/cron"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
block *block.Service
auditHandlers map[string]auditHandler
coinRPC *coinrpc.Service
memberRPC *memberrpc.Service
spyRPC *rpcspy.Service
figureRPC *rpcfigure.Service
accountClient account.AccountClient
cron *cron.Cron
relationRPC *rpcrelation.Service
realnameCrypto *crypto.Realname
mainCryptor *acccrypto.Main
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
coinRPC: coinrpc.New(c.RPCClient.Coin),
memberRPC: memberrpc.New(c.RPCClient.Member),
figureRPC: rpcfigure.New(c.RPCClient.Figure),
spyRPC: rpcspy.New(c.RPCClient.Spy),
relationRPC: rpcrelation.New(c.RPCClient.Relation),
auditHandlers: make(map[string]auditHandler),
cron: cron.New(),
realnameCrypto: crypto.NewRealname(string(c.Realname.RsaPub), string(c.Realname.RsaPriv)),
mainCryptor: acccrypto.NewMain(string(c.Realname.RsaPub), string(c.Realname.RsaPriv)),
}
var err error
if s.accountClient, err = account.NewClient(c.RPCClient.Account); err != nil {
panic(err)
}
s.block = block.New(c, s.dao.BlockImpl(), s.spyRPC, s.figureRPC, s.accountClient, databus.New(c.AccountNotify))
s.initAuditHandler()
s.initCron()
s.cron.Start()
return s
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
s.block.Close()
}
func (s *Service) initCron() {
s.cron.AddFunc("0 */5 * * * *", func() { s.notifyAudit(context.Background()) }) // 用于发送审核数据给目标用户
s.cron.AddFunc("0 */5 * * * *", func() { s.promAuditTotal(context.Background()) }) // 用于上报审核数据给promethues
s.cron.AddFunc("0 */1 * * * *", func() { s.cacheRecentRealnameImage(context.Background()) }) // 用于缓存实名认证的图片数据
s.cron.AddFunc("0 */2 * * * *", func() { s.faceCheckproc(context.Background(), -10*time.Minute, "two minute") }) // 用于AI头像审核首次每隔2分钟审核10分钟内的头像
s.cron.AddFunc("0 */60 * * * *", func() { s.faceCheckproc(context.Background(), -6*time.Hour, "per hour") }) // 用于AI头像审核重新审核每小时重新审核一下6小时内的头像
s.cron.AddFunc("0 */5 * * * *", func() { s.faceAutoPassproc(context.Background()) }) // 头像自动审核每隔5分钟检查一次超过48小时未处理的头像并自动通过
}
// notifyAudit
func (s *Service) notifyAudit(ctx context.Context) {
now := time.Now()
log.Info("start notify audit at: %+v", now)
locked, err := s.dao.TryLockReviewNotify(ctx, now)
if err != nil {
log.Error("Failed to lock review notify at: %+v: %+v", now, err)
return
}
if !locked {
log.Warn("Already locked by other instance at: %+v", now)
return
}
stime := now.Add(-time.Hour * 24 * 7) // 只计算 7 天内的数据
// 绝对锁上了
faceNotify := func() error {
total, firstAt, err := s.faceAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch face audit notify content: %+v", err)
return err
}
log.Info("faceAuditNotifyContent success: total(%v),firstAt(%v)", total, firstAt)
title := fmt.Sprintf("头像审核提醒;消息时间:%s", now.Format("2006-01-02 15:04:05"))
firstAtStr := "null"
if firstAt != nil {
firstAtStr = firstAt.Format("2006-01-02 15:04:05")
}
content := fmt.Sprintf(
"头像审核提醒;消息时间:%s\n头像审核积压%d 条;最早进审时间:%s",
now.Format("2006-01-02 15:04:05"),
total,
firstAtStr,
)
return s.dao.MerakNotify(ctx, title, content)
}
if err := faceNotify(); err != nil {
log.Error("Failed to notify face review stat: %+v", err)
}
monitorNotify := func() error {
total, firstAt, err := s.monitorAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch monitor audit notify content: %+v", err)
return err
}
log.Info("monitorAuditNotifyContent success: total(%v),firstAt(%v)", total, firstAt)
title := fmt.Sprintf("用户信息监控提醒;消息时间:%s", now.Format("2006-01-02 15:04:05"))
firstAtStr := "null"
if firstAt != nil {
firstAtStr = firstAt.Format("2006-01-02 15:04:05")
}
content := fmt.Sprintf(
"用户信息监控提醒;消息时间:%s\n用户信息监控积压%d 条;最早进审时间:%s",
now.Format("2006-01-02 15:04:05"),
total,
firstAtStr,
)
return s.dao.MerakNotify(ctx, title, content)
}
if err := monitorNotify(); err != nil {
log.Error("Failed to notify monitor review stat: %+v", err)
}
// 实名认证待审核通知
realnameNotify := func() error {
total, firstAt, err := s.realnameAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch realname audit notify content: %+v", err)
return err
}
log.Info("realnameAuditNotifyContent success: total(%v),firstAt(%v)", total, firstAt)
title := fmt.Sprintf("实名认证审核提醒;消息时间:%s", now.Format("2006-01-02 15:04:05"))
firstAtStr := "null"
if firstAt != nil {
firstAtStr = firstAt.Format("2006-01-02 15:04:05")
}
content := fmt.Sprintf(
"实名认证审核提醒;消息时间:%s\n实名认证审核积压%d 条;最早进审时间:%s",
now.Format("2006-01-02 15:04:05"),
total,
firstAtStr,
)
return s.dao.MerakNotify(ctx, title, content)
}
if err := realnameNotify(); err != nil {
log.Error("Failed to notify realname list stat: %+v", err)
}
log.Info("end notify audit at: %+v", now)
}
// promAuditTotal
func (s *Service) promAuditTotal(ctx context.Context) {
stime := time.Now().Add(-time.Hour * 24 * 7) // 只计算 7 天内的数据
log.Info("promAuditTotal start %+v", time.Now())
faceAudit := func() {
faceTotal, _, err := s.faceAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch face audit notify content: %+v", err)
return
}
prom.BusinessInfoCount.State("faceAudit-needAudit", int64(faceTotal))
}
monitorAudit := func() {
monitorTotal, _, err := s.monitorAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch monitor audit notify content: %+v", err)
return
}
prom.BusinessInfoCount.State("monitorAudit-needAudit", int64(monitorTotal))
}
realnameAudit := func() {
realnameTotal, _, err := s.realnameAuditNotifyContent(ctx, stime)
if err != nil {
log.Error("Failed to fetch realname audit notify content: %+v", err)
return
}
prom.BusinessInfoCount.State("realnameAudit-needAudit", int64(realnameTotal))
}
faceAudit()
monitorAudit()
realnameAudit()
log.Info("promAuditTotal end %+v", time.Now())
}
func (s *Service) faceAuditNotifyContent(ctx context.Context, stime time.Time) (int, *time.Time, error) {
arg := &model.ArgReviewList{
State: []int8{0},
IsMonitor: false,
Property: []int8{model.ReviewPropertyFace},
IsDesc: false,
Pn: 1,
Ps: 1,
STime: xtime.Time(stime.Unix()),
ForceDB: false,
}
reviews, total, err := s.Reviews(ctx, arg)
if err != nil {
return 0, nil, err
}
if len(reviews) <= 0 {
return 0, nil, nil
}
firstAt := reviews[0].CTime.Time()
return total, &firstAt, nil
}
func (s *Service) monitorAuditNotifyContent(ctx context.Context, stime time.Time) (int, *time.Time, error) {
arg := &model.ArgReviewList{
State: []int8{0},
IsMonitor: true,
IsDesc: false,
Pn: 1,
Ps: 1,
STime: xtime.Time(stime.Unix()),
ForceDB: false,
}
reviews, total, err := s.Reviews(ctx, arg)
if err != nil {
return 0, nil, err
}
if len(reviews) <= 0 {
return 0, nil, nil
}
firstAt := reviews[0].CTime.Time()
return total, &firstAt, nil
}
func (s *Service) realnameAuditNotifyContent(ctx context.Context, stime time.Time) (int, *time.Time, error) {
arg := &model.ArgRealnameList{
Channel: "main", //main : 主站 alipay : 支付宝
TSFrom: stime.Unix(),
State: model.RealnameApplyStatePending,
IsDesc: false,
PN: 1,
PS: 1,
}
mainList, total, err := s.realnameMainList(ctx, arg)
if err != nil {
return 0, nil, err
}
if len(mainList) <= 0 {
return 0, nil, nil
}
firstAt := time.Unix(mainList[0].CreateTS, 0)
return total, &firstAt, nil
}
func (s *Service) faceAutoPassproc(ctx context.Context) {
now := time.Now()
log.Info("faceAutoPassproc start %+v", now)
etime := now.AddDate(0, 0, -2)
if err := s.faceAutoPass(ctx, etime); err != nil {
log.Error("Failed to face auto pass, error: %+v", err)
}
}
func (s *Service) faceAutoPass(ctx context.Context, etime time.Time) error {
property := []int{model.ReviewPropertyFace}
state := []int{model.ReviewStateWait, model.ReviewStateQueuing}
result, err := s.dao.SearchUserPropertyReview(ctx, 0, property,
state, false, false, "", "", etime.Format("2006-01-02 15:04:05"), 1, 100)
if err != nil {
return err
}
ids := result.IDs()
if len(ids) == 0 {
log.Info("face auto pass empty result list, end time: %v", etime)
return nil
}
if err = s.dao.FaceAutoPass(ctx, ids, xtime.Time(etime.Unix())); err != nil {
return err
}
return nil
}
func (s *Service) faceCheckproc(ctx context.Context, duration time.Duration, tag string) {
now := time.Now()
stime := now.Add(duration).Unix()
etime := now.Unix()
log.Info("faceCheckproc:%v start %+v", tag, now)
if err := s.faceAuditAI(ctx, stime, etime); err != nil {
log.Error("Failed to check face, error: %+v", err)
}
}
func (s *Service) faceAuditAI(ctx context.Context, stime, etime int64) error {
rws, err := s.dao.QueuingFaceReviewsByTime(ctx, xtime.Time(stime), xtime.Time(etime))
if err != nil {
log.Warn("Failed to get recent user_property_review image: %+v", err)
return err
}
for _, rw := range rws {
fcr, err := s.faceCheckRes(ctx, path.Base(rw.New))
if err != nil {
log.Error("Failed to get face check res, rw: %+v, error: %+v", rw, err)
continue
}
state := int8(model.ReviewStateWait)
if fcr.Valid() {
state = model.ReviewStatePass
}
remark := fmt.Sprintf("AI: %s", fcr.String())
if err = s.dao.AuditQueuingFace(ctx, rw.ID, remark, state); err != nil {
log.Error("Failed to audit queuing face, rw: %+v, error: %+v", rw, err)
continue
}
log.Info("face check success, rw: %+v", rw)
}
log.Info("faceCheckproc end")
return nil
}
func (s *Service) faceCheckRes(ctx context.Context, fileName string) (*model.FaceCheckRes, error) {
res, err := s.dao.SearchFaceCheckRes(ctx, fileName)
if err != nil {
return nil, err
}
if len(res.Result) == 0 {
return nil, ecode.NothingFound
}
userLog := res.Result[0]
fcr, err := parseFaceCheckRes(userLog.Extra)
if err != nil {
log.Error("Failed to parse faceCheckRes, userLog: %+v error: %+v", userLog, err)
return nil, err
}
return fcr, nil
}
func parseFaceCheckRes(in string) (*model.FaceCheckRes, error) {
res := &model.FaceCheckRes{}
err := json.Unmarshal([]byte(in), res)
if err != nil {
return nil, err
}
return res, nil
}
// BlockImpl is
func (s *Service) BlockImpl() *block.Service {
return s.block
}
func (s *Service) cacheRecentRealnameImage(ctx context.Context) {
images, err := s.dao.RecentRealnameApplyImg(ctx, time.Minute*2)
if err != nil {
log.Warn("Failed to get recent realname apply image: %+v", err)
return
}
for _, image := range images {
data, _ := s.dao.GetRealnameImageCache(ctx, image.IMGData)
if len(data) > 0 {
log.Info("This image has already been cached: %s", image.IMGData)
continue
}
data, err := s.FetchRealnameImage(ctx, asIMGToken(image.IMGData))
if err != nil {
log.Warn("Failed to fetch realname image to cache: %s: %+v", image.IMGData, err)
continue
}
if err := s.dao.SetRealnameImageCache(ctx, image.IMGData, data); err != nil {
log.Warn("Failed to set realname image cache: %s: %+v", image.IMGData, err)
continue
}
log.Info("Succeeded to cache realname image: %s", image.IMGData)
}
}

View File

@@ -0,0 +1,40 @@
package service
import (
"context"
"flag"
"testing"
"time"
"go-common/app/admin/main/member/conf"
"github.com/smartystreets/goconvey/convey"
)
var s *Service
func init() {
flag.Parse()
flag.Set("conf", "../cmd/member-admin-test.toml")
if err := conf.Init(); err != nil {
panic(err)
}
s = New(conf.Conf)
}
func TestPing(t *testing.T) {
convey.Convey("Ping", t, func() {
err := s.Ping(context.Background())
convey.So(err, convey.ShouldBeNil)
})
}
func TestFaceCheck(t *testing.T) {
convey.Convey("faceCheck", t, func() {
etime := time.Now().Unix()
stime := time.Now().AddDate(0, 0, -2).Unix()
err := s.faceAuditAI(context.Background(), stime, etime)
convey.So(err, convey.ShouldBeNil)
})
}