Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"addit.go",
"archive.go",
"databus.go",
"delay.go",
"flow.go",
"oper.go",
"recheck.go",
"report.go",
"task.go",
"track.go",
"video.go",
],
importpath = "go-common/app/job/main/videoup-report/model/archive",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//library/time: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,27 @@
package archive
const (
//UpFromPGC pgc
UpFromPGC = 1
//UpFromPGCSecret pgc secret
UpFromPGCSecret = 5
//UpFromCoopera pgc cooperate
UpFromCoopera = 6
)
// Addit addit struct
type Addit struct {
ID int64
Aid int64
Source string
RedirectURL string
MissionID int64
UpFrom int8
OrderID int
Dynamic string
}
//IsPGC is archive from pgc
func (addit *Addit) IsPGC() bool {
return addit.UpFrom == UpFromPGC || addit.UpFrom == UpFromPGCSecret || addit.UpFrom == UpFromCoopera
}

View File

@@ -0,0 +1,246 @@
package archive
import (
"sync"
"time"
)
const (
//StateOpen state open
StateOpen = 0
//StateOrange 橙色通过
StateOrange = 1
//StateForbidWait 待审
StateForbidWait = -1
//StateForbidRecicle 打回
StateForbidRecicle = -2
//StateForbidPolice 网警锁定
StateForbidPolice = -3
//StateForbidLock 锁定
StateForbidLock = -4
//StateForbidFixed 修复待审
StateForbidFixed = -6
//StateForbidLater 暂缓待审
StateForbidLater = -7
//StateForbidXcodeFail 转码失败
StateForbidXcodeFail = -16
//StateForbidSubmit 创建提交
StateForbidSubmit = -30
//StateForbidUserDelay 定时
StateForbidUserDelay = -40
//StateForbidUpDelete 删除
StateForbidUpDelete = -100
//RoundBegin 开始流转
RoundBegin = 0
//RoundAuditSecond 二审选定分区的多P稿件 及 PGC/活动的单P多P稿件
RoundAuditSecond = 10
//RoundAuditThird 三审:选定分区/PGC/活动 的单P多P稿件
RoundAuditThird = 20
//RoundReviewFirst 分区回查:粉丝小于配置阈值 如 5000 且 指定分区
RoundReviewFirst = 30
//RoundReviewFirstWaitTrigger 点击/粉丝 等待触发中间状态7天内达到阈值进列表未达到自动变99
RoundReviewFirstWaitTrigger = 31
//RoundReviewSecond 社区回查:粉丝大于配置阈值 如 5000 或 优质高危up
RoundReviewSecond = 40
//RoundTriggerFans 粉丝回查:粉丝量达到配置阈值
RoundTriggerFans = 80
//RoundTriggerClick 点击回查:点击量达到配置阈值
RoundTriggerClick = 90
//RoundEnd 流转结束
RoundEnd = 99
//AccessDefault access
AccessDefault = int16(0)
//AccessMember 会员可见
AccessMember = int16(10000)
//CopyrightUnknow copyright
CopyrightUnknow = 0
//CopyrightOriginal 原创
CopyrightOriginal = 1
//CopyrightCopy 转载
CopyrightCopy = 2
//AttrYes attribute yes
AttrYes = int32(1)
//AttrNo attribute no
AttrNo = int32(0)
//AttrBitNoRank 禁止排序
AttrBitNoRank = uint(0)
//AttrBitNoDynamic 禁止动态
AttrBitNoDynamic = uint(1)
//AttrBitNoWeb 禁止web
AttrBitNoWeb = uint(2)
//AttrBitNoMobile 禁止手机端
AttrBitNoMobile = uint(3)
//AttrBitNoSearch 禁止搜索
AttrBitNoSearch = uint(4)
//AttrBitOverseaLock 禁止海外
AttrBitOverseaLock = uint(5)
//AttrBitNoRecommend 禁止推荐
AttrBitNoRecommend = uint(6)
// AttrBitHideCoins = uint(7)
//AttrBitHasHD5 是否高清
AttrBitHasHD5 = uint(8)
// AttrBitVisitorDm = uint(9)
//AttrBitAllowBp 允许承包
AttrBitAllowBp = uint(10)
//AttrBitIsBangumi 番剧
AttrBitIsBangumi = uint(11)
//AttrBitIsPOrder 是否私单
AttrBitIsPOrder = uint(12)
//AttrBitHideClick 点击
AttrBitHideClick = uint(13)
//AttrBitAllowTag 允许操作tag
AttrBitAllowTag = uint(14)
// AttrBitIsFromArcApi = uint(15)
//AttrBitJumpURL 跳转
AttrBitJumpURL = uint(16)
//AttrBitIsMovie is movie
AttrBitIsMovie = uint(17)
//AttrBitBadgepay 付费
AttrBitBadgepay = uint(18)
//ReplyDefault 默认评论状态
ReplyDefault = int64(-1)
//ReplyOn 开评论
ReplyOn = int64(0)
//ReplyOff 关评论
ReplyOff = int64(1)
//LogBusJob 稿件后台任务日志bus
LogBusJob = 211
//LogTypeReply 稿件后台任务type评论
LogTypeReply = 1
)
//ReplyState 评论开关状态
var ReplyState = []int64{
ReplyDefault,
ReplyOn,
ReplyOff,
}
//ReplyDesc 评论状态描述
var ReplyDesc = map[int64]string{
ReplyDefault: "未知状态",
ReplyOn: "开",
ReplyOff: "关",
}
//UpInfo up info
type UpInfo struct {
Nw *Archive
Old *Archive
}
// Oper is archive operate model.
type Oper struct {
ID int64 `json:"id"`
AID int64 `json:"aid"`
UID int64 `json:"uid"`
TypeID int16 `json:"typeid"`
State int `json:"state"`
Content string `json:"-"`
Round int8 `json:"round"`
Attribute int32 `json:"attribute"`
LastID int64 `json:"last_id"`
Remark string `json:"-"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"mtime"`
}
// ArcMoveTypeCache archive move typeid count
type ArcMoveTypeCache struct {
Data map[int8]map[int16]map[string]int
sync.Mutex
}
// ArcRoundFlowCache archive round flow record
type ArcRoundFlowCache struct {
Data map[int8]map[int64]map[string]int
sync.Mutex
}
//Archive archive
type Archive struct {
ID int64 `json:"id"`
AID int64 `json:"aid"` //result库binlog={id:0,aid:xxx}
Mid int64 `json:"mid"`
TypeID int16 `json:"typeid"`
HumanRank int `json:"humanrank"`
Duration int `json:"duration"`
Desc string `json:"desc"`
Title string `json:"title"`
Cover string `json:"cover"`
Content string `json:"content"`
Tag string `json:"tag"`
Attribute int32 `json:"attribute"`
Copyright int8 `json:"copyright"`
AreaLimit int8 `json:"arealimit"`
State int `json:"state"`
Author string `json:"author"`
Access int `json:"access"`
Forward int `json:"forward"`
PubTime string `json:"pubtime"`
Reason string `json:"reject_reason"`
Round int8 `json:"round"`
CTime string `json:"ctime"`
MTime string `json:"mtime"`
PTime string `json:"ptime"`
}
//IsSyncState can archive sync
func (a *Archive) IsSyncState() bool {
if a.State >= 0 || a.State == StateForbidUserDelay || a.State == StateForbidUpDelete || a.State == StateForbidRecicle || a.State == StateForbidPolice ||
a.State == StateForbidLock {
return true
}
return false
}
//ArgStat arg state
type ArgStat struct {
Aid int64
Field int
Value int
RealIP string
}
// AttrVal get attribute value.
func (a *Archive) AttrVal(bit uint) int32 {
return (a.Attribute >> bit) & int32(1)
}
//NormalState normal state
func NormalState(state int) bool {
return state == StateOpen || state == StateOrange
}
//Type archive_type
type Type struct {
ID int16 `json:"id"`
PID int16 `json:"pid"`
Name string `json:"name"`
}
// StateMean the mean for archive state
var StateMean = map[int]string{
StateOpen: "开放浏览",
StateOrange: "橙色通过",
// forbid state
StateForbidWait: "待审",
StateForbidRecicle: "打回",
StateForbidPolice: "网警锁定",
StateForbidLock: "锁定稿件",
StateForbidFixed: "修复待审",
StateForbidLater: "暂缓审核",
//StateForbidAdminDelay: "延迟发布",
StateForbidXcodeFail: "转码失败",
StateForbidSubmit: "创建提交",
StateForbidUserDelay: "用户定时发布",
StateForbidUpDelete: "UP主删除",
}

View File

@@ -0,0 +1,96 @@
package archive
import "encoding/json"
const (
//RouteFirstRound 一转
RouteFirstRound = "first_round"
//RouteUGCFirstRound 一转
RouteUGCFirstRound = "ugc_first_round"
//RouteSecondRound 二转
RouteSecondRound = "second_round"
//RouteAddArchive 新增稿件
RouteAddArchive = "add_archive"
//RouteModifyArchive 稿件编辑
RouteModifyArchive = "modify_archive"
//RouteAutoOpen 自动开放
RouteAutoOpen = "auto_open"
//RouteDelayOpen 定时开放
RouteDelayOpen = "delay_open"
//RoutePostFirstRound first_round后续处理
RoutePostFirstRound = "post_first_round"
)
// Message databus message
type Message struct {
Action string `json:"action"`
Table string `json:"table"`
New json.RawMessage `json:"new"`
Old json.RawMessage `json:"old"`
}
//VideoupMsg msg
type VideoupMsg struct {
Route string `json:"route"`
Filename string `json:"filename"`
Timestamp int64 `json:"timestamp"`
// cid
Cid int64 `json:"cid,omitempty"`
DMIndex string `json:"dm_index,omitempty"`
SendEmail bool `json:"send_email"`
// encode
Xcode int8 `json:"xcode"`
EncodePurpose string `json:"encode_purpose,omitempty"`
EncodeRegionID int16 `json:"encode_region_id,omitempty"`
EncodeTypeID int16 `json:"encode_type_id,omitempty"`
VideoDesign *VideoDesign `json:"video_design,omitempty"`
Status int16 `json:"status,omitempty"`
// add or modify archive
Aid int64 `json:"aid,omitempty"`
EditArchive bool `json:"edit_archive,omitempty"`
EditVideo bool `json:"edit_video,omitempty"`
// ChangeTypeID
ChangeTypeID bool `json:"change_typeid"`
// ChangeCopyright
ChangeCopyright bool `json:"change_copyright"`
// ChangeCover
ChangeCover bool `json:"change_cover"`
// ChangeTitle
ChangeTitle bool `json:"change_title"`
// Notify
Notify bool `json:"send_notify"`
// MissionID
MissionID int64 `json:"mission_id,omitempty"`
// AdminChange
AdminChange bool `json:"admin_change,omitempty"`
FromList string `json:"from_list"`
TagChange bool `json:"tag_change,omitempty"`
AddVideos bool `json:"add_videos,omitempty"`
}
//VideoDesign 自定义马赛克和水印
type VideoDesign struct {
Mosaic []*Mosaic `json:"mosaic,omitempty"`
WaterMark []*WaterMark `json:"watermark,omitempty"`
}
//Mosaic 马赛克
type Mosaic struct {
X int64 `json:"x"`
Y int64 `json:"y"`
W int64 `json:"w"`
H int64 `json:"h"`
Start int64 `json:"start"`
End int64 `json:"end"`
}
//WaterMark 水印
type WaterMark struct {
LOC int8 `json:"loc,omitempty"`
URL string `json:"url,omitempty"`
MD5 string `json:"md5,omitempty"`
Start int64 `json:"start,omitempty"`
End int64 `json:"end,omitempty"`
X int64 `json:"x,omitempty"`
Y int64 `json:"y,omitempty"`
}

View File

@@ -0,0 +1,18 @@
package archive
import "time"
//定时发布类型
const (
DelayTypeForAdmin = int8(1)
DelayTypeForUser = int8(2)
)
//Delay 定时发布结构
type Delay struct {
ID int64
Aid int64
DTime time.Time
Type int8
State int8
}

View File

@@ -0,0 +1,65 @@
package archive
import (
"encoding/json"
"time"
)
const (
//FlowPoolRecheck 回查pool含热门回查、频道回查
FlowPoolRecheck = 4
//FLowGroupIDChannel 频道回查的流量控制分组id
FLowGroupIDChannel = 23
//FlowGroupIDHot 热门回查的流量控制分组id
FlowGroupIDHot = 24
//FlowOpen 开启
FlowOpen = int8(0)
//FlowDelete 取消
FlowDelete = int8(1)
//FlowLogAdd 流量添加日志
FlowLogAdd = int8(1)
//FlowLogUpdate 流量更新日志
FlowLogUpdate = int8(2)
//FlowLogDel 流量删除日志
FlowLogDel = int8(3)
//PoolArc 稿件流量
PoolArc = int8(0)
//PoolUp up主流量
PoolUp = int8(1)
//PoolPrivateOrder 私单流量
PoolPrivateOrder = int8(2)
//PoolArticle 专栏流量
PoolArticle = int8(3)
//PoolArcForbid 稿件禁止流量
PoolArcForbid = int8(4)
)
// Flow info
type Flow struct {
ID int64 `json:"id"`
Remark string `json:"remark"`
Rank int64 `json:"rank"`
Type int8 `json:"type"`
Value json.RawMessage `json:"value"`
CTime time.Time `json:"ctime"`
Pool int8 `json:"pool"`
State int8 `json:"state"`
}
//FlowData Flow data
type FlowData struct {
ID int64 `json:"id"`
Pool int8 `json:"pool"`
OID int64 `json:"oid"`
UID int64 `json:"uid"`
Parent int8 `json:"parent"`
GroupID int64 `json:"group_id"`
Remark string `json:"remark"`
State int8 `json:"state"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"mtime"`
GroupValue []byte `json:"group_value"`
}

View File

@@ -0,0 +1,47 @@
package archive
import "fmt"
const (
//OperTypeNoChannel oper type
OperTypeNoChannel = int8(1)
// OperStyleOne 操作展示类型1[%s]从[%v]设为[%v]
OperStyleOne = int8(1)
// OperStyleTwo 操作展示类型2[%s]%v:%v
OperStyleTwo = int8(2)
)
var (
//FlowOperType type
FlowOperType = map[int64]int8{
FLowGroupIDChannel: OperTypeNoChannel,
}
_operType = map[int8]string{
OperTypeNoChannel: "频道禁止",
}
)
// Operformat oper format.
func Operformat(tagID int8, old, new interface{}, style int8) (cont string) {
var template string
switch style {
case OperStyleOne:
template = "[%s]从[%v]设为[%v]"
case OperStyleTwo:
template = "[%s]%v:%v"
}
cont = fmt.Sprintf(template, _operType[tagID], old, new)
return
}
//VideoOper 视频审核记录结构
type VideoOper struct {
AID int64
UID int64
VID int64
Status int
Content string
Attribute int32
LastID int64
Remark string
}

View File

@@ -0,0 +1,56 @@
package archive
import (
"time"
)
const (
//TypeHotRecheck 热门回查
TypeHotRecheck = 1
//TypeChannelRecheck 频道回查
TypeChannelRecheck = 0
//TypeExcitationRecheck 激励回查
TypeExcitationRecheck = 2
//RecheckStateWait 待回查
RecheckStateWait = int8(-1)
//RecheckStateNoForbid 已回查,且没有禁止(热门) 已回查(频道)
RecheckStateNoForbid = int8(0)
//RecheckStateForbid 已回查,且禁止(热门)
RecheckStateForbid = int8(1)
//RecheckStateIgnore 被忽略不需要回查的状态
RecheckStateIgnore = int8(-2)
// FromListChannelReview 从频道回查列表提交的数据
FromListChannelReview = "channel_review"
// FromListHotReview 从热门回查列表提交的数据
FromListHotReview = "hot_review"
// FromListExcitation 从激励回查列表提交的数据
FromListExcitation = "excitation_list"
)
var (
_recheckTypes = map[int]string{
//TypeChannelRecheck: "频道回查",
TypeHotRecheck: "热门回查",
TypeExcitationRecheck: "激励回查",
}
)
// Recheck archive recheck
type Recheck struct {
ID int64 `json:"id"`
Type int `json:"type"`
Aid int64 `json:"aid"`
UID int64 `json:"uid"`
State int8 `json:"state"`
Remark string `json:"remark"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"mtime"`
}
//RecheckType get recheck type name
func RecheckType(tp int) (str string) {
return _recheckTypes[tp]
}

View File

@@ -0,0 +1,27 @@
package archive
import (
"encoding/json"
"time"
)
// 1 耗时 2 耗时(30分钟) 3 视频进审/过审分布
var (
ReportArchiveRound = map[int8]string{30: "30", 40: "40", 90: "90"}
ReportTypeTookMinute = int8(1)
ReportTypeTookHalfHour = int8(2)
ReportTypeVideoAudit = int8(3)
ReportTypeArcMoveType = int8(4)
ReportTypeArcRoundFlow = int8(5)
ReportTypeXcode = int8(6) //video sd_finish,hd_finish,dispatch take time
ReportTypeTraffic = int8(7) //视频审核耗时统计。10分钟聚合的一转、一审、二转、分发耗时结果
)
// Report struct
type Report struct {
ID int64 `json:"-"`
TypeID int8 `json:"type"`
Content json.RawMessage `json:"content"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"mtime"`
}

View File

@@ -0,0 +1,57 @@
package archive
import (
"sync"
"time"
)
var (
// TookTypeMinute video task took time in 1 minute
TookTypeMinute = int8(1)
// TookTypeHalfHour video task took time in 10 minutes
TookTypeHalfHour = int8(2)
// TaskStateUnclaimed video task belongs to nobody
TaskStateUnclaimed = int8(0)
// TaskStateUntreated video task not submit
TaskStateUntreated = int8(1)
// TaskStateCompleted video task completed
TaskStateCompleted = int8(2)
// TaskStateDelayed video task delayed
TaskStateDelayed = int8(3)
// TaskStateClosed video task closed
TaskStateClosed = int8(4)
)
// TaskCache store task video
type TaskCache struct {
Task map[int64]*Task
Took []*TaskTook
Sort []int
Mtime time.Time
sync.Mutex
}
// Task video task entity
type Task struct {
ID int64 `json:"id"`
Subject int8 `json:"subject"`
Adminid int64 `json:"adminid"`
Pool int8 `json:"pool"`
Aid int64 `json:"aid"`
Cid int64 `json:"cid"`
State int8 `json:"state"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"-"`
}
// TaskTook video task take time
type TaskTook struct {
ID int64 `json:"id"`
M90 int `json:"m90"`
M80 int `json:"m80"`
M60 int `json:"m60"`
M50 int `json:"m50"`
TypeID int8 `json:"type"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"-"`
}

View File

@@ -0,0 +1,28 @@
package archive
import (
"go-common/library/time"
)
//Track archive track info
type Track struct {
// common values
Timestamp time.Time `json:"timestamp"`
// archive stat
State int `json:"state"`
Round int `json:"round"`
// AID int64 `json:"aid,omitempty"`
Remark string `json:"remark,omitempty"`
Attribute int32 `json:"attribute"`
}
//VideoTrack video track info
type VideoTrack struct {
// common values
Timestamp time.Time `json:"timestamp"`
XCodeState int8 `json:"xcode_state"`
// video status
Status int16 `json:"status"`
AID int64 `json:"aid,omitempty"`
Remark string `json:"remark,omitempty"`
}

View File

@@ -0,0 +1,94 @@
package archive
import (
"sync"
)
const (
//VideoUploadInfo 转码 创建上传
VideoUploadInfo = 0
//VideoXcodeSDFail 一转失败
VideoXcodeSDFail = 1
//VideoXcodeSDFinish 一转成功
VideoXcodeSDFinish = 2
//VideoXcodeHDFail 二转失败
VideoXcodeHDFail = 3
//VideoXcodeHDFinish 二转成功
VideoXcodeHDFinish = 4
//VideoDispatchRunning 分发中
VideoDispatchRunning = 5
//VideoDispatchFinish 分发成功
VideoDispatchFinish = 6
//XcodeFailZero fail zero
XcodeFailZero = 0
//VideoStatusOpen 开放浏览
VideoStatusOpen = int16(0)
//VideoStatusAccess 会员可见
VideoStatusAccess = int16(10000)
//VideoStatusWait 待审
VideoStatusWait = int16(-1)
//VideoStatusRecicle 打回
VideoStatusRecicle = int16(-2)
//VideoStatusLock 锁定
VideoStatusLock = int16(-4)
//VideoStatusXcodeFail 转码失败
VideoStatusXcodeFail = int16(-16)
//VideoStatusSubmit 创建提交
VideoStatusSubmit = int16(-30)
//VideoStatusDelete 删除
VideoStatusDelete = int16(-100)
// VideoStatusRecycle video status which be recycled
VideoStatusRecycle = int16(-2)
//VideoRelationBind video relation state
VideoRelationBind = int16(0)
)
//VideoUpInfo info
type VideoUpInfo struct {
Nw *Video
Old *Video
}
// Video struct
type Video struct {
ID int64 `json:"id"`
Filename string `json:"filename"`
Cid int64 `json:"cid"`
Aid int64 `json:"aid"`
Title string `json:"eptitle"`
Desc string `json:"description"`
SrcType string `json:"src_type"`
Duration int64 `json:"duration"`
Filesize int64 `json:"filesize"`
Resolutions string `json:"resolutions"`
Playurl string `json:"playurl"`
FailCode int8 `json:"failinfo"`
Index int `json:"index_order"`
Attribute int32 `json:"attribute"`
XcodeState int8 `json:"xcode_state"`
State int8 `json:"state"`
Status int16 `json:"status"`
CTime string `json:"ctime"`
MTime string `json:"mtime"`
}
// VideoAuditCache video audit count
type VideoAuditCache struct {
Data map[int16]map[string]int
sync.Mutex
}
// XcodeTimeCache store video xcode time list
type XcodeTimeCache struct {
Data map[int8][]int
sync.Mutex
}
// AttrVal get attribute value.
func (v *Video) AttrVal(bit uint) int32 {
return (v.Attribute >> bit) & int32(1)
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["data.go"],
importpath = "go-common/app/job/main/videoup-report/model/data",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
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,13 @@
package data
// HotArchiveRes hot recheck archive response
type HotArchiveRes struct {
Code int `json:"code"`
Note bool `json:"note"`
SourceDate string `json:"source_date"`
Num int `json:"num"`
List []struct {
Aid int64 `json:"aid"`
Score int `json:"score"`
} `json:"list"`
}

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["detector_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//vendor/github.com/smartystreets/goconvey/convey:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"detector.go",
"email.go",
"redis.go",
"template.go",
],
importpath = "go-common/app/job/main/videoup-report/model/email",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
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,76 @@
package email
import (
"fmt"
"time"
)
//FastDetector detecte speed and unique analyze
type FastDetector struct {
lastSec int64
sameSecCnt int
sameSecThreshold int
overspeedCnt int
overspeedThreshold int
uniqueSpeed map[int64]int
fastUnique int64
}
//NewFastDetector new
func NewFastDetector(speedThreshold, overspeedThreshold int) *FastDetector {
return &FastDetector{
lastSec: time.Now().Unix(),
sameSecCnt: 0,
sameSecThreshold: speedThreshold,
overspeedCnt: 0,
overspeedThreshold: overspeedThreshold,
uniqueSpeed: map[int64]int{},
}
}
//String string info
func (fd *FastDetector) String() string {
return fmt.Sprintf("same_sec_cnt=%d,overspeed_cnt=%d,fast_unique=%d,unique_speed=%v",
fd.sameSecCnt, fd.overspeedCnt, fd.fastUnique, fd.uniqueSpeed)
}
//Detect 快慢探查, 超限名单只能被慢速/下一个超速名单/间隔5s后空名单代替超限名单只有在(overspeedthreshold+1) * samesecthreshold时才确定此时返回true
func (fd *FastDetector) Detect(unique int64) (fast bool) {
now := time.Now().Unix()
//连续n次超限
if now == fd.lastSec {
fd.sameSecCnt++
if fd.sameSecCnt == fd.sameSecThreshold {
fd.overspeedCnt++
}
} else {
if fd.sameSecCnt < fd.sameSecThreshold || (now-fd.lastSec > 5) {
fd.overspeedCnt = 0
fd.fastUnique = 0
fd.uniqueSpeed = map[int64]int{}
}
fd.sameSecCnt = 1
fd.lastSec = now
}
//连续超限后最先超限的unique指定为超限名单
if fd.overspeedCnt == fd.overspeedThreshold && fd.sameSecCnt == fd.sameSecThreshold {
fd.uniqueSpeed[unique] = 0
}
if (fd.overspeedCnt == fd.overspeedThreshold && fd.sameSecCnt != fd.sameSecThreshold) || (fd.overspeedCnt > fd.overspeedThreshold) {
fd.uniqueSpeed[unique]++
if fd.uniqueSpeed[unique] >= fd.sameSecThreshold {
fast = true
fd.fastUnique = unique //指定超限名单
fd.uniqueSpeed = map[int64]int{}
fd.overspeedCnt = 0
}
}
return
}
//IsFastUnique 是否为超限名单
func (fd *FastDetector) IsFastUnique(unique int64) bool {
return fd.fastUnique == unique
}

View File

@@ -0,0 +1,125 @@
package email
import (
"testing"
"github.com/smartystreets/goconvey/convey"
"time"
)
var fd *FastDetector
var speedThreshold = 3
var overspeedThreshold = 2
var overlimit = speedThreshold * overspeedThreshold
func TestEmailnewFastDetector(t *testing.T) {
convey.Convey("newFastDetector", t, func(ctx convey.C) {
fd = NewFastDetector(speedThreshold, overspeedThreshold)
ctx.Convey("Then fd should not be nil.", func(ctx convey.C) {
ctx.So(fd, convey.ShouldNotBeNil)
})
})
}
//一秒多少个
func limit(t *testing.T, ids []int64) {
now := time.Now().UnixNano()
limit := len(ids)
for i := 0; i < limit; i++ {
unique := ids[i]
res := fd.Detect(unique)
hit := fd.IsFastUnique(unique)
t.Logf("index=%d, unique=%d, res=%v, hit=%v, detector(%+v)", i, unique, res, hit, fd)
}
if diff := now + 1e9 - time.Now().UnixNano(); diff > 0 {
time.Sleep(time.Duration(diff))
}
return
}
//分片
func batch(tk []int64, length int) (path [][]int64) {
ll := len(tk) / length
if len(tk)%length > 0 {
ll++
}
path = [][]int64{}
item := []int64{}
for i := 0; i < len(tk); i++ {
if i > 0 && i%length == 0 {
path = append(path, item)
item = []int64{}
}
item = append(item, tk[i])
}
if len(item) > 0 {
path = append(path, item)
}
return
}
func TestEmaildetect(t *testing.T) {
TestEmailnewFastDetector(t)
ids := []int64{222, 333}
//convey.Convey("慢速,无超限名额", t, func() {
// tk := []int64{}
// for i := 1; i <= overlimit*2; i++ {
// tk = append(tk, ids[0])
// }
// path := batch(tk, speedThreshold-1)
// for _, tk := range path {
// limit(t, tk)
// }
//})
//return
//convey.Convey("部分超限,但未超次数,无超限名额", t, func() {
// tk := []int64{}
// for i := 1; i < overlimit; i++ {
// tk = append(tk, ids[0])
// }
// path := [][]int64{tk}
// path = append(path, batch(tk, speedThreshold-1)...)
// for _, tk := range path {
// limit(t, tk)
// }
//})
//return
convey.Convey("部分超限且超次数,没有回落,有超限名额且被替代, 几秒后再超限,但没有超限名单", t, func() {
tk := []int64{}
tk2 := []int64{}
for i := 1; i < overlimit*2; i++ {
tk = append(tk, ids[0])
tk2 = append(tk2, ids[1])
}
path := [][]int64{tk}
path = append(path, batch(tk, speedThreshold+1)...)
path = append(path, batch(tk2, speedThreshold+1)...)
for _, tk := range path {
limit(t, tk)
}
limit(t, path[len(path)-1])
sl := int64(5)
time.Sleep(time.Duration(sl) * time.Second)
t.Logf("after %ds sleep", sl)
limit(t, path[len(path)-1])
})
convey.Convey("部分超限且超次数,有超限名额,但有回落,没有超限名额", t, func(ctx convey.C) {
tk := []int64{}
for i := 1; i < overlimit*2; i++ {
tk = append(tk, ids[0])
}
path := batch(tk, speedThreshold+1)
path = append(path, batch(tk, speedThreshold-1)...)
for _, tk := range path {
limit(t, tk)
}
})
}

View File

@@ -0,0 +1,25 @@
package email
//LogBusEmail 稿件邮件任务business id
const LogBusEmail = 141
//LogTypeEmailJob 稿件邮件任务type id, 报备邮件任务
const LogTypeEmailJob = 1
//EmailPrivateVideo 一审私单
const EmailPrivateVideo = "任务/视频私单"
//EmailPrivateArchive 稿件私单
const EmailPrivateArchive = "稿件私单"
//EmailUP 十万粉丝\优质\时政\企业up主报备
const EmailUP = "十万粉报备"
//EmailMonitor 监控报警
const EmailMonitor = "审核监控报警"
//EmailResOK 邮件发送成功
const EmailResOK = "成功"
//EmailResFail 邮件发送失败
const EmailResFail = "失败"

View File

@@ -0,0 +1,21 @@
package email
const (
//MailKey 实时邮件队列
MailKey = "f_mail_list"
//MailFastKey 超限名单的邮件队列
MailFastKey = "f_mail_list_fast"
//RetryListKey 重试列表
RetryListKey = "f_retry_list"
//RetryActionReply 评论重试
RetryActionReply = "reply"
)
//Retry retry
type Retry struct {
AID int64 `json:"aid"`
Action string `json:"action"`
Flag int64 `json:"flag"`
FlagA int64 `json:"flag_a"`
CreateTime int64 `json:"create_time"`
}

View File

@@ -0,0 +1,21 @@
package email
//邮件的发件人、收件列表、抄送列表、主题
const (
FROM = "From"
TO = "To"
CC = "Cc"
SUBJECT = "Subject"
)
//Template 邮件模板
type Template struct {
Headers map[string][]string
Body string
ContentType string
Type string
AID int64
UID int64
Username string
Department string
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["manager.go"],
importpath = "go-common/app/job/main/videoup-report/model/manager",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
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,41 @@
package manager
import "encoding/json"
const (
UpTypeExcitationWhite = 19 //激励回查白名单分组
TableUps = "ups"
)
//User user info
type User struct {
ID int64 `json:"uid"`
Username string `json:"username"`
Department string `json:"department"`
}
// BinMsg manager binlog消息结构
type BinMsg struct {
Action string `json:"action"`
Table string `json:"table"`
New json.RawMessage `json:"new"`
Old json.RawMessage `json:"old"`
}
// Ups UP主分组关联结构
type Ups struct {
ID int64 `json:"id"`
MID int64 `json:"mid"`
Type int64 `json:"type"`
//Note string `json:"note"`
//CTime string `json:"ctime"`
//MTime string `json:"mtime"`
}
// UpGroup UP主分组关联关系
type UpGroup struct {
ID int64 `json:"id"`
MID int64 `json:"mid"`
GroupID int64 `json:"group_id"`
GroupName string `json:"group_name"`
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["mission.go"],
importpath = "go-common/app/job/main/videoup-report/model/mission",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
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,10 @@
package mission
import "time"
type Mission struct {
ID int `json:"id"`
Name string `json:"name"`
Tags string `json:"tags"`
ETime time.Time `json:"etime"`
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["monitor.go"],
importpath = "go-common/app/job/main/videoup-report/model/monitor",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
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,62 @@
package monitor
const (
// RedisPrefix 参数:business。
RedisPrefix = "monitor_stats_%d_"
// SuffixVideo 视频停留统计。参数:state
SuffixVideo = "%d"
// SuffixArc 稿件停留统计。参数:round。参数:state。
SuffixArc = "%d_%d"
BusVideo = 1
BusArc = 2
NotifyTypeEmail = 1
NotityTypeSms = 2
RuleStateOK = 1
RuleStateDisable = 0
)
type RuleResultRes struct {
Code int `json:"code"`
Data []*RuleResultData `json:"data"`
}
type RuleResultData struct {
Rule *Rule `json:"rule"`
Stats *Stats `json:"stats"`
}
// Rule 监控规则信息
type Rule struct {
ID int64 `json:"id"`
Type int8 `json:"type"`
Business int8 `json:"business"`
Name string `json:"name"`
State int8 `json:"state"`
STime string `json:"s_time"`
ETime string `json:"e_time"`
RuleConf *RuleConf `json:"rule_conf"`
}
// RuleConf 监控方案配置结构体
type RuleConf struct {
Name string `json:"name"`
MoniCdt map[string]struct { //监控方案的监控条件
Comp string `json:"comparison"`
Value int64 `json:"value"`
} `json:"moni_cdt"`
NotifyCdt map[string]struct { //达到发送通知的条件
Comp string `json:"comparison"`
Value int64 `json:"value"`
} `json:"notify_cdt"`
Notify struct { //通知类型配置
Way int8 `json:"way"`
Member []string `json:"member"`
} `json:"notify"`
}
type Stats struct {
TotalCount int `json:"total_count"`
MoniCount int `json:"moni_count"`
MaxTime int `json:"max_time"`
}

View File

@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"task_assign.go",
"task_dispatch.go",
"task_weight.go",
],
importpath = "go-common/app/job/main/videoup-report/model/task",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//app/job/main/videoup-report/model/utils: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,21 @@
package task
import (
"time"
)
//AssignConfig 指派配置
type AssignConfig struct {
ID int64
Pool int8
MinDuration int64
MaxDuration int64
MIDs map[int64]struct{}
TIDs map[int16]struct{}
UIDs []int64
AdminID int64
State int8
STime time.Time
ETime time.Time
Index int
}

View File

@@ -0,0 +1,39 @@
package task
import "go-common/app/job/main/videoup-report/model/utils"
const (
// PoolForFirst 一审
PoolForFirst = int8(0)
// PoolForSecond 二审
PoolForSecond = int8(1)
// SubjectForNormal 普通任务
SubjectForNormal = int8(0)
// SubjectForTask 指派任务
SubjectForTask = int8(1)
// StateForTaskDefault 初始化状态(未认领)
StateForTaskDefault = int8(0)
// StateForTaskWork 已认领,未处理
StateForTaskWork = int8(1)
// StateForTaskDelay 延迟审核
StateForTaskDelay = int8(3)
// StateForTaskUserDeleted 被释放
StateForTaskUserDeleted = int8(6)
)
// Task 审核任务
type Task struct {
Pool int8
Subject int8
AdminID int64
Aid int64
Cid int64
UID int64
State int8
ConfigID int64
ConfigState int8
ConfigWeight int64
UPSpecial int8
CFtime utils.FormatTime
Ptime utils.FormatTime
}

View File

@@ -0,0 +1,259 @@
package task
import (
"sort"
"sync"
"time"
"go-common/app/job/main/videoup-report/model/utils"
)
const (
// UpperSuperWhite 优质>10w粉
UpperSuperWhite = int8(1)
// UpperSuperBlack 高危>10w粉
UpperSuperBlack = int8(2)
// UpperWhite 优质
UpperWhite = int8(3)
// UpperBigWhite 优质>1w粉
UpperBigWhite = int8(4)
// UpperBigNormal 普通>1w粉
UpperBigNormal = int8(5)
// UpperSuperNormal 普通>10w粉
UpperSuperNormal = int8(6)
// UpperBlack 高危
UpperBlack = int8(7)
// WConfMid 按照mid配置权重
WConfMid = int8(0)
// WConfTaskID 按照taskid配置权重
WConfTaskID = int8(1)
// WConfType 按照分区配置权重
WConfType = int8(2)
// WConfUpFrom 按照投稿来源配置权重
WConfUpFrom = int8(3)
//UpperTypeWhite 优质
UpperTypeWhite int8 = 1
//UpperTypeBlack 高危
UpperTypeBlack int8 = 2
//UpperTypePGC 生产组
UpperTypePGC int8 = 3
//UpperTypeUGCX don't know
UpperTypeUGCX int8 = 3
//UpperTypePolitices 时政
UpperTypePolitices int8 = 5
//UpperTypeEnterprise 企业
UpperTypeEnterprise int8 = 7
//UpperTypeSigned 签约
UpperTypeSigned int8 = 15
)
var (
// TaskCountTH 插队任务阈值
TaskCountTH = 2000
// SuperUpperTH 粉丝数阈值
SuperUpperTH = int64(100000)
// BigUpperTH 粉丝数阈值
BigUpperTH = int64(10000)
// WLVConf 各个权重等级具体的配置数值
WLVConf = &WeightValueConf{
MaxWeight: int64(200000), //最大权重值
MinWeight: int64(-510),
SubRelease: int64(18), //指派再释放的任务
//特殊任务参数
Slv1: int64(8), // 普通用户>=1W粉
Slv2: int64(10), // 普通用户>=10W粉
Slv3: int64(12), // 优质用户<1W粉
Slv4: int64(15), // 优质用户>=1W粉
Slv5: int64(18), // 优质用户>=10W粉
Slv6: int64(6), // 高危用户>=10W粉
Slv7: int64(0), // 其他高危
//普通任务参数
Nlv1: int64(3),
Nlv2: int64(6),
Nlv3: int64(9),
Nlv4: int64(12),
Nlv5: int64(0),
Nsum9: int64(0), // 等待9分钟总和 3*0
Nsum15: int64(6), // 等待15分钟总和 2*3
Nsum27: int64(30), // 等待27分钟总和 6 + 4*6
Nsum45: int64(84), // 等待45分钟总和 30 + 6*9
//定时任务参数
Tlv1: int64(3),
Tlv2: int64(9),
Tlv3: int64(21),
Tlv4: int64(0),
Tsum2h: int64(120),
Tsum1h: int64(300),
}
)
//WeightValueConf 可配置的权重
type WeightValueConf struct {
MaxWeight int64 `json:"maxweight"`
SubRelease int64 `json:"subrelease"`
MinWeight int64 `json:"minweight"`
Slv1 int64 `json:"slv1"`
Slv2 int64 `json:"slv2"`
Slv3 int64 `json:"slv3"`
Slv4 int64 `json:"slv4"`
Slv5 int64 `json:"slv5"`
Slv6 int64 `json:"slv6"`
Slv7 int64 `json:"slv7"`
Nlv1 int64 `json:"nlv1"`
Nlv2 int64 `json:"nlv2"`
Nlv3 int64 `json:"nlv3"`
Nlv4 int64 `json:"nlv4"`
Nlv5 int64 `json:"nlv5"`
Nsum9 int64 `json:"-"`
Nsum15 int64 `json:"-"`
Nsum27 int64 `json:"-"`
Nsum45 int64 `json:"-"`
Tlv1 int64 `json:"tlv1"`
Tlv2 int64 `json:"tlv2"`
Tlv3 int64 `json:"tlv3"`
Tlv4 int64 `json:"tlv4"`
Tsum2h int64 `json:"-"`
Tsum1h int64 `json:"-"`
}
//WeightConfig task_weight_config记录结构
type WeightConfig struct {
ID int64
Mid int64
TaskID int64
Rule int8
Weight int64
Ctime time.Time
Mtime time.Time
UserName string
Desc string
}
//WeightParams 审核任务权重的相关参数
type WeightParams struct {
TaskID int64 `json:"taskid"`
Weight int64 `json:"weight"` //权重总值
State int8 `json:"state"` //任务状态
Mid int64 `json:"mid"`
Special int8 `json:"special"` //特殊任务
Ctime utils.FormatTime `json:"ctime"` //任务生成时间
Ptime utils.FormatTime `json:"ptime"` //定时发布时间
CfItems []*ConfigItem `json:"cfitems,omitempty"`
Fans int64 `json:"fans"` //粉丝数
AccFailed bool `json:"accfaild"` //账号查询是否失败
UpGroups []int8 `json:"ugs"` //分组
UpFrom int8 `json:"upfrom"` //来源
TypeID int16 `json:"typeid"` //分区
}
// ConfigItem task weight config item
type ConfigItem struct {
ID int64 `json:"id"`
Radio int8 `json:"radio"`
CID int64 `json:"cid"` // config id 四种配置通用
Uname string `json:"user,omitempty"`
Rule int8 `json:"rule"`
Weight int64 `json:"weight"`
Mtime utils.FormatTime `json:"mtime"`
Desc string `json:"desc,omitempty"`
Bt utils.FormatTime `json:"et"`
Et utils.FormatTime `json:"bt"`
}
//WeightLog 权重变更记录
type WeightLog struct {
TaskID int64 `json:"taskid"`
Mid int64 `json:"mid"` //用户id
Weight int64 `json:"weight"` //任务权重总和
CWeight int64 `json:"cweight"` //配置权重
NWeight int64 `json:"nweight"` //普通任务
SWeight int64 `json:"sweight"` //特殊任务
TWeight int64 `json:"tweight"` //定时任务
Uptime utils.FormatTime `json:"uptime"` //更新时间
CfItems []*ConfigItem `json:"cfitems,omitempty"`
}
// JumpList 插队同步的任务
type JumpList struct {
l []*WeightLog
min int64
count int
mux sync.RWMutex
}
// NewJumpList New JumpList
func NewJumpList() *JumpList {
return &JumpList{
l: []*WeightLog{},
min: -1,
count: 0,
}
}
// PUSH 添加
func (jl *JumpList) PUSH(item *WeightLog) {
jl.mux.Lock()
defer jl.mux.Unlock()
if jl.count == TaskCountTH { //队列满了
if item.Weight > jl.min { //剔除最小的
jl.l = jl.l[1:jl.count]
jl.min = jl.l[0].Weight
jl.count--
} else {
return
}
}
inx := sort.SearchInts(jl.List(), int(item.Weight))
switch {
case inx == 0: //头部
jl.l = append([]*WeightLog{item}, jl.l...)
jl.min = item.Weight
case inx == jl.count: //尾部
jl.l = append(jl.l, item)
default:
rear := append([]*WeightLog{}, jl.l[inx:]...)
jl.l = append(jl.l[:inx], item)
jl.l = append(jl.l, rear...)
}
jl.count++
}
// POP 读取
func (jl *JumpList) POP() (item *WeightLog) {
jl.mux.Lock()
defer jl.mux.Unlock()
if jl.count > 1 {
item = jl.l[jl.count-1]
jl.l = jl.l[:jl.count-1]
jl.count--
jl.min = jl.l[jl.count-1].Weight
return
} else if jl.count == 1 {
item = jl.l[0]
jl.l = []*WeightLog{}
jl.count = 0
jl.min = -1
return
}
return nil
}
// Reset 重置
func (jl *JumpList) Reset() {
jl.mux.Lock()
defer jl.mux.Unlock()
jl.l = []*WeightLog{}
jl.min = -1
jl.count = 0
}
// List 待更新权重的任务
func (jl *JumpList) List() []int {
arr := []int{}
for _, jw := range jl.l {
arr = append(arr, int(jw.Weight))
}
return arr
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["time.go"],
importpath = "go-common/app/job/main/videoup-report/model/utils",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
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,58 @@
package utils
import (
"database/sql/driver"
"time"
)
// FormatTime format time
type FormatTime string
// NewFormatTime net formatTime
func NewFormatTime(t time.Time) FormatTime {
ft := new(FormatTime)
ft.Scan(t)
return *ft
}
// Scan scan time.
func (jt *FormatTime) Scan(src interface{}) (err error) {
switch sc := src.(type) {
case time.Time:
*jt = FormatTime(sc.Format("2006-01-02 15:04:05"))
case string:
*jt = FormatTime(sc)
}
return
}
// Value get string value.
func (jt FormatTime) Value() (driver.Value, error) {
return string(jt), nil
}
// TimeValue get time value.
func (jt FormatTime) TimeValue() time.Time {
t, _ := time.ParseInLocation("2006-01-02 15:04:05", string(jt), time.Local)
if t.Unix() <= 0 {
t, _ = time.ParseInLocation("2006-01-02 15:04:05", "0000-00-00 00:00:00", time.Local)
}
return t
}
// UnmarshalJSON implement Unmarshaler
func (jt *FormatTime) UnmarshalJSON(data []byte) error {
if data == nil || len(data) <= 1 {
*jt = FormatTime("0000-00-00 00:00:00")
return nil
}
str := string(data[1 : len(data)-1])
st, err := time.Parse(time.RFC3339, str)
if err == nil {
*jt = FormatTime(st.Format("2006-01-02 15:04:05"))
} else {
*jt = FormatTime(str)
}
return nil
}